【问题标题】:Getting IMetadataDetailsProviders to Run More than Once in ASP.NET Core让 IMetadataDetailsProviders 在 ASP.NET Core 中多次运行
【发布时间】:2018-04-28 01:16:15
【问题描述】:

这是一个棘手的问题,需要深入了解 ASP.NET Core 框架。我将首先解释在 MVC 3 实现中我们的应用程序发生了什么。

有一个复杂的需求需要解决,涉及到特定视图上的 ViewModel 的 ModelMetaData。这是一个高度可配置的应用程序。因此,对于一种“期刊类型”,属性可能是强制性的,而对于另一种,完全相同的属性可能是非强制性的。此外,它可以是一个“期刊类型”的单选按钮和另一个的选择列表。由于所有这些配置选项有大量的组合、混合和匹配,因此为每个可能的排列创建单独的 ViewModel 类型是不切实际的。因此,有一个 ViewModel 类型,并且 ModelMetaData 是在该类型的属性上动态设置的。

这是通过创建自定义ModelMetadataProvider(通过继承DataAnnotationsModelMetadataProvider)来完成的。

Smash-cut 到现在,我们正在升级应用程序并在 ASP.NET Core 中编写服务器内容。我已经确定实现IDisplayMetadataProvider 是在 ASP.NET Core 中修改模型元数据的等效方法。

问题是,框架内置了缓存,任何实现IDisplayMetadataProvider 的类只运行一次。我在调试 ASP.NET Core 框架时发现了这一点,this comment 证实了我的发现。这样的缓存将不再满足我们的要求,因为第一次访问 ViewModel 类型时,MetadataDetailsProvider 将运行并缓存结果。但是,如上所述,由于高度动态的配置,我需要它在每个ModelBinding 之前运行。否则,我们将无法利用ModelState。第一次命中该端点时,元数据会为所有未来的请求设置好。

而且我们有点需要利用使用反射遍历所有属性的递归过程来设置元数据,因为我们不想自己这样做(超出我的薪酬规模的巨大努力)。

所以,如果有人认为我错过了新核心框架中的某些内容,请务必告诉我。即使删除 ModelBindersIDisplayMetadataProviders 的缓存功能很简单(这就是我将在接下来的几天内通过 ASP.NET 源代码研究的内容)。

【问题讨论】:

    标签: asp.net asp.net-core asp.net-core-2.0


    【解决方案1】:

    出于性能考虑,模型元数据被缓存。 DefaultModelMetadataProvider 类是IModelMetadataProvider 接口的默认实现,负责此缓存。如果您的应用程序逻辑需要在每个请求上重新构建元数据,您应该用您自己的实现替换此实现。

    如果您从 DefaultModelMetadataProvider 继承您的实现并覆盖最低限度以实现您的目标,您将使您的生活更轻松。看来GetMetadataForType(Type modelType) 应该够用了:

    public class CustomModelMetadataProvider : DefaultModelMetadataProvider
    {
        public CustomModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider)
            : base(detailsProvider)
        {
        }
    
        public CustomModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider, IOptions<MvcOptions> optionsAccessor)
            : base(detailsProvider, optionsAccessor)
        {
        }
    
        public override ModelMetadata GetMetadataForType(Type modelType)
        {
            //  Optimization for intensively used System.Object
            if (modelType == typeof(object))
            {
                return base.GetMetadataForType(modelType);
            }
    
            var identity = ModelMetadataIdentity.ForType(modelType);
            DefaultMetadataDetails details = CreateTypeDetails(identity);
    
            //  This part contains the same logic as DefaultModelMetadata.DisplayMetadata property
            //  See https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs
    
            var context = new DisplayMetadataProviderContext(identity, details.ModelAttributes);
            //  Here your implementation of IDisplayMetadataProvider will be called
            DetailsProvider.CreateDisplayMetadata(context);
            details.DisplayMetadata = context.DisplayMetadata;
    
            return CreateModelMetadata(details);
        }
    }
    

    要将DefaultModelMetadataProvider 替换为您的CustomModelMetadataProvider,请在ConfigureServices() 中添加以下内容:

    services.AddSingleton<IModelMetadataProvider, CustomModelMetadataProvider>();
    

    【讨论】:

    • 太棒了!我不知道我是怎么错过的!这是我没有仔细检查的少数课程之一。也许我会建立一些智能,以便缓存对所有模型类型保持完整,除了需要每次运行逻辑的模型类型。非常感谢。
    • 他们让事情变得更加难以弄清楚。在旧版本中,您只需将 IsRequired 设置为 false 并完成。现在只是一个吸气剂,所有属性列表都是只读集合。弄清楚如何动态设置是否需要属性被证明是一个巨大的时间槽。
    • 认为我已经弄清楚了那部分。需要实现 IValidationMetadataProvider ,我可以在其中设置 IsRequired 。希望这能让我一路走好。
    • @onefootswill 如果您将最终解决方案作为答案发布,那会很好。
    • @IanKemp 嗨,伊恩。我将不得不尝试挖掘该代码。如果我能找到它,我很乐意在这里发布它。不过我需要一点时间,因为我们正处于迫在眉睫的最后期限之中。
    猜你喜欢
    • 1970-01-01
    • 2019-10-18
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 2020-02-20
    • 2015-10-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多