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

时间:2011-09-13 21:48:34

标签: c# entity-framework asp.net-mvc-3 design-patterns ninject

我目前正在使用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);
    }
}

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

我的问题是:

  1. 我的实施中是否存在任何明显的缺陷?
  2. 我可以在TDD中使用它吗?通常我会看到存储库的模拟,但我没有在上面使用它,会导致问题吗?
  3. 我是否正确使用DI(Ninject)?
  4. 我是一名业余爱好程序员,欢迎对我的代码提出任何意见和/或建议!

3 个答案:

答案 0 :(得分:9)

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

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

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

    关于基于约定的绑定,一般的想法是让绑定代码扫描适当的程序集,并根据命名约定自动将IPhotoService之类的内容绑定到PhotoService。还有另一个扩展here来帮助解决这些问题。有了它,你可以将这样的代码放在你的模块中:

    Kernel.Scan(s =>
                    {
                       s.From(assembly);
                       s.BindWithDefaultConventions();
                    });
    

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

    更新2

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

    Bind<SiteContext>().ToSelf().InRequestScope();
    

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

答案 1 :(得分:4)

IServicesServices类型对我来说似乎是多余的。如果删除它们并将控制器的构造函数更改为

public PhotoController(IPhotoService photoService, IProfileService profileService)
{
  _photoService = photoService;
  _profileService = profileService;
}

实际上取决于它将更加明显。此外,当您创建一个只需要IProfileService的新控制器时,您只需传递一个IProfileService而不是一个完整的IService,从而为新控制器提供更轻的依赖。

答案 2 :(得分:0)

我可以说你的服务看起来很像一个存储库。仔细查看界面:

IQueryable<Photo> GetAll { get; }
Photo GetById(int photoId);
Guid AddPhoto(Guid profileId);

看起来非常像我的存储库。也许是因为这个例子相当简单,但是如果你在它上面添加用例逻辑,我会看到提供服务的重点。而不是这些相当简单的CRUD操作。

你可以说EFs DbSet和DbContext是应用程序的存储库和工作单元......在这一点上,我们进入一个有点超出问题范围的新区域。