Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the "Func<object> modelAccessor" parameter for in MVC's DataAnnotationsModelMetadataProvider?

It's one of the parameters supplied to the CreateMetadata method (which you override if extending metadata support).

ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
                             Type containerType,
                             Func<object> modelAccessor, <<--THIS ONE
                             Type modelType,
                             string propertyName)

I had assumed that it allowed you to access the model object itself (e.g. for setting metadata based on model values), however when I try to use it to cast to my model object I just get null.

Entity ent = (Entity)modelAccessor(); // = Null

If I've missunderstood, can anyone explain what it's purpose is? Or alternatively, how to properly use it?

Thanks

like image 676
UpTheCreek Avatar asked Aug 05 '10 06:08

UpTheCreek


2 Answers

We originally had that as "object model", rather than "Func modelAccessor". We had to change it late in MVC 2's ship cycle.

The purpose is to delay retrieving the actual value of the model until such point as you know you're going to need it (that is, until you call ModelMetadata.Model).

The problem it solves is actually a rather esoteric one related to model binding against a LINQ to SQL class that has a foreign key reference in it. The problem is, if you've retrieved the child object which is represented by a foreign key relationship (which usually means a delay load of that object), then you're no longer allowed to choose a new child object by setting the foreign key ID property. It's very common to model bind the foreign key ID (and not the whole foreign key entity) when model binding, but if we'd retrieved the foreign key entity object (for the purposes of populating the ModelMetadata class) then that binding would no longer be legal, and actually throw an exception. Since ModelMetadata is used for both directions of models -- inbound, via model binding, and outbound, via HTML generation -- we needed to introduce the layer of indirection to protect your ability to use it in both scenarios without disrupting LINQ to SQL's rules.

like image 66
Brad Wilson Avatar answered Nov 17 '22 07:11

Brad Wilson


The modelAccessor parameter does not point to an instance of the object, but rather it is a function that will access some attribute of your object. The Func "encapsulates a method that has no parameters and returns a value of the type specified by the TResult parameter." For example, if we have following class:

public class Bar(){

    [DisplayName("I am Foo.")]
    public string Foo{get;}
}

When the CreateMetaData is called, it will be to create meta data for the Foo property and the modelAccessor will be a function that returns the value of Foo.

I did a little digging and found a way to get to the instance of the object, but it requires using reflection. You can do the following to get the Bar class in my example:

if (modelAccessor != null)
{
    //Use reflection to get the private field that holds the Bar object.
    FieldInfo container = modelAccessor.Target.GetType().GetField("container");

    //Invoke field on the modelAccessor target to get the instance of the Bar object.
    Bar myObject = (Bar)container.GetValue(modelAccessor.Target);
}

I've only run this against a simple test case, so your mileage may vary, but hopefully this will help clarify what is going on.

like image 39
sgriffinusa Avatar answered Nov 17 '22 06:11

sgriffinusa