2012-10-03 23 views
7

Estoy intentando vincular una cadena HTML ingresada por el usuario desde un POST en una variable de cadena simple en un objeto modelo. Esto funciona bien si uso el atributo [AllowHtml]. Sin embargo, me gustaría para desinfectar el código HTML antes de que se abre paso en el modelo, así que he creado un ModelBinder:Uso de un archivador de modelo personalizado para cadenas HTML

public class SafeHtmlModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerCtx, ModelBindingContext bindingCtx) 
    { 
     var bound = base.BindModel(controllerCtx, bindingCtx); 
     // TODO - return a safe HTML fragment string 
     return bound; 
    } 
} 

Y también un CustomModelBinderAttribute:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 
public class SafeHtmlModelBinderAttribute : CustomModelBinderAttribute 
{ 
    public SafeHtmlModelBinderAttribute() 
    { 
     binder = new SafeHtmlModelBinder(); 
    } 

    private IModelBinder binder; 

    public override IModelBinder GetBinder() 
    { 
     return binder; 
    } 
} 

continuación, anoto las del modelo que quiero ser desinfectados con el nuevo atributo:

[Required(AllowEmptyStrings = false, ErrorMessage = "You must fill in your profile summary")] 
[AllowHtml, SafeHtmlModelBinder, WordCount(Min = 1, Max = 300)] 
public string Summary { get; set; } 

Esto está siguiendo el ejemplo de la http://msdn.microsoft.com/en-us/magazine/hh781022.aspx. Desafortunadamente, ¡parece que no funciona! Si coloco un punto de interrupción en mi método BindModel, nunca se golpea. ¿Algunas ideas?

ACTUALIZACIÓN

Sobre la base de la información de Joel he cambiado de IModelBinder para interceptar el valor cuando en el método SetProperty y en lugar de aplicar el SafeHtmlModelBinderAttribute a las propiedades de cadena que contiene la clase que pueden contener HTML. El código comprueba que la propiedad es una cadena y también se permite para contener HTML antes de intentar desinfectar: ​​

public class SafeHtmlModelBinder : DefaultModelBinder 
{ 
    protected override void SetProperty(
     ControllerContext controllerCtx, 
     ModelBindingContext bindingCtx, 
     PropertyDescriptor property, 
     object value) 
    { 
     var propertyIsString = property.PropertyType == typeof(string); 
     var propertyAllowsHtml = property.Attributes.OfType<AllowHtmlAttribute>().Count() >= 1; 

     var input = value as string; 
     if (propertyIsString && propertyAllowsHtml && input != null) 
     { 
      // TODO - sanitize HTML 
      value = input; 
     } 

     base.SetProperty(controllerCtx, bindingCtx, property, value); 
    } 
} 
+0

¿CustomModelBinderAttribute funciona en las propiedades?Mirando el código fuente de MVC ese atributo solo se usa en 'const interno AttributeTargets ValidTargets = AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct; '... lo que me sugiere que el framework puede no esperar ver esto en las propiedades. – Schneider

Respuesta

1

He estado luchando con la misma cosa. Parece que el método GetBinder() nunca se llama. Después de cavar, encontré this post, donde la respuesta aceptada es que no es posible poner un atributo de vinculación de modelo para una propiedad.

Ya sea cierto o no, no lo sé, pero por ahora solo voy a tratar de lograr lo que necesito hacer de otra manera. Una idea sería crear un ModelBinder más genérico y verificar la presencia de su atributo al realizar el enlace, similar a lo que se sugiere en this answer.

1

que he encontrado la siguiente solución derrived de http://aboutcode.net/2011/03/12/mvc-property-binder.html funciona bastante bien

Lo primero que necesita un atributo simple que se puede aplicar a las propiedades

public class PropertyBinderAttribute : Attribute 
     { 
      public PropertyBinderAttribute(Type binderType) 
      { 
       BinderType = binderType; 
      } 

      public Type BinderType { get; private set; } 
     } 

El siguiente modelo de aglutinante

public class DefaultModelBinder : System.Web.Mvc.DefaultModelBinder 
    { 
     protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) 
     { 
      var propertyBinderAttribute = TryFindPropertyBinderAttribute(propertyDescriptor); 
      if (propertyBinderAttribute != null) 
      { 
       var binder = CreateBinder(propertyBinderAttribute); 
       binder.BindProperty(controllerContext, bindingContext, propertyDescriptor); 
      } 
      else // revert to the default behavior. 
      { 
       base.BindProperty(controllerContext, bindingContext, propertyDescriptor); 
      } 
     } 

     IPropertyBinder CreateBinder(PropertyBinderAttribute propertyBinderAttribute) 
     { 
      return (IPropertyBinder)DependencyResolver.Current.GetService(propertyBinderAttribute.BinderType); 
     } 

     PropertyBinderAttribute TryFindPropertyBinderAttribute(PropertyDescriptor propertyDescriptor) 
     { 
      return propertyDescriptor.Attributes.OfType<PropertyBinderAttribute>().FirstOrDefault(); 
     } 
    } 

se anula en Global.asax.cs

ModelBinders.Binders.DefaultBinder = new DefaultModelBinder(); 

entonces crear su aglutinante modelo

public class InvariantCultureDecimalModelBinder : IModelBinder, IPropertyBinder 
    { 
     public void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) 
     { 
      var subPropertyName = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name); 
      if (!bindingContext.ValueProvider.ContainsPrefix(subPropertyName)) 
       return; 

      var attemptedValue = bindingContext.ValueProvider.GetValue(subPropertyName).AttemptedValue; 
      if (String.IsNullOrEmpty(attemptedValue)) 
       return; 

      object actualValue = null; 
      try 
      { 
       actualValue = Convert.ToDecimal(attemptedValue, CultureInfo.InvariantCulture); 
      } 
      catch (FormatException e) 
      { 
       bindingContext.ModelState[propertyDescriptor.Name].Errors.Add(e); 
      } 

      propertyDescriptor.SetValue(bindingContext.Model, actualValue); 
     } 

     public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
     { 
      var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
      var modelState = new ModelState { Value = valueResult }; 
      object actualValue = null; 
      try 
      { 
       if (!String.IsNullOrEmpty(valueResult.AttemptedValue)) 
        actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.InvariantCulture); 
      } 
      catch (FormatException e) 
      { 
       modelState.Errors.Add(e); 
      } 

      bindingContext.ModelState.Add(bindingContext.ModelName, modelState); 
      return actualValue; 
     } 

     //Duplicate code exits in DefaulModelBinder but it is protected internal 
     private string CreateSubPropertyName(string prefix, string propertyName) 
     { 
      if (string.IsNullOrEmpty(prefix)) 
       return propertyName; 
      if (string.IsNullOrEmpty(propertyName)) 
       return prefix; 
      else 
       return prefix + "." + propertyName; 
     } 

    } 

que puede ahora limpiamente ser aplicada de una manera estándar en Características del modelo

[PropertyBinder(typeof(InvariantCultureDecimalModelBinder))] 
public decimal? value 

o utilizando el atributo incorporado en parámetros

public ActionResult DoSomething([ModelBinder(typeof(InvariantCultureDecimalModelBinder))] decimal value) 
Cuestiones relacionadas