MVC 3 – 如何实现服务层,我需要存储库吗?
我目前正在使用EF Code First,SQL CE和Ninject构建我的第一个MVC 3应用程序。 我已经阅读了很多关于使用存储库,工作单元和服务层的信息。 我想我已经完成了基础知识,并且我已经完成了自己的实现。
这是我目前的设置:
实体
public class Entity { public DateTime CreatedDate { get; set; } public Entity() { CreatedDate = DateTime.Now; } } public class Profile : Entity { [Key] public Guid UserId { get; set; } public string ProfileName { get; set; } public virtual ICollection Photos { get; set; } public Profile() { Photos = new List(); } public class Photo : Entity { [Key] public int Id { get; set; } public Guid FileName { get; set; } public string Description { get; set; } public virtual Profile Profile { get; set; } public Photo() { FileName = Guid.NewGuid(); } }
SiteContext
public class SiteContext : DbContext { public DbSet Profiles { get; set; } public DbSet Photos { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove(); } }
接口:IServices
public interface IServices : IDisposable { PhotoService PhotoService { get; } ProfileService ProfileService { get; } void Save(); }
实施:服务
public class Services : IServices, IDisposable { private SiteContext _context = new SiteContext(); private PhotoService _photoService; private ProfileService _profileService; public PhotoService PhotoService { get { if (_photoService == null) _photoService = new PhotoService(_context); return _photoService; } } public ProfileService ProfileService { get { if (_profileService == null) _profileService = new ProfileService(_context); return _profileService; } } public void Save() { _context.SaveChanges(); } private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { _context.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } }
接口
public interface IPhotoService { IQueryable GetAll { get; } Photo GetById(int photoId); Guid AddPhoto(Guid profileId); }
履行
public class PhotoService : IPhotoService { private SiteContext _siteContext; public PhotoService(SiteContext siteContext) { _siteContext = siteContext; } public IQueryable GetAll { get { return _siteContext.Photos; } } public Photo GetById(int photoId) { return _siteContext.Photos.FirstOrDefault(p => p.Id == photoId); } public Guid AddPhoto(Guid profileId) { Photo photo = new Photo(); Profile profile = _siteContext.Profiles.FirstOrDefault(p => p.UserId == profileId); photo.Profile = profile; _siteContext.Photos.Add(photo); return photo.FileName; } }
Global.asax中
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); Database.SetInitializer(new SiteInitializer()); }
NinjectControllerFactory
public class NinjectControllerFactory : DefaultControllerFactory { private IKernel ninjectKernel; public NinjectControllerFactory() { ninjectKernel = new StandardKernel(); AddBindings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType); } private void AddBindings() { ninjectKernel.Bind().To(); } }
PhotoController
public class PhotoController : Controller { private IServices _services; public PhotoController(IServices services) { _services = services; } public ActionResult Show(int photoId) { Photo photo = _services.PhotoService.GetById(photoId); if (photo != null) { string currentProfile = "Profile1"; _services.PhotoService.AddHit(photo, currentProfile); _services.Save(); return View(photo); } else { // Add error message to layout TempData["message"] = "Photo not found!"; return RedirectToAction("List"); } } protected override void Dispose(bool disposing) { _services.Dispose(); base.Dispose(disposing); } }
我可以构建我的解决方案,它似乎正常工作。
我的问题是:
- 我的实施中是否存在任何明显的缺陷?
- 我可以在TDD中使用它吗? 通常我看到存储库的模拟,但我没有在上面使用它,这会导致问题吗?
- 我是否正确使用DI(Ninject)?
我是一名业余爱好程序员,欢迎对我的代码提出任何意见和/或建议!
你有一般的想法,但真正习惯dependency injection需要一段时间。 我看到了一些可能的改进:
- 您的
IServices
界面似乎没必要。 我更喜欢控制器通过其构造函数指定它需要哪些服务(IPhotoService等),而不是像某种强类型服务定位器那样使用IServices
接口。 - 我在那里看到了
DateTime.Now
吗? 您如何validation在unit testing中正确设置日期? 如果您决定稍后支持多个时区怎么办? 如何使用注入日期服务来生成CreatedDate
? - 有一个非常好的Ninject扩展专门针对MVC。 它负责插入MVC 3支持注射的各个点。 它实现了像NinjectControllerFactory之类的东西。 您所要做的就是让
Global
类扩展特定的基于Ninject的应用程序。 - 我建议使用NinjectModules来设置绑定,而不是在ControllerFactory中设置它们。
- 考虑使用约定绑定,这样您就不必将每个服务显式绑定到其实现。
更新
Ninject MVC Extension可以在这里找到。 有关如何扩展NinjectHttpApplication
的示例,请参阅自述文件部分。 此示例使用模块,您可以在此处阅读更多信息。 (它们基本上只是放置绑定代码的地方,这样您就不会违反单一责任原则。)
关于基于约定的绑定,一般的想法是让绑定代码扫描适当的程序集,并根据命名约定自动将IPhotoService
等IPhotoService
绑定到PhotoService
。 这里有另一个扩展来帮助解决这些问题。 有了它,你可以将这样的代码放在你的模块中:
Kernel.Scan(s => { s.From(assembly); s.BindWithDefaultConventions(); });
上面的代码会将给定程序集中的每个类自动绑定到它实现的任何接口,遵循“默认”约定(例如Bind
)。
更新2
关于对整个请求使用相同的DbContext,您可以执行类似的操作(使用Ninject.Web.Common
库,这是MVC扩展所需的):
Bind().ToSelf().InRequestScope();
然后,Ninject创建的任何依赖于上下文的服务将在请求中共享相同的实例。 请注意,我个人使用了较短寿命的上下文,因此我不知道如何强制在请求结束时处理上下文,但我确信它不会太难了。
IServices
和Services
类型对我来说似乎是多余的。 如果删除它们并将控制器的构造函数更改为
public PhotoController(IPhotoService photoService, IProfileService profileService) { _photoService = photoService; _profileService = profileService; }
它将更加明显地实际取决于它。 此外,当您创建一个只需要IProfileService的新控制器时,您只需传递一个IProfileService而不是一个完整的IService,从而使新控制器具有更轻的依赖性。
我可以说你的服务看起来很像一个存储库。 仔细查看界面:
IQueryable GetAll { get; } Photo GetById(int photoId); Guid AddPhoto(Guid profileId);
看起来非常像我的存储库。 也许是因为这个例子很简单,但是如果你在它上面添加用例逻辑,我会看到提供服务的重点。 而不是这些相当简单的CRUD操作。
你可以说EFs DbSet和DbContext是应用程序的存储库和工作单元……在这一点上,我们进入一个有点超出问题范围的新区域。