Web API 模型綁定與 Multipart formdata
有沒有辦法能夠獲得模型綁定(或其他)以從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); }這段程式碼基本上是脆弱的、不可維護的,而且不強制執行模型綁定或數據註釋約束。
有一個更好的方法嗎?
**更新:**我看過這篇文章,這讓我想到 - 我是否必須為我想要支持的每個模型編寫一個新的格式化程序?
@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); } }
這裡有一個用於文件上傳的通用格式化程序的好例子<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")); } } }