Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the recommended approach for getting lists for multiple selections and displaying them in MVC?

I've searched both SO and Google on this but have not found a relevant / acceptable answer.
Background:
* Using MVC 4.5
* I have some Generic Repo<T> that I am using over the top of EF5, which in turn are accessed by generic Service<T>
* I have domain models and view models and I am using Automapper to map between them, this mapping happens in the Service layer
* On top of that, I have my Controllers which again are generic as much as possible.

So, to the question; I have a couple of scenarios where I need to present a list of options to the user and they have to select one or more. The options are user specific, so my domain User has a List<Location> which is their saved locations, and when adding / modifying, an Item, they are required to select at least one Location.

I am resisting the temptation to fetch that List<Location> in my controllers because I want to keep them generic and slim, but at the same time, I'd rather not have two properties in my ItemView model, one for AvailableLocations and one for SelectedLocations, because this model is used not just for adding / modifying but for search results etc.

Options:
* Should I introduce a different model for adding / modifying an Item, e.g. ItemInput?
* Should I use some custom mapping and get Automapper to get the list of available locations?
* In which layer should I fetch these available locations?

What are people's suggestions on a neat and generic approach to this please?
Many thanks!

like image 560
Simon Avatar asked Apr 30 '13 15:04

Simon


People also ask

Which component in ASP.NET MVC can be used to pass data between controller to view assume you have to pass current date and time?

To pass data from the controller to view, either ViewData or ViewBag can be used. To pass data from one controller to another controller, TempData can be used.

Where will be the view for response is decided for a specific user request in MVC?

When ASP.NET MVC attempts to resolve a view template, it will first check within the \Views[Controller] specific directory, and if it can't find the view template there it will look within the \Views\Shared directory.

What must the controller be named in ASP.NET MVC and what folder does it have to be specifically saved under?

In ASP.NET MVC, every controller class name must end with a word "Controller". For example, the home page controller name must be HomeController , and for the student page, it must be the StudentController . Also, every controller class must be located in the Controller folder of the MVC folder structure.


2 Answers

I would do something like this:

    public IEnumerable<Location> GetLocations() {
        return db.GetAll();
    }

Then inside of your controller (I followed this from MVC scaffolding):

 ViewBag.Locations = new SelectList(service.GetLocations, "name", "id");

(or your own checkbox list) and put a listing control on the HTML/View page.

The reason I believe this is the best method is because the logic all resides inside the service. If you put it in your DTO/data model you may come against this problem:

What happens if you require additional logic to pull back locations? i.e. sub locations of locations.

You change your service (or override) to reflect the new changes, and this logic would go inside of the service:

    public IEnumerable<Location> GetLocations(string parent) {
        return db.GetAll().Where(loc => loc.parentname = parent);
    }

p.s. I never use generic services, the reason I have a service is because some of the data access it provides contains logic that is not meant to sit with the generic DAL.

I could make an interface or abstract service to make my life a little easier for common operations between services but once you define say a concrete, a UserManagementSerive surely you are saying you want to manage an object that has Users, and Locations and Items each having its own specific functionality?

like image 97
Smithy Avatar answered Sep 27 '22 18:09

Smithy


I don't think there is only one possible answer to this question.

I would recommend a simple, but not-so-generic approach. I would write what's called ViewModels, i.e. model classes that are related to your specific views. Then I would get your available locations from the controller, and populate an instance of the ViewModel in your controller using the fetched locations.

Basically I would expose some services like:

IEnumerable<Location> GetAvailableLocationsForUser(string userName);

Do note I've used IEnumerable<T>, not IQueryable<T>. Because the implementation will actually request the database, as it's too much bug-prone (at least IMO) if it's the role of the controller to do so (remember the deferred execution of IQueryable<T>). And it returns a domain instance, i.e. an entity, not a mapped model. I wouldn't personally deal with anything but domain classes in the service layer. There could be domain classes that are not entities, but compositions of entities for example. This could help making efficient requests and avoiding using lazy-loading and deferred execution in the controllers. This is helpful when the controller needs a whole object graph and not only an entity.

Then I would write Models and ViewModels like the following, in the web application assembly:

public LocationModel
{
    ...
}

public CreateItemViewModel : ItemModel
{
    public List<LocationModel> AssociatedLocations { get; set; }
    public List<LocationModel> AvailableLocations { get; set; }
    ...
}
  • There are basically Models (ItemModel and LocationModel), which are objects related to the web application. This means there could be some web-related things in those models, for example computed read-only properties or attributes on properties (DisplayAttribute...etc.). I would write those models multiple times, actually, because I don't think this is something that could be generalized: for example, one view could require the use of a navigation property while another view wouldn't. So this changes the depth of the mapping process depending on the views that uses the model. And I wouldn't use AutoMapper at all (only hand-written mappers).
  • There are also ViewModels (CreateItemViewModel), which are objects related to a single view (for example the view that allows to create an Item in this example). The difference between Model and ViewModel is that the ViewModel is related to a single view (and named according to this view). On the other hand, Models are related to multiple views (its namespace would help to know which views. For example, xxx.Item.Models for Models related to all views in the xxx.Item directory). ViewModels are built from scratch in the controller (or in a separate mapper) based on the domain classes.

In the above example, you could build a domain classes that would return AssociatedLocations and AvailableLocations, but it would require your service layer to be aware of the web part (I mean, your service interface and domain classes would know which properties are needed for a particular view). I'm not sure those properties are actually related to a single view in your application, but if it's not the case, you could also build a domain class as a composition of entities that would return AssociatedLocations and AvailableLocations:

public ItemExtended : Item
{
    public List<Location> AssociatedLocations { get; set; }
    public List<Location> AvailableLocations { get; set; }
}

ItemExtended GetItemExtendedById(long idItem);
like image 44
ken2k Avatar answered Sep 27 '22 18:09

ken2k