ASP.NET MVC posted file model binding when parameter is Model
Asked Answered
A

4

24

Is there any way to get posted files (<input type="file" />) to take part in model binding in ASP.NET MVC without manually looking at the request context in a custom model binder, and without creating a separate action method which only takes a posted file as input?

I would have thought that this would work:

class MyModel {
  public HttpPostedFileBase MyFile { get; set; }
  public int? OtherProperty { get; set; }
}

<form enctype="multipart/form-data">
  <input type="file" name="MyFile" />
  <input type="text" name="OtherProperty" />
</form>

public ActionResult Create(MyModel myModel) { ... } 

But given the above scenario, MyFile isn't even part of the value provider values in the binding context. (OtherProperty is, of course.) But it works if I do this:

public ActionResult Create(HttpPostedFileBase postedFile, ...) { ... } 

So, why does no binding occur when the parameter is a model, and how can I make it work? I have no problem with using a custom model binder, but how can I do this in a custom model binder without looking at Request.Files["MyFile"]?

For consistency, clarity and testability, I'd like my code to provide automatic binding of all properties on a model, including those bound to posted files, without manually inspecting the request context. I am currently testing model binding using the approach Scott Hanselman wrote about.

Or am I going about this in the wrong way? How would you solve this? Or is this not possible by design due to the history of separation between Request.Form and Request.Files?

Aoudad answered 6/6, 2009 at 22:19 Comment(0)
A
29

It turns out the reason is that ValueProviderDictionary only looks in Request.Form, RouteData and Request.QueryString to populate the value provider dictionary in the model binding context. So there's no way for a custom model binder to allow posted files to participate in model binding without inspecting the files collection in the request context directly. This is the closest way I've found to accomplish the same thing:

public ActionResult Create(MyModel myModel, HttpPostedFileBase myModelFile) { }

As long as myModelFile is actually the name of the file input form field, there's no need for any custom stuff.

Aoudad answered 8/6, 2009 at 21:21 Comment(4)
Note: do not overlook the enctype attribute on the form. It must be specified as "multipart/form-data". Otherwise, the HttpPostedFileBase argument with matching name as the name attribute on the input tag, will remain null on POST.Hackle
I have use the same but got the error:- Can't bind multiple parameters, in my $.ajax i have set :- type: 'POST', dataType: 'json',contentType: 'multipart/form-data',data: formDataBroken
This solution does not work. It results with the exception: Can't bind multiple parameters to the request's content. See: https://mcmap.net/q/583126/-quot-can-39-t-bind-multiple-parameter-to-the-request-39-s-content-quot-in-web-api-and-angularjs/2719183Brunson
@Brunson this solution is for ASP.NET MVC 1. Is that the version you are using? For a solution for ASP.NET MVC 2, see for example #961187Aoudad
W
14

Another way is to add a hidden field with the same name as the input:

<input type="hidden" name="MyFile" id="MyFileSubmitPlaceHolder" />

The DefaultModelBinder will then see a field and create the correct binder.

Whatnot answered 29/10, 2009 at 21:39 Comment(2)
Looks like ASP.NET MVC 2 RC handles the this without the hidden field.Whatnot
absolutely correct, I'm using ASP.NET MVC 2 and I can successfully bind my file input to my model without doing any extra work whatsoever. Fantastic!Farinose
S
7

Have you looked at this post which he links to from the one you linked to (via another one...)?

If not, it looks quite simple. This is the model binder he uses:

public class HttpPostedFileBaseModelBinder : IModelBinder {
    public ModelBinderResult BindModel(ModelBindingContext bindingContext) {
        HttpPostedFileBase theFile =
            bindingContext.HttpContext.Request.Files[bindingContext.ModelName];
        return new ModelBinderResult(theFile);
    }
}

He registers it in Global.asax.cs as follows:

ModelBinders.Binders[typeof(HttpPostedFileBase)] = 
    new HttpPostedFileBaseModelBinder();

and posts with a form that looks like this:

<form action="/File/UploadAFile" enctype="multipart/form-data" method="post">
    Choose file: <input type="file" name="theFile" />
    <input type="submit" />
</form>

All the code is copied straight off the blog post...

Saunders answered 6/6, 2009 at 22:33 Comment(5)
Actually, this is the approach I'm currently using. But there are two problems with this approach: 1; it uses the request context (via bindingContext.HttpContext.Request), which I really don't want to, and 2; it only handles a scenario where the post only consists of an uploaded file (this could of course easily be changed though).Aoudad
Also, BindModel(ModelBindingContext bindingContext) looks like pre-release code. There's a ControllerContext in there as well.Aoudad
Have you taken a look at the MVC Framework source code? I'm not sure how the "normal" model binders work, but I can't really see how you'd be able to grab form values without using the HttpContext.Current.Request.Form[] collection somewhere along the road... weblogs.asp.net/scottgu/archive/2008/03/21/…Saunders
Actually, I just took a look in the ASP.NET MVC 1.0 source, and the HttpPostedFileBaseModelBinder is in fact built into the framework. You should be able to bind several other parameters as well, by using a signature like ActionResult(HttpPostedFileBase theFile, string theFileName, string theDescription).Saunders
Yeah. And my question is: Is there no way to do this using an action that has a model as its parameter, and not only a posted file? It seems a bit limited to have to restrict one action method to one parameter to make this work automatically.Aoudad
M
-17

You don't need to register a custom binder, HttpPostedFileBase is registered by default in the framework:

public ActionResult Create(HttpPostedFileBase myFile)
{
    ...
}

It helps to read a book every once in awhile, instead of relying solely on blogs and web forums.

Marge answered 7/6, 2009 at 6:34 Comment(4)
I've edited the question to be more clear about the fact that I do not want an action method that takes a posted file as its sole parameter. Is there any way to accomplish the same if the posted file is just one of many properties on the model? (I have the other ASP.NET MVC book, by the way. :)Aoudad
This is the correct answer; I had the same problem a while back. HttpPostedFileBase will bind but HttpPostedFile will not.Integer
No, HttpPostedFileBase will not bind using the standard value provider binding in the default model binder. It will bind in the method in this answer, though.Aoudad
Also helps if you read the question fully and carefully before being smarmy ;)Excursion

© 2022 - 2024 — McMap. All rights reserved.