我正在編寫一個愚蠢的程序,試圖以實用的方式充分理解設計模式中涉及的所有各種概念。例如,我完全理解DI/IOC(我認爲),但我不完全瞭解如何將它應用於實際的ASP.Net MVC 4/5環境。Ninject DI/ASP.Net MVC - 如何添加業務層?
我正在寫一個有發票和產品的商店程序作爲我唯一的2張表。到目前爲止,我已經成功地完全應用了DI/IOC,並完成了以下結構:
Store.Models < ==實體框架類。 (數據訪問層)。
Store.Interfaces < ==接口。
Store.Repositories < ==包含實際進行並獲取或設置數據的代碼。
Store.Web < ==我的MVC應用程序。
所有的依賴關係設置和工作正常。現在,這是我的問題和問題。
我想如下添加一個業務層:
Store.Business
對於練習,我已經決定簡單地計算,因爲給定日期的年數的目的。當然,在正常情況下,我會將它作爲計算字段存儲在數據庫中並檢索它。但我正在爲學術練習做這件事,因爲在某些時候,我會遇到一種情況,我必須對數據集進行一些複雜的計算。我認爲這不應該真的與模型,存儲庫或控制器完成一起存儲。應該有一個單獨的「業務」層。現在這裏是我的問題:
實體框架根據我的模型定義了一個名爲Invoice的類。它是一個很好的課程,直到現在它一直工作。
我定義了一個接口和存儲庫,安裝程序Ninject,所有這些都與MVC一起工作。一切都很完美。不能更快樂。
然後我在發票表中添加了一個日期字段。在EF中更新了我的模型,更新了我需要更新的其他內容,並且一切順利。
接下來我添加了一個Store.Business類項目。我設置了一個新的Invoice類,它從模型繼承了Invoice類並添加了一個新的屬性,構造函數和方法。
namespace Store.Business
{
//NOTE: Because of limitations in EF you cant declare a subclass of the same name.
public class InvoiceBL : Store.Models.Invoice
{
[NotMapped]
public int Age { get; set; }
public InvoiceBL()
{
Age = CalcAge(Date);
}
private int CalcAge(DateTime? Date)
{
Age = 25;
//TODO: Come back and enter proper logic to work out age
return Age;
}
}
}
然後我修改了我的接口,存儲庫,控制器,視圖等使用這個新InvoiceBL類,而不是由EF生成的一個。
我開始使用分部類。但是,我顯然遇到了麻煩,因爲它在一個不同的項目中。我甚至嘗試使用相同的命名空間,但不是。我保持項目分離對我來說至關重要。我想要明確定義圖層。所以,如果這不起作用,我選擇繼承。無論如何,我更喜歡這種方法,因爲我假設部分類是微軟的東西,我希望我的哲學可以輕易轉移到任何可能沒有部分類的OOP語言。另請注意,我將它放回到它自己的名稱空間中,因此它不再位於名稱空間Store.Models中,而是位於Store.Business中。
現在,當我運行該程序,並輸入發票的網址之前,我得到了以下錯誤:
Invalid column name 'Discriminator'.
Invalid column name 'Age'.
當我添加[NotMapped]屬性我只得到這個錯誤:
Invalid column name 'Discriminator'.
下面是所有相關的代碼開始與EF自動生成的模型:
Store.Models:
namespace Store.Models
{
using System;
using System.Collections.Generic;
public partial class Invoice
{
public Invoice()
{
this.Products = new HashSet<Product>();
}
public int Id { get; set; }
public string Details { get; set; }
public Nullable<decimal> Total { get; set; }
public Nullable<System.DateTime> Date { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
}
接下來我們有接口:
namespace Store.Interfaces
{
public interface IInvoice
{
void CreateInvoice(InvoiceBL invoice);
DbSet<InvoiceBL> Invoices { get; }
void UpdateInvoice(InvoiceBL invoice);
InvoiceBL DeleteInvoice(int invoiceId);
}
}
接下來我們有倉庫:
namespace Store.Repositories
{
public class InvoiceRepository : BaseRepository, IInvoice
{
public void CreateInvoice(InvoiceBL invoice)
{
ctx.Invoices.Add(invoice);
ctx.SaveChanges();
}
public DbSet<InvoiceBL> Invoices
{
get { return ctx.Invoices; }
}
public void UpdateInvoice(InvoiceBL invoice)
{
ctx.Entry(invoice).State = EntityState.Modified;
ctx.SaveChanges();
}
public InvoiceBL DeleteInvoice(int invoiceId)
{
InvoiceBL invoice = ctx.Invoices.Find(invoiceId);
if (invoice != null)
{
ctx.Invoices.Remove(invoice);
ctx.SaveChanges();
}
return invoice;
}
}
}
我展示了這兩個接口,和庫層需要業務層。最後
namespace Store.Web.Controllers
{
public class InvoiceController : Controller
{
//---------------------Initialize---------------------------
private IInvoice _invoiceRepository;
private IProduct _productRepository;
public InvoiceController(IInvoice invoiceRepository, IProduct productRepository)
{
_invoiceRepository = invoiceRepository;
_productRepository = productRepository;
}
//-----------------------Create-----------------------------
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Store.Business.InvoiceBL invoice)
{
if (ModelState.IsValid)
{
_invoiceRepository.CreateInvoice(invoice);
return RedirectToAction("Index");
}
return View(invoice);
}
//-------------------------Read-----------------------------
[ActionName("Index")]
public ActionResult List()
{
return View(_invoiceRepository.Invoices);
}
public ViewResult Details(int id)
{
//How is this DI - If your model changes you have to alter the fields
//addressed here.
return View(_invoiceRepository.Invoices.FirstOrDefault(i => i.Id == id));
}
//-----------------------Update-----------------------------
[ActionName("Edit")]
public ActionResult Update(int id)
{
//How is this DI - If your model changes you have to alter the fields
//addressed here.
var invoice = _invoiceRepository.Invoices.FirstOrDefault(i => i.Id == id);
if (invoice == null) return HttpNotFound();
return View(invoice);
}
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public ActionResult Update(Store.Business.InvoiceBL invoice)
{
if (ModelState.IsValid)
{
_invoiceRepository.UpdateInvoice(invoice);
return RedirectToAction("Index");
}
else
{
return View(invoice);
}
}
//-----------------------Delete-----------------------------
public ActionResult Delete(int id = 0)
{
//Do you really want to always delete only the first one found?? Not cool?
//Even though in this case, because Id is unique, it will always get the right one.
//But what if you wanted to delete or update based on name which may not be unique.
//The other method (Find(invoice) would be better. See products for more.
//How is this DI - If your model changes you have to alter the fields
//addressed here.
var invoice = _invoiceRepository.Invoices.FirstOrDefault(i => i.Id == id);
if (invoice == null) return HttpNotFound();
return View(invoice);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
if(_invoiceRepository.DeleteInvoice(id)!=null)
{
//Some code
}
return RedirectToAction("Index");
}
//-----------------------Master/Detail--------------------
}
}
的觀點:所以我會移動到控制器
@model IEnumerable<Store.Business.InvoiceBL>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Age)
</th>
<th>
@Html.DisplayNameFor(model => model.Details)
</th>
<th>
@Html.DisplayNameFor(model => model.Total)
</th>
<th>
@Html.DisplayNameFor(model => model.Date)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Age)
</td>
<td>
@Html.DisplayFor(modelItem => item.Details)
</td>
<td>
@Html.DisplayFor(modelItem => item.Total)
</td>
<td>
@Html.DisplayFor(modelItem => item.Date)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Details", "Details", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
請忽略任何評論中的代碼,因爲他們是爲我自己的參考和指導。
再次,這個問題是關於具體爲什麼我得到的錯誤提及以及我需要改變以解決它。我教過添加[NotMapped]屬性會做到這一點,但沒有。但是,我仍然在學習與MVC相關的設計模式,所以如果任何人對如何更好地構建項目或其他建議可能有所幫助的建議,我也會歡迎。
編輯:我忘了NinjectControllerFactory:
namespace Store.Web.Ninject
{
public class NinjectControllerFactory : DefaultControllerFactory
{
private IKernel ninjectKernel;
public NinjectControllerFactory()
{
ninjectKernel = new StandardKernel();
AddBinding();
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
//return base.GetControllerInstance(requestContext, controllerType);
return controllerType == null
? null
: (IController)ninjectKernel.Get(controllerType);
}
private void AddBinding()
{
//TODO FR: Step 4 - Add your interface and repository to the bindings
ninjectKernel.Bind<IProduct>().To<ProductRepository>(); ;
ninjectKernel.Bind<IInvoice>().To<InvoiceRepository>(); ;
}
}
}
我將發佈不久的人誰願意作出貢獻GitHub上的完整的解決方案。我會在這裏發佈一個鏈接。我只想把它恢復到第一個工作版本,這將需要大約一個小時。 –
我的項目的完整代碼現在位於以下鏈接的github上。 https://github.com/franasm/Store 請轉到破碎的分支獲得上述作爲主分支是最後一個工作日分支我的代碼。感謝任何幫助,並隨時在那裏發表評論,並提出任何改進建議。 –