web-dev-qa-db-ja.com

コンテンツタイプマルチパート用にWebAPIを構成できません

私はWebAPI(Web API 2)に取り組んでいます。私の基本的な必要性は、ユーザーのプロファイルを更新するためのAPIを作成することです。この場合、iosとAndroidはmultipart/form-dataでリクエストを送信します。画像付きのいくつかのパラメーターを送信します。しかし、APIを作成しようとすると、モデルは次のようになります。毎回nullになります。

WebApiConfigにもこの行を追加しました:

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));

これは私のクラスです:

public class UpdateProfileModel
{
   public HttpPostedFileBase ProfileImage { get; set; }
   public string Name { get; set; }
}

これは私のコントローラーです:

[Route("api/Account/UpdateProfile")]
[HttpPost]
public HttpResponseMessage UpdateProfile(UpdateProfileModel model)
{
}

モデルでパラメーター値を取得していません。私は何か間違ったことをしていますか?

これに関連する答えはどれも私には役に立ちませんでした。その約3日目と私はほとんどすべてとすべての方法を試しました。しかし、私はそれを達成することができません。

私はこれを使用できますが、これは以下に示すようになりますが、これは良いアプローチではないようです。だから私はそれを避けています。

var httpRequest = HttpContext.Current.Request;
if (httpRequest.Form["ParameterName"] != null)
{
    var parameterName = httpRequest.Form["ParameterName"];
}

そしてファイルのために私はこれをすることができます:

if (httpRequest.Files.Count > 0)
{
     //i can access my files here and save them
}

良いアプローチがあれば助けてください。または、モデルでこの値を取得できない理由を説明してください。

よろしくお願いします

17
Rohit Arora

JPgrassiが提供する答えは、MultiPartデータを取得するために行うことです。追加する必要のあるものは他に少ないと思うので、自分で答えを書こうと思いました。

MultiPartフォームデータは、その名前が示すように、単一タイプのデータではありませんが、フォームがMultiPart MIMEメッセージとして送信されることを指定しているため、すべてのコンテンツを読み取るための事前定義されたフォーマッターを持つことはできません。 ReadAsync関数を使用して、バイトストリームを読み取り、さまざまなタイプのデータを取得し、それらを識別して、それらを逆シリアル化する必要があります。

内容を読むには2つの方法があります。 1つ目は、すべてを読み取ってメモリに保持することです。2つ目は、すべてのファイルの内容をランダムな名前のファイル(GUIDを使用)にストリーミングし、ファイルにアクセスするためのローカルパスの形式でハンドルを提供するプロバイダーを使用することです(提供されている例jpgrassiが2番目を行っています)。

最初の方法:すべてをメモリ内に保持する

//Async because this is asynchronous process and would read stream data in a buffer. 
//If you don't make this async, you would be only reading a few KBs (buffer size) 
//and you wont be able to know why it is not working
public async Task<HttpResponseMessage> Post()
{

if (!request.Content.IsMimeMultipartContent()) return null;

        Dictionary<string, object> extractedMediaContents = new Dictionary<string, object>();

        //Here I am going with assumption that I am sending data in two parts, 
        //JSON object, which will come to me as string and a file. You need to customize this in the way you want it to.           
        extractedMediaContents.Add(BASE64_FILE_CONTENTS, null);
        extractedMediaContents.Add(SERIALIZED_JSON_CONTENTS, null);

        request.Content.ReadAsMultipartAsync()
                .ContinueWith(multiPart =>
                {
                    if (multiPart.IsFaulted || multiPart.IsCanceled)
                    {
                        Request.CreateErrorResponse(HttpStatusCode.InternalServerError, multiPart.Exception);
                    }

                    foreach (var part in multiPart.Result.Contents)
                    {
                        using (var stream = part.ReadAsStreamAsync())
                        {
                            stream.Wait();
                            Stream requestStream = stream.Result;

                            using (var memoryStream = new MemoryStream())
                            {
                                requestStream.CopyTo(memoryStream);
                                //filename attribute is identifier for file vs other contents.
                                if (part.Headers.ToString().IndexOf("filename") > -1)
                                {                                        
                                    extractedMediaContents[BASE64_FILE_CONTENTS] = memoryStream.ToArray();
                                }
                                else
                                {
                                    string jsonString = System.Text.Encoding.ASCII.GetString(memoryStream.ToArray());
                                   //If you need just string, this is enough, otherwise you need to de-serialize based on the content type. 
                                   //Each content is identified by name in content headers.
                                   extractedMediaContents[SERIALIZED_JSON_CONTENTS] = jsonString;
                                }
                            }
                        }
                    }
                }).Wait();

        //extractedMediaContents; This now has the contents of Request in-memory.
}

2番目の方法:プロバイダーを使用する(jpgrassiで指定)

注意点として、これはファイル名のみです。ファイルを処理したり、別の場所に保存したりする場合は、ファイルを再度ストリーム読み取りする必要があります。

 public async Task<HttpResponseMessage> Post()
{
HttpResponseMessage response;

    //Check if request is MultiPart
    if (!Request.Content.IsMimeMultipartContent())
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }
    //This specifies local path on server where file will be created
    string root = HttpContext.Current.Server.MapPath("~/App_Data");
    var provider = new MultipartFormDataStreamProvider(root);

    //This write the file in your App_Data with a random name
    await Request.Content.ReadAsMultipartAsync(provider);

    foreach (MultipartFileData file in provider.FileData)
    {
        //Here you can get the full file path on the server
        //and other data regarding the file
        //Point to note, this is only filename. If you want to keep / process file, you need to stream read the file again.
        tempFileName = file.LocalFileName;
    }

    // You values are inside FormData. You can access them in this way
    foreach (var key in provider.FormData.AllKeys)
    {
        foreach (var val in provider.FormData.GetValues(key))
        {
            Trace.WriteLine(string.Format("{0}: {1}", key, val));
        }
    }

    //Or directly (not safe)    
    string name = provider.FormData.GetValues("name").FirstOrDefault();


    response = Request.CreateResponse(HttpStatusCode.Ok);              

return response;
}
8
Guanxi

デフォルトでは、マルチパート/フォームデータを処理してモデルバインディングを実行できるメディアタイプフォーマッターはAPIに組み込まれていません。組み込みのメディアタイプフォーマッタは次のとおりです。

 JsonMediaTypeFormatter: application/json, text/json
 XmlMediaTypeFormatter: application/xml, text/xml
 FormUrlEncodedMediaTypeFormatter: application/x-www-form-urlencoded
 JQueryMvcFormUrlEncodedFormatter: application/x-www-form-urlencoded

これが、ほとんどの回答が、コントローラー内の要求から直接データを読み取る責任を引き継ぐことを伴う理由です。ただし、Web API 2フォーマッターコレクションは、開発者の出発点となることを目的としており、すべての実装のソリューションとなることを目的とはしていません。マルチパートフォームデータを処理するMediaFormatterを作成するために作成された他のソリューションがあります。 MediaTypeFormatterクラスが作成されると、WebAPIの複数の実装で再利用できます。

ASP.NET 4.5 Web API用のMultipartFormFormatterを作成する方法

Web api 2ソースコードの完全な実装をダウンロードしてビルドし、メディアフォーマッターのデフォルトの実装がマルチパートデータをネイティブに処理しないことを確認できます。 https://aspnetwebstack.codeplex.com/

7
Bill

Multipart/Formdataを処理する組み込みのメディアタイプフォーマッターがないため、コントローラーにそのようなパラメーターを含めることはできません。独自のフォーマッターを作成しない限り、MultipartFormDataStreamProviderを介してアクセスするファイルとオプションのフィールドにアクセスできます。

Postメソッド

 public async Task<HttpResponseMessage> Post()
{
    HttpResponseMessage response;

        //Check if request is MultiPart
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);

        //This write the file in your App_Data with a random name
        await Request.Content.ReadAsMultipartAsync(provider);

        foreach (MultipartFileData file in provider.FileData)
        {
            //Here you can get the full file path on the server
            //and other data regarding the file
            tempFileName = file.LocalFileName;
        }

        // You values are inside FormData. You can access them in this way
        foreach (var key in provider.FormData.AllKeys)
        {
            foreach (var val in provider.FormData.GetValues(key))
            {
                Trace.WriteLine(string.Format("{0}: {1}", key, val));
            }
        }

        //Or directly (not safe)    
        string name = provider.FormData.GetValues("name").FirstOrDefault();


        response = Request.CreateResponse(HttpStatusCode.Ok);              

    return response;
}

例のより詳細なリストは次のとおりです。 ASP.NET Web APIでのHTMLフォームデータの送信:ファイルのアップロードとマルチパートMIME

3
jpgrassi
2
alj

だから、私のために働いたのは-

[Route("api/Account/UpdateProfile")]
[HttpPost]
public Task<HttpResponseMessage> UpdateProfile(/* UpdateProfileModel model */)
{
     string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);
        await Request.Content.ReadAsMultipartAsync(provider);
        foreach (MultipartFileData file in provider.FileData)
        {

        }
}

また-

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));

必須ではありません。

Multipart/form-dataは、フォームが送信された後、どこかで内部的に処理されると思います。

ここで非常に明確に説明されています-

http://www.asp.net/web-api/overview/advanced/sending-html-form-data-part-2

2