In ASP.NET MVC, deserialize JSON prior to or in controller's action method

24,355

Solution 1

I resolved my problem by implementing an action filter; code sample is provided below. From the research, I learned that there is another solution, model binder, as takepara described above. But I don't really know that pros and cons of doing in either approach.

Thanks to Steve Gentile's blog post for this solution.

public class JsonFilter : ActionFilterAttribute
    {
        public string Parameter { get; set; }
        public Type JsonDataType { get; set; }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.Request.ContentType.Contains("application/json"))
            {
                string inputContent;
                using (var sr = new StreamReader(filterContext.HttpContext.Request.InputStream))
                {
                    inputContent = sr.ReadToEnd();
                }

                var result = JsonConvert.DeserializeObject(inputContent, JsonDataType);
                filterContext.ActionParameters[Parameter] = result;
            }
        }
    }

[AcceptVerbs(HttpVerbs.Post)]
[JsonFilter(Parameter="user", JsonDataType=typeof(User))]
public ActionResult Submit(User user)
{
    // user object is deserialized properly prior to execution of Submit() function

    return View();
}

Solution 2

1.create custom model binder

  public class UserModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      User model;

      if(controllerContext.RequestContext.HttpContext.Request.AcceptTypes.Contains("application/json"))
      {
        var serializer = new JavaScriptSerializer();
        var form = controllerContext.RequestContext.HttpContext.Request.Form.ToString();
        model = serializer.Deserialize<User>(HttpUtility.UrlDecode(form));
      }
      else
      {
        model = (User)ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
      }

      return model;
    }
  }

2.add model binder in application_start event

  ModelBinders.Binders[typeof(User)] = new UserModelBinder();

3.use jQuery $.get/$.post in view client JavaScript code.

  <% using(Html.BeginForm("JsonData","Home",new{},FormMethod.Post, new{id="jsonform"})) { %>

    <% = Html.TextArea("jsonarea","",new {id="jsonarea"}) %><br />

    <input type="button" id="getjson" value="Get Json" />
    <input type="button" id="postjson" value="Post Json" />
  <% } %>
  <script type="text/javascript">
    $(function() {
      $('#getjson').click(function() {
        $.get($('#jsonform').attr('action'), function(data) {
          $('#jsonarea').val(data);
        });
      });

      $('#postjson').click(function() {
        $.post($('#jsonform').attr('action'), $('#jsonarea').val(), function(data) {
          alert("posted!");
        },"json");
      });
    });
  </script>

Solution 3

Try this;

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Submit(FormCollection collection)
{
    User submittedUser = JsonConvert.DeserializeObject<User>(collection["user"]); 
    return View();
}

Solution 4

You could try Json.NET. The documentation is pretty good and it should be able to do what you need. You'll also want to grab JsonNetResult as it returns an ActionResult that can be used in ASP.NET MVC application. It's quite easy to use.

Json.NET also works well with Date serialization. More info regarding that can be found here.

Hope this helps.

Solution 5

After some research, I found Takepara's solution to be the best option for replacing the default MVC JSON deserializer with Newtonsoft's Json.NET. It can also be generalized to all types in an assembly as follows:

using Newtonsoft.Json;

namespace MySite.Web
{
    public class MyModelBinder : IModelBinder
    {
        // make a new Json serializer
        protected static JsonSerializer jsonSerializer = null;

        static MyModelBinder()
        {
            JsonSerializerSettings settings = new JsonSerializerSettings();
            // Set custom serialization settings.
            settings.DateTimeZoneHandling= DateTimeZoneHandling.Utc;
            jsonSerializer = JsonSerializer.Create(settings);
        }

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            object model;

            if (bindingContext.ModelType.Assembly == "MyDtoAssembly")
            {
                var s = controllerContext.RequestContext.HttpContext.Request.InputStream;
                s.Seek(0, SeekOrigin.Begin);
                using (var sw = new StreamReader(s))
                {
                    model = jsonSerializer.Deserialize(sw, bindingContext.ModelType);
                }
            }
            else
            {
                model = ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
            }
            return model;
        }
    }
}

Then, in Global.asax.cs, Application_Start():

        var asmDto = typeof(SomeDto).Assembly;
        foreach (var t in asmDto.GetTypes())
        {
            ModelBinders.Binders[t] = new MyModelBinder();
        }
Share:
24,355
weilin8
Author by

weilin8

Updated on July 09, 2022

Comments

  • weilin8
    weilin8 almost 2 years

    I am working on a website that will post a JSON object (using jQuery Post method) to the server side.

    { 
        "ID" : 1,
        "FullName" : {
           "FirstName" : "John",
           "LastName" : "Smith"
        }
    }
    

    At the same time, I wrote classes on the server side for this data structure.

    public class User
    {
        public int ID { get; set; }
        public Name FullName { get; set;}
    }
    
    public class Name
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    

    When I run the website with following code in my controller class, the FullName property doesn't get deserialized. What am I doing wrong?

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Submit(User user)
    {
        // At this point, user.FullName is NULL. 
    
        return View();
    }
    
  • TJB
    TJB over 14 years
    awww snap, weilin with 133t MVC skills, you better teach me on monday ; )
  • Daniel T.
    Daniel T. over 13 years
    This method has a notable advantage over the custom ModelBinder in that you can define the type to deserialize to. With the custom ModelBinder, it's hard-coded and thus only useful for one type.
  • Dmitriy
    Dmitriy over 9 years
    May be you mean 'ContentType' instead of 'AcceptTypes'