25

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

我看到各种博客文章,但要么在文章和实际发布之间发生了变化,要么它们没有显示模型绑定工作。

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

这就是: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);
}

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

有一个更好的方法吗?

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

4

2 回答 2

9

这里有一个用于文件上传的通用格式化程序的好例子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/

更新

回复:Multipart 方法的有用性,这在此处进行了介绍, 但实际上这归结为 multipart 方法可以很好地构建用于显着大小的二进制有效负载等...

DEFAULT 模型绑定会起作用吗?

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

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

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

分别发送模型和文件?

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

自定义模型粘合剂?

我在自定义模型 binder方面取得了一些小成功,你也许可以用这个做点什么......这可以成为通用的(通过一些适度的努力)并且可以在 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"));
        }
    }
}
于 2012-09-26T07:49:49.267 回答
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);
    }
}
于 2012-09-26T14:19:24.823 回答