hi
The fix code:
AbpSolution1HttpApiHostModule:
public override void ConfigureServices(ServiceConfigurationContext context)
{
PostConfigure<MvcOptions>(options =>
{
options.ModelBinderProviders.RemoveAll(x => x.GetType() == typeof(AbpRemoteStreamContentModelBinderProvider));
options.ModelBinderProviders.Insert(2, new MyAbpRemoteStreamContentModelBinderProvider());
});
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Volo.Abp.AspNetCore.Mvc.ContentFormatters;
using Volo.Abp.Content;
namespace AbpSolution1;
public class MyAbpRemoteStreamContentModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(RemoteStreamContent) ||
typeof(IEnumerable<RemoteStreamContent>).IsAssignableFrom(context.Metadata.ModelType))
{
return new MyAbpRemoteStreamContentModelBinder<RemoteStreamContent>();
}
if (context.Metadata.ModelType == typeof(IRemoteStreamContent) ||
typeof(IEnumerable<IRemoteStreamContent>).IsAssignableFrom(context.Metadata.ModelType))
{
return new MyAbpRemoteStreamContentModelBinder<IRemoteStreamContent>();
}
return null;
}
}
public class MyAbpRemoteStreamContentModelBinder<TRemoteStreamContent> : IModelBinder
where TRemoteStreamContent: class, IRemoteStreamContent
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var postedFiles = GetCompatibleCollection<TRemoteStreamContent>(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
// 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(TRemoteStreamContent))
{
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(TRemoteStreamContent[]))
{
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<TRemoteStreamContent> postedFiles)
{
var request = bindingContext.HttpContext.Request;
if (request.HasFormContentType)
{
var form = await request.ReadFormAsync();
var useMemoryStream = form.Files.Count > 1;
foreach (var file in form.Files)
{
// If there is an <input type="file" ... /> in the form and is left blank.
if (file.Length == 0 && string.IsNullOrEmpty(file.FileName))
{
continue;
}
if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase))
{
if (useMemoryStream)
{
var memoryStream = new MemoryStream();
await file.OpenReadStream().CopyToAsync(memoryStream);
memoryStream.Position = 0;
postedFiles.Add(new RemoteStreamContent(memoryStream, file.FileName, file.ContentType, file.Length, disposeStream: false).As<TRemoteStreamContent>());
bindingContext.HttpContext.Response.OnCompleted(async () =>
{
await memoryStream.DisposeAsync();
});
}
else
{
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.FileName, file.ContentType, file.Length, disposeStream: false).As<TRemoteStreamContent>());
}
}
}
}
else if (bindingContext.IsTopLevelObject)
{
postedFiles.Add(new RemoteStreamContent(request.Body, null, request.ContentType, request.ContentLength).As<TRemoteStreamContent>());
}
}
private static ICollection<T> GetCompatibleCollection<T>(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<T> 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<T>, then activate
// an instance and return that.
//
// Otherwise just give up.
if (typeof(T).IsAssignableFrom(modelType))
{
return new List<T>();
}
if (modelType == typeof(T[]))
{
return new List<T>();
}
// Does collection exist and can it be reused?
if (model is ICollection<T> collection && !collection.IsReadOnly)
{
collection.Clear();
return collection;
}
if (modelType.IsAssignableFrom(typeof(List<T>)))
{
return new List<T>();
}
return (ICollection<T>)Activator.CreateInstance(modelType)!;
}
}
Hi
liming.ma@volosoft.com
hi
Can you share a test project? I will download and check it ,then I will find out the reason. Thanks.
hi
Here is my test code, it works. What should I change to reproduce the exception?
Thanks
public class HomeController : AbpController
{
public async Task<ActionResult> Index(MyFiles file)
{
using (var ms = new MemoryStream())
{
await file.Files.First().GetStream().CopyToAsync(ms);
var filename = file.Files.First().FileName;
var fileContent = ms.ToArray();
}
using (var ms = new MemoryStream())
{
await file.Files.Last().GetStream().CopyToAsync(ms);
var filename = file.Files.Last().FileName;
var fileContent = ms.ToArray();
}
return new OkResult();
}
}
public class MyFiles
{
public List<IRemoteStreamContent> Files { get; set; }
}
Thanks. I will check it asap.
👍
hi @blepo
Can you share your test Blazor project?
I will try to reproduce it in the local environment and find out the reason.
You can share it with liming.ma@volosoft.com
Thanks.
hi
Can you just share the full logs?
I will check it.
Thanks.
Hi
Can you share the logs that contain the http request info?
The context logs .
https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems
You can share it with liming.ma@volosoft.com
Thanks
Hi
Can you share full logs?
Thanks.