Search code examples
asp.net-mvcfile-uploadmodel-bindingredactor

How to bind posted data named "file[]" to an MVC model?


I am using Redactor as an HTML editor, which has a component for uploading images and files.

Redactor takes care of the client side bit, and I need to provide the server side upload functionality.

I have no problem getting the uploads to work if I use Request.Files in the controller.

But I would like to bind the posted files to a Model, and I seem unable to do this, because the parameter they are sent with is files[] - with square brackets in the name.

My question:

Is it possible to bind the posted "file[]" to an MVC model? It's an invalid property name, and using file alone doesn't work.


This file input looks like this. I can specify a name other than file, but Redactor adds [] to the end, regardless of the name.

<input type="file" name="file" multiple="multiple" style="display: none;">

I am trying to bind to a property like this:

public HttpPostedFileBase[] File { get; set; }

When I watch the upload take place, I see this in the request (I presume that redactor may be adding the square brackets behind the scenes):

Content-Disposition: form-data; name="file[]"; filename="my-image.jpg"

Also relevant:

Redactor always sends the uploading request with content-type as multipart/form-data. So you don't need to add this enctype anywhere


Solution

  • You should create a custom model binder to bind uploaded files to one property. First create a model with a HttpPostedFileBase[] property

    public class RactorModel
    {
        public HttpPostedFileBase[] Files { get; set; }
    }
    

    then implement DefaultModelBinder and override BindProperty

    public class RactorModelBinder : DefaultModelBinder
    {
        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            int len = controllerContext.HttpContext.Request.Files.AllKeys.Length;
    
            if (len > 0)
            {
                if (propertyDescriptor.PropertyType == typeof(HttpPostedFileBase[]))
                {
                    string formName = string.Format("{0}[]", propertyDescriptor.Name);
                    HttpPostedFileBase[] files = new HttpPostedFileBase[len];
                    for (int i = 0; i < len; i++)
                    {
                        files[i] = controllerContext.HttpContext.Request.Files[i];
                    }
    
                    propertyDescriptor.SetValue(bindingContext.Model, files);
                    return;
                }
            }
    
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }
    

    Also you should add binder provider to your project, then register it in global.asax

    public class RactorModenBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(Type modelType)
        {
            if (modelType == typeof(RactorModel))
            {
                return new RactorModelBinder();
            }
    
            return null;
        }
    }
    ...
    ModelBinderProviders.BinderProviders.Insert(0, new RactorModenBinderProvider());
    

    this isn't a general solution, but I guess you get the point.