web-dev-qa-db-ja.com

マルチパートフォームデータを使用したWeb APIモデルバインディング

ASP.NET MVC Web APIのマルチパートフォームデータリクエストからモデルを提供するためにモデルバインディング(またはその他)を取得できる方法はありますか?

さまざまなブログ投稿が表示されますが、投稿と実際のリリースの間で変更があったか、モデルバインディングが機能していないかがわかります。

これは古い投稿です: Send HTML Form Data

これも同様です 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);
}

このコードは基本的に壊れやすく、保守が不可能であり、さらにモデルバインディングやデータアノテーションの制約を強制しません。

これを行うより良い方法はありますか?

更新:私はこれを見た post そしてこれは私に考えさせます-私がサポートしたいすべての単一のモデルのために新しいフォーマッタを書かなければなりませんか? ?

24
Mrchief

@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.aspxhttp://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);
    }
}
5
Particleman

http://lonetechie.com/2012/09/23/web-api-generic-mediatypeformatter-for-file-upload/ に、ファイルアップロードの一般的なフォーマッターの良い例があります。複数のコントローラーでファイルのアップロードを受け入れる場合は、これが私が採用するアプローチです。

追伸これを見回したことは、コントローラ内のアップロードのより良い例のようです http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web -api-rtm /

更新

Re:マルチパートアプローチの有用性、これは here でカバーされていますが、実質的には、かなり大きなサイズのマルチパートアプローチが適切に構築されているということになります。バイナリペイロードなど...

DEFAULTモデルバインディングは機能しますか?

WebApiの標準/デフォルトモデルバインダーは、指定したモデル、つまり単純型とストリームとバイト配列を混合したモデル(それほど単純ではない)に対応するように構築されていません...これは 記事からの引用です そのことlonetechieに影響を与えた:

「単純型」はモデルバインディングを使用します。複合型はフォーマッターを使用します。 「単純型」には、プリミティブ、TimeSpan、DateTime、Guid、Decimal、String、または文字列から変換するTypeConverterを含むものが含まれます

モデルでバイト配列を使用し、リクエストのストリーム/コンテンツからそれを作成する必要がある場合は、代わりにフォーマッターを使用するように指示します。

モデルとファイルを別々に送信しますか?

個人的に私はファイルのアップロードをモデルから分離しようとしています...おそらくあなたのためのオプションではありません...このようにあなたは同じコントローラにPOSTマルチパートデータを使用するときにルーティングしますコンテンツタイプこれにより、ファイルアップロードフォーマッターが呼び出され、application/jsonまたはx-www-form-urlencodedを使用すると、単純なタイプモデルバインディングが実行されます... 2つの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)
    {
        //
    }
}

カスタムモデルバインダー:

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"));
        }
    }
}
8
Mark Jones