AllowHtml属性,不引用MVC

时间:2013-11-07 20:15:04

标签: asp.net .net asp.net-mvc security dependency-management

我们已将业务逻辑层和业务对象分离为完全独立的项目/程序集。模型的某些属性可以包含HTML内容。在业务逻辑的前面,我们有一个ASP.NET MVC Web应用程序,用户可以在其中管理业务对象。

  • 要允许特定属性上的HTML内容,我们必须添加AllowHtml属性。但我们不能,因为我们不想在我们的核心项目中引用System.Web.Mvc。
  • 不能在多个程序集中使用部分类。
  • 使用MetadataType属性不是一个选项,因为它会导致对MVC的间接依赖或核心层与Web应用程序之间的循环依赖。
  • 另一个部分解决方案是使用ValidateInput属性关闭整个请求的请求验证,但我们只想关闭特定属性的请求验证。
  • 属性不是虚拟的,因此我们不能简单地创建派生类型来覆盖特定属性。
  • 我们不希望复制业务对象以查看具有完全相同属性和元数据的模型。
  • 覆盖模型绑定逻辑不是一种选择。

那么,我们如何向MVC模型绑定器指出我们想要在(并且仅在)某些特定属性上允许HTML内容,而不在我们的业务逻辑层中引用ASP.NET MVC?或者,如何在没有强引用的情况下从另一个程序集中注入元数据?

谢谢。

4 个答案:

答案 0 :(得分:7)

我必须将BindModel更改为以下(这是基于Russ Cam的答案),以便检查实际属性的属性。我还看了this的答案寻求帮助:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {            
        var holderType = bindingContext.ModelMetadata.ContainerType;
        if (holderType != null)
        {
            var propertyType = holderType.GetProperty(bindingContext.ModelMetadata.PropertyName);
            var attributes = propertyType.GetCustomAttributes(true);
            var hasAttribute = attributes
              .Cast<Attribute>()
              .Any(a => a.GetType().IsEquivalentTo(typeof(MyAllowHtmlAttribute)));
            if (hasAttribute)
            {
                bindingContext.ModelMetadata.RequestValidationEnabled = false;
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }

答案 1 :(得分:4)

实施您自己的IModelBinderAllowHtmlAttribute - 将该属性放入您的核心项目中,并将IModelBinder放入您的MVC应用程序中。

public class MyAllowHtmlAttribute : Attribute
{
}

要实施IModelBinder,只需从DefaultModelBinder继承并添加逻辑,以根据您自己AllowHtmlAttribute

的存在关闭请求验证
public class MyBetterDefaultModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var allowHtmlAttribute = bindingContext.ModelType.GetCustomAttribute<MyAllowHtmlAttribute>();

        if (allowHtmlAttribute != null)
        {
            bindingContext.ModelMetadata.RequestValidationEnabled = false;
        }

        return base.BindModel(controllerContext, bindingContext);
    }
}

然后在Application_Start(或其他启动代码)

中连接您自己的ModelBinder
ModelBinders.Binders.DefaultBinder = new MyBetterDefaultModelBinder();

自定义模型绑定器中的这个逻辑是MVC中的AllowHtmlAttribute所做的,但是你不能轻易地使用它,因为它与MVC中的ModelMetadata有内在联系。

答案 2 :(得分:1)

AllowHtml依赖的请求验证概念,绑定检查特定于Web请求。这里没有分离的问题,它们密切相关。所以不,你不能在没有参考System.Web等的情况下使用它。

您排除(在我看来)最正确的选项 - 查看模型,即使验证和绑定实际上是一个视图模型概念。

您不能拥有具有Web特定绑定和验证概念的可移植业务对​​象。

答案 3 :(得分:0)

在某些情况下这仍然有用: 我有类似的要求,但是我的类是首先由Entity Framework数据库生成的,因此该项目广泛使用了[MetadataType]属性。

我将这个问题的各个部分拼凑在一起,并将问题链接到该解决方案中,以允许该方法与指定[AllowHtml](或类似名称)的Metadata类一起使用

在您的Entity Framework项目中,定义一个属性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class SkipRequestValidationAttribute : Attribute
{
}

然后在您的元数据类中,将此属性分配给相关属性:

[MetadataType(typeof(ActivityLogMetadata))]
public partial class ActivityLog
{
}

public class ActivityLogMetadata
{
    [Required]
    [SkipRequestValidation]
    [Display(Name = "Body")]
    public string Body { get; set; }
}

现在,在您的MVC项目中,添加此自定义模型联编程序以查找这些元属性。

public class MyModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var containerType = bindingContext.ModelMetadata.ContainerType;

        if (containerType != null)
        {
            /* Do we have a Metadata attribute class specified for the Type we're binding? */
            var metaRedirectInfo = containerType
                .GetCustomAttributes(typeof(MetadataTypeAttribute), true)
                .OfType<MetadataTypeAttribute>().FirstOrDefault();

            if (metaRedirectInfo != null)
            {
                /* If our Meta class has a definition for this property, check it */
                var thisProperty = metaRedirectInfo.MetadataClassType.GetProperty(bindingContext.ModelMetadata.PropertyName);

                if (thisProperty != null)
                {
                    var hasAttribute = thisProperty
                        .GetCustomAttributes(false)
                        .Cast<Attribute>()
                        .Any(a => a.GetType().IsEquivalentTo(typeof(SkipRequestValidationAttribute)));

                    /* If we have a SkipRequestValidation attribute, ensure this property isn't validated */
                    if (hasAttribute)
                        bindingContext.ModelMetadata.RequestValidationEnabled = false;
                }
            }
        }

        return base.BindModel(controllerContext, bindingContext);
    }
}

最后,在您的MVC项目启动方法(例如Startup.cs)中,替换默认的模型绑定器:

ModelBinders.Binders.DefaultBinder = new MyModelBinder();