4

我真的需要对这个问题进行第二次观察,所以我希望你们中的一些人能给我一些反馈,我想我已经盯着它太久了。

我正在尝试使用 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 检查中设置的所有属性。

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

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

您能否就您能想到的实现我所描述的最佳方式提供一些建议,或者如果您知道涵盖此示例的其他文章,我非常希望看到它们,但我对 Google 的运气并不好到目前为止,关于这个特定主题。

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

编辑:我让元数据提供程序工作。只需要实现我自己的类并从 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<object>指向 MVC 需要元数据的属性。这带来了一个问题,因为我需要根模型使用不同的属性来确定结构细节。我在这里找到了另一个解决方案,说您可以使用反射来获取模型,但它需要反射。不是我所希望的,但它有效。在我拥有模型之后,我将到目前为止的元数据和模型传递给一个创建的方法,然后我将从那里设置MVC 使用的对象AddSupplimentalMetadata()的其余属性。DataAnnotationsModelMetadata

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

4

2 回答 2

1

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

其次,为了满足安全要求,我将 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。

——丹尼尔

于 2011-04-08T15:42:24.350 回答
-1

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

否决票可以很好​​地提醒您回到这一点,所以你在这里:-)

好的,这就是我最终实施的地方。希望这将帮助你们中的一些人解决自己的情况。我必须透露,我正在给它一个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;
}

验证器提供者

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;        
   }
}

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

于 2011-04-21T16:05:56.493 回答