7

我正在尝试将用户输入的 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);
    }
}
4

2 回答 2

1

我一直在为同样的事情而苦苦挣扎。似乎从未调用过 GetBinder() 方法。在四处挖掘之后,我发现了这篇文章,其中接受的答案是不可能为属性放置模型绑定属性。

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

于 2012-10-04T11:04:16.537 回答
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)
于 2013-08-21T11:44:33.033 回答