7

私の Web サイトには、WPF デスクトップ アプリケーションとの通信に使用される ASP.Net Web API がセットアップされています。クライアント アプリケーションからバイナリ ファイルを受信するように API にアクションを設定しました。ただし、リクエストからすべてのバイトを取得すると、すべてのバイトが読み取られるわけではありません。うまくいけば、常に機能する方法でこれを行う方法のアイデアを教えてください. コードは次のとおりです。

クライアント側:

public static SubmitTurnResult SubmitTurn(int turnId, Stream fileStream)
{
    HttpClient client = CreateHttpClient();

    HttpContent content = new StreamContent(fileStream);
    content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    content.Headers.ContentDisposition.FileName = "new-turn.Civ5Save";
    content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    content.Headers.ContentLength = fileStream.Length;

    HttpResponseMessage response = client.PostAsync(
        string.Format("SubmitTurn?authKey={0}&turnId={1}",
                        LocalSettings.Instance.AuthenticationKey,
                        turnId
                        ),
        content
    ).Result;

    response.EnsureSuccessStatusCode();

    return response.Content.ReadAsAsync<SubmitTurnResult>().Result;
}

SubmitTurnResultは、サーバーでの結果を定義する列挙型です。turnIdは、このファイルが添付されているエンティティの ID です。fileStreamは、ディスクのバイトを読み取る実際の FileStream です。

サーバ側:

[HttpGet, HttpPost]
public SubmitTurnResult SubmitTurn(string authKey, int turnId)
{

    try
    {
        bool worked = false;
        int gameId = 0;

        using (GmrEntities gmrDb = new GmrEntities())
        {
            var player = gmrDb.Users.FirstOrDefault(u => u.AuthKey == authKey);
            if (player != null)
            {
                var turn = player.Turns.FirstOrDefault(t => t.TurnID == turnId);
                if (turn != null)
                {
                    byte[] saveFileBytes = null;

                    using (MemoryStream tempStream = new MemoryStream())
                    {
                        var task = this.Request.Content.CopyToAsync(tempStream);
                        task.Wait();

                        saveFileBytes = tempStream.ToArray();
                        tempStream.Close();
                    }

                    if (saveFileBytes.Length != this.Request.Content.Headers.ContentLength.Value)
                    {
                        throw new Exception(string.Format("Byte array length ({0}) not equal to HTTP content-length header ({1}). This is not good!",
                                    saveFileBytes.Length, this.Request.Content.Headers.ContentLength.Value));
                    }

                    worked = GameManager.SubmitTurn(turn, saveFileBytes, gmrDb);

                    if (worked)
                    {
                        gameId = turn.Game.GameID;

                        gmrDb.SaveChanges();
                    }
                }
            }
        }


        return SubmitTurnResult.OK;
    }
    catch (Exception exc)
    {
        DebugLogger.WriteExceptionWithComments(exc, string.Format("Diplomacy: Sumbitting turn for turnId: {0}", turnId));

        return SubmitTurnResult.UnexpectedError;
    }
}
4

1 に答える 1

11

以前のコメントで述べたように、StreamContent でも同じ動作に遭遇しましたが、Windows Server 2003 Web API サービスからの応答をストリーミングするときに発生しました。2008 では再現しません。 実際には、VM を少量の RAM (712 MB) で構成すると、Windows Server 2008 でも再現しますが、4 GB の RAM では再現しません。また、これは FileStream を使用したリプロのみであることがわかりました。FileStream を MemoryStream に変換すると、問題が回避されます (もちろん、メモリが犠牲になります)。応答ストリームが早期に終了すると、常に 4096 バイトの境界にあり、約 3.5MB で上限に達することがわかりました。

あなたのコード例に合わせて調整された、私のために物事を修正した回避策は次のとおりです。

public static SubmitTurnResult SubmitTurn(int turnId, Stream fileStream)
{
    HttpClient client = CreateHttpClient();

    var memoryStream = new MemoryStream((int)fileStream.Length);
    fileStream.CopyTo(memoryStream);
    fileStream.Close();
    memoryStream.Seek(0, SeekOrigin.Begin);
    HttpContent content = new StreamContent(memoryStream);

必要に応じて、Stream が FileStream の場合にのみ、条件付きで MemoryStream コピーを実行できます。

于 2013-11-08T19:44:42.083 に答える