【问题标题】:MVC scaffolding is duplicating my model fieldsMVC 脚手架正在复制我的模型字段
【发布时间】:2015-05-19 23:17:55
【问题描述】:

我似乎遇到了一个奇怪的问题,经过数小时的挠头,我似乎已将问题缩小到partial classesvirtual properties 的组合。当我覆盖位于单独文件中的部分类中的属性时,MVC 会复制我视图中的字段。我正在使用 Visual Studio 2013,可以按照以下步骤复制该问题:

  1. 打开 Visual Studio 并创建一个新项目。在类别下选择 Web,然后选择“ASP.NET Web Application”。我的目标是 .NET 4.5。
  2. 从模板选择中选择“空”,然后选中 MVC 复选框,以便添加核心文件夹和引用。
  3. 创建项目后,右键单击Models 文件夹并创建一个名为MyModel.cs 的新类。

将这些行添加到新文件中:

public abstract partial class MyOriginalModel
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
}

public partial class MyModel : MyOriginalModel
{

}
  1. 现在再次右键单击Models 文件夹并创建另一个名为MyModelCustom.cs 的新类。

将这些行添加到文件中:

public partial class MyModel
{
    [System.ComponentModel.DisplayName("First Name")]
    [System.ComponentModel.DataAnnotations.Required]
    public override string FirstName
    {
        get
        {
            return base.FirstName;
        }
        set
        {
            base.FirstName = value;
        }
    }

    [System.ComponentModel.DisplayName("Last Name")]
    [System.ComponentModel.DataAnnotations.Required]
    public override string LastName
    {
        get
        {
            return base.LastName;
        }
        set
        {
            base.LastName = value;
        }
    }
}
  1. 现在构建项目,然后右键单击Controllers 文件夹并添加一个新控制器。选择“具有读/写操作的 MVC 5 控制器”并将其命名为 NamesController。右键单击 Create 方法并转到“添加视图”。在模板下拉列表中,选择Create,对于模型类下拉列表,选择MyModel

一旦 MVC 创建模板,您将看到它添加了两次 First NameLast Name。这个问题似乎与部分类有关,因为如果我将MyModelCustom.cs 的内容移动到MyModel.cs,一切正常。但是,它不仅仅是部分类。如果我在部分类中创建一个新属性(而不是重载一个),它不会复制该属性。所以它似乎是部分类和覆盖虚拟属性的组合。

有人可以确认这是一个错误还是我做错了什么?

【问题讨论】:

  • 是的,MVC 使用反射。你直观地理解它必须。您会期望 Scaffolding 引擎会按类型分析您的模型,递归地为脚手架创建元数据映射。但该框架试图尽可能多地从模板和元数据中提取。与将模型映射到自然表达式相比,该框架似乎不太关心模型的准确性(大多数 Razor 助手使用未编译的函数 -- 表达式)。
  • 这就是你的问题。脚手架引擎扫描您的每个部分,并使用表达式映射它们的属性。它忽略了虚拟可以并且正在被覆盖的事实。 BC 它们可以被覆盖,头脑简单的预编译器不会引发任何标志。但它只是预编译——完成了一半的工作。我怀疑如果您不扔这么多曲线球,该框架会更好地理解。部分问题并不是什么新鲜事。部分和继承得到普遍的抱怨。但虚拟可能是最糟糕的。也许使虚拟抽象可以解决它...
  • 我完全同意戴夫的观点。只有一件事我想尝试看看它是否有效,就是在属性被覆盖的 MyModel 上继承你的类 MyOriginalModel 。并且让空的 MyModel 类不继承任何东西。
  • @ShashankChaturvedi,我很好奇会发生什么。我敢打赌它会起作用。
  • 我马上要去这里开会。但今天晚些时候,我会尝试一下,让你们知道它是否有效。

标签: c# asp.net-mvc asp.net-mvc-5


【解决方案1】:

两者兼而有之。错误与否,如果 MVC 不正确地构建脚手架,您将不得不不断地与框架抗争,或者改变解决问题的方法。

作为一般规则,我发现当您必须与 MVC 框架作斗争以使其按照您想要的方式运行时,那么改变您解决问题的方法要容易得多。否则,您最终将反复进行那场特定的战斗,直到您最终遵守。从一个以艰难的方式吸取教训的人那里得到它。

考虑到更简单的方法,您可以尝试以下几种方法:

  1. 如果要覆盖大量属性,请使用属性的通用名称(FirstName、LastName)创建单独的类。然后使用Best way to clone properties of disparate objects 来编组对象之间的数据。

  2. 您还可以使用Fody PropertyChange 侦听器来处理更改这些值时所需的任何逻辑,从而完全消除对部分覆盖的需要。

  3. 最后一个选项是 override the scaffolding templates 跳过被覆盖的属性。不知道你会如何检测到这一点。

【讨论】:

  • 我承认,我不太了解 MVC 脚手架如何在幕后工作,但我的假设一直是 MVC 使用反射来反映我的模型类来确定如何脚手架和创建模板视图。如果是这种情况,我很困惑为什么将代码移动到部分类会导致它被复制,因为没有任何东西可以确定它来自部分类。部分类只是告诉编译器如何编译和组装完整的类。
【解决方案2】:

查看 MvcScaffolding 的 CodePlex 源代码EnvDTETypeLocator.cs

    /// <summary>
    /// Out of a set of CodeType instances, some of them may be different partials of the same class.
    /// This method filters down such a set so that you get only one partial per class.
    /// </summary>
    private static List<CodeType> PickArbitraryRepresentativeOfPartialClasses(IEnumerable<CodeType> codeTypes)
    {
        var representatives = new List<CodeType>();
        foreach (var codeType in codeTypes) {
            var codeClass2 = codeType as CodeClass2;
            if (codeClass2 != null) {
                var matchesExistingRepresentative = (from candidate in representatives.OfType<CodeClass2>()
                                                     let candidatePartials = candidate.PartialClasses.OfType<CodeClass2>()
                                                     where candidatePartials.Contains(codeClass2)
                                                     select candidate).Any();
                if (!matchesExistingRepresentative)
                    representatives.Add(codeType);
            } else {
                // Can't have partials because it's not a CodeClass2, so it can't clash with others
                representatives.Add(codeType);
            }
        }
        return representatives;
    }
}

1) PickArbitraryRepresentativeOfPartialClasses,该方法使用Linq any() 来确认codeType as CodeClass2 有成员。

CodeClass2EnvDTE 的部分类类型,它是 Visual Studio 的核心自动化库,负责 IDE 代码生成(设计时反射)。

2) 如果转换为CodeClass2 的类确实有成员,则将该类添加到representatives

3) 评估分部类时,将在不同的上下文中访问每个文件(通常会导致应覆盖的元素合并)


运行时反射和设计时反射之间有趣的区别:sic

一个 ASP.NET 控件有两组不同的功能,当它在页面内运行时执行或在主机设计器内在设计时使用时。运行时功能根据配置确定控件输出的标记。相反,设计时功能受益于诸如 Microsoft Visual Studio 2005 之类的可视化设计器。设计时功能让页面作者可以在声明性和所见即所得(所见即所得)中配置运行时控件-you-get) 方式。

结论:

MVC Scaffolding 确实使用反射,但它是不太可靠的设计时反射。

设计时反射与运行时反射不同。完全编译的类是继承解析和部分组合以及优先级解析的最终结果。 Design Time Reflection 对如何处理复杂的多部分类型做出了最佳猜测。

如果你想依赖 Scaffolding,最好不要突破它的极限。当你遇到这样的错误时,试着简化你的 ViewModel:

  • 尝试合并部分类
  • 尝试删除您的摘要/虚拟内容

【讨论】:

  • 是的,你做到了。感谢您的解释
猜你喜欢
  • 1970-01-01
  • 2016-05-07
  • 1970-01-01
  • 1970-01-01
  • 2014-10-01
  • 2011-09-10
  • 2020-05-16
  • 2011-10-30
  • 1970-01-01
相关资源
最近更新 更多