开发者

Get a model's property value from within a custom attribute

I have a custom attribute called FileLink that uses DisplayForModel() to generate an ActionLink to a controller with an ID value.

The attribute looks like this:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class FileLinkAttribute : Attribute
{
    public string ActionName { get; set; }
    public string ControllerName { get; set; }
    public string IdPropertyName { get; set; }

    public FileLinkAttribute(string actionName, string controllerName, string idPropertyName)
    {
        ActionName = actionName;
        ControllerName = controllerName;
        IdPropertyName = idName;
    }
}

Is it used in a viewmodel, like so:

public class MyViewModel
{
    [HiddenInput(DisplayValue = false)]
    public int? Id { get; set; }

    [HiddenInput(DisplayValue = false)]
    public int? TargetId { get; set; }

    [FileLink("SomeAction", "SomeController", "TargetId")]
    public string Name { get; set; }
}

I have a custom MetadataProvider derived from DataAnnotationsModelMetadataProvider that puts the attribute values into the metadata AdditionalValues as so:

    // retrieve the values to generate a file link
    var fileLinkAttributes = attributes.OfType<FileLinkAttribute>();
    if (fileLinkAttributes.Any())
    {
        var fileLinkAttribute = fileLinkAttributes.First();
        metadata.AdditionalValues["FileLinkAttribute"] = fileLinkAttribute;
        metadata.TemplateHint = "FileLink";
    }

The goal is to have DisplayForModel call the DisplayTemplate below to generate a link with the value in TargetId from MyViewModel. So if TargetId is 5, the link will be "SomeController/SomeAction/5"

// This DisplayTemplate for use with the "FileLinkAttribute"
//
// Usage: [FileLi开发者_JAVA技巧nkAttribute("ActionName","ControllerName","IdPropertyName")]

var fileLinkAttribute = (FileLinkAttribute)ViewData.ModelMetadata.AdditionalValues["FileLinkAttribute"];

var targetId = *<GET VALUE OF CONTAINER MODEL IdPropertyName>*;

@Html.ActionLink((string)ViewData.Model, fileLinkAttribute.ActionName, fileLinkAttribute.ControllerName, new { Id = targetId}, null)

With all this in place, I am unable to access the containing object to pull the property value from TargetId. I have tried to do this within the Attribute, within the Provider, and within the DisplayTemplate, with no luck.

Is there a way or place I can access this value to accomplish my intent to read a property value from the viewmodel object containing the attribute?


After reading this post describing the purpose of modelAccessor in a custom DataAnnotationsModelMetadataProvider:

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

I was able to piece together this (very hackish) solution implemented in the custom provider:

    // retrieve the values to generate a file link
    var fileLinkAttributes = attributes.OfType<FileLinkAttribute>();
    if (fileLinkAttributes.Any())
    {
        var fileLinkAttribute = fileLinkAttributes.First();

        // modelAccessor contains a pointer the container, but the type is not correct but contains the name of the correct type
        if (modelAccessor.Target.GetType().ToString().Contains("System.Web.Mvc.AssociatedMetadataProvider"))
        {
            // get the container model for this metadata
            FieldInfo containerInfo = modelAccessor.Target.GetType().GetField("container");
            var containerObject = containerInfo.GetValue(modelAccessor.Target);

            // get the value of the requested property
            PropertyInfo pi = containerObject.GetType().GetProperty(fileLinkAttribute.IdPropertyName);
            string idPropertyValue = pi.GetValue(containerObject, null).ToString();
            fileLinkAttribute.IdPropertyValue = idPropertyValue;
        }

        metadata.AdditionalValues["FileLinkAttribute"] = fileLinkAttribute;
        metadata.TemplateHint = "FileLink";
    }

I am not sure how to derefence a Func to get it's actual target, and the type name comes out all munged wierdly in this example, such as

"System.Web.Mvc.AssociatedMetadataProvider+<>c__DisplayClassb"

So I convert to a string to check if it contains "System.Web.Mvc.AssociatedMetadataProvider". Probably not the best thing but this is the only way I could get it to work.

Oh, I also added a 4th property to the attribute to contain the value:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class FileLinkAttribute : Attribute { public string ActionName { get; set; } public string ControllerName { get; set; } public string IdPropertyName { get; set; } public string IdPropertyValue { get; set; }

public FileLinkAttribute(string actionName, string controllerName, string idPropertyName)
{
    ActionName = actionName;
    ControllerName = controllerName;
    IdPropertyName = idPropertyName;
}

}

and in the DisplayTemplate I do:

var modelId = fileLinkAttribute.IdPropertyValue;


This should be an editor template for strsing or object as that'swhat you are applying the attribute on. So inside ~/Views/Shared/EditorTemplates/string.cshtml:

@model string

@{
    var fileLinkAttribute = (FileLinkAttribute)ViewData.ModelMetadata.AdditionalValues["FileLinkAttribute"];
    var targetId = fileLinkAttribute.IdPropertyName;
}

@Html.ActionLink(Model, fileLinkAttribute.ActionName, fileLinkAttribute.ControllerName, new { Id = targetId}, null)

and in your main view:

@Html.EditorFor(x => x.Name)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜