2

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

2
  • Are you sure you want to have a Dictionary in which you can throw anything and base your views on that? Before even starting, I would rethink this. I would say solutions like this too often end up in complex hacks and become unmaintainable. Not to mention how will you perform binding with this - it will require another hack probably. Commented Feb 10, 2011 at 11:01
  • Well, I agree with you that having this in a custom, single MVC project would be a maintenance nightmare. The thing is, this is intended to be used for our custom made CMS, and one of the requirements is to have this feature mentioned above. Binding part should not be a problem with custom model binder. Commented Feb 10, 2011 at 12:42

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for a good answer. I'll see if something along these lines is doable in our case.
Why is _viewDataThunk defined as Func<ViewDataDictionary> and not as ViewDataDictionary. What's the advantage of doing so?
@JRoppert It is a performance trick. By wrapping the object in Func<T> it effectively acts as a lazy loader. In this way, the contained ViewDataDictionary is not executed until it is consumed.

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.