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:
I am a hobby programmer, so any comments and/or suggestions to my code are welcome!
Yeah, your repository could be used to get lightweight entities from EF4; and your service layer could be used to send these back to a specialised model manager (Model in your scenario).
Basically, a repository allows you to populate data in memory that comes from the database in the form of the domain entities. Once the entities are in memory, they can be changed and then persisted back to the database through transactions.
No, the repository/unit-of-work pattern (shortened to Rep/UoW) isn't useful with EF Core. EF Core already implements a Rep/UoW pattern, so layering another Rep/UoW pattern on top of EF Core isn't helpful.
The repository pattern is intended to create an abstraction layer between the data access layer and the business logic layer of an application. It is a data access pattern that prompts a more loosely coupled approach to data access.
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 the IServices
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 that CreatedDate
?Global
class extend a specific Ninject-based application.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>()
).
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With