Asp.net-Mvc

Web API 模型綁定與 Multipart formdata

  • September 26, 2012

有沒有辦法能夠獲得模型綁定(或其他)以從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&lt;T&gt;
{
   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&lt;ValidationResult&gt; ValidationResults = new List&lt;ValidationResult&gt;(); 

   public FileUpload(byte[] buffer, string mediaType, 
                     string fileName, string value)
   {
       Buffer = buffer;
       MediaType = mediaType;
       FileName = fileName.Replace("\"","");
       _RawValue = value;

       Value = JsonConvert.DeserializeObject&lt;T&gt;(_RawValue);

       foreach (PropertyInfo Property in Value.GetType().GetProperties())
       {
           var Results = new List&lt;ValidationResult&gt;();
           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"));
       }
   }
}

引用自:https://stackoverflow.com/questions/12593001