【问题标题】:AllowHtml attribute without referencing MVC不引用 MVC 的 AllowHtml 属性
【发布时间】:2013-11-07 20:15:03
【问题描述】:

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

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

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

谢谢。

【问题讨论】:

  • 在您的核心项目中引用 System.Web.Mvc(进而引用 System.Web)是否会出现问题?
  • 你是认真的吗!?按照您的模式,我可以参考 Windows Phone、Windows RT、WPF、Windows 窗体、ASP.NET MVC ......以及分离的核心程序集中的所有技术。根本没有分离的意义。
  • 您排除解决方案 - 查看模型或包括参考。坦率地说,其他任何事情都会比它的价值更麻烦。
  • 绝对不能引用任何特定的东西。我们的核心层甚至可以建立在 PCL 上。 ViewModel 是,但复制所有内容并保持一致需要大量工作。
  • 你不必在视图模型中复制,事实上那是没有意义的。视图模型应该就是这样 - 为视图优化,而不是业务对象的副本。

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


【解决方案1】:

我必须将 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);
    }

【讨论】:

  • 这是检查单个属性的自定义属性所需要的。
【解决方案2】:

实现您自己的 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 相关联。

【讨论】:

  • 不错,这样我们就不用把BO-s复制到VM-s了,所有的依赖规则都保留了。但它扩大了允许 HTML 从特定属性到模型的范围。但是如果我设置 PropertyMetadata 而不是 ModelMetadata,这似乎是最好的解决方案。谢谢你。请在您的答案中更改它,所以经过一些调查,如果我认为它是正确的,我可以接受它作为一个回答。
  • @BlueCode 不,它不会“扩大允许 HTML 从特定属性到模型的范围” - 除非您在应用程序中定义了其他 ModelBinders,否则模型的属性将依次为使用BindModel() 方法由自定义模型绑定器绑定,因此只需为您要允许html 的属性添加属性。在aspnetwebstack.codeplex.com/SourceControl/latest#src/…查看DefaultModelBinder的源代码
  • 这是一个很好的答案,它检查被绑定的模型类上是否存在自定义属性。为了检查类 cmour 的单个属性的答案就可以了。
【解决方案3】:

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

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

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

【讨论】:

  • 查看模型似乎是上述所有方面的最佳解决方案。但是 AllowHtml 是唯一的属性,不能在该上下文中使用。所有验证逻辑都可以使用组件模型属性并通过 IValidateableObject 接口单独实现。甚至数据类型也被标记为 HTML。
【解决方案4】:

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

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

在您的实体框架项目中,定义一个属性:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多