diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 8cbba0fc46..873c532ba3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Auditing; -using Volo.Abp.AspNetCore.Mvc.Content; +using Volo.Abp.AspNetCore.Mvc.ContentFormatters; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.Features; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs deleted file mode 100644 index aaaa849f4f..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.IO; -using Microsoft.AspNetCore.Http; -using Volo.Abp.Content; - -namespace Volo.Abp.AspNetCore.Mvc.Content -{ - internal class InternalRemoteStreamContent : IRemoteStreamContent - { - private readonly HttpContext _httpContext; - - public InternalRemoteStreamContent(HttpContext httpContext) - { - _httpContext = httpContext; - } - - public string ContentType => _httpContext.Request.ContentType; - - public long? ContentLength => _httpContext.Request.ContentLength; - - public Stream GetStream() - { - return _httpContext.Request.Body; - } - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentInputFormatter.cs similarity index 77% rename from framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentInputFormatter.cs index a4f5668b7d..27116d3da6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentInputFormatter.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; -using Microsoft.Net.Http.Headers; using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; using Volo.Abp.Content; -namespace Volo.Abp.AspNetCore.Mvc.Content +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { public class RemoteStreamContentInputFormatter : InputFormatter { @@ -20,9 +20,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Content public override Task ReadRequestBodyAsync(InputFormatterContext context) { - return InputFormatterResult.SuccessAsync( - new InternalRemoteStreamContent(context.HttpContext) - ); + return InputFormatterResult.SuccessAsync(new RemoteStreamContent(context.HttpContext.Request.Body)); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs similarity index 65% rename from framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs rename to framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs index 706e514254..685822a4b6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; using Volo.Abp.Content; -namespace Volo.Abp.AspNetCore.Mvc.Content +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { public class RemoteStreamContentOutputFormatter : OutputFormatter { @@ -21,9 +21,16 @@ namespace Volo.Abp.AspNetCore.Mvc.Content public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context) { var remoteStream = (IRemoteStreamContent)context.Object; - using (var stream = remoteStream.GetStream()) + + if (remoteStream != null) { - await stream.CopyToAsync(context.HttpContext.Response.Body); + context.HttpContext.Response.ContentType = remoteStream.ContentType; + + using (var stream = remoteStream.GetStream()) + { + stream.Position = 0; + await stream.CopyToAsync(context.HttpContext.Response.Body); + } } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Content/ReferencedRemoteStreamContent.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Content/ReferencedRemoteStreamContent.cs deleted file mode 100644 index b99cec8a51..0000000000 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Content/ReferencedRemoteStreamContent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.IO; -using Volo.Abp.Content; - -namespace Volo.Abp.Http.Client.Content -{ - internal class ReferencedRemoteStreamContent : RemoteStreamContent - { - private readonly object[] _references; - - public ReferencedRemoteStreamContent(Stream stream, params object[] references) - : base(stream) - { - this._references = references; - } - } -} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 7eb4e5ad78..dd4a681ba5 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -14,7 +14,6 @@ using Volo.Abp.Content; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; using Volo.Abp.Http.Client.Authentication; -using Volo.Abp.Http.Client.Content; using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; @@ -112,7 +111,10 @@ namespace Volo.Abp.Http.Client.DynamicProxying /* returning a class that holds a reference to response * content just to be sure that GC does not dispose of * it before we finish doing our work with the stream */ - return (T)((object)new ReferencedRemoteStreamContent(await responseContent.ReadAsStreamAsync(), responseContent)); + return (T)(object)new RemoteStreamContent(await responseContent.ReadAsStreamAsync()) + { + ContentType = responseContent.Headers.ContentType?.ToString() + }; } var stringContent = await responseContent.ReadAsStringAsync(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs new file mode 100644 index 0000000000..8e49b77604 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs @@ -0,0 +1,35 @@ +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Content; + +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters +{ + [Route("api/remote-stream-content-test")] + public class RemoteStreamContentTestController : AbpController + { + [HttpGet] + [Route("Download")] + public async Task DownloadAsync() + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("stream")); + + return new RemoteStreamContent(memoryStream) + { + ContentType = "audio/mpeg" + }; + } + + [HttpPost] + [Route("Upload")] + public async Task UploadAsync([FromBody]IRemoteStreamContent streamContent) + { + using (var reader = new StreamReader(streamContent.GetStream())) + { + return await reader.ReadToEndAsync(); + } + } + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs new file mode 100644 index 0000000000..898423ab58 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters +{ + public class RemoteStreamContentTestController_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task DownloadAsync() + { + var result = await GetResponseAsync("/api/remote-stream-content-test/download"); + result.Content.Headers.ContentType?.ToString().ShouldBe("audio/mpeg"); + (await result.Content.ReadAsStringAsync()).ShouldBe("stream"); + } + + [Fact] + public async Task UploadAsync() + { + using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/remote-stream-content-test/upload")) + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("upload")); + + memoryStream.Position = 0; + requestMessage.Content = new StreamContent(memoryStream); + requestMessage.Content.Headers.Add("Content-Type", "*/*"); + + var response = await Client.SendAsync(requestMessage); + + (await response.Content.ReadAsStringAsync()).ShouldBe("upload"); + } + } + } +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs index e53e3cabbf..93299b944c 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using NSubstitute.Extensions; using Shouldly; using Volo.Abp.Application.Dtos; +using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; using Volo.Abp.Http.Client; using Volo.Abp.TestApp.Application; @@ -168,5 +171,30 @@ namespace Volo.Abp.Http.DynamicProxying result.Inner1.Value2.ShouldBe("value two"); result.Inner1.Inner2.Value3.ShouldBe("value three"); } + + [Fact] + public async Task DownloadAsync() + { + var result = await _peopleAppService.DownloadAsync(); + + result.ContentType.ShouldBe("audio/mpeg"); + using (var reader = new StreamReader(result.GetStream())) + { + var str = await reader.ReadToEndAsync(); + str.ShouldBe("stream"); + } + } + + [Fact] + public async Task UploadAsync() + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("upload")); + var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream) + { + ContentType = "application/rtf" + }); + result.ShouldBe("upload"); + } } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index a34cd19ebb..2344cb7c9b 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Content; using Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.TestApp.Application @@ -20,5 +21,9 @@ namespace Volo.Abp.TestApp.Application Task GetWithAuthorized(); Task GetWithComplexType(GetWithComplexTypeInput input); + + Task DownloadAsync(); + + Task UploadAsync(IRemoteStreamContent streamContent); } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index cff72c7869..ec9332d44e 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.TestApp.Domain; using Volo.Abp.Domain.Repositories; using Volo.Abp.Application.Services; +using Volo.Abp.Content; using Volo.Abp.TestApp.Application.Dto; namespace Volo.Abp.TestApp.Application @@ -64,5 +67,24 @@ namespace Volo.Abp.TestApp.Application { return Task.FromResult(input); } + + public async Task DownloadAsync() + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("stream")); + + return new RemoteStreamContent(memoryStream) + { + ContentType = "audio/mpeg" + }; + } + + public async Task UploadAsync(IRemoteStreamContent streamContent) + { + using (var reader = new StreamReader(streamContent.GetStream())) + { + return await reader.ReadToEndAsync(); + } + } } }