使用HTML字符串的自定义模型绑定器

时间:2012-10-03 09:51:32

标签: c# asp.net-mvc model-binding

我试图将用户输入的HTML字符串从POST绑定到模型对象上的简单字符串变量中。如果我使用[AllowHtml]属性,这可以正常工作。但是,我想在HTML进入模型之前对其进行清理,因此我创建了一个ModelBinder:

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

还有一个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;
    }
}

然后,我使用新属性注释我想要清理的模型属性:

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

这是关注http://msdn.microsoft.com/en-us/magazine/hh781022.aspx的示例。不幸的是,它似乎没有用!如果我在BindModel方法中放置一个断点,它就永远不会被击中。有什么想法吗?

更新

基于来自Joel的信息,我更改了我的IModelBinder以在SetProperty方法中截取值,而是将SafeHtmlModelBinderAttribute应用于包含可包含HTML的字符串属性的类。代码检查属性是否为字符串,并且在尝试清理之前还允许包含HTML:

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

2 个答案:

答案 0 :(得分:1)

我一直在努力做同样的事情。似乎永远不会调用GetBinder()方法。在挖掘之后,我找到this post,其中接受的答案是不可能为属性设置模型绑定属性。

这是否真实我不知道但是现在我只是想尝试以不同的方式实现我需要做的事情。一个想法是创建一个更通用的ModelBinder,并在执行绑定时检查您的属性是否存在,类似于this answer中的建议。

答案 1 :(得分:1)

我发现以下解决方案来自于 http://aboutcode.net/2011/03/12/mvc-property-binder.html 效果很好

首先,您需要一个可以应用于属性的简单属性

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

            public Type BinderType { get; private set; }
        }

以下模型装订器

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();
        }
    }
然后在Global.asax.cs

中覆盖

ModelBinders.Binders.DefaultBinder = new DefaultModelBinder();

然后创建模型绑定器

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

    }

现在可以干净地以标准方式应用于模型属性

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

或使用参数的内置属性

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