diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs index da58641d7e..30161b77b1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs @@ -110,7 +110,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { postedFiles.Add(new RemoteStreamContent(file.OpenReadStream()) { - ContentType = file.ContentType + ContentType = file.ContentType, + FileName = file.FileName }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs index 188306227a..35e0922f36 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs @@ -26,6 +26,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { context.HttpContext.Response.ContentType = remoteStream.ContentType; + if (!remoteStream.FileName.IsNullOrWhiteSpace()) + { + var contentDisposition = new ContentDispositionHeaderValue("attachment"); + contentDisposition.SetHttpFileName(remoteStream.FileName); + context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); + } + using (var stream = remoteStream.GetStream()) { if (stream.CanSeek) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs index bca259d422..0145305f09 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Content long? ContentLength { get; } + string FileName { get; } + Stream GetStream(); } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs index f217101cea..f24b4d83bd 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs @@ -11,9 +11,18 @@ namespace Volo.Abp.Content _stream = stream; } + public RemoteStreamContent(Stream stream, string fileName) + : this(stream) + { + FileName = fileName; + ContentType = "application/octet-stream"; + } + public virtual string ContentType { get; set; } - public virtual long? ContentLength => _stream.Length; + public virtual long? ContentLength => GetStream().Length; + + public virtual string FileName { get; set; } public virtual Stream GetStream() { 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 7914068bca..b94a5a339d 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 @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Volo.Abp.Content; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; @@ -57,7 +58,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying ICancellationTokenProvider cancellationTokenProvider, ICorrelationIdProvider correlationIdProvider, IOptions correlationIdOptions, - ICurrentTenant currentTenant, + ICurrentTenant currentTenant, IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider) { CancellationTokenProvider = cancellationTokenProvider; @@ -114,7 +115,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying * it before we finish doing our work with the stream */ return (T)(object)new RemoteStreamContent(await responseContent.ReadAsStreamAsync()) { - ContentType = responseContent.Headers.ContentType?.ToString() + ContentType = responseContent.Headers.ContentType?.ToString(), + FileName = responseContent.Headers?.ContentDisposition?.FileNameStar ?? + RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString() }; } @@ -293,6 +296,16 @@ namespace Volo.Abp.Http.Client.DynamicProxying }; } + protected virtual StringSegment RemoveQuotes(StringSegment input) + { + if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"') + { + input = input.Subsegment(1, input.Length - 2); + } + + return input; + } + protected virtual CancellationToken GetCancellationToken() { return CancellationTokenProvider.Token; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs index 1096fa971e..b121852c2c 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs @@ -90,8 +90,9 @@ namespace Volo.Abp.Http.Client.DynamicProxying if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace()) { streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); + } - formData.Add(streamContent, parameter.Name, parameter.Name); + formData.Add(streamContent, parameter.Name, remoteStreamContent.FileName ?? parameter.Name); } else if (value is IEnumerable remoteStreamContents) { @@ -107,7 +108,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); } - formData.Add(streamContent, parameter.Name, parameter.Name); + formData.Add(streamContent, parameter.Name, content.FileName ?? parameter.Name); } } else 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 index e74c9d52e8..7743e575ed 100644 --- 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 @@ -17,7 +17,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters var memoryStream = new MemoryStream(); await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync")); - return new RemoteStreamContent(memoryStream) + return new RemoteStreamContent(memoryStream, "download.rtf") { ContentType = "application/rtf" }; @@ -29,7 +29,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { using (var reader = new StreamReader(file.GetStream())) { - return await reader.ReadToEndAsync() + ":" + file.ContentType; + return await reader.ReadToEndAsync() + ":" + file.ContentType + ":" + file.FileName; } } } 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 index 12745da732..8f42a5cb37 100644 --- 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 @@ -15,6 +15,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { var result = await GetResponseAsync("/api/remote-stream-content-test/download"); result.Content.Headers.ContentType?.ToString().ShouldBe("application/rtf"); + result.Content.Headers.ContentDisposition?.FileName.ShouldBe("download.rtf"); (await result.Content.ReadAsStringAsync()).ShouldBe("DownloadAsync"); } @@ -30,11 +31,11 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters var streamContent = new StreamContent(memoryStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/rtf"); - requestMessage.Content = new MultipartFormDataContent {{streamContent, "file", "file"}}; + requestMessage.Content = new MultipartFormDataContent {{streamContent, "file", "upload.rtf"}}; var response = await Client.SendAsync(requestMessage); - (await response.Content.ReadAsStringAsync()).ShouldBe("UploadAsync:application/rtf"); + (await response.Content.ReadAsStringAsync()).ShouldBe("UploadAsync:application/rtf:upload.rtf"); } } } 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 026373d568..d92e1ad5d1 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 @@ -62,7 +62,7 @@ namespace Volo.Abp.Http.DynamicProxying { id1, id2 - }, new[] {"name1", "name2"}); + }, new[] { "name1", "name2" }); @params.ShouldContain(id1.ToString("N")); @params.ShouldContain(id2.ToString("N")); @@ -87,10 +87,10 @@ namespace Volo.Abp.Http.DynamicProxying var uniquePersonName = Guid.NewGuid().ToString(); var person = await _peopleAppService.CreateAsync(new PersonDto - { - Name = uniquePersonName, - Age = 42 - } + { + Name = uniquePersonName, + Age = 42 + } ); person.ShouldNotBeNull(); @@ -108,9 +108,9 @@ namespace Volo.Abp.Http.DynamicProxying await Assert.ThrowsAsync(async () => { var person = await _peopleAppService.CreateAsync(new PersonDto - { - Age = 42 - } + { + Age = 42 + } ); }); } @@ -180,6 +180,7 @@ namespace Volo.Abp.Http.DynamicProxying { var result = await _peopleAppService.DownloadAsync(); + result.FileName.ShouldBe("download.rtf"); result.ContentType.ShouldBe("application/rtf"); using (var reader = new StreamReader(result.GetStream())) { @@ -194,11 +195,11 @@ namespace Volo.Abp.Http.DynamicProxying var memoryStream = new MemoryStream(); await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("UploadAsync")); memoryStream.Position = 0; - var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream) + var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream, "upload.rtf") { ContentType = "application/rtf" }); - result.ShouldBe("UploadAsync:application/rtf"); + result.ShouldBe("UploadAsync:application/rtf:upload.rtf"); } [Fact] @@ -214,17 +215,17 @@ namespace Volo.Abp.Http.DynamicProxying var result = await _peopleAppService.UploadMultipleAsync(new List() { - new RemoteStreamContent(memoryStream) + new RemoteStreamContent(memoryStream, "File1.rtf") { ContentType = "application/rtf" }, - new RemoteStreamContent(memoryStream2) + new RemoteStreamContent(memoryStream2, "File2.rtf") { ContentType = "application/rtf2" } }); - result.ShouldBe("File1:application/rtfFile2:application/rtf2"); + result.ShouldBe("File1:application/rtf:File1.rtfFile2:application/rtf2:File2.rtf"); } [Fact] @@ -236,12 +237,12 @@ namespace Volo.Abp.Http.DynamicProxying var result = await _peopleAppService.CreateFileAsync(new CreateFileInput() { Name = "123.rtf", - Content = new RemoteStreamContent(memoryStream) + Content = new RemoteStreamContent(memoryStream, "create.rtf") { ContentType = "application/rtf" } }); - result.ShouldBe("123.rtf:CreateFileAsync:application/rtf"); + result.ShouldBe("123.rtf:CreateFileAsync:application/rtf:create.rtf"); } [Fact] @@ -264,12 +265,12 @@ namespace Volo.Abp.Http.DynamicProxying Name = "123.rtf", Contents = new List() { - new RemoteStreamContent(memoryStream) + new RemoteStreamContent(memoryStream, "1-1.rtf") { ContentType = "application/rtf" }, - new RemoteStreamContent(memoryStream2) + new RemoteStreamContent(memoryStream2, "1-2.rtf") { ContentType = "application/rtf2" } @@ -277,13 +278,13 @@ namespace Volo.Abp.Http.DynamicProxying Inner = new CreateFileInput() { Name = "789.rtf", - Content = new RemoteStreamContent(memoryStream3) + Content = new RemoteStreamContent(memoryStream3, "i-789.rtf") { ContentType = "application/rtf3" } } }); - result.ShouldBe("123.rtf:File1:application/rtf123.rtf:File2:application/rtf2789.rtf:File3:application/rtf3"); + result.ShouldBe("123.rtf:File1:application/rtf:1-1.rtf123.rtf:File2:application/rtf2:1-2.rtf789.rtf:File3:application/rtf3:i-789.rtf"); } } } 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 edd6a3b93f..f3dfef2891 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 @@ -73,7 +73,7 @@ namespace Volo.Abp.TestApp.Application var memoryStream = new MemoryStream(); await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync")); - return new RemoteStreamContent(memoryStream) + return new RemoteStreamContent(memoryStream, "download.rtf") { ContentType = "application/rtf" }; @@ -83,7 +83,7 @@ namespace Volo.Abp.TestApp.Application { using (var reader = new StreamReader(streamContent.GetStream())) { - return await reader.ReadToEndAsync() + ":" + streamContent.ContentType; + return await reader.ReadToEndAsync() + ":" + streamContent.ContentType + ":" + streamContent.FileName; } } @@ -94,7 +94,7 @@ namespace Volo.Abp.TestApp.Application { using (var reader = new StreamReader(content.GetStream())) { - str += await reader.ReadToEndAsync() + ":" + content.ContentType; + str += await reader.ReadToEndAsync() + ":" + content.ContentType + ":" + content.FileName; } } @@ -105,7 +105,7 @@ namespace Volo.Abp.TestApp.Application { using (var reader = new StreamReader(input.Content.GetStream())) { - return input.Name + ":" + await reader.ReadToEndAsync() + ":" + input.Content.ContentType; + return input.Name + ":" + await reader.ReadToEndAsync() + ":" + input.Content.ContentType + ":" + input.Content.FileName; } } @@ -116,13 +116,13 @@ namespace Volo.Abp.TestApp.Application { using (var reader = new StreamReader(content.GetStream())) { - str += input.Name + ":" + await reader.ReadToEndAsync() + ":" + content.ContentType; + str += input.Name + ":" + await reader.ReadToEndAsync() + ":" + content.ContentType + ":" + content.FileName; } } using (var reader = new StreamReader(input.Inner.Content.GetStream())) { - str += input.Inner.Name + ":" + await reader.ReadToEndAsync() + ":" + input.Inner.Content.ContentType; + str += input.Inner.Name + ":" + await reader.ReadToEndAsync() + ":" + input.Inner.Content.ContentType + ":" + input.Inner.Content.FileName; } return str;