I use an IModelEnricher
combined with Automapper and attributes that define relationships between a type of list and select list provider. I return an entity etc using a specific ActionResult that then automaps my entity to a ViewModel and enriches with data required for select lists (and any additional data required). Also keeping the select list data as part of your ViewModel keeps your controller, model, and view responsibilities clear.
Defining a ViewModel ernicher means that anywhere that ViewModel is used it can use the same enricher to get its properties. So you can return the ViewModel in multiple places and it will just get populated with the correct data.
In my case this looks something like this in the controller:
public virtual ActionResult Edit(int id)
{
return AutoMappedEnrichedView<PersonEditModel>(_personRepository.Find(id));
}
[HttpPost]
public virtual ActionResult Edit(PersonEditModel person)
{
if (ModelState.IsValid){
//This is simplified (probably don't use Automapper to go VM-->Entity)
var insertPerson = Mapper.Map<PersonEditModel , Person>(person);
_personRepository.InsertOrUpdate(insertPerson);
_requirementRepository.Save();
return RedirectToAction(Actions.Index());
}
return EnrichedView(person);
}
This sort of ViewModel:
public class PersonEditModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int FavouriteTeam { get; set; }
public IEnumerable<SelectListItem> Teams {get;set;}
}
With this sort of Enricher:
public class PersonEditModelEnricher :
IModelEnricher<PersonEditModel>
{
private readonly ISelectListService _selectListService;
public PersonEditModelEnricher(ISelectListService selectListService)
{
_selectListService = selectListService;
}
public PersonEditModelEnrich(PersonEditModel model)
{
model.Teams = new SelectList(_selectListService.AllTeams(), "Value", "Text")
return model;
}
}
One other option is to decorate the ViewModel with attributes that define how the data is located to populate the select list. Like:
public class PersonEditModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int FavouriteTeam { get; set; }
[LoadSelectListData("Teams")]
public IEnumerable<SelectListItem> Teams {get;set;}
}
Now you can decorate an appropriate method in your select service with an attribute like:
[ProvideSelectData("Teams")]
public IEnumerable Teams()
{
return _teamRepository.All.ToSelectList(a => a.Name, a => a.TeamId);
}
Then for simple models with no complex enrichment just the generic enrichment process can handle it. If you want to do anything more complex you can define an enricher and it will be used if it exists.
Other options could be a convention over configuration approach where the Enricher looks at property name and type e.g. IEnumerable<SelectListItem> PossibleFirstDivisionTeams
{get;set;} then matches this if it exists with a select list provider name in a class that say implements a marker interface ISelectListProvider
. We went the attribute based one and just created Enums representing the various lists E.g. SelectList.AllFirstDivisionTeams
. Could also try interfaces on ViewModel that just have a property collection for a selectlist. I don't really like interfaces on my ViewModels so we never did this
It all really depends on the scale of your application and how frequently same type of select list data is required across multiple models. Any specific questions or points you need clarified let me know
See this question. Also this blog post and this. Also this question on Automapper forum