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 000716fa62..ea633f569a 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 @@ -8,7 +8,8 @@ using Volo.Abp.Content; namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters; -public class AbpRemoteStreamContentModelBinder : IModelBinder +public class AbpRemoteStreamContentModelBinder : IModelBinder + where TRemoteStreamContent: class, IRemoteStreamContent { public async Task BindModelAsync(ModelBindingContext bindingContext) { @@ -17,7 +18,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder throw new ArgumentNullException(nameof(bindingContext)); } - var postedFiles = new List(); + var postedFiles = GetCompatibleCollection(bindingContext); // 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 @@ -42,7 +43,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder } object value; - if (bindingContext.ModelType == typeof(IRemoteStreamContent) || bindingContext.ModelType == typeof(RemoteStreamContent)) + if (bindingContext.ModelType == typeof(TRemoteStreamContent)) { if (postedFiles.Count == 0) { @@ -63,7 +64,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder // Perform any final type mangling needed. var modelType = bindingContext.ModelType; - if (modelType == typeof(IRemoteStreamContent[]) || modelType == typeof(RemoteStreamContent[])) + if (modelType == typeof(TRemoteStreamContent[])) { value = postedFiles.ToArray(); } @@ -91,7 +92,7 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder private async Task GetFormFilesAsync( string modelName, ModelBindingContext bindingContext, - ICollection postedFiles) + ICollection postedFiles) { var request = bindingContext.HttpContext.Request; if (request.HasFormContentType) @@ -108,13 +109,52 @@ public class AbpRemoteStreamContentModelBinder : IModelBinder if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)) { - postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length)); + postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length).As()); } } } else if (bindingContext.IsTopLevelObject) { - postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength)); + postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength).As()); } } + + private static ICollection GetCompatibleCollection(ModelBindingContext bindingContext) + { + var model = bindingContext.Model; + var modelType = bindingContext.ModelType; + + // There's a limited set of collection types we can create here. + // + // For the simple cases: Choose List if the destination type supports it (at least as an intermediary). + // + // For more complex cases: If the destination type is a class that implements ICollection, then activate + // an instance and return that. + // + // Otherwise just give up. + if (typeof(T).IsAssignableFrom(modelType)) + { + return new List(); + } + + if (modelType == typeof(T[])) + { + return new List(); + } + + // Does collection exist and can it be reused? + if (model is ICollection collection && !collection.IsReadOnly) + { + collection.Clear(); + + return collection; + } + + if (modelType.IsAssignableFrom(typeof(List))) + { + return new List(); + } + + return (ICollection)Activator.CreateInstance(modelType); + } } 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 index ef49dc89f6..7892e6a1c2 100644 --- 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 @@ -14,12 +14,16 @@ public class AbpRemoteStreamContentModelBinderProvider : IModelBinderProvider throw new ArgumentNullException(nameof(context)); } - if (context.Metadata.ModelType == typeof(IRemoteStreamContent) || - context.Metadata.ModelType == typeof(RemoteStreamContent) || - typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType) || + if (context.Metadata.ModelType == typeof(RemoteStreamContent) || typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType)) { - return new AbpRemoteStreamContentModelBinder(); + return new AbpRemoteStreamContentModelBinder(); + } + + if (context.Metadata.ModelType == typeof(IRemoteStreamContent) || + typeof(IEnumerable).IsAssignableFrom(context.Metadata.ModelType)) + { + return new AbpRemoteStreamContentModelBinder(); } return null; 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 b25eb8023d..fbaf6b4d3e 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 @@ -59,10 +59,10 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var id2 = Guid.NewGuid(); var @params = await _peopleAppService.GetParams(new List - { - id1, - id2 - }, new[] { "name1", "name2" }); + { + id1, + id2 + }, new[] { "name1", "name2" }); @params.ShouldContain(id1.ToString("N")); @params.ShouldContain(id2.ToString("N")); @@ -224,10 +224,10 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase memoryStream2.Position = 0; var result = await _peopleAppService.UploadMultipleAsync(new List() - { - new RemoteStreamContent(memoryStream, "File1.rtf", "application/rtf"), - new RemoteStreamContent(memoryStream2, "File2.rtf", "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"); } @@ -263,11 +263,11 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var result = await _peopleAppService.CreateMultipleFileAsync(new CreateMultipleFileInput() { Name = "123.rtf", - Contents = new List() - { - new RemoteStreamContent(memoryStream, "1-1.rtf", "application/rtf"), - new RemoteStreamContent(memoryStream2, "1-2.rtf", "application/rtf2") - }, + Contents = new List() + { + new RemoteStreamContent(memoryStream, "1-1.rtf", "application/rtf"), + new RemoteStreamContent(memoryStream2, "1-2.rtf", "application/rtf2") + }, Inner = new CreateFileInput() { Name = "789.rtf", @@ -283,18 +283,18 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var result = await _peopleAppService.GetParamsFromQueryAsync(new GetParamsInput() { NameValues = new List() + { + new GetParamsNameValue() { - new GetParamsNameValue() - { - Name = "name1", - Value = "value1" - }, - new GetParamsNameValue() - { - Name = "name2", - Value = "value2" - } + Name = "name1", + Value = "value1" }, + new GetParamsNameValue() + { + Name = "name2", + Value = "value2" + } + }, NameValue = new GetParamsNameValue() { Name = "name3", @@ -310,18 +310,18 @@ public class PersonAppServiceClientProxy_Tests : AbpHttpClientTestBase var result = await _peopleAppService.GetParamsFromFormAsync(new GetParamsInput() { NameValues = new List() + { + new GetParamsNameValue() { - new GetParamsNameValue() - { - Name = "name1", - Value = "value1" - }, - new GetParamsNameValue() - { - Name = "name2", - Value = "value2" - } + Name = "name1", + Value = "value1" }, + new GetParamsNameValue() + { + Name = "name2", + Value = "value2" + } + }, NameValue = new GetParamsNameValue() { Name = "name3", 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 index 9c81620bdd..d11809d810 100644 --- 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 @@ -6,5 +6,5 @@ public class CreateFileInput { public string Name { get; set; } - public RemoteStreamContent Content { get; set; } + public IRemoteStreamContent 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 index ca55eb30ec..99cbefc6cf 100644 --- 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 @@ -7,7 +7,7 @@ public class CreateMultipleFileInput { public string Name { get; set; } - public IEnumerable Contents { get; set; } + public IEnumerable Contents { get; set; } public CreateFileInput Inner { get; set; } }