4

There are quite a few questions around JSON deserialization but a lot of them seem to be for MVC 1 or MVC 2. I don't seem to have found a satisfactory answer to this specifically for MVC 3.

I have an object with immutable properties and no default constructor, which I want to deserialize to in an ASP.NET MVC 3 application. Here is a simplified version:

public class EmailAddress
{
    public EmailAddress(string nameAndEmailAddress)
    {
        Name = parseNameFromNameAndAddress(nameAndEmailAddress);
        Address = parseAddressFromNameAndAddress(nameAndEmailAddress);
    }

    public EmailAddress(string name, string address)
    {
        Guard.Against<FormatException>(!isNameValid(name), "Value is invalid for EmailAddress.Name: [{0}]", name);
        Guard.Against<FormatException>(!isAddressValid(address), "Value is invalid for EmailAddress.Address: [{0}]", address);
        Name = name;
        Address = address;
    }

    public string Address { get; private set; }
    public string Name { get; private set; }

    // Other stuff
}

An example controller action might be:

[HttpPost]
public ActionResult ShowSomething(EmailAddress emailAddress)
{
    return View(emailAddress)
}

The JSON coming in is:

{"Address":"[email protected]","Name":"Joe Bloggs"}

What is the best way to get this to deserialize in MVC3? Is there some way of implementing a custom model binder or deserializer class that can handle this?

A solution that doesn't interfere with the object itself would be preferable (ie. a separate deserializer class, rather than adding attributes to properties, etc), although open to any good suggestions.

I found a similar question (with no answer) here: Can I deserialize to an immutable object using JavascriptSerializer?

2
  • Duffman, you are confusing your concepts. MVC 3 automatically deserializes JSON in the JsonValueProviderFactory. This deserialized information is then made available to be bound to your model. The solution suggested by Darin will work under all circumstances, even where the EmailAddress class is embedded within a different class. Commented Sep 9, 2011 at 13:50
  • @counsellorben - right you are, thanks for clarifying it for me! Commented Sep 9, 2011 at 13:59

2 Answers 2

6

Is there some way of implementing a custom model binder or deserializer class that can handle this?

Yes, you could write a custom model binder:

public class EmailAddressModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var addressKey = "Address";
        var nameKey = "Name";
        if (!string.IsNullOrEmpty(bindingContext.ModelName))
        {
            addressKey = bindingContext.ModelName + "." + addressKey;
            nameKey = bindingContext.ModelName + "." + nameKey;
        }

        var addressValue = bindingContext.ValueProvider.GetValue(addressKey);
        var nameValue = bindingContext.ValueProvider.GetValue(nameKey);
        if (addressValue == null || nameValue == null)
        {
            throw new Exception("You must supply an address and name");
        }
        return new EmailAddress(nameValue.AttemptedValue, addressValue.AttemptedValue);
    }
}

which will be registered in Application_Start:

ModelBinders.Binders.Add(typeof(EmailAddress), new EmailAddressModelBinder());

and finally all that's left is to invoke the action:

$.ajax({
    url: '@Url.Action("ShowSomething")',
    type: 'POST',
    data: JSON.stringify({ "Address": "[email protected]", "Name": "Joe Bloggs" }),
    contentType: 'application/json',
    succes: function (result) {
        alert('success');
    }
});
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks - this is a great answer, and good for the specific case mentioned in the question, where EmailAddress is the model for the action. I do also, however, have cases where EmailAddress is a property of different models - is there a more generic deserialization technique other than using a ModelBinder?
Ignore that last comment - now I understand better how this works it's just what I need!
0

EDITED ANSWER:

I misread the code, looked at the constructor parameters, instead of the properties.

The cause of your problem is the private set of the properties.

Ie, it should be:

public string Address { get; set; }
public string Name { get; set; }

If you make that change, it should all work.

Just remember:

The model binder looks for PROPERTIES, not the constructor!

5 Comments

I don't understand your answer - both the JSON and the property are the same case, ie "Name" and "Name". Also - the deserialization works if there is a default constructor and public setters. Do you mean that the constructor parameters need to also be the same case?
Have confirmed that this doesn't work - also has nothing to do with textboxes as the data is being posted via JSON from another application.
Oops, I was looking at the constructor parameters. Your issue is the private set. The model binder will try to find a public property, not a constructor with the right parameters.
Indeed - I've retracted the down vote for you - however I'm specifically asking how I can deserialize to a class that has private setters and no default constructor. Still looking for a solution!
I think I'll read the question a bit more carefully in future, things a bit hectic round here...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.