Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 688
Martin Hansen Lennox Avatar asked Sep 05 '18 11:09

Martin Hansen Lennox


People also ask

How do you bind the data model in MVC?

Step 1: Open the VS 2019 and select the ASP.NET core web application. Step 2: Select the template of the web application (MVC). Step 3: Go to the home controller and student basic type binding. Step 5: Now, press f5 and run the solution.

Does MVC use data binding?

MVC doesn't use data bindings like old web api. You have to use model bindings in a MVC or MVVM approach.

What is the use of bind attribute in MVC?

When we have an ASP.NET MVC View that accepts user input and posts those inputs to a server we have the option to use the built-in Model-binding features to provide more control and security, We can restrict the properties that are allowed to be bound automatically.

How do I bind a model to view in MVC core?

How does model binding work in ASP.NET Core MVC. In an empty project, change Startup class to add services and middleware for MVC. Add the following code to HomeController, demonstrating binding of simple types. Add the following code to HomeController, demonstrating binding of complex types.


2 Answers

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.

like image 197
Mohammad Azhdari Avatar answered Sep 24 '22 19:09

Mohammad Azhdari


I encountered similar problem during the integration of jQuery.filer in an ASP.NET MVC project. As jQuery.filer adds "[]" to the end of name attribute of input (i.e. from files to files[]), I had to change the value of name attribute manually as shown below:

$('#FileUpload').attr('name', 'FileUpload');

Here is my approach used in some of project via AJAX and working without any problem. You might give a try and let me know if it works:

ViewModel:

[Display(Name = "Attachments")]
[DataType(DataType.Upload)]
public IEnumerable<HttpPostedFileBase> FileUpload { get; set; }


View:

@model ViewModel

@using (Html.BeginForm("Insert", "Controller", FormMethod.Post, 
    new { id = "frmCreate", enctype = "multipart/form-data" })) 
{   
    @Html.TextBoxFor(m => m.FileUpload, new { type = "file", multiple = "multiple" })
    <button id="btnSubmit" onclick="insert(event)" type="button">Save</button>
}    

<script>     
function insert(event) {     
    event.preventDefault();

    //As jQuery.filer adds "[]" to the end of name attribute of input (i.e. from files to files[])
    //we have to change the value of name attribute manually
    $('#FileUpload').attr('name', 'FileUpload');        
    var formdata = new FormData($('#frmCreate').get(0)); 

    $.ajax({
        type: "POST",
        url: '@Url.Action("Insert", "Cotroller")',
        cache: false,
        dataType: "json",
        data: formdata,

        /* If you are uploading files, then processData and contentType must be set 
        to falsein order for FormData to work (otherwise comment out both of them) */
        processData: false, 
        contentType: false, 

        success: function (response, textStatus, XMLHttpRequest) {
            //...
        }
    });
};

$(document).ready(function () {         
    $('#FileUpload').filer({        
        //code omitted for brevity
    });  
});  
</script>


Controller:

public JsonResult Insert([Bind(Exclude = null)] ViewModel model)
{
    if (ModelState.IsValid)
    {   
        List<FileAttachment> fa = new List<FileAttachment>();
        if (model.FileUpload != null)
        {
            FileAttachment fileAttachment = new FileAttachment //entity model
            {
                Created = DateTime.Now,
                FileMimeType = upload.ContentType,
                FileData = new byte[upload.ContentLength],
                FileName = upload.FileName,
                AuthorId = 1
            };
            upload.InputStream.Read(fileAttachment.FileData, 0, upload.ContentLength);
            fa.Add(fileAttachment);
        }

        //code omitted for brevity
        repository.SaveExperimentWithAttachment(model, fa);
        return Json(new { success = true, message = "Record has been created." });
    }
    // If we got this far, something failed, redisplay form
    return Json(new { success = false, message = "Please check the form and try again." });
}

Hope this helps...

like image 26
Murat Yıldız Avatar answered Sep 22 '22 19:09

Murat Yıldız