User model binding in Asp.Net Core

I am trying to associate a model with the IFormFile or IFormFileCollection property in my regular CommonFile class. I did not find as much documentation on the Internet about this using the asp.net core, I tried to follow this link User-specific model binding in ASP.Net Core 1.0 but it binds the SimpleType property and I need to bind a complex type. Anyway, I tried to make my version of this binding, and I have the following code:

FormFileModelBinderProvider.cs

public class FormFileModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (!context.Metadata.IsComplexType) return null; var isIEnumerableFormFiles = context.Metadata.ModelType.GetInterfaces().Contains(typeof(IEnumerable<CommonFile>)); var isFormFile = context.Metadata.ModelType.IsAssignableFrom(typeof(CommonFile)); if (!isFormFile && !isIEnumerableFormFiles) return null; var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder); return new FormFileModelBinder(propertyBinders); } } 

FromFileModelBinder.cs

the following code is incomplete because I am not getting any result using bindingContext.ValueProvider.GetValue(bindingContext.ModelName); while I am debugging everything is going fine, while bindContext.ModelName doesn't matter and I can't bind my model. From httpContext to a strongly typed Model.

 public class FormFileModelBinder : IModelBinder { private readonly ComplexTypeModelBinder _baseBinder; public FormFileModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) { _baseBinder = new ComplexTypeModelBinder(propertyBinders); } public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); return Task.CompletedTask; } } 

Any suggestions?

+6
source share
1 answer

After 10 months, I found the solution I wanted to make.

In summary: I want to replace IFormFile IFormFileCollection with my own classes not bound to Asp. Net, because my view models are in another project with poco classes. My custom classes are: ICommonFile, ICommonFileCollection, IFormFile (not the main Asp.net class), and IFormFileCollection.

I will talk about it here:

ICommonFile.cs

 /// <summary> /// File with common Parameters including bytes /// </summary> public interface ICommonFile { /// <summary> /// Stream File /// </summary> Stream File { get; } /// <summary> /// Name of the file /// </summary> string Name { get; } /// <summary> /// Gets the file name with extension. /// </summary> string FileName { get; } /// <summary> /// Gets the file length in bytes. /// </summary> long Length { get; } /// <summary> /// Copies the contents of the uploaded file to the <paramref name="target"/> stream. /// </summary> /// <param name="target">The stream to copy the file contents to.</param> void CopyTo(Stream target); /// <summary> /// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream. /// </summary> /// <param name="target">The stream to copy the file contents to.</param> /// <param name="cancellationToken">Enables cooperative cancellation between threads</param> Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken)); } 

ICommonFileCollection.cs

 /// <inheritdoc /> /// <summary> /// Represents the collection of files. /// </summary> public interface ICommonFileCollection : IReadOnlyList<ICommonFile> { /// <summary> /// File Indexer by name /// </summary> /// <param name="name">File name index</param> /// <returns>File with related file name index</returns> ICommonFile this[string name] { get; } /// <summary> /// Gets file by name /// </summary> /// <param name="name">file name</param> /// <returns>File with related file name index</returns> ICommonFile GetFile(string name); /// <summary> /// Gets Files by name /// </summary> /// <param name="name"></param>> /// <returns>Files with related file name index</returns> IReadOnlyList<ICommonFile> GetFiles(string name); } 

IFormFile.cs

  /// <inheritdoc /> /// <summary> /// File transferred by HttpProtocol, this is an independent /// Asp.net core interface /// </summary> public interface IFormFile : ICommonFile { /// <summary> /// Gets the raw Content-Type header of the uploaded file. /// </summary> string ContentType { get; } /// <summary> /// Gets the raw Content-Disposition header of the uploaded file. /// </summary> string ContentDisposition { get; } } 

IFormFileCollection.cs

 /// <summary> /// File Collection transferred by HttpProtocol, this is an independent /// Asp.net core implementation /// </summary> public interface IFormFileCollection { //Use it when you need to implement new features to Form File collection over HttpProtocol } 

Finally, I created my model bindings, I will also share it:

FormFileModelBinderProvider.cs

  /// <inheritdoc /> /// <summary> /// Model Binder Provider, it inspects /// any model when the request is triggered /// </summary> public class FormFileModelBinderProvider : IModelBinderProvider { /// <inheritdoc /> /// <summary> /// Inspects a Model for any CommonFile class or Collection with /// same class if exist the FormFileModelBinder initiates /// </summary> /// <param name="context">Model provider context</param> /// <returns>a new Instance o FormFileModelBinder if type is found otherwise null</returns> public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (!context.Metadata.IsComplexType) return null; var isSingleCommonFile = IsSingleCommonFile(context.Metadata.ModelType); var isCommonFileCollection = IsCommonFileCollection(context.Metadata.ModelType); if (!isSingleCommonFile && !isCommonFileCollection) return null; return new FormFileModelBinder(); } /// <summary> /// Checks if object type is a CommonFile Collection /// </summary> /// <param name="modelType">Context Meta data ModelType</param> /// <returns>If modelType is a collection of CommonFile returns true otherwise false</returns> private static bool IsCommonFileCollection(Type modelType) { if (typeof(ICommonFileCollection).IsAssignableFrom(modelType)) { return true; } var hasCommonFileArguments = modelType.GetGenericArguments() .AsParallel().Any(t => typeof(ICommonFile).IsAssignableFrom(t)); if (typeof(IEnumerable).IsAssignableFrom(modelType) && hasCommonFileArguments) { return true; } if (typeof(IAsyncEnumerable<object>).IsAssignableFrom(modelType) && hasCommonFileArguments) { return true; } return false; } /// <summary> /// Checks if object type is CommonFile or an implementation of ICommonFile /// </summary> /// <param name="modelType"></param> /// <returns></returns> private static bool IsSingleCommonFile(Type modelType) { if (modelType == typeof(ICommonFile) || modelType.GetInterfaces().Contains(typeof(ICommonFile))) { return true; } return false; } } 

FormFileModelBinder.cs

 /// <inheritdoc /> /// <summary> /// Form File Model binder /// Parses the Form file object type to a commonFile /// </summary> public class FormFileModelBinder : IModelBinder { /// <summary> /// Expression to map IFormFile object type to CommonFile /// </summary> private readonly Func<Microsoft.AspNetCore.Http.IFormFile, ICommonFile> _expression; /// <summary> /// FormFile Model binder constructor /// </summary> public FormFileModelBinder() { _expression = x => new CommonFile(x.OpenReadStream(), x.Length, x.Name, x.FileName); } /// <inheritdoc /> /// <summary> /// It Binds IFormFile to Common file, getting the file /// from the binding context /// </summary> /// <param name="bindingContext">Http Context</param> /// <returns>Completed Task</returns> // TODO: Bind this context to ICommonFile or ICommonFileCollection object public Task BindModelAsync(ModelBindingContext bindingContext) { dynamic model; if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); var formFiles = bindingContext.ActionContext.HttpContext.Request.Form.Files; if (!formFiles.Any()) return Task.CompletedTask; if (formFiles.Count > 1) { model = formFiles.AsParallel().Select(_expression); } else { model = new FormFileCollection(); model.AddRange(filteredFiles.AsParallel().Select(_expression)); } bindingContext.Result = ModelBindingResult.Success(model); return Task.CompletedTask; } } 

In fact, everything works well, except when I have nested models. I give an example of my models that I use, and I will make some comments with working scripts that are not Test.cs

 public class Test { //It Working public ICommonFileCollection Files { get; set; } //It Working public ICommonFileCollection Files2 { get; set; } //This is a nested model public TestExtra TestExtra { get; set; } } 

TestExtra.cs

 public class TestExtra { //It not working public ICommonFileCollection Files { get; set; } } 

Actually, when I make a request to my API, I have the following (Screenshot): Visual Studio Debugging

I share a screenshot of my postman request, too, to refine my request. Mail request

If there is any attempt to make this work with a nested model, it would be great.

The UPDATE Asp Net Core Model Binder will not bind the model to only one property, if you have one property in the class, this property will be null, but if you add two or more, it will be associated with it. my mistake: I had one property in a nested class. All code is correct.

+1
source

All Articles