Ninject DI / ASP.Net MVC - 如何添加业务层?

时间:2014-05-05 00:06:17

标签: c# asp.net-mvc entity-framework dependency-injection ninject

我正在编写一个愚蠢的程序,试图以实用的方式完全理解设计模式中涉及的所有各种概念。例如,我完全理解DI / IOC,(我认为),但我不完全理解如何在实际的ASP.Net MVC 4/5环境中应用它。

我正在编写一个商店程序,发票和产品是我唯一的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 Auto Generated模型开头的所有相关代码:

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;
        }
    }
}

我已经向您展示了Interfaces和Repositories层所需的业务层。所以我将转到控制器:

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>(); ;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

在添加列后,您没有提及EF是否自动重新生成Invoice实体。假设您使用的是代码优先并且未通过T4模板(.TT文件)生成实体,则您自己维护实体。这一代是帮助你入门的一次性事情,因此你不必从头开始编写所有实体。

在这种情况下,您可以将Age字段添加到Invoice实体,并让您的业务服务在Invoice函数中使用CalcAge实体实例,或者只是将DateTime传递给该函数并获得年龄。通常,您希望使用视图模型而不是为此目的使用EF实体,并且您可能将出生日期存储在数据库中并计算Age字段,无论是在DB上还是在属性getter中的实体逻辑(它将像你已经拥有的那样是[NotMapped])。

您不希望将业务层中的类耦合到实际的EF实体,而是对实体执行操作,无论是在新创建的实体还是通过存储库层从DB检索到的实体,就像你现在一样。

由于您要使用业务层,您可以执行以下操作:

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; }

        [NotMapped]
        public int Age {get; set;
    // ...

商业服务:

using Store.Models;

namespace Store.Business
{
    public class InvoiceBL
    {
        public int CalcAge(DateTime? date)
        {
            Age = 25;
            //TODO: Come back and enter proper logic to work out age
            // Something like:
            // return date != null ? <DateTime.Now.Year - date.Year etc.> : null
            return Age;
        }

在控制器中,您必须计算年龄字段,并在每次返回Invoice View.时将其设置为 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Store.Model invoice) { if (ModelState.IsValid) { _invoiceRepository.CreateInvoice(invoice); // _service is your business service, injected as a dependency via the constructor, same as the _invoiceRepository is now invoice.Age = __service.CalcAge(invoice.BirthDate); // or some such thing return RedirectToAction("Index"); } return View(invoice); } 作为您的数据模型它确实使用了业务层。

Invoice

您还必须为更新操作等执行此操作;任何将@model IEnumerable<Store.Models.Invoice> @{ ViewBag.Title = "Index"; } // ... and so on 作为视图模型返回的操作。

视图的模型将绑定到Invoice实体:

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>();
            // Add this, assuming there isn't an interface for your service
            ninjectKernel.Bind<InvoiceBL>().ToSelf();            
        }
    }
}

您的Ninject容器将绑定服务,它将成为控制器的依赖项。我个人会将存储库作为注入服务的依赖项,并将服务注入控制器,而不是将服务和存储库分离到控制器内部,但我会使用您拥有的内容。

Discriminator

我没有看到有关{{1}}列的任何代码,但如果它在实体中并且已映射,则需要在数据库表中。映射可以通过上下文中使用的类(或直接在上下文中完成),也可以使用DataAnnotation属性进行设置,如[NotMapped]是。