自定义ModelMetadataProvider添加的属性未在模型绑定中考虑 背景解决方案问题

问题描述

免责声明:这是一个漫长的声明。几乎肯定需要对ASP.NET Core MVC内部知识不了解。这是龙。


背景

我正在尝试实现一种在ASP.NET MVC Core中扩展类型元数据的方法

我要这样做的原因是我的数据模型被多个项目使用,因此我将它们放在NuGet包中进行了共享:

// defined in NuGet package
public class MyModel
{
    public int MyProperty { get; set; }

    public MynestedModel nestedModel { get; set; }
}

public class MynestedModel
{
    public bool nestedProperty { get; set; }
}

^清单1

但是,某些项目将需要对模型类型应用其他元数据-例如,在ASP.NET Core项目中,这些模型将用作输入并因此参与模型绑定,因此它们需要FromQueryFromHeader属性。我显然不能在程序包中执行此操作,因为不同的消费者项目将需要根据其用例应用不同的属性

最简单且最保证工作的方法是将所有模型属性修改virtual,然后让子类在添加属性时根据需要覆盖这些属性

// defined in NuGet package
public class MyModel
{
    public virtual int MyProperty { get; set; }

    public virtual MynestedModel nestedModel { get; set; }
}

public class MynestedModel
{
    public virtual bool nestedProperty { get; set; }
}

// defined in ASP.NET MVC project consuming above package
public class Myviewmodel : MyModel
{
    [FromQuery(Name = "foo")]
    public override int MyProperty { get; set; }

    public new // can't use override,as type is different
        Mynestedviewmodel nestedModel { get; set; }
}

[Bind(Prefix = "")]
public class Mynestedviewmodel : MynestedModel
{
    [FromQuery(Name = "bar")]
    public override bool nestedProperty { get; set; }
}

^清单2

我不想这样做,因为在每个具有子模型属性的模型中,都必须对该子模型进行子类化,然后才能将其用作替代,但必须用new重新声明-new的语义在这里对我不起作用。另外,我并不打算将模型类型进行子类化。

我知道了ModelMetadataTypeAttribute,但是该属性的预期用途是在要增强的模型类型上,而不是在进行增强的类型上。因为在我的情况下,无法提前知道实际的元数据类型是什么(因为它们在使用项目中已定义),所以我无法使用ModelMetadataTypeAttribute。子类和子类也将不起作用-ModelMetadataTypeAttribute仅适用于指定的类,partial不能跨程序集。

解决方

实质上,ModelMetadataTypeAttribute的“反向”版本应用于类型进行增强,并指向要增强的模型类型

// defined in ASP.NET MVC project consuming package from Listing 1
[ReverseModelMetadataType(typeof(MyModel))]
public class MyModelMetadata
{
    [FromQuery(Name = "foo")]
    public int MyProperty { get; set; }

    public MynestedModel nestedModel { get; set; }
}

[ReverseModelMetadataType(typeof(nestedModel))]
[Bind(Prefix = "")]
public class nestedModelMetadata
{
    [FromQuery(Name = "bar")]
    public bool nestedProperty { get; set; }
}

^清单3

目的是在运行时,模型绑定基础结构将使用MyModel.MyProperty来绑定[FromQuery(Name = "foo")],即从查询字符串中获取名为foo的变量。同样,MyModel.nestedModel.nestedProperty应该简单地与查询字符串绑定为bar

为了完成这项工作,我实现了一个自定义ModelMetadataProvider,它继承了DefaultModelMetadataProvider并覆盖了CreatePropertyDetails方法

// defined in same ASP.NET Core MVC project as Listing 3
public class MyModelMetadataProvider : DefaultModelMetadataProvider
{
    protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key)
    {
        var defaultPropertyDetails = base.CreatePropertyDetails(key);

        // check if the key.ModelType should be augmented
        // if so,mutate the relevant element(s) of the defaultPropertyDetails array to do the augmentation

        return defaultPropertyDetails;
    }
}

变异部分有效地更改了相关的DefaultMetadataDetails.modelattributes成员,以将其他属性应用于用ReverseModelMetadataTypeAttribute装饰的类型的属性。然后,通过DefaultModelMetadataProvider中的以下内容,使ASP.NET MVC Core使用我的提供程序而不是其Startup.cs#ConfigureServices

services.AddSingleton<IModelMetadataProvider,MyModelMetadataProvider>();

我已经验证了提供者已注册并且突变代码可以正常工作:击中MyModelMetadataProvider.CreatePropertyDetails并且返回的defaultPropertyDetails确实包含清单3中我的*ModelMetadata类应用的额外属性


问题

模型绑定不起作用::ASP.NET Core MVC本质上表现为好像清单3中添加属性不存在,我也不知道为什么。据我所知,对各种IModelMetadataProvider方法调用结果将被缓存,然后用于随后所需的所有元数据查找,包括用于绑定模型。

我希望有人在我自己逐步经历ASP.NET Core源代码的痛苦之前,可以就如何使其工作提供一些建议。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)