Miroslav Popovic
Miroslav Popovic

Reputation: 12128

How to inject custom ModelMetadata properties and use them in Html helpers

The idea (simplified) is to have user definable properties in ViewModel contained in a dictionary. Something along these lines:

public class MyViewModel
{
    [Required]
    public string Name { get; set; }

    [DisplayName("User address")]
    public string Address { get; set; }

    // ...

    public IDictionary<string, string> MetaData { get; set; }
}

Let's say that MetaData contains several additional properties: PhoneNumber, Email, etc. that you can access with myViewModel.MetaData["PhoneNumber"].

What I would like to do is to be able to use those additional MetaData properties in Html helpers on View side, just like I would use normal properties.

So, in addition to using standard properties as:

Html.TextBox("Name")

I would also like to use those additional properties:

Html.TextBox("PhoneNumber")

My research lead me to inheriting from DataAnnotationsModelMetadataProvider (since it's necessary to also support standard DataAnnotations attributes for standard properties) and trying to figure out what exactly to override there in order to inject additional properties as additional ModelMetadata elements, but I'm kind of stuck.

Am I on the right path? Any additional pointer that could help me here?

Thanks

Upvotes: 2

Views: 1582

Answers (1)

Nathan Taylor
Nathan Taylor

Reputation: 24606

An alternative option might be to construct a dynamic object similar to ViewBag/ViewData in MVC 3. You would have an object which you could access via Model.MetaData.Foo and Foo would actually map to a key in your dictionary.

The type which backs the ViewBag object is System.Web.Mvc.DynamicViewDataDictionary; this class is internal and sealed so you would have to make a custom implementation of it (unless there's a better option I'm unaware of). A quick glance at the MVC 3 sources furnished this:

internal sealed class DynamicViewDataDictionary : DynamicObject {
    private readonly Func<ViewDataDictionary> _viewDataThunk;

    public DynamicViewDataDictionary(Func<ViewDataDictionary> viewDataThunk) {
        _viewDataThunk = viewDataThunk;
    }

    private ViewDataDictionary ViewData {
        get {
            ViewDataDictionary viewData = _viewDataThunk();
            Debug.Assert(viewData != null);
            return viewData;
        }
    }

    // Implementing this function improves the debugging experience as it provides the debugger with the list of all
    // the properties currently defined on the object
    public override IEnumerable<string> GetDynamicMemberNames() {
        return ViewData.Keys;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        result = ViewData[binder.Name];
        // since ViewDataDictionary always returns a result even if the key does not exist, always return true
        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value) {
        ViewData[binder.Name] = value;
        // you can always set a key in the dictionary so return true
        return true;
    }
}

One possible advantage to this solution over modifying the ModelMetadataProvider is that you wouldn't have to spend time building all the custom components for your scenario- the default providers should be sufficient.

Upvotes: 2

Related Questions