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<Photo> Photos { get; set; }
public Profile()
{
Photos = new List<Photo>();
}
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<Profile> Profiles { get; set; }
public DbSet<Photo> Photos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
接口: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<Photo> 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<Photo> 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<SiteContext>(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<IServices>().To<Services>();
}
}
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)?
我是一名业余程序员,因此欢迎对我的代码提出任何意见和/或建议!
I am currently building my first MVC 3 application, using EF Code First, SQL CE and Ninject.
I have read a lot about using Repositories, Unit of Work and Service Layers. I think I have got the basics sorted out, and I have made my own implementation.
This is my current setup:
Entities
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<Photo> Photos { get; set; }
public Profile()
{
Photos = new List<Photo>();
}
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<Profile> Profiles { get; set; }
public DbSet<Photo> Photos { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Interface: IServices
public interface IServices : IDisposable
{
PhotoService PhotoService { get; }
ProfileService ProfileService { get; }
void Save();
}
Implementation: Services
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);
}
}
Interface
public interface IPhotoService
{
IQueryable<Photo> GetAll { get; }
Photo GetById(int photoId);
Guid AddPhoto(Guid profileId);
}
Implementation
public class PhotoService : IPhotoService
{
private SiteContext _siteContext;
public PhotoService(SiteContext siteContext)
{
_siteContext = siteContext;
}
public IQueryable<Photo> 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<SiteContext>(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<IServices>().To<Services>();
}
}
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);
}
}
I can build my solution and it seems to be working correctly.
My questions are:
- Are there any obvious flaws in my implementation that I am missing?
- Will I be able to use this with TDD? Usually I see mocking of repositories but I haven't used that in the above, will that cause issues?
- Am I using DI (Ninject) correctly and enough?
I am a hobby programmer, so any comments and/or suggestions to my code are welcome!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您已经有了大概的想法,但需要一段时间才能真正习惯依赖注入。我发现有许多可能需要改进的地方:
IServices
界面似乎没有必要。我更愿意让控制器通过其构造函数指定它需要哪些服务(IPhotoService 等),而不是使用像某种强类型服务定位器那样的 IServices 接口。DateTime.Now
吗?您将如何验证单元测试中的日期设置是否正确?如果您稍后决定支持多个时区怎么办?使用注入的日期服务来生成 CreatedDate 怎么样?更新
Ninject MVC 扩展可以在此处找到。有关如何扩展
NinjectHttpApplication
的示例,请参阅 README 部分。此示例使用模块,您可以在此处阅读有关模块的更多信息。 (它们基本上只是放置绑定代码的地方,这样您就不会违反单一职责原则。)关于基于约定的绑定,总体思路是让您的绑定代码扫描适当的程序集并自动绑定诸如根据命名约定将
IPhotoService
更改为PhotoService
。 此处还有另一个扩展可以帮助解决此类问题。有了它,您可以将这样的代码放入模块中:上面的代码将自动将给定程序集中的每个类绑定到它实现的遵循“默认”约定的任何接口(例如
Bind()。到()
)。更新 2
关于对整个请求使用相同的 DbContext,您可以执行以下操作(使用 MVC 扩展所需的
Ninject.Web.Common
库):然后是任何依赖于上下文的服务Ninject 创建的将在请求中共享相同的实例。请注意,我个人使用过较短生命周期的上下文,所以我根本不知道如何强制在请求结束时处理上下文,但我确信它不会太难了。
You've got the general idea, but it takes a while to really get used to Dependency Injection. I see a number of possible improvements to be made:
IServices
interface seems unnecessary. I'd prefer to have the controller specify which services it needs (IPhotoService, etc.) via its constructor, rather than using theIServices
interface like some kind of strongly-typed service locator.DateTime.Now
in there? How are you going to verify that the date gets set correctly in a unit test? What if you decide to support multiple time zones later? How about using an injected date service to produce thatCreatedDate
?Global
class extend a specific Ninject-based application.Update
The Ninject MVC Extension can be found here. See the README section for an example of how to extend the
NinjectHttpApplication
. This example uses Modules, which you can read more about here. (They're basically just a place to put your binding code so that you don't violate the Single Responsibility Principle.)Regarding conventions-based bindings, the general idea is to have your binding code scan the appropriate assemblies and automatically bind things like
IPhotoService
toPhotoService
based on the naming convention. There is another extension here to help with such things. With it, you can put code like this in your module:The above code will auto-bind every class in the given assembly to any interface it implements that follows the "Default" conventions (e.g.
Bind<IPhotoService>().To<PhotoService>()
).Update 2
Regarding using the same DbContext for an entire request, you can do something like this (using the
Ninject.Web.Common
library, which is required by the MVC extension):Then any context-dependent services that Ninject creates will share the same instance across a request. Note that I have personally used shorter-lived contexts, so I don't know off the top of my head how you'd force the context to be disposed at the end of the request, but I'm sure it wouldn't be too difficult.
IServices
和Services
类型对我来说似乎是多余的。如果您删除它们并将控制器的构造函数更改为,那么它实际上取决于什么将会更加明显。此外,当您创建一个新控制器时,它实际上只需要 IProfileService,您可以只传递 IProfileService 而不是完整的 IService,从而为新控制器提供更轻的依赖关系。
The
IServices
andServices
types seem superfluous to me. If you drop them and change your controller's constructor to beit will be more apparent what it is actually depending on. Moreover, when you create a new controller, that only really needs IProfileService, you can just pass an IProfileService instead of a full IService, thus giving the new controller a lighter dependency.
我可以说你的服务看起来非常有存储库。仔细观察界面:
对我来说看起来非常像一个存储库。也许是因为这个例子相当简单,但我看到了如果在服务上添加用例逻辑就拥有服务的意义。而不是这些相当简单的 CRUD 操作。
您可能会说 EF DbSet 和 DbContext 是应用程序的存储库和工作单元……此时我们进入一个新区域,这在某种程度上超出了问题的范围。
I could argue that your services look very much with a repository. Look closely to the interface:
Looks very much like a repository to me. Maybe because the example is rather simple but I see the point of having a service if you add use case logic on it. instead of these rather simpel CRUD operations.
And you could argue that EFs DbSet and DbContext are the repositories and unit of work of the app...and at this point we enter a new zone that is somewhat out of scope of the question.