Web API Model Binding with Multipart formdata

前端 未结 2 566
醉酒成梦
醉酒成梦 2020-12-05 07:35

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

相关标签:
2条回答
  • 2020-12-05 08:06

    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"));
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-05 08:10

    @Mark Jones linked over to my blog post http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/ which led me here. I got to thinking about how to do what you want.

    I believe if you combine my method along with TryValidateProperty() you should be able accomplish what you need. My method will get an object deserialized, however it does not handle any validation. You would need to possibly use reflection to loop through the properties of the object then manually call TryValidateProperty() on each one. This method it is a little more hands on but I'm not sure how else to do it.

    http://msdn.microsoft.com/en-us/library/dd382181.aspx http://www.codeproject.com/Questions/310997/TryValidateProperty-not-work-with-generic-function

    Edit: Someone else asked this question and I decided to code it just to make sure it would work. Here is my updated code from my blog with validation checks.

    public class FileUpload<T>
    {
        private readonly string _RawValue;
    
        public T Value { get; set; }
        public string FileName { get; set; }
        public string MediaType { get; set; }
        public byte[] Buffer { get; set; }
    
        public List<ValidationResult> ValidationResults = new List<ValidationResult>(); 
    
        public FileUpload(byte[] buffer, string mediaType, 
                          string fileName, string value)
        {
            Buffer = buffer;
            MediaType = mediaType;
            FileName = fileName.Replace("\"","");
            _RawValue = value;
    
            Value = JsonConvert.DeserializeObject<T>(_RawValue);
    
            foreach (PropertyInfo Property in Value.GetType().GetProperties())
            {
                var Results = new List<ValidationResult>();
                Validator.TryValidateProperty(Property.GetValue(Value),
                                              new ValidationContext(Value) 
                                              {MemberName = Property.Name}, Results);
                ValidationResults.AddRange(Results);
            }
        }
    
        public void Save(string path, int userId)
        {
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
    
            var SafeFileName = Md5Hash.GetSaltedFileName(userId,FileName);
            var NewPath = Path.Combine(path, SafeFileName);
    
            if (File.Exists(NewPath))
            {
                File.Delete(NewPath);
            }
    
            File.WriteAllBytes(NewPath, Buffer);
    
            var Property = Value.GetType().GetProperty("FileName");
            Property.SetValue(Value, SafeFileName, null);
        }
    }
    
    0 讨论(0)
提交回复
热议问题