Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access C# model attribute within EditorFor

I have a model like below:

public class CreateStockcheckJobModel
{
    [Engineer(true)]
    public EngineerModel Engineer { get; set; }
}

I'm rendering the Engineer property in a View<CreateStockcheckJobModel> using Html.EditorFor(m => m.Engineer, "EngineerEditor").

How do I access the value in the Engineer attribute (in this case true) from within the code in my partial view (EngineerEditor.ascx)?


Below is my editor code

<%@ Control Language="C#" Inherits="ViewUserControl<EngineerModel>" %>
<% if (PropertyImRenderingHasAttributeWithTrueBooleanValue) // What goes here?
   { %>
<p>Render one thing</p>
<% }
   else
   { %>
<p>Render another thing</p>
<% } %>

I'm aware of reflection, however i'm unsure how to use it as the attribute isn't added to the EngineerModel class it's added to the Engineer property of the CreateStockcheckJobModel class. If i could get the PropertyInfo that I'm rendering from the editor code then I'd be sorted, but I don't know how to get that information. If I go down the route of enumerate all properties in the CreateStockcheckJobModel class then I'm going to get issues if I have more than one EngineerModel property (one might have the attribute with True, another might have False).

like image 956
George Duckett Avatar asked Sep 17 '12 09:09

George Duckett


2 Answers

This could be done easily in ASP.NET MVC 3 and later by implementing the IMetadataAware interface on your custom EngineerAttribute:

public class EngineerAttribute : Attribute, IMetadataAware
{
    public EngineerAttribute(bool isFoo)
    {
        IsFoo = isFoo;
    }

    public bool IsFoo { get; private set; }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues["IsFoo"] = IsFoo;
    }
}

and then inside the template:

<%@ Control Language="C#" Inherits="ViewUserControl<EngineerModel>" %>
<%
    var isFoo = (bool)ViewData.ModelMetadata.AdditionalValues["IsFoo"];
%>
<% if (isFoo) { %>
    <p>Render one thing</p>
<% } else { %>
    <p>Render another thing</p>
<% } %>

Unfortunately this interface doesn't exist in ASP.NET MVC 2. To achieve the same functionality you could write a custom metadata provider:

public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes, 
        Type containerType, 
        Func<object> modelAccessor, 
        Type modelType, 
        string propertyName
    )
    {
        var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        var engineer = attributes.OfType<EngineerAttribute>().FirstOrDefault();
        if (engineer != null)
        {
            metadata.AdditionalValues["IsFoo"] = engineer.IsFoo;
        }
        return metadata;
    }
}

that you will register in your Application_Start in order to replace the default one:

ModelMetadataProviders.Current = new MyMetadataProvider();

And now you could access this metadata in your template the same way as I showed earlier, using ViewData.ModelMetadata.AdditionalValues["IsFoo"]. Obviously you could put an arbitrarily complex object inside the AdditionalValues property, not just booleans.

Also you might find the following article useful about metadata.

like image 109
Darin Dimitrov Avatar answered Nov 13 '22 00:11

Darin Dimitrov


NOTE: Below is a possible way to do this but using IMetadataAware as described in the accepted answer is a far better way. It's usually better to avoid reflection if you can.

You can only do this via reflection. Here's a good example on how to do this for method attributes.

Code example for getting value of EngineerAttribute on property Engineer:

PropertyInfo pi = Model.GetType().GetProperty("Engineer");
EngineerAttribute a =
    System.Attribute.GetCustomAttribute(pi, typeof(EngineerAttribute));

Code example for getting all properties that have the attribute [Engineer(true)]:

var t = Model.GetType();
var engineerProperties =
    from p in t.GetProperties()
    let ca = System.Attribute.GetCustomAttribute(p, typeof(EngineerAttribute))
    where ca != null && ca.BooleanProperty == true
    select p;

And when you have these properties, you can use an overload of EditorFor that allows you to specify additional view data, that you can then use in your subview:

Html.EditorFor(m => m.Engineer, new { IsEngineer = isEngineer });
like image 43
Ronald Wildenberg Avatar answered Nov 13 '22 00:11

Ronald Wildenberg