Jason Butera
Jason Butera

Reputation: 2454

ASP.Net MVC Model Binding to JSON in EditorFor

I want to be able to pass information from my view model to my controller via JSON in hidden textboxes. I'm working with polygons in the Google maps API. As the user edits the polygon, I am storing the vertices in a hidden input via javascript.

var p = mypolygon.getPath().getArray();
var s = '';
for (var i = 0; i < p.length; i++)
    s += ((i > 0) ? ', ' : '') + '{ lat: ' + p[i].lat() + ', lng: ' + p[i].lng() + ' }';
$('#@Html.IdFor(m => m.GeofencePoints)').val('[' + s + ']');

It results in this:

  <input id="GeofencePoints" name="GeofencePoints" type="hidden" value="[{ lat: 38.221276965853264, lng: -97.6892964859955 }, { lat: 38.21294239796929, lng: -97.68770861825868 }, { lat: 38.2122680083775, lng: -97.67782997884831 }, { lat: 38.220434074436966, lng: -97.67787289419255 }]">

I would like to have the following view model bound to the view:

public class MyMapViewModel
{
    public GoogleMapPoint[] GeofencePoints {get;set;}
    public string OtherProperty {get;set;}
}

public class GoogleMapPoint
{
    public double lat {get;set;}
    public double lng {get;set;}
}

This is a little different from examples I've seen since I only want one of my properties posted as Json. Can anybody point me in the right direction? I know I could just pass it along as a string and serialize/deserialize on my own. However, I was hoping for an elegant customer model binder solution.

UPDATE

I figured out a generic solution based on this post I found: http://mkramar.blogspot.com/2011/05/mvc-complex-model-postback-bind-field.html

public class JsonBindableAttribute : Attribute
{
}

public class MyModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder)
    {
        if (propertyDescriptor.Attributes.OfType<Attribute>().Any(x => (x is JsonBindableAttribute)))
        {
            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
            return JsonConvert.DeserializeObject(value, propertyDescriptor.PropertyType);
        }

        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}

In my model:

[JsonBindable]
[UIHint("GoogleMapPoints")]
public GoogleMapPoint[] GeofencePoints { get; set; }

And then in global.asax Application_Start()

ModelBinders.Binders.DefaultBinder = new MyModelBinder();

Unfortunately this only gets me so far. This binds form values to my class just fine. However, it doesn't solve rendering a property as Json. As you can see, I created a custom editor GoogleMapPoints.cshtml which I basically have to recreate for every class I'll have as jsonbindable.

@model IEnumerable<GoogleMapPoint>
@Html.Hidden("", Newtonsoft.Json.JsonConvert.SerializeObject(Model))

Does anybody know of a way to have a generic custom editor that would pay attention to attributes rather than types so that EditorFor for properties colored with my JsonBindable attribute always render as Json in a hidden field regardless of type/class?

Upvotes: 2

Views: 1227

Answers (1)

Daniel J.G.
Daniel J.G.

Reputation: 35002

You could create a model binder for that particular model. This will extend the default binder by adding some specific logic for the property that holds the map points, deserializing the json from the request parameter.

[ModelBinder(typeof(MyMapModelBinder))]
public class MyMapViewModel
{
    public List<GoogleMapPoint> GeofencePoints { get; set; }
    public string OtherProperty { get; set; }
}

public class GoogleMapPoint
{
    public double lat { get; set; }
    public double lng { get; set; }
}

public class MyMapModelBinder : DefaultModelBinder
{
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
        if (propertyDescriptor.Name == "GeofencePoints")
        {
            var model = bindingContext.Model as MyMapViewModel;
            if (model != null)
            {
                var value = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
                var jsonMapPoints = value.AttemptedValue;

                if (String.IsNullOrEmpty(jsonMapPoints))                    
                    return ;                    

                MyMapViewModel mapModel = model as MyMapViewModel;
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                mapModel.GeofencePoints = (List<GoogleMapPoint>)serializer.Deserialize(jsonMapPoints, typeof(List<GoogleMapPoint>));
                return;
            }
        }
        base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
    }

}

Upvotes: 1

Related Questions