asp.net-mvc 具有多部分表单数据的 Web API 模型绑定
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12593001/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Web API Model Binding with Multipart formdata
提问by Mrchief
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?
有没有办法能够获得模型绑定(或其他)以从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
这是一篇过时的帖子:发送 HTML 表单数据
and so is this: Asynchronous File Upload using ASP.NET Web API
这就是:使用 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 postand this makes me think - do I have to write a new formatter for every single model that I want to support?
更新:我看过这篇文章,这让我想到 - 我是否必须为我想要支持的每个模型编写一个新的格式化程序?
采纳答案by Particleman
@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.
@Mark Jones 链接到我的博客文章http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/这让我来到这里。我必须考虑如何做你想做的事。
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.
我相信如果你将我的方法与 TryValidateProperty() 结合起来,你应该能够完成你所需要的。我的方法将反序列化一个对象,但它不处理任何验证。您可能需要使用反射来遍历对象的属性,然后在每个属性上手动调用 TryValidateProperty()。这种方法需要更多的动手,但我不知道该怎么做。
http://msdn.microsoft.com/en-us/library/dd382181.aspxhttp://www.codeproject.com/Questions/310997/TryValidateProperty-not-work-with-generic-function
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);
}
}
回答by Mark Jones
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.
这里有一个用于文件上传的通用格式化程序的好例子http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/。如果我要让多个控制器接受文件上传,那么这将是我会采取的方法。
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/
PS 环顾四周,这似乎是您在控制器中上传的更好示例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 herebut effectively this boils down to the multipart approach being well build for significantly sized binary payloads etc...
回复:Multipart 方法的用处,这里有介绍,但实际上这归结为 multipart 方法可以很好地构建用于显着大小的二进制有效载荷等......
Is DEFAULT model binding going to work?
DEFAULT 模型绑定会起作用吗?
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 articlethat inspired the lonetechie's:
WebApi 的标准/默认模型绑定器不是为处理您指定的模型而构建的,即混合简单类型和流和字节数组的模型(不是那么简单)......这是启发 lonetechie 的文章的引用:
“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
“简单类型”使用模型绑定。复杂类型使用格式化程序。“简单类型”包括:原语、TimeSpan、DateTime、Guid、Decimal、String 或带有从字符串转换的 TypeConverter 的东西
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...
就我个人而言,我希望将文件上传与模型分开......也许不是你的选择......这样你会发布到同一个控制器并在你使用 MultiPart 数据内容类型时路由这将调用文件上传格式化程序并且当您使用 application/json 或 x-www-form-urlencoded 时,它将进行简单的类型模型绑定...两个 POST 对您来说可能是不可能的,但它是一个选项...
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"));
}
}
}

