Web API模型与Multipart formdata绑定

Mrc*_*ief 24 asp.net-mvc c#-4.0 asp.net-mvc-3 asp.net-web-api

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

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

这是一篇过时的帖子:发送HTML表单数据

这是这样的:使用ASP.NET Web API进行异步文件上载

我发现这个代码(并修改了一下)在某处手动读取值:

模型:

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

    [Required]
    public string MimeType { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

控制器:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

测试:

[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);
}
Run Code Online (Sandbox Code Playgroud)

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

有一个更好的方法吗?

更新:我看过这篇文章,这让我觉得 - 我是否必须为每一个我想支持的模型编写一个新的格式化程序?

Mar*_*nes 8

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

PS环顾四周似乎是您在控制器中上传的更好示例http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web- API-RTM /

更新

回复:多部分方法的实用性,这是盖在这里 ,但实际上,这归结为多途径幸福建立显著大小的二进制有效载荷等..

DEFAULT模型绑定是否有效?

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

"简单类型"使用模型绑定.复杂类型使用格式化程序."简单类型"包括:基元,TimeSpan,DateTime,Guid,Decimal,String或具有从字符串转换的TypeConverter的东西

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

分别发送模型和文件?

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

定制型号粘合剂?

我在自定义模型绑定器上取得了一些小小的成功,你可以用这个做点什么......这可以做成通用的(有一些适度的努力),并且可以在绑定器提供程序中全局注册以便重用...

这可能值得玩吗?

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)
    {
        //
    }
}
Run Code Online (Sandbox Code Playgroud)

定制模型粘合剂:

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"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Par*_*man 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!我会试一试.这让我很奇怪,为什么要使用多部分表单数据方法呢?如果我切换到JSON有效负载,我不必处理任何单一事情.字节数组可以作为base64编码的字符串传输,这是非常标准的.多部分为我们所有这些额外的头痛买东西吗? (3认同)