2012-10-03 16 views
7

Sto tentando di associare una stringa HTML immessa dall'utente da un POST a una variabile stringa semplice su un oggetto modello. Funziona bene se utilizzo l'attributo [AllowHtml]. Tuttavia, mi piacerebbe sanificare l'HTML prima si fa strada nel modello così ho creato un ModelBinder:Utilizzo di un Raccoglitore modello personalizzato per stringhe 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; 
    } 
} 

E anche 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; 
    } 
} 

Ho quindi annotare le proprietà modello che voglio essere sterilizzate con il nuovo attributo:

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

Questo sta seguendo l'esempio a http://msdn.microsoft.com/en-us/magazine/hh781022.aspx. Sfortunatamente, non sembra funzionare! Se metto un breakpoint nel mio metodo BindModel non viene mai colpito. Qualche idea?

UPDATE

Sulla base delle informazioni da Joel ho cambiato IModelBinder di intercettare il valore quando nel metodo SetProperty e applicare invece la SafeHtmlModelBinderAttribute alle proprietà della classe String contenente che possono contenere HTML. Il codice di verifica che la proprietà è una stringa e è anche permesso di contenere HTML prima di provare a disinfettare:

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

Do CustomModelBinderAttribute funziona sulle proprietà?Osservando il codice sorgente MVC, questo attributo viene utilizzato solo su 'internal const AttributeTargets ValidTargets = AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct; '... che mi suggerisce che il framework non può aspettarsi di vederlo sulle proprietà. – Schneider

risposta

1

Sono appena stato in lotta con la stessa cosa. Sembra che il metodo GetBinder() non venga mai chiamato. Dopo aver scavato intorno ho trovato this post dove la risposta accettata è che non è possibile inserire un attributo di associazione modello per una proprietà.

Se è vero o no, non lo so, ma per ora cercherò di ottenere ciò che devo fare in un modo diverso. Un'idea potrebbe essere quella di creare un ModelBinder più generico e verificare la presenza dell'attributo quando si esegue l'associazione, in modo simile a quanto suggerito in this answer.

1

ho trovato la seguente soluzione derrived da http://aboutcode.net/2011/03/12/mvc-property-binder.html funziona abbastanza bene

primo luogo è necessario un attributo semplice che è possibile applicare alle proprietà

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

      public Type BinderType { get; private set; } 
     } 

Il seguente modello di legante

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(); 
     } 
    } 

viene quindi sostituito in Global.asax.cs

ModelBinders.Binders.DefaultBinder = new DefaultModelBinder(); 

quindi creare il legante modello

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; 
     } 

    } 

che ora pulitamente essere applicata in modo standard su proprietà modello

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

o utilizzando l'attributo built-in sui parametri

public ActionResult DoSomething([ModelBinder(typeof(InvariantCultureDecimalModelBinder))] decimal value) 
Problemi correlati