So we have added a new programmer to the team and he has some thoughts on how models should be created within MVC that is different from how we have created models previously. Lets say for example we have a system in which users submit requests for documents and that there is a page in that systems where users calculate a fee for fulfilling that document request. This fee creation page would have the abiltiy to type in some data about the fee and also the associated invoice. Users could add invoice line items and use those to auto calculate a fee total. In that scenario we would normally create models like the below.
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; }
}
Our new programmer believes that this is not a good approach because there are different data needs for different operations. For instance when you create a fee you will need to know the corresponding request ID. However when you update a fee you will only need to know the FeeID. Therefore when he has created his models he has created them in such a fashion as that there are multiple layers of inheritance in an effort to control the data that is updated in the service layer and presented on the view. His thought is that we should be able to assume that any model passed in for a transaction should have all its data points used and not have to guess on what the data depending on circumstance.
To me this adds a ton of needless complexity to our models and makes it far more difficult to work with them on other modules. Below is a sample which illustrates this.
/// <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;
}
I'm just looking for an answer here on what the best practices for creating models are in MVC. Should you create separate models whether through inheritance partial classes or other means to accommodate which operation you are conducting creating, reading, updating, or deleting. Or is it better to have one viewmodel that translates to what is presented on/passed from the view and the logic to filter out what is important that's coming from the view model when accessing the data?
The typical approach that we take is to have a ViewModel that is tightly coupled to a view and only contains that information. The same goes for InputModels, they should only contain the properties that will be passed in. As for the inheritance piece, I would stay far far away from that approach. Just create simple, flat DTOs and map them from your domain model. There should be no logic so DRY doesn't really apply to this layer of your app.