MVC 3 - 如何实现服务层,我是否需要存储库?

esb*_*sba 21 c# design-patterns entity-framework ninject asp.net-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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

接口:IServices

public interface IServices : IDisposable
{
    PhotoService PhotoService { get; }
    ProfileService ProfileService { get; }

    void Save();
}
Run Code Online (Sandbox Code Playgroud)

实施:服务

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

接口

public interface IPhotoService
{
    IQueryable<Photo> GetAll { get; }
    Photo GetById(int photoId);
    Guid AddPhoto(Guid profileId);
}
Run Code Online (Sandbox Code Playgroud)

履行

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Global.asax中

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);

        ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());

        Database.SetInitializer<SiteContext>(new SiteInitializer());
    }
Run Code Online (Sandbox Code Playgroud)

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以构建我的解决方案,它似乎正常工作.

我的问题是:

  1. 我的实施中是否存在任何明显的缺陷?
  2. 我可以在TDD中使用它吗?通常我看到存储库的模拟,但我没有在上面使用它,这会导致问题吗?
  3. 我正确使用DI(Ninject)吗?

我是一名业余爱好程序员,欢迎对我的代码提出任何意见和/或建议!

Str*_*ior 9

你有一般的想法,但真正习惯依赖注入需要一段时间.我看到了一些可能的改进:

  1. 您的IServices界面似乎没必要.我更喜欢控制器通过其构造函数指定它需要哪些服务(IPhotoService等),而不是IServices像某种类型的强类型服务定位器那样使用接口.
  2. DateTime.Now在那里看到了吗?您如何验证在单元测试中正确设置日期?如果您决定稍后支持多个时区怎么办?如何使用注入日期服务来生成它CreatedDate
  3. 有一个非常好的Ninject扩展专门针对MVC.它负责插入MVC 3支持注射的各个点.它实现了像NinjectControllerFactory之类的东西.您所要做的就是让您的Global类扩展特定的基于Ninject的应用程序.
  4. 我建议使用NinjectModules来设置绑定,而不是在ControllerFactory中设置它们.
  5. 考虑使用约定绑定,这样您就不必将每个服务显式绑定到其实现.

更新

Ninject MVC Extension可以在这里找到.有关如何扩展的示例,请参阅自述文件部分NinjectHttpApplication.此示例使用模块,您可以在此处阅读更多信息.(它们基本上只是放置绑定代码的地方,这样您就不会违反单一责任原则.)

根据有关约定,绑定,一般的想法是让你的绑定代码扫描适当的组件和自动绑定之类的东西IPhotoService,以PhotoService基于命名约定.还有一个扩展这里帮了这样的事情.有了它,你可以将这样的代码放在你的模块中:

Kernel.Scan(s =>
                {
                   s.From(assembly);
                   s.BindWithDefaultConventions();
                });
Run Code Online (Sandbox Code Playgroud)

上面的代码会将给定程序集中的每个类自动绑定到它实现的任何接口,遵循"默认"约定(例如Bind<IPhotoService>().To<PhotoService>()).

更新2

关于对整个请求使用相同的DbContext,您可以执行类似的操作(使用Ninject.Web.CommonMVC扩展所需的库):

Bind<SiteContext>().ToSelf().InRequestScope();
Run Code Online (Sandbox Code Playgroud)

然后,Ninject创建的任何与上下文相关的服务将在请求中共享相同的实例.请注意,我个人使用了较短寿命的上下文,因此我不知道如何强制在请求结束时处理上下文,但我确信它不会太难了.