zday
zday

Reputation: 133

MVC Layered Project Structure

We are starting a new web project using C# / MVC 4 and Entity Framework 5 for data access. I've decided to go with an n-layered approach for the structure of the project and I would like some feedback on my design decisions.

This is how the solution is structured:

Project.Model (Class Library): Contains EF .edmx, entity models, and view models

Project.DAL (Class Library): Contains EF DbContext and Repository classes

Project.BLL (Class Library): Contains business logic classes

Project (MVC Project)

DAL

The Data Access Layer is only concerned with simple CRUD like operations. I've decided to go with a repository approach. Here are the Repository interfaces:

public interface IRepository
{
}

public interface IRepository<T> : IRepository, IDisposable 
    where T : class, new()
{
    T Add(T item);

    T Get(object id);

    T Get(Expression<Func<T, bool>> predicate);

    IQueryable<T> GetAll();

    IQueryable<T> GetAll(Expression<Func<T, bool>> predicate);

    void Update(T item);

    void Delete(T item);
}

After doing some research on using Entity Framework in web projects, the general consensus is that there should only be one DbContext/ObjectContext per request. So to create and dispose the single context for each request, I've written an HttpModule that injects the DbContext into the HttpContext.

public class DbContextModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += context_BeginRequest;
        context.EndRequest += context_EndRequest; 
    }

    public void Dispose()
    {
    }

    private void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext httpContext = application.Context;

        httpContext.Items.Add(Repository.ContextKey, new ProjectEntities());
    }

    private void context_EndRequest(object sender, EventArgs e)
    {
        HttpApplication application = (HttpApplication)sender;
        HttpContext httpContext = application.Context;

        var entities = (ProjectEntities)httpContext.Items[Repository.ContextKey];

        entities.Dispose();
        entities = null;

        application.Context.Items.Remove(Repository.ContextKey);
    }
}

Next is the Repository base class. Note that the constructor utilizes the injected DbContext from the HttpModule above

public abstract class Repository<T> : IRepository<T> where T : class, new()
{
    protected Repository()
    {
        if (HttpContext.Current == null)
        {
            throw new Exception("Cannot create repository - current HttpContext is null.");
        }

        _entities = (ProjectEntities)HttpContext.Current.Items[Repository.ContextKey];

        if (_entities == null)
        {
            throw new Exception("Cannot create repository - no DBContext in the current HttpContext.");
        }
    }

    private ProjectEntities _entities;

    public T Add(T item)
    {
        _entities.Set<T>().Add(item);
        _entities.SaveChanges();

        return item;
    }

    public T Get(object id)
    {
        return _entities.Set<T>().Find(id);
    }

    public T Get(Expression<Func<T, bool>> predicate)
    {
        return _entities.Set<T>().AsQueryable().FirstOrDefault(predicate);
    }

    public IQueryable<T> GetAll()
    {
        return _entities.Set<T>().AsQueryable();
    }

    public IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
    {
        return _entities.Set<T>().AsQueryable().Where(predicate);
    }

    public void Update(T item)
    {
        _entities.Entry(item).State = EntityState.Modified;
        _entities.SaveChanges();
    }

    public void Delete(T item)
    {
        _entities.Set<T>().Remove(item);
        _entities.SaveChanges();
    }
}

And a simple example of an implementation...

public class AdminRepository : Repository<Admin>
{
    public Admin GetByEmail(string email)
    {
        return Get(x => x.Email == email);
    }
}

BLL

The Business Logic Layer encapsulates all business logic. To keep constraints, I've written the base "Logic" class like this:

public abstract class Logic<TRepository> where TRepository : class, IRepository, new()
{
    private static Expression<Func<TRepository>> _x = () => new TRepository();
    private static Func<TRepository> _compiled = _x.Compile(); 

    protected Logic()
    {
        Repository = _compiled();
    }

    protected internal TRepository Repository { get; private set; }
}

The constructor automatically creates the needed Repository class, so no additional code is needed in child classes to instantiate the repository. Here is a simple example of an implementation

public class AdminLogic : Logic<AdminRepository>
{
    public Admin Add(Admin admin)
    {
        return Repository.Add(admin);
    }

    public Admin Get(object id)
    {
        return Repository.Get(id);
    }

    public Admin GetByEmail(string email)
    {
        return Repository.GetByEmail(email);
    }

    public IQueryable<Admin> GetAll()
    {
        return Repository.GetAll();
    }

    public void Update(Admin admin)
    {
        Repository.Update(admin);
    }
}

This example is more of a pass-through for the DAL repository, but adding business logic later won't be a problem. I'm choosing to return IQueryable from the BLL because we are using some third party tools that require an IQueryable for deferred execution.

Project (MVC Project)

Now finally here is what a simple controller action will look like:

public ActionResult Index(int? page)
{
    // Instantiate logic object
    AdminLogic logic = new AdminLogic();

    // Call GetAll() and use AutoMapper to project the results to the viewmodel
    IQueryable<AdminModel> admins = logic.GetAll().Project().To<AdminModel>();

    // Paging (using PagedList https://github.com/TroyGoode/PagedList)
    IPagedList<AdminModel> paged = admins.ToPagedList(page ?? 1, 25);

    return View(paged);
}

Everything works as expected, and tests show that the EF context is properly disposed and the overall speed is good.

Is this a pretty good way to go about this?

Thank you for your time.

Upvotes: 2

Views: 2648

Answers (1)

pedrommuller
pedrommuller

Reputation: 16056

I think if you are going to use the repository pattern you should consider your repositories as aggregates to me doing a repository could have some level of abstraction which is fine but doing it that way could lead the repositories to be Entity Centric and sometimes that makes difficult to communicate with other objects that belongs to the same aggregate root.

I had that dilema some time ago, instead of using repository(T) I ended up using methods(T) inside the "generic repository" which made me a lit of bit easier to operations with other objects that belongs to the aggregate root (I did this because that suited good to me but it doesn't mean that will suit good to you, I'm putting this example to the table just for you to take it into consideration), you can take a look to this quesion and this article which I found very interesting.

Jeremy miller talks about how you could implement an IRepository:

public interface IRepository { // Find an entity by its primary key // We assume and enforce that every Entity // is identified by an "Id" property of // type long T Find<T>(long id) where T : Entity; // Query for a specific type of Entity // with Linq expressions. More on this later 
IQueryable<T> Query<T>(); 
IQueryable<T> Query<T>(Expression<Func<T, bool>> where); // Basic operations on an Entity 
void Delete(object target); 
void Save(object target); 
void Insert(object target); 
T[] GetAll<T>(); }

which is pretty much similar of what you have done in your repo.

potentially, I think would need 2 more layers (depending on your requirements), one is for services to deal with common operations or actions across your application and maybe another layer for components (email, logs, cache manager, cryptography, helpers, etc)

about how your are handling the logic for the BL look like and overkill to me, practically you are coupling the repositories into the logic which personlally I dont think that's fine.

try to implement Dependency Injection in your application because you'll get a lof of benefits from it.

let's say that you want to create a UserService which have a login method for your BL

public class UserService:IService {
    public UserService(IUserRepository, IMailer, ILogger){
      // for example you can follow the next use case in your BL
      // try to login, if failed reteat after 3 time you block the accunt and send a mail

    }

   public bool login(string username, string password){
   }

}

then you can implement that service in your controller(and inject it in the constructor if you are using a container and just call the login service) which is going to make much cleaner your implementation

public ActionResult Index(){
  //then you're going to be able to use  _userService.login()
}

that way you will have a loosely coupled application, which in theory will be easier to maintain.

just my two cents

Upvotes: 2

Related Questions