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 30161b77b1..2b1fc8c125 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 @@ -108,14 +108,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)) { - postedFiles.Add(new RemoteStreamContent(file.OpenReadStream()) - { - ContentType = file.ContentType, - FileName = file.FileName - }); + postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length)); } } } + else if (bindingContext.IsTopLevelObject) + { + postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength)); + } } } } 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 35e0922f36..50d65ce02d 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 @@ -33,14 +33,9 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); } - using (var stream = remoteStream.GetStream()) + using (remoteStream) { - if (stream.CanSeek) - { - stream.Position = 0; - } - - await stream.CopyToAsync(context.HttpContext.Response.Body); + await remoteStream.GetStream().CopyToAsync(context.HttpContext.Response.Body); } } } 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 0145305f09..ba564df18e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs @@ -1,15 +1,16 @@ -using System.IO; +using System; +using System.IO; namespace Volo.Abp.Content { - public interface IRemoteStreamContent + public interface IRemoteStreamContent : IDisposable { + string FileName { get; } + string ContentType { get; } 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 f24b4d83bd..4b301a3ad0 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs @@ -5,28 +5,40 @@ namespace Volo.Abp.Content public class RemoteStreamContent : IRemoteStreamContent { private readonly Stream _stream; + private readonly bool _disposeStream; + private bool _disposed; - public RemoteStreamContent(Stream stream) + public virtual string FileName { get; } + + public virtual string ContentType { get; } = "application/octet-stream"; + + public virtual long? ContentLength { get; } + + public RemoteStreamContent(Stream stream, string fileName = null, string contentType = null, long? readOnlyLength = null, bool disposeStream = true) { _stream = stream; - } - public RemoteStreamContent(Stream stream, string fileName) - : this(stream) - { FileName = fileName; - ContentType = "application/octet-stream"; + if (contentType != null) + { + ContentType = contentType; + } + ContentLength = readOnlyLength ?? (_stream.CanSeek ? _stream.Length - _stream.Position : null); + _disposeStream = disposeStream; } - public virtual string ContentType { get; set; } - - public virtual long? ContentLength => GetStream().Length; - - public virtual string FileName { get; set; } - public virtual Stream GetStream() { return _stream; } + + public virtual void Dispose() + { + if (!_disposed && _disposeStream) + { + _disposed = true; + _stream?.Dispose(); + } + } } } 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 df890812bb..b1763d8825 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 @@ -113,12 +113,11 @@ 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 RemoteStreamContent(await responseContent.ReadAsStreamAsync()) - { - ContentType = responseContent.Headers.ContentType?.ToString(), - FileName = responseContent.Headers?.ContentDisposition?.FileNameStar ?? - RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString() - }; + return (T) (object) new RemoteStreamContent( + await responseContent.ReadAsStreamAsync(), + responseContent.Headers?.ContentDisposition?.FileNameStar ?? RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), + responseContent.Headers?.ContentType?.ToString(), + responseContent.Headers?.ContentLength); } var stringContent = await responseContent.ReadAsStringAsync(); 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 b121852c2c..d83b319b28 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 @@ -82,16 +82,12 @@ namespace Volo.Abp.Http.Client.DynamicProxying if (value is IRemoteStreamContent remoteStreamContent) { var stream = remoteStreamContent.GetStream(); - if (stream.CanSeek) - { - stream.Position = 0; - } var streamContent = new StreamContent(stream); if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace()) { streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); - } + streamContent.Headers.ContentLength = remoteStreamContent.ContentLength; formData.Add(streamContent, parameter.Name, remoteStreamContent.FileName ?? parameter.Name); } else if (value is IEnumerable remoteStreamContents) @@ -99,15 +95,12 @@ namespace Volo.Abp.Http.Client.DynamicProxying foreach (var content in remoteStreamContents) { var stream = content.GetStream(); - if (stream.CanSeek) - { - stream.Position = 0; - } var streamContent = new StreamContent(stream); if (!content.ContentType.IsNullOrWhiteSpace()) { streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); } + streamContent.Headers.ContentLength = content.ContentLength; formData.Add(streamContent, parameter.Name, content.FileName ?? parameter.Name); } } 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 7743e575ed..c9183d41d7 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 @@ -16,11 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters { var memoryStream = new MemoryStream(); await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync")); - - return new RemoteStreamContent(memoryStream, "download.rtf") - { - ContentType = "application/rtf" - }; + memoryStream.Position = 0; + return new RemoteStreamContent(memoryStream, "download.rtf", "application/rtf"); } [HttpPost] @@ -32,5 +29,15 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters return await reader.ReadToEndAsync() + ":" + file.ContentType + ":" + file.FileName; } } + + [HttpPost] + [Route("Upload-Raw")] + public async Task UploadRawAsync(IRemoteStreamContent file) + { + using (var reader = new StreamReader(file.GetStream())) + { + return await reader.ReadToEndAsync() + ":" + file.ContentType; + } + } } } 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 8f42a5cb37..14d755e1b2 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 @@ -30,13 +30,32 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters var streamContent = new StreamContent(memoryStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/rtf"); - - requestMessage.Content = new MultipartFormDataContent {{streamContent, "file", "upload.rtf"}}; + requestMessage.Content = new MultipartFormDataContent {{ streamContent, "file", "upload.rtf" }}; var response = await Client.SendAsync(requestMessage); (await response.Content.ReadAsStringAsync()).ShouldBe("UploadAsync:application/rtf:upload.rtf"); } } + + [Fact] + public async Task UploadRawAsync() + { + using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/remote-stream-content-test/upload-raw")) + { + var memoryStream = new MemoryStream(); + var text = @"{ ""hello"": ""world"" }"; + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes(text)); + memoryStream.Position = 0; + + var streamContent = new StreamContent(memoryStream); + streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + requestMessage.Content = streamContent; + + var response = await Client.SendAsync(requestMessage); + (await response.Content.ReadAsStringAsync()).ShouldBe($"{text}:application/json"); + } + } } } 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 d92e1ad5d1..b040c3e3d4 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 @@ -49,7 +49,7 @@ namespace Volo.Abp.Http.DynamicProxying { var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto()); people.TotalCount.ShouldBeGreaterThan(0); - people.Items.Count.ShouldBe((int) people.TotalCount); + people.Items.Count.ShouldBe((int)people.TotalCount); } [Fact] @@ -90,8 +90,7 @@ namespace Volo.Abp.Http.DynamicProxying { Name = uniquePersonName, Age = 42 - } - ); + }); person.ShouldNotBeNull(); person.Id.ShouldNotBe(Guid.Empty); @@ -110,8 +109,7 @@ namespace Volo.Abp.Http.DynamicProxying var person = await _peopleAppService.CreateAsync(new PersonDto { Age = 42 - } - ); + }); }); } @@ -195,10 +193,22 @@ 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, "upload.rtf") - { - ContentType = "application/rtf" - }); + + var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream, "upload.rtf", "application/rtf")); + result.ShouldBe("UploadAsync:application/rtf:upload.rtf"); + } + + [Fact] + public async Task UploadPartialAsync() + { + var memoryStream = new MemoryStream(); + var rawData = new byte[16]; + var text = Encoding.UTF8.GetBytes("UploadAsync"); + await memoryStream.WriteAsync(rawData); + await memoryStream.WriteAsync(text); + memoryStream.Position = rawData.Length; + + var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream, "upload.rtf", "application/rtf")); result.ShouldBe("UploadAsync:application/rtf:upload.rtf"); } @@ -215,15 +225,8 @@ namespace Volo.Abp.Http.DynamicProxying var result = await _peopleAppService.UploadMultipleAsync(new List() { - new RemoteStreamContent(memoryStream, "File1.rtf") - { - ContentType = "application/rtf" - }, - - new RemoteStreamContent(memoryStream2, "File2.rtf") - { - ContentType = "application/rtf2" - } + new RemoteStreamContent(memoryStream, "File1.rtf", "application/rtf"), + new RemoteStreamContent(memoryStream2, "File2.rtf", "application/rtf2") }); result.ShouldBe("File1:application/rtf:File1.rtfFile2:application/rtf2:File2.rtf"); } @@ -237,10 +240,7 @@ namespace Volo.Abp.Http.DynamicProxying var result = await _peopleAppService.CreateFileAsync(new CreateFileInput() { Name = "123.rtf", - Content = new RemoteStreamContent(memoryStream, "create.rtf") - { - ContentType = "application/rtf" - } + Content = new RemoteStreamContent(memoryStream, "create.rtf", "application/rtf") }); result.ShouldBe("123.rtf:CreateFileAsync:application/rtf:create.rtf"); } @@ -265,23 +265,13 @@ namespace Volo.Abp.Http.DynamicProxying Name = "123.rtf", Contents = new List() { - new RemoteStreamContent(memoryStream, "1-1.rtf") - { - ContentType = "application/rtf" - }, - - new RemoteStreamContent(memoryStream2, "1-2.rtf") - { - ContentType = "application/rtf2" - } + new RemoteStreamContent(memoryStream, "1-1.rtf", "application/rtf"), + new RemoteStreamContent(memoryStream2, "1-2.rtf", "application/rtf2") }, Inner = new CreateFileInput() { Name = "789.rtf", - Content = new RemoteStreamContent(memoryStream3, "i-789.rtf") - { - ContentType = "application/rtf3" - } + Content = new RemoteStreamContent(memoryStream3, "i-789.rtf", "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 f3dfef2891..a3d8764423 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 @@ -72,11 +72,9 @@ namespace Volo.Abp.TestApp.Application { var memoryStream = new MemoryStream(); await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync")); + memoryStream.Position = 0; - return new RemoteStreamContent(memoryStream, "download.rtf") - { - ContentType = "application/rtf" - }; + return new RemoteStreamContent(memoryStream, "download.rtf", "application/rtf"); } public async Task UploadAsync(IRemoteStreamContent streamContent) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/MediaDescriptors/CreateMediaInputStream.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/MediaDescriptors/CreateMediaInputStream.cs deleted file mode 100644 index 0f66ab5fef..0000000000 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application.Contracts/Volo/CmsKit/Admin/MediaDescriptors/CreateMediaInputStream.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.IO; -using Volo.Abp.Content; -using Volo.Abp.Validation; -using Volo.CmsKit.MediaDescriptors; - -namespace Volo.CmsKit.Admin.MediaDescriptors -{ - public class CreateMediaInputStream : RemoteStreamContent - { - [Required] - [DynamicStringLength(typeof(MediaDescriptorConsts), nameof(MediaDescriptorConsts.MaxEntityTypeLength))] - public string EntityType { get; set; } - - [Required] - [DynamicStringLength(typeof(MediaDescriptorConsts), nameof(MediaDescriptorConsts.MaxNameLength))] - public string Name { get; set; } - - public CreateMediaInputStream(Stream stream) : base(stream) - { - } - } -} \ No newline at end of file diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/MediaDescriptors/MediaDescriptorAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/MediaDescriptors/MediaDescriptorAdminAppService.cs index fa799cb65b..bab338e503 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/MediaDescriptors/MediaDescriptorAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/MediaDescriptors/MediaDescriptorAdminAppService.cs @@ -18,7 +18,7 @@ namespace Volo.CmsKit.Admin.MediaDescriptors public MediaDescriptorAdminAppService( IBlobContainer mediaContainer, IMediaDescriptorRepository mediaDescriptorRepository, - MediaDescriptorManager mediaDescriptorManager, + MediaDescriptorManager mediaDescriptorManager, IMediaDescriptorDefinitionStore mediaDescriptorDefinitionStore) { MediaContainer = mediaContainer; @@ -34,15 +34,12 @@ namespace Volo.CmsKit.Admin.MediaDescriptors /* TODO: Shouldn't CreatePolicies be a dictionary and we check for inputStream.EntityType? */ await CheckAnyOfPoliciesAsync(definition.CreatePolicies); - using (var stream = inputStream.File.GetStream()) - { - var newEntity = await MediaDescriptorManager.CreateAsync(entityType, inputStream.Name, inputStream.File.ContentType, inputStream.File.ContentLength ?? 0); + var newEntity = await MediaDescriptorManager.CreateAsync(entityType, inputStream.Name, inputStream.File.ContentType, inputStream.File.ContentLength ?? 0); - await MediaContainer.SaveAsync(newEntity.Id.ToString(), stream); - await MediaDescriptorRepository.InsertAsync(newEntity); + await MediaContainer.SaveAsync(newEntity.Id.ToString(), inputStream.File.GetStream()); + await MediaDescriptorRepository.InsertAsync(newEntity); - return ObjectMapper.Map(newEntity); - } + return ObjectMapper.Map(newEntity); } public virtual async Task DeleteAsync(Guid id) @@ -58,4 +55,4 @@ namespace Volo.CmsKit.Admin.MediaDescriptors await MediaDescriptorRepository.DeleteAsync(id); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/MediaDescriptors/MediaDescriptorAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/MediaDescriptors/MediaDescriptorAppService.cs index 0068390312..99700630e7 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/MediaDescriptors/MediaDescriptorAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Common.Application/Volo/CmsKit/MediaDescriptors/MediaDescriptorAppService.cs @@ -24,10 +24,7 @@ namespace Volo.CmsKit.MediaDescriptors var entity = await MediaDescriptorRepository.GetAsync(id); var stream = await MediaContainer.GetAsync(id.ToString()); - return new RemoteStreamContent(stream) - { - ContentType = entity.MimeType - }; + return new RemoteStreamContent(stream, entity.Name, entity.MimeType); } } -} \ No newline at end of file +} diff --git a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/MediaDescriptors/MediaDescriptorAdminAppService_Tests.cs b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/MediaDescriptors/MediaDescriptorAdminAppService_Tests.cs index 38a8b472bd..2559d6ab90 100644 --- a/modules/cms-kit/test/Volo.CmsKit.Application.Tests/MediaDescriptors/MediaDescriptorAdminAppService_Tests.cs +++ b/modules/cms-kit/test/Volo.CmsKit.Application.Tests/MediaDescriptors/MediaDescriptorAdminAppService_Tests.cs @@ -35,15 +35,12 @@ namespace Volo.CmsKit.MediaDescriptors var media = await _mediaDescriptorAdminAppService.CreateAsync(_cmsKitTestData.Media_1_EntityType, new CreateMediaInputWithStream { Name = mediaName, - File = new RemoteStreamContent(stream) - { - ContentType = mediaType - } + File = new RemoteStreamContent(stream, mediaName, mediaType) }); - + media.ShouldNotBeNull(); } - + [Fact] public async Task Should_Delete_Media() { @@ -52,4 +49,4 @@ namespace Volo.CmsKit.MediaDescriptors (await _mediaDescriptorRepository.FindAsync(_cmsKitTestData.Media_1_Id)).ShouldBeNull(); } } -} \ No newline at end of file +}