ASP.NET WebAPI vs MVC subtle behavioural deviations when it comes to json-(de)serialization of parameters -


let's assume have following simple ajax-call:

 $.ajax({     url: "/somecontroller/someaction",     data: json.stringify({         somestring1: "",         somestring2: null,         somearray1: [],         somearray2: null     }),     method: "post",     datatype: "json",     contenttype: "application/json; charset=utf-8" })     .done(function (response) {         console.log(response);     }); 

the ajax call targets action of asp.net controller. asp.net website has default ("factory") settings when comes handling json-serialization tweak being newtonsoft.json.dll installed via nuget , web.config contains following section:

   <dependentassembly>        <assemblyidentity name="newtonsoft.json" publickeytoken="30ad4fe6b2a6aeed" culture="neutral" />        <bindingredirect oldversion="0.0.0.0-9.0.0.0" newversion="9.0.0.0" />    </dependentassembly> 

the configuration sections both webapi , mvc inside global.asax.cs have remained where. having said this, noticed if controller 'somecontroller' webapi controller:

public class foocontroller : apicontroller {     public class     {         public string somestring1 { get; set; }         public string somestring2 { get; set; }         public long[] somearray1 { get; set; }         public long[] somearray2 { get; set; }     }      [httppost]     public ihttpactionresult bar([frombody] entity)     {         return ok(new {ping1 = (string) null, ping2 = "", ping3 = new long[0]});     } } 

then data received in c# world inside 'someaction' method so:

    entity.somestring1: "",     entity.somestring2: null,     entity.somearray1: [],     entity.somearray2: null 

however, if controller mvc controller (mvc4 precise):

public class foocontroller : system.web.mvc.controller {     public class     {         public string somestring1 { get; set; }         public string somestring2 { get; set; }         public long[] somearray1 { get; set; }         public long[] somearray2 { get; set; }     }      [httppost]     public system.web.mvc.jsonresult bar([frombody] entity)     {         return json(new { ping1 = (string)null, ping2 = "", ping3 = new long[0] });     } } 

then data received in csharp world inside method so:

    entity.somestring1: null,     entity.somestring2: null,     entity.somearray1: null,     entity.somearray2: null 

it's apparent there deviation between webapi , mvc controllers in terms of how deserialization of parameters works both when comes empty arrays , empty strings. have managed work around quirks of mvc controller enforce "webapi" behaviour both empty strings , empty arrays (i post solution @ end completeness).

my question this:

why deviation in regards deserialization exists in first place?

i can't come terms done merely sake of "convenience" given how room default mvc-settings leave bugs nerve-racking discern , fix , consistently @ action/dto-level.

addendum: interested here's how forced mvc controller behave "webapi" way when comes deserializing parameters before feeding them action-methods:

  //inside application_start   modelbinders.binders.defaultbinder = new custommodelbinder_mvc();    valueproviderfactories.factories.remove(       valueproviderfactories.factories.oftype<jsonvalueproviderfactory>().firstordefault()   );    valueproviderfactories.factories.add(new jsonnetvalueproviderfactory_mvc()); 

utility classes:

  using system.web.mvc;    namespace project.utilities   {       public sealed class custommodelbinder_mvc : defaultmodelbinder //0       {           public override object bindmodel(controllercontext controllercontext, modelbindingcontext bindingcontext)           {               bindingcontext.modelmetadata.convertemptystringtonull = false;               binders = new modelbinderdictionary { defaultbinder = };               return base.bindmodel(controllercontext, bindingcontext);           }       }       //0 respect empty ajaxstrings aka "{ foo: '' }" gets converted foo="" instead of null  http://stackoverflow.com/a/12734370/863651   } 

and

    using newtonsoft.json;     using newtonsoft.json.converters;     using newtonsoft.json.serialization;     using system;     using system.collections;     using system.collections.generic;     using system.dynamic;     using system.globalization;     using system.io;     using system.web.mvc;     using ivalueprovider = system.web.mvc.ivalueprovider;      // resharper disable redundantcast      namespace project.utilities     {         public sealed class jsonnetvalueproviderfactory_mvc : valueproviderfactory //parameter deserializer         {             public override ivalueprovider getvalueprovider(controllercontext controllercontext)             {                 if (controllercontext == null)                     throw new argumentnullexception(nameof(controllercontext));                  if (!controllercontext.httpcontext.request.contenttype.startswith("application/json", stringcomparison.ordinalignorecase))                     return null;                  var jsonreader = new jsontextreader(new streamreader(controllercontext.httpcontext.request.inputstream));                 if (!jsonreader.read())                     return null;                  var jsonserializer = new jsonserializer //newtonsoft                 {                     converters = {new expandoobjectconverter(), new isodatetimeconverter()},                     contractresolver = new camelcasepropertynamescontractresolver(),                     referenceloophandling = referenceloophandling.ignore,                     preservereferenceshandling = preservereferenceshandling.none     #if debug                     ,formatting = formatting.indented     #endif                 };                  var jsonobject = jsonreader.tokentype == jsontoken.startarray //0                     ? (object)jsonserializer.deserialize<list<expandoobject>>(jsonreader)                     : (object)jsonserializer.deserialize<expandoobject>(jsonreader);                  return new dictionaryvalueprovider<object>(addtobackingstore(jsonobject), cultureinfo.currentculture); //1             }             //0 use jsonnet deserialize object dynamic expando object  if start [ treat array             //1 return object in dictionary value provider mvc can understand              //todo   refactor being nonrecursive             private static idictionary<string, object> addtobackingstore(object value, string prefix = "", idictionary<string, object> backingstore = null)             {                 backingstore = backingstore ?? new dictionary<string, object>(stringcomparer.ordinalignorecase);                  var d = value idictionary<string, object>;                 if (d != null)                 {                     foreach (var entry in d)                     {                         addtobackingstore(entry.value, makepropertykey(prefix, entry.key), backingstore);                     }                     return backingstore;                 }                  var l = value ilist;                 if (l != null)                 {                     if (l.count == 0) //0 here dragons                     {                         backingstore[prefix] = new object[0]; //0 here dragons                     }                     else                     {                         (var = 0; < l.count; i++)                         {                             addtobackingstore(l[i], makearraykey(prefix, i), backingstore);                         }                     }                     return backingstore;                 }                  backingstore[prefix] = value;                 return backingstore;             }              private static string makearraykey(string prefix, int index) => $"{prefix}[{index.tostring(cultureinfo.invariantculture)}]";             private static string makepropertykey(string prefix, string propertyname) => string.isnullorempty(prefix) ? propertyname : $"{prefix}.{propertyname}";         }         //0 here dragons      vital deserialize empty jsarrays "{ foo: [] }" empty csharp array aka new object[0]         //0 here dragons      without tweak null wrong     } 

why deviation in regards deserialization exist in first place?

history.

when asp.net mvc first created in 2009, used native .net javascriptserializer class handle json serialization. when web api came along 3 years later, authors decided switch using increasingly popular json.net serializer because more robust , full-featured older javascriptserializer. however, apparently felt not change mvc match backward compatibility reasons-- existing projects relied on specific javascriptserializer behaviors break unexpectedly when upgraded. so, decision created discrepancy between mvc , web api.

in asp.net mvc core, internals of mvc , web api have been unified , use json.net.


Comments

Popular posts from this blog

commonjs - How to write a typescript definition file for a node module that exports a function? -

openid - Okta: Failed to get authorization code through API call -

thorough guide for profiling racket code -