Pergunta

Let's say I am creating a "TODO list" web application with following features:

  • have ability to register/login users
  • each user has it's own TODO list that is independent from other users

So I've created a simple model that has class ToDoItem.

I'd like to use good practices so I created a generic repository that should fetch TODO items from database:

public interface IRepository<T>
{
    IQueryable<T> FindAll();
    IQueryable<T> Find(Expression<Func<T, bool>> predicate);

    void Add(T newEntity);
    void Remove(T entity);

    T FindById(long id);
}

(implementation is done with EF and code-first approach but that's not important right now)

This repository will be injected into controller that allows user to list, add, remove, edit TODO items. This is done via custom controller factory I've created that uses Ninject DI container to resolve repository interface to concrete implementation. So the controller looks like this:

public class HomeController : Controller
{
    IRepository<ToDoItem> _repository;
    public HomeController(IRepository<ToDoItem> repository)
    {
        _repository = repository;
    }

    // list all TODO items for this user
    [Authorize]
    public ActionResult ListItems()
    {
        var todoItems = _repository.FindAll();
        return View(todoItems);
    }

}

My question what is the best way to make controller return TODO list only for the currently logged in user? Ideally I'd like to have controller work with repository that is injected and pre-set with the currently logged user. In other words I'd like to avoid this kind of code in action method:

    // list all TODO items for this user
    [Authorize]
    public ActionResult ListItems()
    {
        var todoItems = _repository.FindAll(User.Identity);
        return View(todoItems);
    }

I was thinking that possible solution would be to make controller factory somehow know which user is logged so it would initialize concrete repository and set user ID so that controller doesn't have to do that in every action method. Would this be good approach and if so how can I implement it? If not, what are better alternatives?

Foi útil?

Solução

I would tackle this in one of two ways:

1. Make the lifestyle of the repository be per web request and take a dependency on the User.Identity such that this can be used inside of the repository methods. e.g.

public class Repository<ToDoItem> : IRepository<ToDoItem>
{
    private IIdentity _identity;

    // let the container inject the IIdentity into the repository
    // (you will need to register a service with 
    //  the container for IIdentity for this)
    public Repository(IIdentity identity)
    {
        _identity = identity;
    }

    IQueryable<ToDoItem> FindAll()
    {
        return FromSomeContext().Where(x => x.Username == _identity.Name);
    }

    // ....
}

Then register a method with Ninject that it can call to resolve an IIdentity for any component that requires it. (You may decide that injecting an IPrincipal is more useful as you can get information about user roles with it too).

kernel.Bind<IIdentity>()
      .ToMethod(ctx => HttpContext.Current.User.Identity)
      .InRequestScope();

Now, assuming that Ninject is also constructing your controllers for you and you have registered components for the IRepository<T> services that your application requires, The current user IIdentity will be injected into the Repository<ToDoItem> for you by Ninject.

2. Create an extension method for IRepository<ToDoItem> (or even IRepository<T> if appropriate) that wraps adding the Where() expression for limiting the returned TODO items only to those that are relevant to the current user.

Outras dicas

For those, who use Windsor Castle:

container.Register(
...
Component.For<IIdentity>()
    .UsingFactoryMethod(() => { return HttpContext.Current.User.Identity; })
    .LifeStyle.PerWebRequest,
...);

Note:

Component.For<ICustomer>().Instance(HttpContext.Current.User.Identity)

does not work, because "When you register an existing instance, even if you specify a lifestyle it will be ignored.", see Windsor Castle Documentation

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top