JBeckton
JBeckton

Reputation: 7111

Can I modify Model Metadata inside an action filter?

I am trying to implement a requirement that will allow me to change the state of an html control based on the users role or other business logic. For example some users should not be able to edit a certain model property or in some cases I do not even want the control to render to the html DOM.

Within the HTML helpers the Metadata can tell the helper whether or not to render a control or it can make it read-only/disabled. Rather than re-invent the wheel here I just want to use what is already in place by changing the metadata within an action filter based on logic at run time. I do not want this logic within my view and I do not want to have to create custom helpers for every single html control if I do not have to..

What I was trying to do was create an action filter to handle evaluating logic rules in my business layer and then make changes to the metadata for a given model.property.

The problem is that my modifications to the metadata do not seem to be making it to the view. It almost seems like my changes to the metadata within the action filter are not by reference so therefore I am not modifying the same instance that is used for the view?

public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
    var trans = filterContext.Controller.ViewData.ModelMetadata.Properties.Where(e => e.PropertyName == "Transaction").FirstOrDefault();
    trans.DisplayName = "Show FOO!";
}

What am I doing wrong?

Upvotes: 1

Views: 1909

Answers (2)

nemesv
nemesv

Reputation: 139768

You cannot change the ModelMetadata.Properties values in a filter in fact you cannot change them at all.

The problem is inside the built in DataAnnotationsModelMetadataProvider (to be precise AssociatedMetadataProvider's GetMetadataForProperties method) namelly the ModelMetadata.Properties gets regenerated from the attributes every time you iterate over it.

So the items inside the ModelMetadata.Properties collection are immutable.

You can check it with debugging:

public override void OnResultExecuting(ResultExecutingContext filterContext)
{
     var properties = filterContext.Controller.ViewData.ModelMetadata.Properties
     var trans1 = properties.Where(e => e.PropertyName == "Transaction").First();
     trans1.DisplayName = "Show FOO!";
     var trans2 = properties.Where(e => e.PropertyName == "Transaction").First();
}

The trans1 and trans2 will be different instances (you can check their hashcode) so trans2 displayname will be the original displayname not "Show FOO!".

So you need to Extend/Write the a new MetadataProvider to achieve your goal.

Upvotes: 1

Ryand.Johnson
Ryand.Johnson

Reputation: 1906

you could check the user's role in the view and make adjustments that way. The role is evaluated server side before rendering the html

@if(HttpContext.Current.User.IsInRole("MyRole")) {
   <input type='text' id='txtSomeInput' />
}

Upvotes: 0

Related Questions