导入DI / ASP.. Net MVC -如何添加业务层

本文关键字:添加 业务 何添加 DI ASP Net MVC 导入 | 更新日期: 2023-09-27 18:01:43

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

我正在编写一个商店程序,其中发票和产品作为我仅有的2个表。到目前为止,我已经成功地完全应用了DI/IOC,并完成了以下结构:

商店。模型<==实体框架类。(数据访问层)。
商店。接口<==接口。
商店。Repositories <==包含实际获取或设置数据的代码。
商店。Web <==我的MVC应用。

所有依赖项都设置好并正常工作。这是我的问题和问题。

我想添加一个业务层如下:

商店。业务

出于练习的目的,我决定简单地计算从给定日期开始的年数。当然,在正常情况下,我会将其作为计算字段存储在数据库中并检索它。但我这样做是为了学术练习,因为在某些时候,我会遇到一种情况,我必须在数据集上做一些复杂的计算。我的观点是,这不应该与模型、存储库一起存储,也不应该在控制器中完成。应该有一个单独的"业务"层。现在我的问题是:

实体框架根据我的模型定义了一个叫做Invoice的类。这是一个很好的类,它一直工作到现在。

我定义了一个接口和存储库,设置Ninject,让它与MVC一起工作。一切都很完美。再高兴不过了。

然后,我向发票表添加了一个日期字段。在EF中更新了我的模型,更新了我需要更新的其他东西,一切都很顺利。

接下来我添加了一个Store。商务类项目。我建立了一个新的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中。模型,但在Store.Business.

现在,当我运行程序,并在url中输入发票时,我得到以下错误:

Invalid column name 'Discriminator'.
Invalid column name 'Age'.

当我添加[NotMapped]属性时,我只得到这个错误:

Invalid column name 'Discriminator'.

以下是以EF Auto Generated模型开头的所有相关代码:

商店。模型:

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

导入DI / ASP.. Net MVC -如何添加业务层

您没有提到在添加列后是否使用EF自动生成Invoice实体。假设您使用的是代码优先,并且不通过T4模板生成实体。TT文件),您自己维护您的实体。生成是一次性的,可以帮助您入门,因此您不必从头编写所有实体。

在这种情况下,您可以将Age字段添加到您的Invoice实体中,并让您的业务服务在CalcAge函数中获取Invoice实体实例,或者只是将DateTime传递给该函数并获得年龄。通常您希望使用视图模型而不是EF实体来实现此目的,并且您可能会在DB中存储出生日期,并在DB或属性getter中的实体逻辑中计算Age字段(它将像您已经拥有的那样是[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);
    }

对于Update操作也必须这样做,等等;任何返回Invoice作为视图模型的操作。

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

@model IEnumerable<Store.Models.Invoice>
@{
    ViewBag.Title = "Index";
 }
 // ... and so on

你的Ninject容器将绑定服务,它将成为控制器的依赖项。我个人会把存储库作为一个依赖项注入到服务中,而把服务注入到控制器中,而不是在控制器中把服务和存储库分开,但我还是按照你的方式来做。

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

我没有看到任何关于Discriminator列的代码,但如果它在实体中并且它被映射,它需要在DB表中。映射可以通过在上下文中使用的类(或直接在上下文中完成),或者使用DataAnnotation属性设置,如[NotMapped] is。