【问题标题】:Best practice for creating models in MVC (CRUD)在 MVC (CRUD) 中创建模型的最佳实践
【发布时间】:2015-11-11 16:23:12
【问题描述】:

所以我们在团队中增加了一位新程序员,他对如何在 MVC 中创建模型有一些想法,这与我们之前创建模型的方式不同。例如,假设我们有一个系统,用户在该系统中提交文档请求,并且该系统中有一个页面,用户可以在其中计算完成该文档请求的费用。这个费用创建页面可以输入一些关于费用的数据以及相关的发票。用户可以添加发票行项目并使用这些项目来自动计算费用总额。在那种情况下,我们通常会创建如下所示的模型。

    public class Fee
{
    public virtual Guid RequestID { get; set; }
    public virtual Guid FeeID { get; set; }
    public string FeeTitle { get; set; }
    public decimal FeeAmount { get; set; }
    public DateTime? DueDate { get; set; }
    public decimal AmountPaid { get; set; }
    public Invoice Invoice { get; set; }
    public List<InvoiceLineItem> LineItems { get; set; }
}

public class Invoice
{ 
    // Additional Invoice Data (To, From, etc)
}

public class InvoiceLineItem
{
    public string LineItemTitle { get; set; }
    public int Quantity { get; set; }
    public decimal PricePerUnit { get; set; }
    public decimal Subtotal { get; set; }

}

我们的新程序员认为这不是一个好方法,因为不同的操作有不同的数据需求。例如,当您创建费用时,您需要知道相应的请求 ID。但是,当您更新费用时,您只需要知道 FeeID。因此,当他创建他的模型时,他以这样一种方式创建它们,即存在多个继承层,以努力控制在服务层中更新并呈现在视图上的数据。他的想法是,我们应该能够假设为交易传入的任何模型都应该使用其所有数据点,而不必根据情况猜测数据是什么。

对我来说,这给我们的模型增加了大量不必要的复杂性,并使得在其他模块上使用它们变得更加困难。以下是说明这一点的示例。

 /// <summary>
/// This model is used to present data in a read fashion to the end user
/// </summary>
public class FeeViewModel : FeeModel_Create
{
    public string FullRequestNumber { get; set; }
    public decimal Balance { get; set; }
    public List<String> States { get; set; }
    public List<FeeAttachmentEditModel> Attachments { get; set; }
    public List<PaymentViewModel> Payments { get; set; }

}

/// <summary>
/// This model adds a request id to the fee update model because we need to know which request this fee is associated with
/// </summary>
public class FeeModel_Create : FeeModel_Update
{
    public Guid RequestID { get; set; }

}

/// <summary>
/// Represents the parameters required to update a fee
/// </summary>
public class FeeModel_Update
{
    public virtual Guid FeeID { get; set; }
    public decimal FeeAmount { get; set; }
    public DateTime? DueDate { get; set; }
    public string FeeTitle { get; set; }
    public decimal AmountPaid { get; set; }
    public List<MaterialList> MaterialTypes { get; set; }
    public List<InvoiceLineItem_Adhoc> LineItems { get; set; }
    public Invoice Invoice { get; set; }


    public void InjectValuesIntoInvoiceModel(Invoice Invoice)
    {
        Invoice.Description = this.Invoice.Description;
        Invoice.Terms = this.Invoice.Terms;
        Invoice.To_Name = this.Invoice.To_Name;
        Invoice.To_Address = this.Invoice.To_Address;
        Invoice.To_Address2 = this.Invoice.To_Address2;
        Invoice.To_City = this.Invoice.To_City;
        Invoice.To_State = this.Invoice.To_State;
        Invoice.To_Zip = this.Invoice.To_Zip;
        Invoice.From_Name = this.Invoice.From_Name;
        Invoice.From_Address = this.Invoice.From_Address;
        Invoice.From_Address2 = this.Invoice.From_Address2;
        Invoice.From_City = this.Invoice.From_City;
        Invoice.From_State = this.Invoice.From_State;
        Invoice.From_Zip = this.Invoice.From_Zip;
    }
}


public class InvoiceLineItem_Adhoc
{
    public string Type { get; set; }
    public string EnteredBy { get; set; }
    public decimal Quantity { get; set; }
    public decimal UnitCost { get; set; }
    public InvoiceLineItem ToLineItem(Guid InvoiceID)
    {
        var lineItem = new InvoiceLineItem();
        StaticValueInjecter.InjectFrom(lineItem, this);
        lineItem.InvoiceLineItemID = Guid.NewGuid();
        lineItem.InvoiceID = InvoiceID;
        lineItem.UserID = 1;
        return lineItem;
    }
}

public class PaymentViewModel
{
    public Guid RequestID { get; set; }
    public Guid FeeID { get; set; }
    public string FullRequestNumber { get; set; }
    public string FeeTitle { get; set; }
    public virtual Guid PaymentID { get; set; }
    public decimal PaymentAmount { get; set; }
    public Nullable<System.DateTime> DatePaid { get; set; }
}

public class FeeAttachmentEditModel
{
    public Guid RequestID { get; set; }
    public Guid FeeID { get; set; }
    public string FullRequestNumber { get; set; }
    public string FeeTitle { get; set; }
    public virtual System.Guid FeeAttachmentID { get; set; }
    public System.Guid AttachmentTypeID { get; set; }
    public string AttachmentName { get; set; }
    public byte[] Data { get; set; }
    public string Extension { get; set; }
    public string mimeType { get; set; }
    public string AttachmentBody { get; set; }
    public HttpPostedFileBase FileUpload { get; set; }
    public string FileName { get; set; }

    public bool HadError = false;
}

我只是在这里寻找有关在 MVC 中创建模型的最佳实践的答案。您是否应该通过继承部分类或其他方式创建单独的模型以适应您正在执行创建、读取、更新或删除的操作。还是拥有一个视图模型来转换为视图上呈现/传递的内容以及在访问数据时过滤掉来自视图模型的重要内容的逻辑更好?

【问题讨论】:

    标签: c# asp.net-mvc model-view-controller asp.net-mvc-viewmodel


    【解决方案1】:

    我们采用的典型方法是让 ViewModel 与视图紧密耦合,并且只包含该信息。 InputModels 也是如此,它们应该只包含将被传入的属性。至于继承部分,我会远离这种方法。只需创建简单、扁平的 DTO 并从您的域模型映射它们。应该没有逻辑,因此 DRY 并不真正适用于您的应用的这一层。

    【讨论】:

    • 好的,所以我们通常只有一个视图模型,所以输入模型的概念有点新。我们是否应该有一个视图模型被提交,然后通过自动映射器转换为输入模型,然后传递回服务层?
    • ViewModel 是在您渲染页面时创建的,而 InputModel 用于在您的操作中绑定输入。拥有单独的模型可以让您制作一个正是您所需要的 DTO。 ViewModel 通常会有多个 InputModel,因为它会有用于显示的字符串值和用于下拉菜单等的项目。
    【解决方案2】:

    根据第一个答案,我们还没有看到视图模型将如何在页面上使用。就像他说的,视图模型应该只包含满足显示视图的数据。您不应该只是盲目地将域模型中的每个字段复制到视图模型中。

    我也不喜欢在视图模型中有 HasError 标志。使用数据注释或从 IValidateableObject 继承来对您的 POST 执行验证。

    我也不认为你需要像你一样将值注入到视图模型中。如果您应该能够从 linq 查询或 Web 服务请求直接投影到您的视图模型中。

    最后一点是,对于页面上的下拉菜单,仅在字典或某个小类中返回您需要的数据(ID 和描述),因此您不会返回所有数据。

    【讨论】:

      【解决方案3】:

      有点不清楚你在这里处理的是什么。 “模型”是一个加载的术语,对于各种人来说可能意味着各种事情。

      如果FeeInvoice 等类是实体,也就是说它们直接与数据库表相关,那么为了视图的目的对它们进行子类化,就像您的开发人员正在做的那样,是100% 错误。但是,如果它们也只是视图模型,那么对它们进行子类化可能有好处,也可能没有。

      如果它们是实体,那么您的开发人员是正确的,因为您不应该将整个实体传递到视图/从视图传递。但是,解决方案是创建视图模型,在这种情况下,它只包含视图所需的属性。然后,您将实体中的数据映射到这些视图模型/从这些视图模型映射。

      关于使用 AutoMapper 进行映射的小问题:您真的不应该使用 AutoMapper 将 映射到 实体。图书馆的开发者自己也说了这么多。 AutoMapper 从未打算用于将数据映射回实体,如果这样做,您将遇到 Entity Framework 的各种特殊问题。这些都可以解决(在我知道更好之前,我已经亲自做过很多次了),但是你开始需要的代码量和你必须做的自定义事情开始否定使用 AutoMapper 的所有好处地方。我建议只需手动将数据从您的视图模型映射回您的实体。

      【讨论】:

        【解决方案4】:

        您的新开发人员的风格由技术(在本例中为实体框架)决定 - 在 MHO 中并不总是适合正确的设计。

        我的方法是……

        模型应该使用接口进行逻辑设计 - 与它们的使用方式无关 但会迎合未来的需求。 继承应该保持尽可能浅而不破坏良好的 模型设计。

        视图只能通过控制器与 BS 交互。 数据服务应该只与业务服务交互。 每个演示文稿都应该有自己的 ViewModel,它应该尽可能平坦。 然而,一个 ViewModel 可能有子 ViewModel。

        我在我的数据库服务层中使用 DAPPER 并创建了一个代码生成器,它将 生成 DS 方法和存储过程。 DS 方法只有相关的 在数据库中插入/更新字段的属性。 这使您的代码精简、快速且易于管理 当您离开团队并且必须由其他人接管时。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-02-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-02-25
          • 2011-09-16
          • 2020-07-12
          • 1970-01-01
          相关资源
          最近更新 更多