2010-04-15 15 views
12

En una reciente question sobre los atributos MVC, alguien preguntó si el uso de los atributos HttpPost y HttpDelete en un método de acción daría como resultado que se permitiera o no se permitía ninguna solicitud (ya que no puede ser tanto una publicación como una eliminación en el Mismo tiempo). Noté que ActionMethodSelectorAttribute, de la que HttpPostAttribute y HttpDeleteAttribute ambos derivan está decorado con¿Cuál es el punto de indicar AllowMultiple = false en una clase de atributo abstracta?

[AttributeUsage(AttributeTargets.Method, 
       AllowMultiple = false, 
       Inherited = true)] 

que había esperado que no permite tanto HttpPost y HttpDelete en el mismo método debido a esto, pero el compilador no se queja. Mis pruebas limitadas me dicen que el uso de atributos en la clase base simplemente se ignora. AllowMultiple aparentemente solo permite que dos de los mismos atributos se apliquen a un método/clase y no parece considerar si esos atributos se derivan de la misma clase que está configurada para no permitir múltiplos. Además, el uso de atributos en la clase base ni siquiera le impide cambiar el uso del atributo en una clase derivada. Siendo ese el caso, ¿de qué sirve incluso establecer los valores en la clase de atributo base? ¿Es solo un aviso o me estoy perdiendo algo fundamental en cómo funcionan?

FYI - resulta que usar ambos básicamente impide que ese método sea considerado. Los atributos se evalúan de forma independiente y uno de ellos siempre indicará que el método no es válido para la solicitud, ya que no puede ser simultáneamente tanto una Publicación como una Eliminación.

Respuesta

8

La configuración AllowMultiple en un atributo base esencialmente establece un valor predeterminado para todos los atributos que se derivan de ella. Si desea que todos los atributos derivados de un atributo base permitan múltiples instancias, puede guardar la duplicación aplicando un atributo [AttributeUsage] para este efecto al atributo base, evitando la necesidad de hacer lo mismo con todos los atributos derivados.

Por ejemplo, suponga que desea permitir esto:

public abstract class MyAttributeBase : Attribute 
{ 
} 

public sealed class FooAttribute : MyAttributeBase 
{ 
} 

public sealed class BarAttribute : MyAttributeBase 
{ 
} 

[Foo] 
[Foo] 
[Bar] 
[Bar] 
public class A 
{ 
} 

Tal como está, esto genera un error del compilador ya los atributos personalizados, por defecto, no permita que varias instancias. Ahora, se podría aplicar un [AttribteUsage] tanto [Foo] y [Bar] así:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 
public sealed class FooAttribute : MyAttributeBase 
{ 
} 

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 
public sealed class BarAttribute : MyAttributeBase 
{ 
} 

Pero, alternativamente, en su lugar podría simplemente aplicarlo al atributo base:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 
public abstract class MyAttributeBase : Attribute 
{ 
} 

public sealed class FooAttribute : MyAttributeBase 
{ 
} 

public sealed class BarAttribute : MyAttributeBase 
{ 
} 

Ambos enfoques tienen el mismo efecto directo (Se permiten varias instancias de [Foo] y [Bar]) pero el segundo enfoque también tiene el efecto indirecto de que cualquier otros atributos derivados de [MyAttribute] permitirá ahora varias instancias a menos que tiene su propio [AttributeUsage] que anula esa configuración.

+0

Su problema es que permite múltiples de la misma MyAttributeBase está permitido cuando AllowMultiple = false. Entonces solo se te permitiría un solo [Foo] O [Bar]. – Rangoric

+0

No hay forma de hacerlo en .NET, ya que el encuestador descubrió y dejó en claro en la pregunta. Estaba abordando la pregunta de "¿por qué alguna vez aplicaría AttributeUsage a un atributo base?" –

0

AllowMultiple permite/no permite que el atributo específico se use más de una vez. No tiene ningún efecto sobre si otros atributos pueden combinarse con él.

Así, por ejemplo, si tiene una ObfuscationAttribute que controla si el cambio de nombre del método está activado o desactivado, usted no desea que los usuarios sean capaces de hacer esto:

[Obfuscation("DisableRenaming")] 
[Obfuscation("EnableRenaming")] 
void MyMethod() 
{ 
} 

En este caso, la ofuscación no puede ser tanto habilitado como deshabilitado, por lo que usaría AllowMultiple = false para garantizar que el método solo se marque una vez con este atributo en particular.

Lo que podría hacer hipotéticamente, en su caso mutuamente exclusivo, es usar un único atributo llamado HttpSettings, que tomó un paranetro que indica si se aplicaba al "Modo" Publicar o Eliminar. Esto podría ser PermitirMúltiple = falso para imponer la exclusividad mutua de las opciones.

+0

Su explicación ya es parte de la pregunta. La propuesta requiere la propiedad del código. La pregunta base es sobre la herencia. –

+0

Ya existe una AcceptVerbsAttribute que hace exactamente lo que describes. Como @Henk indica, lo que me interesa es cómo entra en juego el uso de los atributos de la clase base, si es que lo hace. – tvanfosson

+0

Ok, lo siento, he malinterpretado su pregunta. –

0

Vamos a hacer una prueba corta:

using System; 
using System.Text; 
using System.Collections.Generic; 
using System.Linq; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using System.Reflection; 

namespace TestAttrs { 
    public abstract class BaseAttribute : Attribute { 
     public string text; 
    } 

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] 
    public class MultipleInheritedAttribute : BaseAttribute { } 

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] 
    public class MultipleNonInheritedAttribute : BaseAttribute { } 

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 
    public class SingleInheritedAttribute : BaseAttribute { } 

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 
    public class SingleNonInheritedAttribute : BaseAttribute { } 

    public class BaseClass { 
     [MultipleInherited(text = "MultipleInheritedBase")] 
     [MultipleNonInherited(text = "MultipleNonInheritedBase")] 
     [SingleInherited(text = "SingleInheritedBase")] 
     [SingleNonInherited(text = "SingleNonInheritedBase")] 
     public virtual void Method() { ; } 
    } 

    public class DerivedClass : BaseClass { 
     [MultipleInherited(text = "MultipleInheritedDerived")] 
     [MultipleNonInherited(text = "MultipleNonInheritedDerived")] 
     [SingleInherited(text = "SingleInheritedDerived")] 
     [SingleNonInherited(text = "SingleNonInheritedDerived")] 
     public override void Method() { 
      base.Method(); 
     } 
    } 

    [TestClass] 
    public class AttributesTest { 
     [TestMethod] 
     public void TestAttributes() { 
      MemberInfo mi = typeof(DerivedClass).GetMember("Method")[0]; 
      object[] attrs = mi.GetCustomAttributes(true); 

      string log = ""; 
      foreach(BaseAttribute attr in attrs) { 
       log += attr.text+"|"; 
      } 
      Assert.AreEqual("MultipleInheritedDerived|SingleInheritedDerived|SingleNonInheritedDerived|MultipleNonInheritedDerived|MultipleInheritedBase|", log); 
     } 
    } 
} 

Como se puede ver, si el atributo está marcado Inherted=true entonces será devuelto para que las clases derivadas, pero si método heredado está marcado con el mismo atributo - será suprimido si AllowMultiple=false. Entonces, en nuestra prueba, la cadena de registro contiene "MultipleHheritedDerived" y "MultipleInheritedBase", pero no "SingleInheritedBase".

Respondiendo a su pregunta, ¿qué sentido tiene? Esta combinación le permite tener un controlador base con método virtual que puede anular sin preocuparse por el atributo (se tomará del método base), pero al mismo tiempo puede anularlo si lo desea. HttpPostAttribute no es un buen ejemplo, porque no tiene parámetros, pero otros atributos pueden beneficiarse de tales configuraciones.

Además, tenga en cuenta que los atributos que consume código:

 object[] attrs = mi.GetCustomAttributes(true); 

especifique que está interesado en atributos heredados. Si escribe

 object[] attrs = mi.GetCustomAttributes(false); 

entonces el resultado contendría 4 atributos independientemente de su configuración de uso. Por lo tanto, es posible que el desarrollador ignore la configuración de uso de atributos heredados.

+0

Mi problema no era cómo se aplican los atributos a los métodos, sino por qué el atributo base tiene un uso de atributo que no parece importar. No se puede aplicar un ActionMethodSelectorAttribute porque es abstracto. Tienes que aplicar uno de sus hijos. AllowMultiple = false en el atributo base abstracto parece no tener importancia porque puede aplicar múltiples atributos secundarios del atributo base siempre que sean de diferentes tipos derivados. Para aplicar su ejemplo, el uso del atributo debería aplicarse a BaseAttribute. – tvanfosson

+0

@tvanfosson Ahora finalmente veo a qué te refieres. AllowMultiple especifica solo instancias de los mismos tipos finales de atributos, no su clase base. Para muchos usos, tiene un sentido perfecto. En este caso particular, es inútil, pero aún así lo guiará si, por ejemplo, va a hacer su propio descendiente no abstracto de ActionMethodSelectorAttribute, algo así como [HttpMethod (HttpMethod.Post)] –

Cuestiones relacionadas