使用StructureMap扩展ASP.NET MVC三层架构3-Service层的实现

本篇文章介绍使用StructureMap依赖注入扩展ASP.NET MVC三层架构中的Service层,Service层是业务逻辑层,Service层就需要注入Repository了,但是本片文章暂时不介绍StructureMap的配置,遇到注入的地方只是先写出来声明一下,下一篇文章将StructureMap的配置。

MVC中的三层结构很重要的一层就是业务逻辑层,这一层是Controller和Repository之间的桥梁,我们最好不要在Controller中写太多的业务逻辑,有可能的话不在Controller中写任何的业务逻辑代码,Controller只负责跳转,这样我们就需要Service层来完成所有的业务逻辑。

 

这一层涉及到TYStudioDemo.DTO,TYStudioDemo.Interfaces这两个工程,所以也一并介绍这两个工程,还有就是异常的处理,首先我们顶一个规则,不在Service层和Repository中Catch任何的异常,你可以增加一些友好异常信息,但是绝对不能Catch住,Catch是Controller层的事情,我们在那里处理异常并记录日志。

mvc-structuremap-dto

mvc-structuremap-service mvc-structuremap-service-inferace

数据交互层TYStudioDemo.DTO工程

这个工程目前天屹的构架只把ViewModel放在这里面,DTO是Data Transfer Object简称翻译过来就是数据交互对象,所以我觉得这一层的命名可能有一些不合适,如果你觉得他长得太丑了,自己起一个漂亮的名字吧。

SupplierViewModel

这个类和JAVA的POJO类很想只有一些属性的和Set,Get方法,基本的内容是Supplier表中的各个字段名字,当然你可以根据具View具体的显示需求随意扩展。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.ComponentModel.DataAnnotations;
  7.  
  8. namespace TYStudioDemo.DTO
  9. {
  10.     public class SupplierViewModel
  11.     {
  12.         [Key]
  13.         [Display(Name = "编号")]
  14.         public int SupplierID { set; get; }
  15.  
  16.         [Required(ErrorMessage = "请填写公司名称")]
  17.         [Display(Name = "公司名称")]
  18.         public string CompanyName { set; get; }
  19.  
  20.         [Display(Name = "联系名称")]
  21.         public string ContactName { set; get; }
  22.  
  23.         [Display(Name = "联系标题")]
  24.         public string ContactTitle { set; get; }
  25.  
  26.         [Display(Name = "地址")]
  27.         public string Address { set; get; }
  28.  
  29.         [Display(Name = "城市")]
  30.         public string City { set; get; }
  31.  
  32.         [Display(Name = "地区")]
  33.         public string Region { set; get; }
  34.  
  35.         [Display(Name = "邮编")]
  36.         public string PostalCode { set; get; }
  37.  
  38.         [Display(Name = "国家")]
  39.         public string Country { set; get; }
  40.  
  41.         [Display(Name = "电话")]
  42.         public string Phone { set; get; }
  43.  
  44.         [Display(Name = "传真")]
  45.         public string Fax { set; get; }
  46.  
  47.         [Display(Name = "公司网站")]
  48.         public string HomePage { set; get; }
  49.     }
  50. }

代码中的[Key],[Diaplay]等标识属性,天屹这里就不在具体介绍,可以自行百度一下,如果再不明白,留下评论也可以。

顶级接口ITYService

这里和repository一样,同样定义了一个顶级接口ITYService。作用和ITYRepository是一样的,都是声明一些通用方法,你可以根据自己的实际需求添加相应的公共方法,先来看一下这个接口的代码吧。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6.  
  7. namespace TYStudioDemo.Interfaces
  8. {
  9.     /// <summary>
  10.     /// Service的顶级接口,所有Service类都要实现此接口
  11.     /// 声明Service公共的方法,根据需求可以加入自己想要的方法
  12.     /// 例如:管理系统可以加入CRUD的方法
  13.     ///
  14.     /// 本教程只声明了一个将Entity转化为DTO的方法
  15.     /// </summary>
  16.     public interface ITYService
  17.     {
  18.         /// <summary>
  19.         /// 将Entity转化成ViewModel对象
  20.         /// 这样做是为了使View,也就是UI层和Model database层分开,降低耦合度
  21.         /// 因为所有View都将使用ViewModel,而不是Entity
  22.         ///
  23.         /// 这里指定object为所有的参数和返回类型
  24.         /// 因为虽有的对象都是从object继承下来的
  25.         /// 将来你传进来什么他就是什么~是不是很酷,那句话叫什么来着:父类引用指向子类对象
  26.         /// </summary>
  27.         /// <param name="entity"></param>
  28.         /// <returns></returns>
  29.         object ConvertToViewModel(object entity);
  30.     }
  31. }

 

你可能会有一个问题,业务逻辑不会像repository那样会有通用的那些曾删改查,也不会像repository那样只处理一个表,一个service通常会处理多个表多个entity,而且实现也是不一样的,所以注意一点这个顶级接口并不是必须的,如果你觉得他很碍事完全可以删掉它。但是天屹这个系统这个系统有一个DTO(数据交互层,可能命名不是很合适,暂且叫他这个名字吧)工程,这里面放了一堆的ViewModel,这些ViewModel是直接给前台的View用的(强类型会使用到),所以我觉得每个Service都要有一个方法ConvertToViewModel的,你可以在上面代码注释中找到这个方法的具体用途。

上面说了Service层不想repository那样会有统一的实现,所以没有想TYRepository那样为他单独实现一个类。

ISupplierService和SupplierService介绍

ISupplierService接口

先粗略的看一下ISupplierService代码:(为什么天屹每次都先贴截图和代码呢,我觉得有了一个实际的东西,你再去理解他就会变得容易多了)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using TYStudioDemo.DTO;
  7.  
  8. namespace TYStudioDemo.Interfaces
  9. {
  10.     /// <summary>
  11.     /// 声明Supplier供应商独有的业务逻辑方法
  12.     ///
  13.     /// 本教程声明了CRUD和Search方法
  14.     /// </summary>
  15.     public interface ISupplierService : ITYService
  16.     {
  17.         IEnumerable<SupplierViewModel> GetAll();
  18.         SupplierViewModel GetByID(int ID);
  19.         void Create(SupplierViewModel dto);
  20.         void Delete(int ID);
  21.         void Update(SupplierViewModel dto);
  22.         IEnumerable<SupplierViewModel> SearchByCriteria(string companyName);
  23.     }
  24. }

 

看到代码是不是有一种很熟悉的感觉,没错他和ITYRepository很像,原因上面的ITYService已经介绍了,这也是你可以删掉ITYService的原因,你可以把ConvertToViewModel声明在这里。没有什么需要特别说明的,这里就是一些曾删改查的方法。

SupplierService实现类

首先是代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using TYStudioDemo.Interfaces;
  7. using TYStudioDemo.DTO;
  8. using TYStudioDemo.Models;
  9. using TYStudioDemo.Commons;
  10.  
  11. namespace TYStudioDemo.Services
  12. {
  13.     public class SupplierService : ISupplierService
  14.     {
  15.         ISupplierRepository<Supplier> _supplierRepository;
  16.         IProductRepository<Product> _productRepository;
  17.  
  18.         public SupplierService(ISupplierRepository<Supplier> supplierRepotitory,
  19.                                     IProductRepository<Product> productRepository)
  20.         {
  21.             _supplierRepository = supplierRepotitory;
  22.             _productRepository = productRepository;
  23.         }
  24.  
  25.         public IEnumerable<SupplierViewModel> GetAll()
  26.         {
  27.             List<SupplierViewModel> list = new List<SupplierViewModel>();
  28.             foreach(Supplier entity in _supplierRepository.GetAll())
  29.             {
  30.                 list.Add(ConvertToViewModel(entity) as SupplierViewModel);
  31.             }
  32.             return list.AsEnumerable<SupplierViewModel>();
  33.         }
  34.  
  35.         public SupplierViewModel GetByID(int ID)
  36.         {
  37.             return ConvertToViewModel(_supplierRepository.Single(e => e.SupplierID == ID)) as SupplierViewModel;
  38.         }
  39.  
  40.         public void Create(SupplierViewModel vModel)
  41.         {
  42.             try
  43.             {
  44.                 Supplier supplier = new Supplier();
  45.                 supplier.SupplierID = vModel.SupplierID;
  46.                 supplier.Address = vModel.Address;
  47.                 supplier.City = vModel.City;
  48.                 supplier.CompanyName = vModel.CompanyName;
  49.                 supplier.ContactName = vModel.ContactName;
  50.                 supplier.ContactTitle = vModel.ContactTitle;
  51.                 supplier.Country = vModel.Country;
  52.                 supplier.Fax = vModel.Fax;
  53.                 supplier.HomePage = vModel.HomePage;
  54.                 supplier.Phone = vModel.Phone;
  55.                 supplier.PostalCode = supplier.PostalCode;
  56.                 supplier.Region = vModel.Region;
  57.                 _supplierRepository.Create(supplier);
  58.  
  59.                 //为了显示事务的处理,这里添加一个临时的Product
  60.                 //由于Product数据并没有通过vModel传递过来,此处只是测试数据。
  61.                 Product product = new Product();
  62.  
  63.                 //给product属性赋值
  64.                 //其他的属性略去,只写一个必须的名称
  65.                 product.ProductName = "testProductName";
  66.  
  67.                 //虽然还没有调用datacontext的保存方法,没有在数据库中生成supplierid,
  68.                 //但是EntityFramework内部会指定一个新的supplierid,所以这里supplierid是有值的
  69.                 product.SupplierID = supplier.SupplierID;
  70.  
  71.                 _productRepository.Create(product);
  72.  
  73.                 //TYEntities.Current保存修改数据
  74.                 //当处理多个表时,只需要调用一次SaveChanges(),所以都在一个Transaction里面。
  75.                 TYEntities.Current.SaveChanges();
  76.             }
  77.             catch (Exception ex)
  78.             {
  79.                 //虽然此处谢了try catch语句,但是我们并不处理这里的异常,继续throw到Controller层
  80.                 //所有异常都在Controller层catch,返回错误信息。
  81.                 //此处的try catch不是必须的,当让如果你想完善异常信息的处理,这里可以自定义你的异常信息。
  82.                 Exception exception = new Exception("添加Supplier出错" + ex.Message);
  83.  
  84.                 //抛到Controller层
  85.                 throw exception;
  86.             }
  87.         }
  88.  
  89.         public void Delete(int ID)
  90.         {
  91.             Supplier entity = _supplierRepository.Single(e => e.SupplierID == ID);
  92.             _supplierRepository.Delete(entity);
  93.             TYEntities.Current.SaveChanges();
  94.         }
  95.  
  96.         public void Update(SupplierViewModel vModel)
  97.         {
  98.             Supplier entity = _supplierRepository.Single(e => e.SupplierID == vModel.SupplierID);
  99.             entity.SupplierID = vModel.SupplierID;
  100.             entity.Address = vModel.Address;
  101.             entity.City = vModel.City;
  102.             entity.CompanyName = vModel.CompanyName;
  103.             entity.ContactName = vModel.ContactName;
  104.             entity.ContactTitle = vModel.ContactTitle;
  105.             entity.Country = vModel.Country;
  106.             entity.Fax = vModel.Fax;
  107.             entity.HomePage = vModel.HomePage;
  108.             entity.Phone = vModel.Phone;
  109.             entity.PostalCode = entity.PostalCode;
  110.             entity.Region = vModel.Region;
  111.  
  112.             TYEntities.Current.SaveChanges();
  113.         }
  114.  
  115.         public object ConvertToViewModel(object entity)
  116.         {
  117.             Supplier supplier = (Supplier)entity;
  118.             SupplierViewModel vModel = new SupplierViewModel();
  119.             vModel.SupplierID = supplier.SupplierID;
  120.             vModel.Address = supplier.Address;
  121.             vModel.City = supplier.City;
  122.             vModel.CompanyName = supplier.CompanyName;
  123.             vModel.ContactName = supplier.ContactName;
  124.             vModel.ContactTitle = supplier.ContactTitle;
  125.             vModel.Country = supplier.Country;
  126.             vModel.Fax = supplier.Fax;
  127.             vModel.HomePage = supplier.HomePage;
  128.             vModel.Phone = supplier.Phone;
  129.             vModel.PostalCode = supplier.PostalCode;
  130.             vModel.Region = supplier.Region;
  131.             return vModel;
  132.         }
  133.  
  134.         /// <summary>
  135.         /// 搜索方法,参数根据需求可以自由添加
  136.         /// </summary>
  137.         /// <param name="companyName"></param>
  138.         /// <returns></returns>
  139.         public IEnumerable<SupplierViewModel> SearchByCriteria(string companyName)
  140.         {
  141.             //此处实现你的搜索
  142.             throw new NotImplementedException();
  143.         }
  144.  
  145.     }
  146. }

 

这个类主要有一个方法需要介绍一下Create(SupplierViewModel vModel),其他的方法相信你一看代码就会明白,还是先看一下这个方法,方法中介绍Transaction事务和异常的处理方法,详细的解释我写在了注释里面,见上面的注释。

总结:

MVC中的Service业务逻辑层中不要Catch任何异常,抛到Controller层。SupplierService层中的两个repository构造方法是StructureMap依赖注入需要用的。以后会具体介绍。

下一篇文章我们来看UI层,Controller和View。

 



2 评论

  1. superj   •  

    为什么大部分代码都放在service层里。这里不大明白。 repository层里没什么东西。
    repository层难道不才是数据持久层吗?

    • 天屹   •     作者

      service(服务)是业务逻辑层不直接和数据库打交道,repository负责和数据库和model数据库的持久。这样将来数据库由于一些原因更换,service层几乎可以不做修改,只修改repository的实现就可以了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>