在MVC中创建模型的最佳实践(CRUD)

时间:2015-11-11 16:23:13

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

所以我们为团队添加了一个新的程序员,他对如何在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中创建模型的最佳实践。您是应该创建单独的模型,无论是通过继承部分类还是其他方式来适应您正在进行创建,读取,更新或删除的操作。或者,最好是将一个视图模型转换为视图中显示/传递的内容以及逻辑,以过滤掉访问数据时来自视图模型的重要内容?

4 个答案:

答案 0 :(得分:4)

我们采用的典型方法是将ViewModel紧密耦合到视图并仅包含该信息。对于InputModels也是如此,它们应该只包含将被传入的属性。对于继承片,我会远离这种方法。只需创建简单,平坦的DTO并从您的域模型中映射它们。应该没有逻辑,所以DRY并不真正适用于你的应用程序的这一层。

答案 1 :(得分:1)

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

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

我也认为你不需要像浏览模型那样注入值。如果您应该能够从linq查询或Web服务请求直接进入您的viewmodel。

最后一部分是对于页面上的下拉列表,只返回您需要的数据(Id和描述)在字典或一些小类中,因此您不会返回所有数据。

答案 2 :(得分:1)

有点不清楚你在这里处理什么。 “模型”是一个加载的术语,可以为各种各样的人提供各种各样的东西。

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

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

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

答案 3 :(得分:0)

您的新开发人员风格由该技术(在本例中为Entity Framework)决定-在MHO中,该技术并不总是适合适当的设计。

我的方法是...

模型应该在逻辑上使用接口进行设计-不知道如何使用它们 但可以满足将来的要求。 继承应保持尽可能浅,而不会破坏继承 模型设计。

视图仅应通过控制器与BS交互。 数据服务应仅与业务服务进行交互。 每个演示文稿应具有自己的ViewModel,该ViewModel应该尽可能平坦。 但是,ViewModel可能具有子ViewModel。

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