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

发布于 2024-12-04 05:13:39 字数 5737 浏览 2 评论 0原文

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

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

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:

  1. Are there any obvious flaws in my implementation that I am missing?
  2. 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?
  3. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

_畞蕅 2024-12-11 05:13:39

您已经有了大概的想法,但需要一段时间才能真正习惯依赖注入。我发现有许多可能需要改进的地方:

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

更新

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

关于基于约定的绑定,总体思路是让您的绑定代码扫描适当的程序集并自动绑定诸如根据命名约定将 IPhotoService 更改为 PhotoService此处还有另一个扩展可以帮助解决此类问题。有了它,您可以将这样的代码放入模块中:

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

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

更新 2

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

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

然后是任何依赖于上下文的服务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:

  1. Your IServices interface seems unnecessary. I'd prefer to have the controller specify which services it needs (IPhotoService, etc.) via its constructor, rather than using the IServices interface like some kind of strongly-typed service locator.
  2. Did I see a 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 that CreatedDate?
  3. There is a very good Ninject extension specifically for MVC. It takes care of plugging into the various points that MVC 3 supports for injection. It implements things like your NinjectControllerFactory. All you have to do is make your Global class extend a specific Ninject-based application.
  4. I'd suggest using NinjectModules for setting your bindings, rather than setting them in your ControllerFactory.
  5. Consider using Binding by Convention so that you don't have to explicitly bind each service to its implementation.

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 to PhotoService 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:

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

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):

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

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.

污味仙女 2024-12-11 05:13:39

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

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

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

The IServices and Services types seem superfluous to me. If you drop them and change your controller's constructor to be

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

it 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.

变身佩奇 2024-12-11 05:13:39

我可以说你的服务看起来非常有存储库。仔细观察界面:

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

对我来说看起来非常像一个存储库。也许是因为这个例子相当简单,但我看到了如果在服务上添加用例逻辑就拥有服务的意义。而不是这些相当简单的 CRUD 操作。

您可能会说 EF DbSet 和 DbContext 是应用程序的存储库和工作单元……此时我们进入一个新区域,这在某种程度上超出了问题的范围。

I could argue that your services look very much with a repository. Look closely to the interface:

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文