Pergunta

In MVC3 (.Net) it is possible to set a Bind Attribute on a parameter Type in the method signature for a Controller method:

[HttpPost]
public ActionResult Edit([Bind(Exclude = "Name")]User user)
{
   ...
}

I have written some Custom ModelBinders. It would be nice to be able to affect their behavior based on attributes set on a parameter Type, like so:

[HttpPost]
public ActionResult Edit([CustomModelBinderSettings(DoCustomThing = "True")]User user)
{
   ...
}

However, I can't seem to find a way to recover the attribute data. Is this possible?


Edit

I am trying to access the AttributeData from within a custom ModelBinder. In the example below "settings" is always null

public class TestBinder : DefaultModelBinder {
        public override object BindModel(
            ControllerContext controllerContext, 
            ModelBindingContext bindingContext) {

            //Try and get attribute from ModelType
            var settings = (CustomModelBinderSettingsAttribute) 
                TypeDescriptor.GetAttributes(bindingContext.ModelType)[typeof(CustomModelBinderSettingsAttribute)];

            ...

Thanks for any help.

Foi útil?

Solução 2

Answer: not possible :(

I stepped through the MVC source code, and eventually found that the ControllorActionInvoker class explicitly accesses the bind attribute from the Action Method parameters, and sets them on a property of the bindingContext. Without overriding or rewriting large parts of the MVC infrastructure, it is not possible for attributes added to Action Parameters to be accessed from a ModelBinder.

However, it is possible to retrieve attributes set on a ViewModel using the code illustrated in LukLeds post:

var attr = ("GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)];

Alas, that is not what I set out to do in this case, but it will have to do for now.

Outras dicas

You shouldn't have to do anything more than you've done to get access to the value of the DoCustomThing property from within your CustomModelBinderAttribute. Where are you trying to read the value of DoCustomThing that it's not available?

public class CustomModelBinderSettingsAttribute : CustomModelBinderAttribute
{
    public string DoCustomThing { get; set; }
    public override IModelBinder GetBinder()
    {
        // Pass the value of DoCustomThing to the custom model binder instance.
        MyCustomizableModelBinder binder = new MyCustomizableModelBinder(this.DoCustomThing);
        return binder;
    }
}

It's possible if you have (or create new) AuthorizeAttribute (filter attribute), that will store filterContext.ActionDescriptor (your action) in HttpContext.Current.Items and later in ModelBinder that value will be retrieved. Having ActionDescriptor you can find desired parameter by bindingContext.ModelName and check for attribute presence.

Things to note:

  • ModelBinder is not bound to Action executed
  • using ControllerContext you can get ActionName called, but not actual method called (may have multiple overloads with your parameter)
  • AuthorizeFilter is called before ModelBinder, normal ActionFilter is called after ModelBinder

You can get both the action's and its parameters' attributes by "intercepting" Controller.ActionInvoker.FindAction() and storing the attributes in HttpContext.Current.Items as mentioned here, or extended ControllerContext.RequestContext, as follows:

public class MyControllerActionInvoker : ControllerActionInvoker
{
    protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
    {
        var action = base.FindAction(controllerContext, controllerDescriptor, actionName);

        if (action != null)
        {
            var requestContext = ExtendedRequestContext.Bind(controllerContext);

            // if necessary, use action.GetCustomAttributes() to add action's attributes

            foreach (var parameter in action.GetParameters())
            {
                var attr = parameter.GetCustomAttributes(typeof(MyBinderAttribute), false).FirstOrDefault();
                if (attr != null)
                    requestContext.Attributes.Add(parameter.ParameterName, (MyBinderAttribute)attr);
            }
        }

        return action;
    }
}

public class ExtendedRequestContext : RequestContext
{
    public Dictionary<string, MyBinderAttribute> Attributes { get; private set; }

    public static ExtendedRequestContext Bind(ControllerContext controllerContext)
    {
        var requestContext = new ExtendedRequestContext
        {
            HttpContext = controllerContext.RequestContext.HttpContext,
            RouteData = controllerContext.RequestContext.RouteData,
            Attributes = new Dictionary<string, MyBinderAttribute>()
        };

        controllerContext.RequestContext = requestContext;
        return requestContext;
    }
}

The default action invoker is replaced either in your controller's constructor or in a custom controllers factory:

public MyController() : base()
{
    ActionInvoker = new MyControllerActionInvoker();
}

By the way, Controller.TempData already contains an item of ReflectedParameterDescriptor type, which gives you access to ActionDescriptor, so the above code may be redundant. However, beware this is implementation specific, so may change over time.

Finally, get the attributes from that storage in your binder class:

var requestContext = (ExtendedRequestContext)controllerContext.RequestContext;

if (requestContext.Attributes.ContainsKey(bindingContext.ModelName))
{
    var attr = requestContext.Attributes[bindingContext.ModelName];
    // apply your logic here
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top