1. ホーム

ASP.NET MVCのWebApiへのAjax呼び出しで、500 Internal Server Errorが返される。

2022-02-22 09:02:32

ASP.NET MVCで導入されたWebApiは、ajaxのインタラクションニーズに自然かつうまく対応していますが、jQuery ajaxでWebApiを呼び出すと、500 Internal Server Errorを返してしまうというエラーを見つけるのは簡単ではありません。実際のプロジェクトでは、WebApiのメソッドはすべてtry-catchで例外をキャッチし、独自のエラーメッセージを返しており、エラーがあればキャッチできることが当然と考えられています。しかし、最近、サイトが常に500エラーで動作するようになり、何が問題なのかを見つけるのに苦労しました。

<スパン         デバッグを重ねた結果、このエラーは WebApi メソッドの外部で発生させるべきものであることが判明しました(メソッドは try-catch で内部的にキャッチする必要があります)。当初は、ASP.NET MVC フレームワークの JSON シリアライズがオブジェクトを返したときに発生するランタイム エラーでした。返されるオブジェクトをシリアライズする JsonConvert.SerializeObject() メソッドで、クラスオブジェクトの get プロパティ(派生値)がゼロで割られているというエラーが発覚しました。一般に、オブジェクトのgetプロパティは、そのプロパティにアクセスしたときだけ、その中のコードが実行されます。どうやら、オブジェクトのJSONシリアライズは、オブジェクトのgetプロパティのコードをすべて呼び出し、永続的なプロパティ値を取得するようです。

       結論から言うと

  1. JSONデータを返す際にWebApiのシリアライズ操作で発生する例外は、WebApiメソッド外の例外であり、当面はキャッチできない(作者以下の.NET Framework 4.0では当面はキャッチする方法が見つかっていない)、その場合、500 Internal Server Errorエラーを返すことになります。
  2. JSONがオブジェクトをシリアライズするとき、そのオブジェクトのすべてのgetプロパティ値(つまり、getプロパティを実行するコード)を取得することになります。
  3. 例外が直接スローされ、キャッチされるように、JSONシリアライズ操作をプログラム的にエミュレートすることが可能である。

この記事で紹介した方法は、Visual Studio Community 2015、.NET 4.0、ASP.NET MVC4でデバッグを通過させます。

  追記です。

      何日もかけてWebを調査しデバッグした結果、WebApiメソッドのJsonシリアライズによって発生する例外をキャッチする汎用的な方法を発見しました。基本的なアイデアは、ASP.NET MVCのデフォルトのJsonシリアライゼーションコンバータJsonConverter(このクラスはNewtonsoftのダイナミックライブラリで提供されています)をカスタマイズして、シリアライズストリームを読み書きするときに例外をキャッチするようにすることです。

      書き換えたJsonConverterコンバータクラスのコードは以下の通りである。

using System;
Net.Http.Formatting;
Tasks;
Tasks; using Newtonsoft;
Http; using System;
Http; using System;
Http; using System;

namespace CSUST.Kyz
Kyz
    public class TJsonConverter : MediaTypeFormatter
    Kyz {
        public TJsonConverter()
        {
            SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
        }

        public override bool CanWriteType(Type type)
        {
            return true;
        }

        public override bool CanReadType(Type type)
        {
            return true;
        }

        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            var task = Task<object>.Factory.StartNew(() =>
            StartNew() => {
                StartNew() => { try
                {
                    string json;
                    using (var sr = new StreamReader(readStream, System.Text.Encoding.UTF8))
                    {
                        json = sr.ReadToEnd();
                        sr.Close();
                    }

                    return JsonConvert.DeserializeObject(json); 
                }
                catch(Exception err)
                {
                    TLogs.Save("read except:" + err.StackTrace); // Save the exception message
                    throw err;
                }
            });

            return task;
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
        {
            var task = Task.Factory.StartNew(() =>
            StartNew() => {
                StartNew() => { try
                { try
                    var json = JsonConvert.SerializeObject(value);
                    GetBytes(json). byte[] buf = System.Text;
                    WriteStream.Write(buf, 0, buf;)
                    WriteStream.Flush();
                }
                catch(Exception err)
                {
                    TLogs.Save("write except:" + err.StackTrace); // Save the exception message
                    throw err;
                }
            });

            return task;
        }
    }
}

       また、上記のカスタムJsonシリアライゼーションコンバータを、以下のコードでグローバル設定ファイルに登録する必要があります(global.asax.csの関数)。

protected void Application_Start()
{        
    AreaRegistration.RegisterAllAreas();

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable;)

    GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear(); // Clear all data formats
    GlobalConfiguration.Configuration.Formatters.Insert(0, new TJsonConverter()); // Only the serializer and Json format are defined
}