Web API模型与Multipart formdata绑定

时间:2012-09-26 00:32:27

标签: asp.net-mvc asp.net-mvc-3 c#-4.0 asp.net-web-api

有没有办法能够从 ASP.NET MVC Web API 中的多部分表单数据请求中获取模型绑定(或其他)来提供模型?

我看到各种博文,但是在帖子和实际版本之间发生了变化,或者他们没有显示模型绑定工作。

这是一篇过时的帖子:Sending HTML Form Data

这是:Asynchronous File Upload using ASP.NET Web API

我发现这个代码(并且稍微修改了一下)手动读取值:

型号:

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

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

控制器:

    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);
    }

测试

[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);
}

此代码基本上是脆弱的,不可维护的,并且不会强制执行模型绑定或数据注释约束。

有更好的方法吗?

更新:我已经看到了post,这让我觉得 - 我是否必须为每个我想支持的模型编写一个新的格式化程序?

2 个答案:

答案 0 :(得分:8)

这里有一个用于文件上传的通用格式化程序的好例子http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/。如果我要让多个控制器接受文件上传,那么这将是我采取的方法。

P.S。环顾四周似乎是在控制器http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web-api-rtm/

中上传的更好示例

<强> 更新

Re:Multipart方法的实用性,这涵盖了here,但实际上这归结为多部分方法正在为大量二进制有效负载等构建良好...

DEFAULT模型绑定是否正常工作?

WebApi的标准/默认模型绑定器不是为了应对您指定的模型而构建的,即混合简单类型和Streams&amp;字节数组(不那么简单)......这是article引用lonetechie的引用:

  

“简单类型”使用模型绑定。复杂类型使用格式化程序。   “简单类型”包括:原语,TimeSpan,DateTime,Guid,   Decimal,String或具有转换自的TypeConverter的内容   字符串

您在模型上使用字节数组以及从请求的流/内容创建字节数组的需要将指导您使用格式化程序。

单独发送模型和文件?

就我个人而言,我希望将文件上传与模型分开...也许不是你的选择......这样你就可以在使用MultiPart数据内容类型时POST到同一个Controller和路由,这将调用文件上传格式化程序,当你使用application / json或x-www-form-urlencoded然后它会做简单的类型模型绑定...两个POST对你来说可能是不可能的,但它是一个选项......

自定义模型绑定器?

我使用custom model binder获得了一些小小的成功,你可以用这个做点什么......这可以做成通用的(有一些适度的努力),并且可以在绑定器提供程序中全局注册以便重用。 。

这可能值得玩吗?

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)
    {
        //
    }
}

自定义模型绑定器:

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"));
        }
    }
}

答案 1 :(得分:5)

@Mark Jones链接到我的博客帖子http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/,这使我在这里。我开始思考如何做你想做的事。

我相信如果你将我的方法与TryValidateProperty()结合起来,你应该能够完成你所需要的。我的方法将获得反序列化的对象,但它不处理任何验证。您可能需要使用反射来遍历对象的属性,然后在每个对象上手动调用TryValidateProperty()。这种方法有点动手,但我不知道该怎么做。

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

编辑:其他人问过这个问题,我决定编写它以确保它能够正常工作。这是我的博客中的更新代码,带有验证检查。

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);
    }
}