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 873c532ba3..cfdeb2e707 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,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Auditing; using Volo.Abp.AspNetCore.Mvc.ContentFormatters; @@ -10,6 +13,7 @@ using Volo.Abp.AspNetCore.Mvc.ModelBinding; using Volo.Abp.AspNetCore.Mvc.Response; using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; +using Volo.Abp.Content; namespace Volo.Abp.AspNetCore.Mvc { @@ -27,7 +31,6 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddFormatters(MvcOptions options) { - options.InputFormatters.Insert(0, new RemoteStreamContentInputFormatter()); options.OutputFormatters.Insert(0, new RemoteStreamContentOutputFormatter()); } @@ -60,13 +63,16 @@ namespace Volo.Abp.AspNetCore.Mvc { options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider()); options.ModelBinderProviders.Insert(1, new AbpExtraPropertiesDictionaryModelBinderProvider()); + options.ModelBinderProviders.Insert(2, new AbpRemoteStreamContentModelBinderProvider()); } private static void AddMetadataProviders(MvcOptions options, IServiceCollection services) { - options.ModelMetadataDetailsProviders.Add( - new AbpDataAnnotationAutoLocalizationMetadataDetailsProvider(services) - ); + options.ModelMetadataDetailsProviders.Add(new AbpDataAnnotationAutoLocalizationMetadataDetailsProvider(services)); + + options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IRemoteStreamContent), BindingSource.FormFile)); + options.ModelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IEnumerable), BindingSource.FormFile)); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IRemoteStreamContent))); } } } 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 new file mode 100644 index 0000000000..8b7264fef0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Volo.Abp.Content; + +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters +{ + public class AbpRemoteStreamContentModelBinder : IModelBinder + { + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + var postedFiles = new List(); + + // If we're at the top level, then use the FieldName (parameter or property name). + // This handles the fact that there will be nothing in the ValueProviders for this parameter + // and so we'll do the right thing even though we 'fell-back' to the empty prefix. + var modelName = bindingContext.IsTopLevelObject + ? bindingContext.BinderModelName ?? bindingContext.FieldName + : bindingContext.ModelName; + + await GetFormFilesAsync(modelName, bindingContext, postedFiles); + + // If ParameterBinder incorrectly overrode ModelName, fall back to OriginalModelName prefix. Comparisons + // are tedious because e.g. top-level parameter or property is named Blah and it contains a BlahBlah + // property. OriginalModelName may be null in tests. + if (postedFiles.Count == 0 && + bindingContext.OriginalModelName != null && + !string.Equals(modelName, bindingContext.OriginalModelName, StringComparison.Ordinal) && + !modelName.StartsWith(bindingContext.OriginalModelName + "[", StringComparison.Ordinal) && + !modelName.StartsWith(bindingContext.OriginalModelName + ".", StringComparison.Ordinal)) + { + modelName = ModelNames.CreatePropertyModelName(bindingContext.OriginalModelName, modelName); + await GetFormFilesAsync(modelName, bindingContext, postedFiles); + } + + object value; + if (bindingContext.ModelType == typeof(IRemoteStreamContent) || bindingContext.ModelType == typeof(RemoteStreamContent)) + { + if (postedFiles.Count == 0) + { + // Silently fail if the named file does not exist in the request. + return; + } + + value = postedFiles.First(); + } + else + { + if (postedFiles.Count == 0 && !bindingContext.IsTopLevelObject) + { + // Silently fail if no files match. Will bind to an empty collection (treat empty as a success + // case and not reach here) if binding to a top-level object. + return; + } + + // Perform any final type mangling needed. + var modelType = bindingContext.ModelType; + if (modelType == typeof(IRemoteStreamContent[]) || modelType == typeof(RemoteStreamContent[])) + { + value = postedFiles.ToArray(); + } + else + { + value = postedFiles; + } + } + + // We need to add a ValidationState entry because the modelName might be non-standard. Otherwise + // the entry we create in model state might not be marked as valid. + bindingContext.ValidationState.Add(value, new ValidationStateEntry() + { + Key = modelName, + }); + + bindingContext.ModelState.SetModelValue( + modelName, + rawValue: null, + attemptedValue: null); + + bindingContext.Result = ModelBindingResult.Success(value); + } + + private async Task GetFormFilesAsync( + string modelName, + ModelBindingContext bindingContext, + ICollection postedFiles) + { + var request = bindingContext.HttpContext.Request; + if (request.HasFormContentType) + { + var form = await request.ReadFormAsync(); + + foreach (var file in form.Files) + { + // If there is an in the form and is left blank. + if (file.Length == 0 && string.IsNullOrEmpty(file.FileName)) + { + continue; + } + + if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)) + { + postedFiles.Add(new RemoteStreamContent(file.OpenReadStream()) + { + ContentType = file.ContentType + }); + } + } + } + else + { + postedFiles.Add(new RemoteStreamContent(request.Body) + { + ContentType = request.ContentType + }); + } + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs new file mode 100644 index 0000000000..6c2e106c57 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinderProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Volo.Abp.Content; + +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters +{ + public class AbpRemoteStreamContentModelBinderProvider : IModelBinderProvider + { + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType == typeof(IRemoteStreamContent) || + context.Metadata.ModelType == typeof(RemoteStreamContent) || + typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType) || + typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType)) + { + return new AbpRemoteStreamContentModelBinder(); + } + + return null; + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentInputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentInputFormatter.cs deleted file mode 100644 index 340544f79d..0000000000 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentInputFormatter.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.Net.Http.Headers; -using Volo.Abp.Content; - -namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters -{ - public class RemoteStreamContentInputFormatter : InputFormatter - { - public RemoteStreamContentInputFormatter() - { - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*")); - } - - protected override bool CanReadType(Type type) - { - return type == typeof(IRemoteStreamContent) || - type == typeof(RemoteStreamContent); - } - - public override Task ReadRequestBodyAsync(InputFormatterContext context) - { - return InputFormatterResult.SuccessAsync(new RemoteStreamContent(context.HttpContext.Request.Body) - { - ContentType = context.HttpContext.Request.ContentType - }); - } - } -} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs index d22fb78aab..6327fe9008 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpConventionalControllerOptions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Reflection; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; +using Volo.Abp.Content; using Volo.Abp.Http.Modeling; namespace Volo.Abp.AspNetCore.Mvc.Conventions @@ -25,7 +26,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions FormBodyBindingIgnoredTypes = new List { - typeof(IFormFile) + typeof(IFormFile), + typeof(IRemoteStreamContent) }; } 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 7aacfaad02..6b82951f2f 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 @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using JetBrains.Annotations; using Volo.Abp.Content; @@ -68,7 +69,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { var parameters = action .Parameters - .Where(p => p.BindingSourceId == ParameterBindingSources.Form) + .Where(p => p.BindingSourceId == ParameterBindingSources.Form || p.BindingSourceId == ParameterBindingSources.FormFile) .ToArray(); if (!parameters.Any()) @@ -76,24 +77,61 @@ namespace Volo.Abp.Http.Client.DynamicProxying return null; } - var postDataBuilder = new StringBuilder(); - - var isFirstParam = true; - foreach (var queryStringParameter in parameters) + if (parameters.Any(x => x.BindingSourceId == ParameterBindingSources.FormFile)) { - var value = HttpActionParameterHelper.FindParameterValue(methodArguments, queryStringParameter); - if (value == null) + var postDataBuilder = new MultipartFormDataContent(); + foreach (var parameter in parameters) { - continue; - } + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); + if (value == null) + { + continue; + } - postDataBuilder.Append(isFirstParam ? "?" : "&"); - postDataBuilder.Append(queryStringParameter.Name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); + if (value is IRemoteStreamContent remoteStreamContent) + { + var streamContent = new StreamContent(remoteStreamContent.GetStream()); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType); + postDataBuilder.Add(streamContent, parameter.Name, parameter.Name); + } + else if (value is IEnumerable remoteStreamContents) + { + foreach (var content in remoteStreamContents) + { + var streamContent = new StreamContent(content.GetStream()); + streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType); + postDataBuilder.Add(streamContent, parameter.Name, parameter.Name); + } + } + else + { + postDataBuilder.Add(new StringContent(value.ToString(), Encoding.UTF8), parameter.Name); + } + } - isFirstParam = false; + return postDataBuilder; } + else + { + var postDataBuilder = new StringBuilder(); - return new StringContent(postDataBuilder.ToString(), Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded); + var isFirstParam = true; + foreach (var parameter in parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Form)) + { + var value = HttpActionParameterHelper.FindParameterValue(methodArguments, parameter); + if (value == null) + { + continue; + } + + postDataBuilder.Append(isFirstParam ? "?" : "&"); + postDataBuilder.Append(parameter.Name + "=" + System.Net.WebUtility.UrlEncode(value.ToString())); + + isFirstParam = false; + } + + return new StringContent(postDataBuilder.ToString(), Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded); + } } } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs index ee710ebb08..b986867f4a 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs @@ -7,8 +7,9 @@ namespace Volo.Abp.Http.ProxyScripting.Generators public const string Body = "Body"; public const string Path = "Path"; public const string Form = "Form"; + public const string FormFile = "FormFile"; public const string Header = "Header"; public const string Custom = "Custom"; public const string Services = "Services"; } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs index 6a18b06620..966ab01840 100644 --- a/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs +++ b/framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpClientTestModule.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.Http.Client; using Volo.Abp.Http.DynamicProxying; using Volo.Abp.Http.Localization; @@ -7,6 +8,7 @@ using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; using Volo.Abp.TestApp; +using Volo.Abp.TestApp.Application.Dto; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Http @@ -44,6 +46,12 @@ namespace Volo.Abp.Http { options.MapCodeNamespace("Volo.Abp.Http.DynamicProxying", typeof(HttpClientTestResource)); }); + + Configure(options => + { + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateFileInput)); + options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateMultipleFileInput)); + }); } } } 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 c2fc5ea4b3..026373d568 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 @@ -4,10 +4,13 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using NSubstitute.Extensions; using Shouldly; using Volo.Abp.Application.Dtos; +using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.Content; using Volo.Abp.Domain.Repositories; using Volo.Abp.Http.Client; @@ -197,5 +200,90 @@ namespace Volo.Abp.Http.DynamicProxying }); result.ShouldBe("UploadAsync:application/rtf"); } + + [Fact] + public async Task UploadMultipleAsync() + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("File1")); + memoryStream.Position = 0; + + var memoryStream2 = new MemoryStream(); + await memoryStream2.WriteAsync(Encoding.UTF8.GetBytes("File2")); + memoryStream2.Position = 0; + + var result = await _peopleAppService.UploadMultipleAsync(new List() + { + new RemoteStreamContent(memoryStream) + { + ContentType = "application/rtf" + }, + + new RemoteStreamContent(memoryStream2) + { + ContentType = "application/rtf2" + } + }); + result.ShouldBe("File1:application/rtfFile2:application/rtf2"); + } + + [Fact] + public async Task CreateFileAsync() + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("CreateFileAsync")); + memoryStream.Position = 0; + var result = await _peopleAppService.CreateFileAsync(new CreateFileInput() + { + Name = "123.rtf", + Content = new RemoteStreamContent(memoryStream) + { + ContentType = "application/rtf" + } + }); + result.ShouldBe("123.rtf:CreateFileAsync:application/rtf"); + } + + [Fact] + public async Task CreateMultipleFileAsync() + { + var memoryStream = new MemoryStream(); + await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("File1")); + memoryStream.Position = 0; + + var memoryStream2 = new MemoryStream(); + await memoryStream2.WriteAsync(Encoding.UTF8.GetBytes("File2")); + memoryStream2.Position = 0; + + var memoryStream3 = new MemoryStream(); + await memoryStream3.WriteAsync(Encoding.UTF8.GetBytes("File3")); + memoryStream3.Position = 0; + + var result = await _peopleAppService.CreateMultipleFileAsync(new CreateMultipleFileInput() + { + Name = "123.rtf", + Contents = new List() + { + new RemoteStreamContent(memoryStream) + { + ContentType = "application/rtf" + }, + + new RemoteStreamContent(memoryStream2) + { + ContentType = "application/rtf2" + } + }, + Inner = new CreateFileInput() + { + Name = "789.rtf", + Content = new RemoteStreamContent(memoryStream3) + { + ContentType = "application/rtf3" + } + } + }); + result.ShouldBe("123.rtf:File1:application/rtf123.rtf:File2:application/rtf2789.rtf:File3:application/rtf3"); + } } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs new file mode 100644 index 0000000000..9f0b3fe4e7 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateFileInput.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Content; + +namespace Volo.Abp.TestApp.Application.Dto +{ + public class CreateFileInput + { + public string Name { get; set; } + + public RemoteStreamContent Content { get; set; } + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs new file mode 100644 index 0000000000..e9697af933 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/Dto/CreateMultipleFileInput.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Volo.Abp.Content; + +namespace Volo.Abp.TestApp.Application.Dto +{ + public class CreateMultipleFileInput + { + public string Name { get; set; } + + public IEnumerable Contents { get; set; } + + public CreateFileInput Inner { get; set; } + } +} 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 2344cb7c9b..844e7fd383 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 @@ -25,5 +25,12 @@ namespace Volo.Abp.TestApp.Application Task DownloadAsync(); Task UploadAsync(IRemoteStreamContent streamContent); + + Task UploadMultipleAsync(IEnumerable streamContents); + + Task CreateFileAsync(CreateFileInput input); + + Task CreateMultipleFileAsync(CreateMultipleFileInput input); + } } 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 788304900d..edd6a3b93f 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 @@ -86,5 +86,46 @@ namespace Volo.Abp.TestApp.Application return await reader.ReadToEndAsync() + ":" + streamContent.ContentType; } } + + public async Task UploadMultipleAsync(IEnumerable streamContents) + { + var str = ""; + foreach (var content in streamContents) + { + using (var reader = new StreamReader(content.GetStream())) + { + str += await reader.ReadToEndAsync() + ":" + content.ContentType; + } + } + + return str; + } + + public async Task CreateFileAsync(CreateFileInput input) + { + using (var reader = new StreamReader(input.Content.GetStream())) + { + return input.Name + ":" + await reader.ReadToEndAsync() + ":" + input.Content.ContentType; + } + } + + public async Task CreateMultipleFileAsync(CreateMultipleFileInput input) + { + var str = ""; + foreach (var content in input.Contents) + { + using (var reader = new StreamReader(content.GetStream())) + { + str += input.Name + ":" + await reader.ReadToEndAsync() + ":" + content.ContentType; + } + } + + using (var reader = new StreamReader(input.Inner.Content.GetStream())) + { + str += input.Inner.Name + ":" + await reader.ReadToEndAsync() + ":" + input.Inner.Content.ContentType; + } + + return str; + } } }