views:

365

answers:

2
+2  Q: 

ASP.NET MVC design

As I've stated before I'm working on a digg clone to teach myself ASP.NET MVC Inside and out but I've hit a road bump that I can't seem to avoid.

I want to be able to optimize this application as much as possible so I have my DAL which is a bunch of classes of ...Repository : Repository. Now to help optimize for performance I have my base repository classes return my ViewData objects so that they can select extra fields needed without having to create an anonymous type.

Stories have Users who have created them and Users have Votes for Stories. Pretty easy DB layout. Now I handle my own membership because the default ASP.NET membership is so bloated. In my view for the list of stories I have to determine if the current user has voted on the story being rendered. Now since I figured data access in the View shouldn't be happening it should be in either my controller or my DAL. Since I'm already returning ViewData from my DAL i added another property on the StoryViewData type named "UserVotedOn" that returns true if the user has voted on that story.

Problem with this is I have to either A) make the DAL aware of membership or B) pass in the User ID into the query methods on the DAL. Neither of these feel right to me and I'm looking for some good solutions. Any feedback is welcome.

+1  A: 

It looks like you're missing the BLL.

Actually, the right architecture of an MVC application is what many people still trying to figure out.

I personally consider UserID to be somewhat a translayer concept. It will appear on both DAL and BLL levels.

Basically, your controller method should have just a few very basic calls to the BLL, only to determine how to react to user input, whether to return on view or another.

Your view should only deal with model objects. A model should probably be filled by the business logic. You could call BL methods in a controller method in order to initialize you model object and then pass it to the view.

Controller should not communicate directly with the database. Neither should it probably deal with low level objects which comprise your domain objects and models.

P.S. i would try to avoid extensive use of ViewData. Strongly-typed model classes are a much better option. You can also group them in hierarchies to inherit some common properties. Just like your domain model classes could derive from a base class that has a UserID property defined.

User
Well, when I said DAL it technically is a BLL. It wraps the datacontext. When I said ViewData I meant I have ViewData classes I pass into strongly-typed views. Thanks for the input, I'll soak it in.
Chad Moran
I have myself been trying to come up with the "perfect" MVC architecture. After several months of experimenting I can say that a perfect architecture and an ideal separation of concerns are not possible (who'd doubt). You'll just need to agree with yourself and made some compromises.
User
+3  A: 

In my MVC apps I'm using architecture that Rob Conery showed on his MVC Storefront video series and it works like charm for me.

Repository => Service + Filters => Controller => View

I've tried to simulate what you want to achieve and managed todo like this

Edit1: Changed IList to IQueryable in repository and filters

Repository

public interface IRepository
{
    IQueryable<Vote> GetVotes();
    IQueryable<Story> GetStories();
}

Service for getting what you want

public class Service : IService
{
    private IRepository _repository;

    public Service(IRepository repository)
    {
        _repository = repository;
        if (_repository == null) throw new InvalidOperationException("Repository cannot be null");
    }
    public IList<Vote> GetUserVotes(int userID)
    {
        return _repository.GetVotes().WithUserID(userID).ToList();
    }
    public IList<Story> GetNotVotedStories(IList<Vote> votes)
    {
        return _repository.GetStories().WithoutVotes(votes).ToList();
    }
}

Filters to filter your stories and user votes (These are basically extension methods). Not the nicest implementation out there, but you can rewrite later

public static class Filters
{
    public static IQueryable<Vote> WithUserID(this IQueryable <Vote> qry, int userID)
    {
        return from c in qry
               where c.UserID == userID
               select c;
    }
    public static IQueryable<Story> WithoutVotes(this IQueryable <Story> qry, IList <Vote> votes)
    {
        return  from c in qry
                where votes.Where(x => x.StoryID == c.StoryID).ToList().Count > 0
                select c;
    }
}

And then you can pass current UserID in controller, not in DAL or View like you had to do before

public class HomeController : Controller
{
    private readonly IRepository _repository;
    private readonly IService _service;

    public HomeController()
    {
        _repository = new Repository();
        _service = new Service.Service(_repository);
    }

    public ActionResult Index()
    {
        var userVotes = _service.GetUserVotes(CurrentUserID);
        var unvotedStories = _service.GetNotVotedStories(userVotes);

        return View(unvotedStories);
    }
}

This allows you to stay away from adding user related UserVotedOn property to your Story model

Paul G.
I can see a performance issue when using IList<> instead of IQueryable<>. In the latter case, the query itself can be further enhanced by the filters before it's delay-executed by LINQ.
Dave Van den Eynde
I absolutely agree on that, as I've said I'd choose IQueryable too on repository and filters, and only later in service return cast to IList
Paul G.
I like it. Though I kind of need to know if the current user has voted on a given story or not during rendering for the VOTE/UNVOTE link. I can adapt this. Thanks a lot for your help.
Chad Moran