【问题标题】:MVC3/4 Security & Model Binding for a dynamic object动态对象的 MVC3/4 安全和模型绑定
【发布时间】:2011-07-12 05:44:30
【问题描述】:

我真的需要重新审视这个问题,所以我希望你们中的一些人能给我一些反馈,我想我已经盯着它太久了。

我正在尝试使用 ASP.NET MVC3 建立一个网站,在这个网站中我需要灵活地创建动态对象。但我的意思是在我的数据库中设置了一系列表来存储有关这些动态对象中包含的结构和数据的信息。我正在使用一个预先存在的数据库,所以我(在一定程度上)我可以修改的内容是有限的。当我在数据库中查询一个动态对象(不是 .NET 4.0 的动态对象)时,我传入了我的 Id,我得到的是一个简单的对象,它可能有一些属性,这些属性仅供内部使用,还有一个属性是一个集合,其中包含我的动态对象的所有属性。因此,如果我的动态对象是针对具有 Name、DoB 和 Sex 的人,那么我的集合将包含三个对象,每个属性一个。这允许站点管理员在运行时添加新字段,并且网站将自动呈现它们,允许更新等。现在我的模型绑定当前可用于此数据结构的显示和回发,对于集合中的每个对象,我渲染两条数据,属性的唯一 ID(目前是隐藏字段,Id 是 Guid)和属性的值。我的问题是安全方面。

如果我正在处理强类型对象,我可以创建自定义 ViewModel 并完成它,或者将 Bind() 属性添加到操作的签名中,但由于这些对象的属性是灵活的集合,我不知道如何处理它.操作级别的安全性很简单,我可以创建一个自定义的 Authorize 属性并查询数据库的权限,但我需要能够限制集合行为以根据用户权限显示和接受信息。例如,如果我要为 person 对象添加一个社会安全号码属性,我不希望它为某些人呈现到屏幕上。但是因为属性是可以在运行时改变的东西,所以权限也可以。

就我的想法而言,这就是我所处的位置......

所以我需要一种方法来确定属性集合中的哪些对象可以呈现到屏幕上或在回发时绑定到,具体取决于用户权限。 对于显示对象,我认为我别无选择,只能以某种方式将权限包含在 ViewModel 对象中,并在用于属性集合中使用的对象类型的 DisplayTemplate 中查询这些权限。或者我可以编写某种自定义 ModelBinder,因为它用于调用 Html.Display() 和 Html.Editor() 并考虑过滤 ModelBinder 内的列表。

不过,我也有类似的回发问题。当它被发回时,我有一组数据被传回,只有一个 Guid 和一个值。但是我需要确保用户没有将自己的字段注入到表单中,并且我还需要确保对于传递回操作的属性,用户具有足够的权限。理想情况下,我想将此检查集成到模型绑定中,并在可能的情况下重用从 MetaData 填充的一些信息,例如,这样它就可以简单地忽略传入的用户无权更改的数据,或者否则,请检查用户是否有权访问他们尝试在处理回发的操作开始时完成的 IsValid 检查中设置的所有属性。

然后是基于数据库中的信息为每个属性调用 Html.Display() 和 Html.Editor() 的 MetaData 的动态构建,因为我没有物理属性是 I 类可以用数据注释来装饰。

问题是,当涉及到覆盖 ModelBinders、ModelMetaDataProviders 或 ModelValidationProviders 之类的默认实现时,我不熟悉 MVC 的内部结构。

您能否就您能想到的最佳方式来实现我所描述的内容提供一些建议,或者如果您知道涵盖此示例的其他文章,我非常希望看到它们,我没有太多到目前为止,谷歌在这个特定的主题上很幸运。

编辑:有关我所做的全部详细信息,请参阅下面的答案

编辑:我让元数据提供程序正常工作。只需要实现我自己的类并从 ModelMetadataProvider 继承。

public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
    {
        ModelMetadata metadata;
        if (containerType == typeof(PseudoObjectAttributeViewModel))
        {
            switch (propertyName)
            {
                case "StringValue":
                    metadata = new ModelMetadata(this, typeof(PseudoObjectAttribute), modelAccessor, typeof(string), propertyName);
                    break;
                case "DateValue":
                    metadata = new ModelMetadata(this, typeof(PseudoObjectAttribute), modelAccessor, typeof(DateTime?), propertyName);
                    break;
                case "DoubleValue":
                    metadata = new ModelMetadata(this, typeof(PseudoObjectAttribute), modelAccessor, typeof(double?), propertyName);
                    break;
                case "LongValue":
                    metadata = new ModelMetadata(this, typeof(PseudoObjectAttribute), modelAccessor, typeof(long?), propertyName);
                    break;
                case "BooleanValue":
                    metadata = new ModelMetadata(this, typeof(PseudoObjectAttribute), modelAccessor, typeof(bool?), propertyName);
                    break;
                case "GuidValue":
                    metadata = new ModelMetadata(this, typeof(PseudoObjectAttribute), modelAccessor, typeof(Guid?), propertyName);
                    break;
                default:
                    return defaultMetadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName);
                    break;
            }
            DataAnnotationsModelMetadata daMetadata = (DataAnnotationsModelMetadata)metadata;

            System.Reflection.FieldInfo container = modelAccessor.Target.GetType().GetField("vdi");
            AddSupplimentalMetadata(daMetadata, (PseudoObjectAttributeViewModel)((System.Web.Mvc.ViewDataInfo)container.GetValue(modelAccessor.Target)).Container);
        }
        else
            metadata =  defaultMetadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName);
        return metadata;
    }

第一部分很容易解释,首先使用 GetMetadataForType() 填充元数据,方法是传入与从中提取数据的列名最匹配的 .NET 类型。 (我的编辑器模板通过动态选择定义该数据的数据结构中定义的数据所在的列来帮助解决此问题)

Html.Editor(Model.PseudoObjectStructure.PseudoObjectControl.DataType)

使用起来有点奇怪,但就像我说的,它是一种预先存在的数据结构。

在 switch 语句之后是奇怪的地方。据我了解,在 MVC2 中,GetMetadataForProperty() 方法不再将模型本身作为参数并使用propertyName 查找属性,而是传入Func&lt;object&gt; 类型的表达式,该表达式指向 MVC 需要元数据的属性为了。这带来了一个问题,因为我需要根模型使用不同的属性来确定结构细节。我在这里找到了另一个解决方案,说您可以使用反射来获取模型,但它需要反射。不是我所希望的,但它有效。获得模型后,我将到目前为止的元数据和模型传递给创建的名为 AddSupplimentalMetadata() 的方法,然后我将在 MVC 使用的 DataAnnotationsModelMetadata 对象上设置其余属性。

现在我只需要找出一种方法来根据用户权限动态选择渲染或不渲染某些属性。我想我可能需要做的是在将模型传递给视图之前过滤属性列表,使用 LINQ 或类似的东西。我不喜欢将业务逻辑放在 Display/EditorTemplate 中的想法。为了保存更改,我仍然需要查看验证系统,看看是否可以使用该系统来验证用户尝试传递信息的属性。

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-3 asp.net-mvc-4 modelbinders modelmetadata


    【解决方案1】:

    首先,我建议您使用字典作为视图模型。这使您可以添加您喜欢的任何属性(名称/值)。

    其次,为了满足安全要求,我将 a) 对用户进行身份验证 (Forms/Windows),b) 创建一些查询数据库的功能,以查看允许用户提交/编辑/查看的对象的外观。查询的结果可能只是一个包含允许字段名称的字符串数组 - 这是您的元数据。使用这些数据,您可以轻松去除未经授权的值。 ModelBinder 就是这样做的地方。

    第三,为了验证,您可以扩展 METADATA,例如,将字符串数组替换为 Touple(Of string, bool) 列表,其中存储一个布尔值,指示该值是否是必需的用户输入。您仍然可以通过实现 A MetaDataProvider 来依赖 ASP.NET MVC 默认值。这可能是一个首发:http://buildstarted.com/2010/09/14/creating-your-own-modelmetadataprovider-to-handle-custom-attributes/

    最后,DisplayTemplates 和 EditorTemplates 将使驱动动态 UI 变得容易。为常用数据类型创建模板并为 Dictionary 创建模板。最后一个只是迭代它的 KeyValuePairs 写入标签并调用具体的数据类型模板。 同样在这里,可以使用 MetaDataProvider 扩展 METADATA 并将其提供给 ASP.NET MVC。

    --丹尼尔

    【讨论】:

      【解决方案2】:

      发布答案以接受,因为我自己找到了我需要的解决方案,尽管我还剩下一部分,如果我需要额外的帮助,我会将它作为一个单独的问题发布,而不是让这个问题不予回答。有关详细信息,请参阅我的原始帖子。

      否决票是一个很好的提醒,让你回到这个话题,所以你在这里:-)

      好的,这就是我最终实施的地方。希望这将帮助你们中的一些人解决自己的情况。我必须透露,我正在给它一个 Works on my Machine 批准印章,您应该自己测试它是否满足您的需求。某些决定是为了遵守现有的数据/实践。如果您在解决此代码时遇到任何问题,请随时回复此帖子,以便其他人受益。为了简洁起见,我会尽量缩短代码,因为其中一些细节属于我的雇主。这是它的要点,因为它与 MVC 有关。

      注意:我目前使用的是 MVC4,但这也可能适用于 MVC3。虚拟修饰符用于 nHibernate

      POCO

      public class PseudoObject
      {
          // Other properties and such...
          public virtual IList<PseudoObjectAttribute> Attributes { get; set; }
          // Other methods, etc...
      }
      
      public class PseudoObjectAttribute
      {
          // Other properties and such...
          public virtual string Value { get; set; }
      
          //This holds all of the info I need for determine metadata & validation
          public virtual PseudoObjectStructure Structure { get; set; }
          // Other methods, etc...
      }
      
      public class PseudoObjectStructure
      { 
          public virtual bool IsRequired { get; set; }
          public virtual string RegularExpression { get; set; }
          public virtual string RegularExpressionErrorMessage { get; set; }
      }
      

      元数据提供者

      protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
      {
          //We only care about providing custom model metadata to PseudoObjectAttribute objects
          if ((containerType != typeof(PseudoObjectAttribute) && modelType != typeof(PseudoObjectAttribute)) || modelAccessor == null)
              return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
      
          ModelMetadata metadata = null;
          PseudoObjectAttribute attributeViewModel = null;
          System.Reflection.FieldInfo container = null;
      
          //The contents of this if statement allows me to get the PseudoObjectAttribute instance I need to work with.
      
          //This happens when we want metadata for the PseudoObjectAttribute type as a whole, not a specific attribute
          if (modelType == typeof(PseudoObjectAttribute) && containerType == null)
          {
              //
              if (modelAccessor.Target is ViewDataDictionary)
                  attributeViewModel = (PseudoObjectAttribute)((ViewDataDictionary)modelAccessor.Target).Model;
              else
              {
                  container = modelAccessor.Target.GetType().GetField("item");
                  if (container != null)
                  {
                      attributeViewModel = (PseudoObjectAttribute)container.GetValue(modelAccessor.Target);
                  }
                  container = modelAccessor.Target.GetType().GetField("model");
                  if (container != null)
                      attributeViewModel = (PseudoObjectAttribute)container.GetValue(modelAccessor.Target);
              }
          }
          else if(!string.IsNullOrEmpty(propertyName))
          {
              if (modelAccessor.Method.Name.Contains("FromStringExpression"))
              {
                  //This happens when we want metadata for a specific property on the PseudoObjectAttribute
                  container = modelAccessor.Target.GetType().GetField("vdi");
                  attributeViewModel = (PseudoObjectAttribute)((System.Web.Mvc.ViewDataInfo)container.GetValue(modelAccessor.Target)).Container;
              }
                  //GetPropertyValueAccessor is used when you bind the posted back form
              else if (modelAccessor.Method.Name.Contains("FromLambdaExpression") || modelAccessor.Method.Name.Contains("GetPropertyValueAccessor"))
              {
                  //Accessed property via lambda
                  container = modelAccessor.Target.GetType().GetField("container");
                  var accessor = container.GetValue(modelAccessor.Target);
                  //Sometimes the property is access straight from the parent object for display purposes in the view ex. someRegistration["ProductId"].GuidValue
                  //In these situations the access is the Registration object and is not something we can use to derive the attribute.
                  if (accessor is PseudoObjectAttribute)
                      attributeViewModel = (PseudoObjectAttribute)accessor;
              }
          }
      
          // At this point I have an instance of the actual PseudoObjectAttribute object I'm trying to derive Metadata for and can build my Metadata easily using it.
          // I'm using typeof (String) as a starting point to build my custom metadata from but it could be any value type if you wanted to befit from the defaults
          metadata = new ModelMetadata(this, typeof (PseudoObjectAttribute), modelAccessor, typeof (String), propertyName);
      
          // Be sure to store any of the information you've obtained here that is needed to derive validation rules in the AdditionalValues
          metadata.AdditionalValues.Add("Structure", attributeViewModel.Structure);
      
          //TODO: Populate the rest of the Metadata here....
      
          return metadata;
      }
      

      ValidatorProvider

      public class PseudoObjectAttributeValidatorProvider : ModelValidatorProvider
      {
          public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
          {
      
              if (metadata.ContainerType == typeof(PseudoObjectAttribute) && metadata.PropertyName == "StringValue")
              {
                  PseudoObjectStructure  structure = null;
                  try
                  {
                      if (metadata.AdditionalValues.Any())
                      structure = (PseudoObjectStructure)metadata.AdditionalValues["Structure"];
                  }
                  catch (KeyNotFoundException) { }
      
                  if (structure != null)
                  {
                      if (structure.IsRequired)
                          yield return new RequiredAttributeAdapter(metadata, context, new RequiredAttribute());
      
                      if (structure.RegularExpression != null)
                          yield return new RegularExpressionAttributeAdapter(metadata, context, new RegularExpressionAttribute(structure.RegularExpression) { ErrorMessage = structure.RegularExpressionErrorMessage });
                  }
              }
              else
                  yield break;        
         }
      }
      

      认为就是这样。如果我错过了什么,请告诉我。

      【讨论】:

      • 如果您使用解决方案的详细信息编辑此答案将会很有帮助。
      • 请添加您的答案的详细信息,这对其他努力回答您的问题的人也是公平的
      • 我在原始问题上附加了一些我的答案,但我一直想回到这个问题上,并就我如何解决这个要求发布一个更彻底的答案。只要我有时间整理一下,我就会发布一个更新的答案。
      • 现在包括更新的细节!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-11
      • 2011-04-24
      相关资源
      最近更新 更多