Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
790 views
in Technique[技术] by (71.8m points)

asp.net mvc - Web API Model Binding with Multipart formdata

Is there a way to be able to get model binding (or whatever) to give out the model from a multipart form data request in ASP.NET MVC Web API?

I see various blog posts but either things have changed between the post and actual release or they don't show model binding working.

This is an outdated post: Sending HTML Form Data

and so is this: Asynchronous File Upload using ASP.NET Web API

I found this code (and modified a bit) somewhere which reads the values manually:

Model:

public class TestModel
{
    [Required]
    public byte[] Stream { get; set; }

    [Required]
    public string MimeType { get; set; }
}

Controller:

    public HttpResponseMessage Post()
    {
        if (!Request.Content.IsMimeMultipartContent("form-data"))
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        IEnumerable<HttpContent> parts = Request.Content.ReadAsMultipartAsync().Result.Contents;


        string mimeType;
        if (!parts.TryGetFormFieldValue("mimeType", out mimeType))
        {
            return Request.CreateResponse(HttpStatusCode.BadRequest);
        }

        var media = parts.ToArray()[1].ReadAsByteArrayAsync().Result;

        // create the model here
        var model = new TestModel()
            {
                MimeType = mimeType,
                Stream = media
            };
        // save the model or do something with it
        // repository.Save(model)

        return Request.CreateResponse(HttpStatusCode.OK);
    }

Test:

[DeploymentItem("test_sound.aac")]
[TestMethod]
public void CanPostMultiPartData()
{
    var content = new MultipartFormDataContent { { new StringContent("audio/aac"),  "mimeType"}, new ByteArrayContent(File.ReadAllBytes("test_sound.aac")) };

    this.controller.Request = new HttpRequestMessage {Content = content};
    var response = this.controller.Post();

    Assert.AreEqual(response.StatusCode, HttpStatusCode.OK);
}

This code is basically fragile, un-maintainable and further, doesn't enforce the model binding or data annotation constraints.

Is there a better way to do this?

Update: I've seen this post and this makes me think - do I have to write a new formatter for every single model that I want to support?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There is a good example of a generic formatter for file uploads here http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/. If I was going to have multiple controllers accepting file uploads then this would be the approach I would take.

P.S. Having looked around this seems like a better example for your upload within the controller http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web-api-rtm/

Update

Re: The usefulness of the Multipart approach, this is covered here but effectively this boils down to the multipart approach being well build for significantly sized binary payloads etc...

Is DEFAULT model binding going to work?

The standard/default model binder for WebApi is not built to cope with the model you have specified i.e. one that mixes simple types and Streams & byte arrays (not so simple)... This is a quote from the article that inspired the lonetechie's:

“Simple types” uses model binding. Complex types uses the formatters. A “simple type” includes: primitives, TimeSpan, DateTime, Guid, Decimal, String, or something with a TypeConverter that converts from strings

Your use of a byte array on your model and the need to create that from a stream/content of the request is going to direct you to using formatters instead.

Send model and files separately?

Personally I would look to separate the file uploading from the model... perhaps not an option for you... this way you would POST to the same Controller and route when you use a MultiPart data content type this will invoke the file uploading formatter and when you use application/json or x-www-form-urlencoded then it will do simple type model binding... Two POST's may be out of the question for you but it is an option...

Custom model binder?

I had some minor success with a custom model binder, you can do something with this perhaps... this could be made generic (with some moderate effort) and could be registered globally in the binder provider for reuse...

This may be worth a play?

public class Foo
{
    public byte[] Stream { get; set; }
    public string Bar { get; set; }
}

public class FoosController : ApiController
{

    public void Post([ModelBinder(typeof(FileModelBinder))] Foo foo)
    {
        //
    }
}

Custom model binder:

public class FileModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
    public FileModelBinder()
    {

    }

    public bool BindModel(
        System.Web.Http.Controllers.HttpActionContext actionContext,
        System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
    {
        if (actionContext.Request.Content.IsMimeMultipartContent())
        {
            var inputModel = new Foo();

            inputModel.Bar = "";  //From the actionContext.Request etc
            inputModel.Stream = actionContext.Request.Content.ReadAsByteArrayAsync()
                                            .Result;

            bindingContext.Model = inputModel;
            return true;
        }
        else
        {
            throw new HttpResponseException(actionContext.Request.CreateResponse(
             HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...