Are there any good ways to keep my controllers simpler when they have models that depend on a lot of select lists? I try to keep most of my controller actions as simple as possible (hopefully no more than 10 or so lines) but on pages that require a lot of dropdowns my actions usually exceed this:
public class Model
{
public IEnumerable<SelectListItem> AllLocations { get; set; }
public IEnumerable<SelectListItem> TopLocations { get; set; }
public IEnumerable<SelectListItem> AllTemplates { get; set; }
public IEnumerable<SelectListItem> TopTemplates { get; set; }
// ...
}
[HttpGet]
public ActionResult Index(int id)
{
var domain = Repository.Get(id);
var model = Mapper.Map<Domain, ViewModel>(item);
// any way to abstract this type of code?
model.AllLocations = new SelectList(repository.GetAllLocations(), "Value", "Text");
model.TopLocations = new SelectList(repository.GetTopLocations(), "Value", "Text");
model.AllTemplates = new SelectList(repository.GetAllTemplates(), "Value", "Text");
model.TopTemplates = new SelectList(repository.GetTopTemplates(), "Value", "Text");
// etc. etc.
return View(model);
}
[HttpPost]
public ActionResult Index(ViewModel model)
{
// any way to abstract this type of code?
model.AllLocations = new SelectList(repository.GetAllLocations(), "Value", "Text");
model.TopLocations = new SelectList(repository.GetTopLocations(), "Value", "Text");
model.AllTemplates = new SelectList(repository.GetAllTemplates(), "Value", "Text");
model.TopTemplates = new SelectList(repository.GetTopTemplates(), "Value", "Text");
// etc. etc.
return View(model);
}
As you say keeping controller actions small is great. As Jimmy Bogard says put your controllers on a diet!
I use an IModelEnricher combined with Automapper. 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). This method keeps your code DRY and controllers thin like a super model :-)! 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= new List<SelectListItem>();
}
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= new List<SelectListItem>();
}
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.
See this question. Also this blog post and this. Also this question on Automapper forum
You can setup a helper class, put every select list in a static method.Then,in view you can get every select list by htmlhelper. Controller will clear. At the same time, other view can use these select list too.
eg:
public class SelectHelper
{
public static List<SelectListItem> AllLocations()
{
//TODO repository.GetAllLocations()
}
public static List<SelectListItem> TopLocations()
{
//TODO repository.GetTopLocations()
}
...
}
view code:
#Html.DropDownList("selectname", SelectHelper.AllLocations())
Sure, just refactor it to a method, like so:
public class Model
{
public IEnumerable<SelectListItem> AllLocations { get; set; }
public IEnumerable<SelectListItem> TopLocations { get; set; }
public IEnumerable<SelectListItem> AllTemplates { get; set; }
public IEnumerable<SelectListItem> TopTemplates { get; set; }
// ...
}
[HttpGet]
public ActionResult Index(int id)
{
var domain = Repository.Get(id);
var model = Mapper.Map<Domain, ViewModel>(item);
InitializeSelectLists(model);
return View(model);
}
[HttpPost]
public ActionResult Index(ViewModel model)
{
InitializeSelectLists(model);
View(model);
}
private void InitializeSelectLists(Model model)
{
model.AllLocations = new SelectList(repository.GetAllLocations(), "Value", "Text");
model.TopLocations = new SelectList(repository.GetTopLocations(), "Value", "Text");
model.AllTemplates = new SelectList(repository.GetAllTemplates(), "Value", "Text");
model.TopTemplates = new SelectList(repository.GetTopTemplates(), "Value", "Text");
// etc. etc.
}
Or you can even do it in a constructor for your model or a facade service if you want to.
Related
Okay, so i have my ViewModel and I understand what the controller does, I'm just having difficulty implementing it. I don't know how to code a controller for the ViewModel, i've tried researching it myself and can't find anything.
Here is my viewModel, how would I go about constructing the controller? Not asking you to do it for me, just how to do it
public class ViewOrderViewModel
{
//From ORDER Table
public int OrderId { get; set; }
public System.DateTime OrderDate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Email { get; set; }
public decimal Total { get; set; }
//from Products
public List<Product> Products { get; set; }
}
UPDATE
public class ViewOrderController : Controller
{
// GET: ViewOrder
public ActionResult ViewOrders()
{
var order = new Order();
var viewModel = GetViewModel(order);
return View(viewModel);
}
public ViewOrderViewModel GetViewModel(Order orderObject)
{
ViewOrderViewModel viewModel = new ViewOrderViewModel();
viewModel.OrderId = orderObject.OrderId;
viewModel.OrderDate = orderObject.OrderDate;
viewModel.FirstName = orderObject.FirstName;
viewModel.LastName = orderObject.LastName;
viewModel.City = orderObject.City;
viewModel.Address = orderObject.Address;
viewModel.Country = orderObject.Country;
viewModel.Email = orderObject.Email;
viewModel.PostalCode = orderObject.PostalCode;
viewModel.Total = orderObject.Total;
return viewModel;
}
}
Still unsure about how to map the List of products in the ViewModel class to the list of products in the db
Typically, your view is going to be "bound" to the ViewModel. It's like saying "Ok, I'm the view for an 'Order' and I only need to worry about the properties that you defined in ViewOrderViewModel".
The controller is not required for that binding to happen. The binding is declared at the top of your view:
Order.cshtml
#Model MyProject.Web.ViewModels.ViewOrderViewModel
<div>
<!-- Html for the view-->
</div>
This allows you to access properties on that model within the view. Razor has some functions that make life easy. For example, if you want to display the OrderId it might look like this:
<span>OrderId: #Model.OrderId </span>
The view doesn't care what values are set for each of those properties, it only cares that the properties exist. Where the controller comes into play is populating those properties with the values you want and then passing the ViewModel to the view:
public ActionResult Order()
{
var viewModel = new ViewOrderViewModel();
// Load data into each property
viewModel.OrderId = 123; // etc..
// Return it to the view. Asp.net knows to return
// it to the Order.cshtml view because the view
// and the controller action share the same name.
return View(viewModel);
}
Edit: In response to your questions in the comments: If you need to populate your ViewModel with values from a different model (such as a database model) you can create a mapper like so:
public ViewOrderViewModel GetViewModel(Order orderObject)
{
ViewOrderViewModel viewModel = new ViewOrderViewModel();
viewModel.OrderId = orderObject.OrderId;
viewModel.FirstName = orderObject.FirstName;
// etc...
return viewModel;
}
and then in your controller you would do something like this:
// var order = new Order()
var viewModel = GetViewModel(order);
I have been struggling with a loading a display list of items from a database table that was created through the entity framework.
I have found code examples online but many have a static list of items created and then added to the dropdown list list like.
http://www.aspnetmvcninja.com/general/asp-net-mvc-dropdown-list-example. I found an article by Scott Allen that is the basis for what I have (http://odetocode.com/Blogs/scott/archive/2010/01/18/drop-down-lists-and-asp-net-mvc.aspx) but I think that issue is that Im not correctly loading the data into _jobs?
Here is the code that I have in my controller and other classes, any help or guidance is greatly appreciated as I feel that Im so close and yet not.
public class JobsController : Controller
{
var tasks = db.JobTypes.Select(c => new SelectListItem {Value = c.JobTypeID.ToString(), Text = c.JobDescription});
ViewBag.JobTypeAllNames = tasks;
return View();
}
I have two classs that are being used for my entity framework code first. The Job class references the JobTypes class.
public class Job
{
[Key]
[Display(Name="Job ID")]
public int JobID { get; set; }
[Display(Name = "Job Type")]
public int JobTypesID
{
get;
set;
}
private readonly List<JobType> _jobs;
public IEnumerable<SelectListItem> JobTypeItems
{
get
{
return new SelectList(_jobs, "JobTypesID", "JobDescription",1);
}
}
}
public class JobType
{
public int JobTypeID { get; set; }
[Display(Name = "Task")]
public string JobDescription { get; set; }
}
In my view I have
#Html.DropDownListFor(model => model.JobTypesID, Model.JobTypeItems);
This is not going to compile:
public class JobsController : Controller
{
var tasks = db.JobTypes.Select(c => new SelectListItem {Value = c.JobTypeID.ToString(), Text = c.JobDescription});
ViewBag.JobTypeAllNames = tasks;
return View();
}
You need to have an action that will return an ActionResult and I would use a model like you have within your view rather than use the ViewBag:
public class JobsController : Controller
{
public ActionResult Index()
{
var model = new Job();
var tasks = db.JobTypes.Select(c => new SelectListItem {Value = c.JobTypeID.ToString(), Text = c.JobDescription});
model.JobTypeItems = tasks;
return View(model);
}
}
ps You will need to adapt your Job model to include a setter and remove un-used _jobs:
public class Job
{
[Key]
[Display(Name="Job ID")]
public int JobID { get; set; }
[Display(Name = "Job Type")]
public int JobTypesID
{
get;
set;
}
public IEnumerable<SelectListItem> JobTypeItems
{
get; set;
}
}
I am getting stuck using a ViewModel. Suppose I want to give a logged-in person an Edit Form with only a few of the fields from my Person domain model (maybe I want to custom annotate validations in the ViewModel).
I am getting stuck in two separate places in the controller (I have marked them with "<<< >>>").
When I pass the whole Person object as a property to my ViewModel, I know what to do. I can get my code to only update the name fields, but then I have lost my ability to validate the individual properties in my ViewModel. On the hand if I limit the properties in my ViewModel to only to a few properties, then my code in the GET section where I cann vm.Person doesn't work, since I am not passing the Person.
I scanned many examples on SO, but they were all using AutoMapper. Can I accomplish this without a mapper, and/or how do I write my own? And thanks in advance!
Model:
public class Person()
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
}
ViewModel:
public class LoggedInPersonViewModel()
{
public int PersonId { get; set; }
[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
public string FirstName { get; set; }
}
Repository:
public Person GetLoggedInPerson()
{
var user = HttpContext.Current.User.Identity;
var userid = user.GetUserId();
return db.People.SingleOrDefault(i => i.UserId == userid);
}
Controller:
public class RegistrationController : Controller
{
//Get Logged in User, Edit Form
public ActionResult UpdateDetails()
{
LoggedInPersonViewModel vm = new LoggedInPersonViewModel();
<<<Do I also need a Person property in my ViewModel>>>
vm.Person = repository.GetLoggedInPerson();
return View(vm);
}
//POST
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UpdateDetails(LoggedInPersonViewModel loggedinpersonviewmodel)
{
if (ModelState.IsValid)
{
<<<what do i do here? is this correct? Again I cannot use Person if not in my VM.>>>
//Person person = db.People.Find(loggedinpersonviewmodel.PersonId);
//Person.FirstName = loggedinpersonviewmodel.FirstName;
//Person.LastName = loggedinpersonviewmodel.LastName;
//db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Person");
}
return View(loggedinpersonviewmodel);
}
}
}
Is there a way...or do I have to use AutoMapper for this?
I think you need to Map LoggedInPersonViewModel and Person. Example
public ActionResult UpdateDetails()
{
var person = repository.GetLoggedInPerson();
LoggedInPersonViewModel vm = new LoggedInPersonViewModel();
vm.PersonId = person.PersonId;
//Rest of properties
...
//return view model
return View(vm);
}
I would recommend AutoMapper this type of work. i.e. AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another.
So I've looked around StackOverflow a lot and found a nice solution to my problem.
MVC3 DropDownListFor - a simple example?, this ought to do it for me. BUT, it returned a null value... Somehow I have no idea how to go around this so assistance will be appreciated.
Model AccountModel.cs
...
public string Address { get; set; }
public string City { get; set; }
public class StateList
{
public int StateID { get; set; }
public string Value { get; set; }
}
public IEnumerable<StateList> StateListOptions = new List<StateList>
{
//new StateList { StateID = -1, Value = "Select State" },
new StateList { StateID = 0, Value = "NY" },
new StateList { StateID = 1, Value = "PO" }
};
public string State { get; set; }
public string Zip { get; set; }
...
Register.cshtml
#Html.DropDownListFor(m => m.State, new SelectList(Model.StateListOptions, "StateID", "Value", Model.StateListOptions.First().StateID))
I thought maybe my StateID = -1 made it output a null for some reason... but it didn't, you can see it commented out here. What did I do wrong?!
Get Action
public ActionResult Register()
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View();
}
Create an object of your Model/ ViewModel and send that to view.
public ActionResult Register()
{
AccountModel vm=new AccountModel();
//Not sure Why you use ViewData here.Better make it as a property
// of your AccountModel class and pass it.
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
return View(vm);
}
Now your View should be strongly typed to this Model
So in your Register.cshtml view,
#model AccountModel
#using(Html.BeginForm())
{
//Other form elements also
#Html.DropDownListFor(m => m.State, new SelectList(Model.StateListOptions,
"StateID", "Value")"Select")
<input type="submit" />
}
To Get the Selected State in POST, You can check the State Property value.
[HttpPost]
public ActionResult Register(AccountModel model)
{
if(ModelState.IsValid)
{
// Check for Model.State property value here for selected state
// Save and Redirect (PRG Pattern)
}
return View(model);
}
I am coverting my app from webforms to mvc, at the moment i am at a design issue (well i just dont know how to do it in mvc).
Basically my model would be something like this:
public class DamagedItem
{
public Int32 LoanId {get;set;}
public String IdentityCode {get;set;}
public virtual ICollection<DamagedItems> DamagedItems {get;set;}
}
In my controller i would like to do:
public ActionResult Add(DamagedItem damagedItem)
{
//Do update logic here
}
Then in my view i can add to the ICollection as needed.
But, i can't do this because if i try and access the ICollection from my controller it is null.
Here is an image of when i want to do:
I just dont know how to lay it out in my view, how to i add such items to my ICollection, update the view then when i need to save i have access to what i have added from my controller?
Thanks,
Nick
Edit:
I was thinking of using a partial in the view and doing all the logic for the bottom half using ajax and storing it in a session variable, but i would prefer NOT to make it reliant on ajax.
It is better to separate: you shoud have 2 actions, which produce 2 view.
You should have LoadInformationModel classe:
public class LoadInformationModel
{
public string StudentCode { get; set; }
public string FirstName { get; set; }
// etc..
public ICollection<Damage> Type { get; set; }
}
corresponding action
[HttpGet]
public ActionResult LoanInformation(int id)
{
var loanInfo = // get data by given id..
var model = new LoadInformationModel {
StudentCode = loanInfo.StudentCode,
// etc
Type = new List<Damage> { new Damage { Value = "Damaged"}, new Damage { Value = "Damaged Again" }
}
return View(model);
}
As well as RepairDataModel class
public class RepairDataModel
{
public bool CoveredByWarranty { get; set; }
public ICollection Status { get; set; }
}
And corresponding action
[HttpGet]
public ActionResult Repair(int id)
{
// logic
return View(model);
}
Your task is to create Post handler, that would save data to DB then form submitted
[HttpPost]
public ActionResult(RepairDataModel model)
{
// save to db
return View();
}
The view returned by Index() method, could be created like
#Html.RenderAction("LoanInformation")
#Html.RenderAction("Repair")
The rest depends on your desing and imagination. I hope that would give you direction.
What I can see is only the DamagedItem lacks a contructor with values for Collection;
public class DamagedItem
{
public DamagedItem()
{
DamagedItems = new List<DamagedItems>();
DamagedItems.Add(new DamagedItem { Description = "Damaged" } );
}
public Int32 LoanId {get;set;}
public String IdentityCode {get;set;}
public virtual ICollection<DamagedItems> DamagedItems {get;set;}
}