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 をメソッド内で値をインターセプトするように変更し、代わりに、HTML を含むことができる文字列プロパティを含むクラスに をSetProperty適用しました。SafeHtmlModelBinderAttributeこのコードは、サニタイズを試みる前に、プロパティが文字列であり、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

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 に答える
1

私はちょうど同じことで苦労してきました。GetBinder() メソッドが呼び出されないようです。掘り下げた後、受け入れられた答えは、プロパティのモデルバインディング属性を配置することはできないというこの投稿を見つけました。

それが本当かどうかはわかりませんが、今のところ、別の方法で必要なことを達成しようとしています. この回答で提案されているものと同様に、より一般的な ModelBinder を作成し、バインディングを実行するときに属性の存在を確認することが 1 つのアイデアです。

于 2012-10-04T11:04:16.537 に答える