2016-01-05 12 views
5

JSONモデルをWebAPIコントローラメソッドに投稿すると、JSONモデルにnullオブジェクトがある場合、モデルバインダーはサーバーでnullを保持する代わりにこれらのアイテムをインスタンス化しますサイドオブジェクト。WebAPI JSONでnullの場合でもオブジェクトをインスタンス化

これは、通常のMVCコントローラがデータをバインドする方法とは対照的です。は、JSONでnullの場合、オブジェクトをインスタンス化します。

MVCコントローラ

public class HomeController : Controller 
{ 
    [HttpPost] 
    public ActionResult Test(Model model) 
    { 
     return Json(model); 
    } 
} 

WebAPIのコントローラ

public class APIController : ApiController 
{ 
    [HttpPost] 
    public Model Test(Model model) 
    { 
     return model; 
    } 
} 
POSTされます

Modelクラス

public class Model 
{ 
    public int ID { get; set; } 
    public Widget MyWidget { get; set; } 
} 

私は、各コントローラにJSONモデルを投稿するときは、Modelクラスに使用されるクラス

public class Widget 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

ここに私の結果である:あなたが見ることができる

$.post('/Home/Test', { ID: 29, MyWidget: null }) 
//Results in: {"ID":29,"MyWidget":null} 

$.post('/api/api/Test', { ID: 29, MyWidget: null }) 
//Results in: {"ID":29,"MyWidget":{"ID":0,"Name":null}} 

として、WebAPIの方法はではMyWidgetプロパティをインスタンス化MVCアクションはそれをnullのままにしましたが、オブジェクトです。

WebAPIがこのように機能するのはわかりません。それはなぜこれをするのだろうか?この点で、MVCアクションのように動作させることはできますか?

+0

'Model'がデフォルトのコンストラクタに' Widget'を作成し、それを 'MyWidget'に代入することはありません。 – dbc

+0

どのWeb APIのバージョンを使用していますか? –

+0

@dbc、クラスはあなたがそれを見るのとまったく同じものではありません。コンストラクタは指定されていません。さらに、それがコンストラクタベースの問題であれば、両方のコントローラで発生します。 –

答えて

2

私はこれまでのプロジェクトで経験した問題と似ていると思います。あなたは、次のいずれかにjQueryのためのポストコードを変更する必要が

$.ajax({ 
      type: 'POST', 
      url: '/api/api/Test', 
      data: JSON.stringify({ ID: 29, MyWidget: null }), 
      contentType: "application/json", 
      dataType: 'json', 
      timeout: 30000 
     }) 
     .done(function (data) { 
     }) 
     .fail(function() { 
     }); 

デフォルトjQueryの「ポスト」によっては、フォームのURLエンコードされたデータなどのパラメータを送信します。

アプリケーション/ x-www-form-urlencodedで
ID 29
はMyWidget

ID = 29 &はMyWidget =

だから、それが絶対的に正しくデシリアライズされました。 MyWidgetは空文字列なので、Widgetクラスの値は空になります。また

私はWEBAPIコントローラのフォーマッタの設定を追加することをお勧めいたします:

public static void Register(HttpConfiguration config) 
    { 
     // Web API configuration and services 

     // Web API routes 
     config.MapHttpAttributeRoutes(); 

     config.Routes.MapHttpRoute(
      name: "DefaultApi", 
      routeTemplate: "api/{controller}/{id}", 
      defaults: new { id = RouteParameter.Optional } 
     ); 

     // Formatters 
     JsonMediaTypeFormatter json = config.Formatters.JsonFormatter; 

     config.Formatters.Clear(); 
     config.Formatters.Add(json); 
    } 

ですから、APIコールにのみJSON-フォーマッタを使用します。MVCコントローラに渡さ

UPDATE

主な違いは、そのフォームのURLエンコードされたデータは、ランタイムによって処理され、最終的にDefaultModelBinderによって処理される(カスタムバインダーが使用できない場合)。したがって、通常はHTMLフォームのポストによってデータが生成されるため、form-urlとしてエンコードされたデータはMVCでは通常です。しかし、Web APIは設計によって特定のエンコーディングに依存していません。したがって、データを解析するために特定のメカニズム(フォーマッタ)を使用します。例えば、上記のようなjsonです。 System.Net.Http.FormattingのFormUrlEncodedMediaTypeFormatterとSystem.Web.MvcのDefaultModelBinderは、空の文字列を異なる方法で処理します。

DefaultModelBinderの空の文字列はnullに変換されます。それがプロパティを記入します後

if (model == null) 
{ 
    model = CreateModel(controllerContext, bindingContext, modelType); 
} 

を::

// call into the property's model binder 
     IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType); 
     object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model); 
     ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; 
     propertyMetadata.Model = originalPropertyValue; 
     ModelBindingContext innerBindingContext = new ModelBindingContext() 
     { 
      ModelMetadata = propertyMetadata, 
      ModelName = fullPropertyKey, 
      ModelState = bindingContext.ModelState, 
      ValueProvider = bindingContext.ValueProvider 
     }; 
     object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder); 

を最後にGetBinderは、ウィジェットのタイプ(プロパティのタイプ)のためfallbackBinderを返します の解析コードIはBindModel方法は、まず、空のモデルを作成することを決定することができます。そして、文字列は次のように処理されるfallbackBinder自体がConvertSimpleTypeを呼び出します:

 string valueAsString = value as string; 
     if (valueAsString != null && String.IsNullOrWhiteSpace(valueAsString)) 
     { 
      return null; 
     } 

を私はC#のオブジェクトへのURLエンコードされた文字列からの変換を記述していない任意の基準があると思います。だから私はどちらが正しいかわからない。いずれにしても、form-url-encodedデータではなくAJAX呼び出しを介してjsonを渡す必要があると確信しています。

+0

それは(特に$ .ajaxを使って)うまくいきました。あなたの洞察に感謝します! MVCアクションとWebAPIメソッドがこの点で異なって動作するのは面白いことです。 –

+0

元の回答の違いについての説明を追加します。 – Maxim

+0

優れた説明、ありがとう!あなたの答えにもう一度投票してもらいたいです。 –

0

newtonsoftシリアライザを使用します。 Newtonsoft.Jsonはnullを維持します

using Newtonsoft.Json; 
using Newtonsoft.Json.Converters; 
using Newtonsoft.Json.Serialization; 


public static class WebApiConfig 
     { 
      public static void Register(HttpConfiguration config) 
      { 
       var jsonformatter = new JsonMediaTypeFormatter(); 
       config.Formatters.Clear(); 
       config.Formatters.Add(jsonformatter); 
     } 
    } 
+3

Web APIはデフォルトでNewtonsoft.Jsonシリアライザを使用しませんか? –

+0

@NikolaiSamteladzeあなたは正しいです。私は今チェックした。しかし、Json.Netはnullオブジェクトを初期化しません。 – halit

+0

オブジェクトはバインディング時にインスタンス化されています。コントローラーアクションでブレークポイントを設定すると、既に作成された新しいオブジェクト(MVCコントローラーの場合はnull)が表示されます。 –

関連する問題