I am just getting into .NET MVC2 (.NET in general even) and I am having a hard time getting familiar with the "flow" of things. The MVC framework, I get.. for the most part. The part that I am getting tripped up on is applying standard programming practices to .NET MVC.
For example
public ActionResult Index()
{
var dataContext = new SiteContentDataContext();
var data = from c in dataContext.SiteContents where c.Slug == "home-page" select c;
// call/return getMainNav()
// call/return getSubNav()
return View(data);
}
public ActionResult SiteContent(string strSlug)
{
var dataContext = new SiteContentDataContext();
var data = from c in dataContext.SiteContents where c.Slug == strSlug select c;
// call/return getMainNav()
// call/return getSubNav()
return View(data);
}
private void getSubNav()
{
// get subnav records from db.
// return subnav records.
}
private void getMainNav()
{
// get main nav records from db.
// return main nav records.
}
The Index and SiteContent view are identical except for the fact that the Index view uses a different master page. Both views have a subnav and a main nav that will be dynamic content from a database.
The question, finally, is how would I go about populating the getSubNav and getMainNav functions and second, how would I return that data to the view properly?
Look into ViewModel objects, objects you create whose purpose in life is to carry data to and from your Views. The Models folder created for you by default in a new MVC project would hold exactly those classes.
You have options besides the ViewModel object methodology, but none are as clean. (The ViewData dictionary is around to help but it's not intended to be the primary means of providing data to your views.) Here's an example of how to set the Model property of the ViewData object to an instantiated, populated viewmodel instance:
public ActionResult SiteContent(string strSlug) {
SiteContentVM model = new SiteContentVM();
SiteService siteService = new SiteService();
model.Slug = siteService.GetALittleSlimyCreature(strSlug);
model.List1 = siteService.GetList1();
model.List2 = siteService.GetList2();
ViewData.Model = model;
return View();
}
You can now create a strongly typed view (complete with intellisense) to reference any properties of your ViewModel object instance from within your view simply through the Model property:
<% foreach (var item in Model.List1) { %>
<% Html.Encode(item.StringField) %> <!-- <= writing a property -->
<% Html.RenderPartial("PartialNameHere", item); %> <!-- <= the equivalent of a usercontrol -->
<% } %>
As you see above the process of getting data from the database does not change for MVC. You do it just as you would in a normal webforms project. (...usually this involves instantiating a business logic or service class of some sort rather than actually writing the code in the MVC project.)
Enjoy MVC!
You should look into DDD and TDD for ASP.NET MVC. For the looks of it you seem to be using Linq To Sql. I'm going to try to explain in a few words what I do to accomplish a good architecture.
Architecture
DB Context
Domain Model
Repository Pattern
It's good practice not to tie your Database Context with your Controllers. What you want to do is have your Controllers call your Repository, which in turn will return your Model. Now here's the tricky part you must convert the DB Context Objects into your Model Objects.
Imagine you have an Products table which Linq To SQL will give you as the Products Class.
That Products Class is part of the DB Context and what you want to do is alienate your context, in fact your Controllers won't even know it exists.
Why would I need a Model when I have Linq To SQL?
Well for starters LTS will regenerate all Objects everytime you change your Database meaning you wont have the ability to make change to the DB Context. And also you want to be able to use ASP.NET MVC 2 Annotations for validation and more.
Now create a Products Class for your Model
namespace MvcApplication.Models
{
public class Product
{
public int Id { get; set; }
[Required]
[StringLength(10)]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[DisplayName("Price")]
[Required]
[RegularExpression(#"^\$?\d+(\.(\d{2}))?$")]
public decimal UnitPrice { get; set; }
}
}
Now you see this Class is part of the Model totally disconnected from the DB Context. Now what we do next is create our Repository Class.
namespace MvcApplication.Repository
{
public class AppRepository {
DbContext _context = new DbContext();
public IQueryable<Products> GetProducts()
{
return from p in _context.Products
select new Product {
Name = p.Name,
UnitPrice = p.UnitPrice
}
}
}
}
Now in your Controller you just call GetProducts();
public ActionResult SiteContent(string strSlug)
{
var repository = new AppRepository();
return View(repository.GetProducts());
}
Pretty isn't it.
You can use AutoMapper to map your DB Context objects to your Model objects.
http://automapper.codeplex.com/
Well ,,, i think what you are looking for here is Partial Views.
You can embed the MainNav & SubNav Views into your SiteContent View.
here's how this goes.
create your MainNav & SubNav as partial views.
in your SiteContent view use the Html.RendarPartial Method to include the other two views.
<%= Html.RenderPartial("MainNav", Model); %>
<%= Html.RenderPartial("SubNav", Model); %>
Now to the remaining part about how to get the data to the MainNav & SubNav views. Now is a good time to get familiar with ViewModels. View models are nothing but classes with some properties that you want to give to a view to display.
In your case i would create 3 view models.
SiteContentViewModel contains the content that will be displayed in your page.
MainNavViewModel contains the data that will be displayed insdie the MainNav.
SubNavVIewModel contains the data that will be displayed insdie the SubNav.
then i would include the MainNavViewModel & SubNavVIewModel inside the SiteContentViewModel.
(if you are sure that every SiteContent View will have a MainNav & a SubNav )
Now it's up to you to fill each view model with that data that you need.
here's how the code will look like.
public class SiteContentViewModel {
public MainNavViewModel MainNav { get; set;}
public SubNavVIewModel SubNav { get; set;}
// Any Other Data Needed In The SiteContent View (ex. PageTitle)
}
public class MainNavViewModel {
// Any Data Needed In The MainNav View
}
public class SubNavVIewModel {
// Any Data Needed In The SubNav View
}
Now back to the Partial Views ,,, using the View Models we created we can include the partials like this.
<%= Html.RenderPartial("MainNav", Model.MainNav); %>
<%= Html.RenderPartial("SubNav", Model.SubNav); %>
one important thing is to make our views strongly typed.
SiteContent view of type SiteContentViewModel
MainNav view of type MainNavViewModel
SubNav vIew of type SubNavViewModel
and in your SiteContent action method you will do something like this
// Initialize the ViewModels.
SiteContentViewModel model = new SiteContentViewModel();
model.MainNav = new MainNavViewModel();
model.SubNav = new SubNavVIewModel();
// Get Data From DB and set the properties that you created in your view models.
// examples.
model.PageTitle = // some data from db.
model.MainNav.(Some Property) = // some data from db.
model.SubNav.(Some Property ) = // some data from db.
return View(model);
hope that helps ... for more information you can see this link
Related
I have various viewmodels that have properties that must be populated. For example, imagine a database table full of countries. I have a create user page, and one of the properties on the viewmodel is List<string> Countries.
Originally I populated this in a parameterless constructor, ie.
public CreateUserViewModel()
{
this.Countries = new CountryManager().GetCountries();
}
But I read this is a bad practice, and I should pass them in.
public CreateUserViewModel(IEnumerable<string> countries)
{
this.Countries = countries;
}
But in my post, this data is lost, and if validation fails it redirects to the view, but then the countries property is null.
I am wondering how I should be repopulating this value. Manually putting some code into the controller post method seems bad, eg.
[HttpPost]
public ActionResult CreateUser(CreateUserViewModel vm)
{
if (Model.IsValid)
{
new UserManager().CreateUser(vm);
}
else
{
vm.Countries = new CountryManager().GetCountries();
return View(vm);
}
}
I am struggling to google what is probably a very common question. Any ideas?
The rationale behind avoiding parameterless constructors is for inversion of control. The logic in this case would be that Countries is a dependency and by externalizing that dependency (so that it is inject into the class, instead), you make your class less brittle and more open to extension.
However, I would argue that doesn't apply actually in your scenario, because Countries is not really a dependency of your class, but rather of your view. The view model is there to serve the view and is somewhat closed and unextensible anyways as a result. In other words, do follow inversion of control for things like services, repositories, utility classes, etc., but for view models, it's not really necessary or important.
Anyways, here's how I handle this type of thing:
public class FooViewModel
{
...
// Countries is not initialize by a constructor
public IEnumerable<SelectListItem> Countries { get; set; }
}
Then in your controller:
public class FooController : Controller
{
internal void PopulateCountryChoices(FooViewModel model)
{
// fetch countries
model.Countries = countries.Select(m => new SelectListItem
{
Text = m.Name,
Value = m.Id.ToString()
});
}
public ActionResult Bar()
{
var model = new FooViewModel();
PopulateCountryChoices(model);
return View(model);
}
[HttpPost]
public ActionResult Bar(FooViewModel model)
{
if (ModelState.IsValid)
{
// save and redirect
}
PopulateCountryChoices(model);
return View(model);
}
}
Instead of populating this static data in model, We can populate Dropdownlist values in the view itself -
#Html.DropDownListFor(model => model.State,
new SelectList(Utils.GetCountries()),
"value",
"text",
2)
Where Utils is a Helper class which returns all the countries.
This way of populating all the static data (I mean all options of Dropdownlist or Listbox etc) will be taken over by the View, which makes model to be free from holding this data.
This is my first time using MVC, first time writing a web application as well.
So far, I managed to have a view for a list of employees, and an edit view for the Employee model.
If I had 25 models that I need displayed as lists, and edited, will I have to create 50 different views?
or is there a way to have one common List View and one common Edit View?
(edit below)
Solved the List View issue.
Sorry for the long code.
I created a ModelPropertyInfo class that describes model properties. For now I only added the Label, but I might add more properties like "Format", "InputType", ...
// Model field information class. Used by views to display model info properly
public class ModelPropertyInfo
{
public ModelPropertyInfo() { }
public string Name { get; set; }
public string Label { get; set; }
}
Then the ShowInListAttribute attribute class to decorate only model properties that I want to appear in the list view
// Attribute class used to specify Labels for model fields
public class ShowInListAttribute : Attribute
{
public ShowInListAttribute(string header)
{
Header = header;
}
public string Header { get; set; }
}
And a ModelBase class that all my models will inherit. This class will give the ability to get any property value from the class by passing its name as string
// Base class for all models
public class ModelBase
{
public static List<ModelPropertyInfo> ModelProperties(Type modelType)
{
List<ModelPropertyInfo> result = new List<ModelPropertyInfo>();
foreach (PropertyInfo pi in modelType.GetProperties())
{
ShowInListAttribute att = (ShowInListAttribute)pi.GetCustomAttributes(typeof(ShowInListAttribute), true).FirstOrDefault();
if (att != null)
result.Add(new ModelPropertyInfo { Label = att.Header, Name = pi.Name });
}
return result;
}
public object GetPropertyValue(string propName)
{
return this.GetType().GetProperty(propName).GetValue(this, null);
}
}
Now, here's my Employee model class
[Table("Employee")]
public class Employee : ModelBase
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public decimal ID { get; set; }
[ShowInList("First Name")]
public string FirstName { get; set; }
[ShowInList("Last Name")]
public string LastName { get; set; }
public decimal DepartmentID { get; set; }
[ShowInList("Department")]
[DatabaseGeneratedAttribute(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
public string DepartmentName { get; set; }
}
So, to put all the above to use, here's the Index method in my EmployeeController
public ActionResult Index()
{
ViewBag.Columns = ModelBase.ModelProperties(typeof(Employee));
ViewBag.Title = "Employee List";
return View("ListShared", db.Employees.ToList());
}
Finally, the result, the SharedListView that I will use to display a list of any model I want
#using SharedListView.Models
#model IEnumerable<ModelBase>
<h2>#ViewBag.Title</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
#foreach (ModelPropertyInfo col in ViewBag.Columns)
{
<th>
#col.Label
</th>
}
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
#foreach (ModelPropertyInfo col in ViewBag.Columns)
{
<td width='100px'>
#item.GetPropertyValue(col.Name).ToString()
</td>
}
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.GetPropertyValue("ID") }) |
#Html.ActionLink("Details", "Details", new { id=item.GetPropertyValue("ID") }) |
#Html.ActionLink("Delete", "Delete", new { id=item.GetPropertyValue("ID") })
</td>
</tr>
}
</table>
Still stuck on a common Edit view, any help would be appreciated.
Again, sorry for the long edit.
You don't need to do that. The ASP.NET MVC supports ContentFor method and EditorFor method. So in your case, you only need to designing your view models, and afterwards on your view, you can use its like
#Html.ContentFor(Model.Employee) // for display - that mean, it should be read-only
#Html.EditorFor(Model.Employee) // for editing.
You can see the post about that topic here
I would suggest you have this structure for each model object:
ListView: Display the list of item. And create a viewmodel class for each item in the view
CreateView: Used when creating new object. Also having ViewModel class for this
EditView: the same as CreateView, except it is for edit mode
This structure will create many views with ViewModel that look similar. However, they are not since in the nature they are different for different purposes. The structure will improve the code in term of separation of concern, help in maintenance. Easy to work with.
checkout Knockout.js. I have written applications like what you are talking about, a view for a collection of data and a view for editing the individual records. knockout makes it fairly easy to integrate the editing portion of the views into the collection viewing. It does help to have some understanding of WPF and Silverlight style data bindings. All of my views now use knockout and I integrate the editing functionality in the collection views with proper data binding using the visible binding to a editor area.
Your view will use a model to display or edit the data. If you have 25 different models, every view should have a different model.
If you want to use just one model, mainly because they share similar properties, this can be done but it is not ideal. The way it can be done is if all the models have similar properties, you can include all the properties in one model. Then you can just use the properties you need in other views. This is not the ideal way of doing it. Every view should have its own model.
You could create some sort of class that generates the html for all the different types of "form inputs" you will need in your app. Then add ability to the class to receive data from the models (ie. takes a string from the model and creates a TEXT input with the value set to that string .... or a SELECT dropdown can receive all it's OPTIONs from a model, etc).
Then all these form inputs can be generated from within the models (utilizing your class) and can be pumped into an array that is passed to your single view. The view would contain all the surrounding divs and other html, but somewhere in the view you would put a small "for loop" that outputs your array's contents. This is valid MVC, in that you are only using a simple for-loop in your view. And your models, to some extent may be partially responsible in deciding how the data if formatted coming out of the database (in this case, form inputs). To style the form inputs, you can keep the styling in a css file or at the top of the view.
Ultimately it depends on the future of your app. This is an idea if your app and models fit in a nice repetitive structure. But if you suspect parts of your app might evolve, where sometimes you might need a view to look much differently, or you want more control over how you arrange each of those generated "form inputs" in the views, then you will probably be creating a higher number of views.
I noticed that in the NerdDinner application that if the ModelState is invalid for a dinner, it merely returns the view for the model:
if (ModelState.IsValid) {
...
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
return View(dinner);
However, in my application the model (a view model in this situation) contains multiple SelectLists. These lists are not instantiated at this point because this view model was just populated from the form submission. What is the recommended way to repopulate this SelectLists before sending them back to the user?
This is what I want my controller to do:
public ActionResult Save(MyModel model)
{
if (ModelState.IsValid)
{
businessClass.Save(model);
return RedirectToAction("Index", "Home");
}
// This won't work because model has uninstantiated SelectLists
return View("MyView", model);
}
I don't want to send the model to my business logic if the ModelState is invalid, but it doesn't seem to make sense to put SelectList population code in my controller. Should I create a public method in my business logic solely for doing this kind of stuff on my view model(s)?
Personally I like to keep it simple:-
[HttpGet]
public Edit(int id) {
EditForm form = new EditForm();
// Populate from the db or whatever...
PopulateEditPageSelectLists(form);
return View(form);
}
[HttpPost]
public Edit(EditForm form) {
if (ModelState.IsValid) {
// Do stuff and redirect...
}
PopulateEditPageSelectLists(form);
return View(form);
}
public void PopulateEditPageSelectLists(form) {
// Get lookup data from the db or whatever.
}
If the logic to populate the select lists is all kinds crazy it might be worthwhile moving to a separate class or whatever it but as a first step this is the best place to start.
You dont say how much reusability would you like. But personally, i like things "clear" (dont invading controller) and reausable as possible, and that in MVC means - filters.
Look at this :
public class SupplyLanguagesAttribute : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
{
filterContext.Controller.ViewData["languagesList"] =
someService.LoadLanguagesAsDictionary();
base.OnActionExecuting(filterContext);
}
}
then you just use it with every action method where you "might" need languages :
[SupplyLanguages]
public ActionResult DoSomething()
{
...
}
And then in view, you can use the data directly for DropDownList from ViewData, or you can even "wrap" this too (and avoid "magic strings" in views), with custom reusable DropDown :
public static MvcHtmlString LanguageDropDown(this HtmlHelper html, string name, object selectValue, bool defaultOption = false)
{
var languages = html.ViewData["languagesList"] as IDictionary<string,string>;
if (languages == null || languages.Count() == 0)
throw new ArgumentNullException("LanguageDropDown cannot operate without list of languages loaded in ViewData. Use SupplyLanguages filter.");
var list = new SelectList(languages, "Key", "Value", selectValue);
return SelectExtensions.DropDownList(html, name, list);
}
My controllers populate the SelectLists on my Model if the ModelState is not valid.
Following Separation of Concerns, your business classes shouldn't know anything about the view model at all. If your view needs a list of employees your controller gets a list of employees from your business layer and creates the SelectList that your view needs.
Example
public ActionResult Save(MyModel model)
{
if (ModelState.IsValid)
{
businessClass.Save(model);
return RedirectToAction("Index", "Home");
}
model.PossibleEmployees
= _employeeRepository.All().Select(e =>
new SelectListItem{Text=e.Name,
Value=e.Id});
return View("MyView", model);
}
Update
If your select list population code is determining WHICH options to present I think you probably should move that to a service in your business layer. If reusability is the big concern, rouen's answer looks like it has the most possibility for reuse.
I use to fill lists even when the model is invalid. One other possible solution is to have an action returning the json information and build the select via ajax. SOmetimes I've also resorted to static properties / cached collections. I guess it's always depending on the particular case.
PS: You can use a local Model in each action, so I can leave initialization inside the Model constructor. (often I override a base model with [NonAction] utilities as well).
For example, I have an Employee list used widely in your application.
I've added some utility method in a base controller to build up SelectListItems and the likes. Since each and every model inherits from the base, I've got them almost everywhere in the app. Of course the Collection is filled via a dedicated business objec.
What I do is I have a static function in a class that returns a SelectList. The method accepts an Enum value which defines which SelectList to return. In the View the DropDownList or DropDownListFor functions call this function to get the SelectList.
The static function looks like this:
class HelperMethods
{
enum LookupType {Users, Companies, States};
public static SelectList CommonSelectList(LookupType type, int? filterValue = null)
//filterValue can be used if the results need to be filtered in some way
var db = new WhateverEntities();
switch (type)
{
case LookupType.Users:
var list = db.Users.OrderBy(u => u.LastName).ToList()
return new SelectList(list, "ID", "FullName")
break;
case LookupType.Companies
var list = db.Companies.OrderBy(u => u.Name).ToList()
return new SelectList(list, "ID", "Name")
break;
//and so on...
}
}
}
And the view contains this:
#Html.DropDownListFor(m => m.UserID, HelperMethods.CommonSelectList(LookupType.Users))
This way the Model and Controller does not need code to configure a SelectList to send over to the View. It makes it very easy to reuse a SelectList that has already been configured. Also, if a View needs to loop through a list of objects, then this same function can be used to get a list for that. This is the simplest and most convenient way I found of doing this.
I'm fairly new to ASP.NET MVC and am trying to work out the best way to do this. It's probably simple but I just want to do things correctly so I thought I'd ask.
Lets say I have a model that is this:
Task - Id, Description, AssignedStaffMember
StaffMember - Id, FirstName, LastName
and in my view I want to create a new task. I make a strongly typed Razor view, and can use EditorFor to create textboxes for Description but what about AssignedStaffMember?
I want a drop down list of all current staff and have the option of selecting one, then this gets submitted to an action method which is
NewTask(string description, StaffMember assignedStaffMember)
either that or I could have an int for staffId instead of the StaffMember object and look it up in the action method.
What is the best way to do this? I need to go to the database to get the list off staff, so here's what I thought:
Make a partial view for the listing of staff drop down, which will be used a few times and use #Html.Action("ListStaff", "Staff") to call it. The action method then has
public ActionResult ListStaff()
{
IEnumerable<StaffMember> model = _serviceLayer.GetAllStaff();
return PartialView(model);
}
However I'm not sure on how this will work with model binding, my understanding is that it has to have the correct name for the form to submit it, I'd need to pass the name to the partial view to put on the element I guess?
Instead of having it call a controller to get the staff, make a ViewModel that contains my Task and a IEnumerable possibleStaff collection. possibly send this information to a partial view.
a Html Helper ?
EditorFor could somehow be used?
which one (or is there more) would be best? and how would I do the model binding?
Here is one way to do this. Create a TaskDetailsViewModel
public class TaskDetailsViewModel
{
public TaskDetailsViewModel()
{
this.Task = new Task();
this.StaffMembers = new List<StaffMember>();
}
public Task Task { get; set; }
public IEnumerable<StaffMember> StaffMembers { get; set; }
}
In Controller
public ActionResult Edit(int id)
{
var task = taskRepository.GetTaskByID(id);
var taskDetailsViewModel = new TaskDetailsViewModel();
// Populate taskDetailsViewModel from task and staff
return View(taskDetailsViewModel);
}
[HttpPost]
public ActionResult Edit(TaskDetailsViewModel taskDetailsViewModel)
{
if (ModelState.IsValid)
{
taskRepository.Save(taskDetailsViewModel.Task);
}
else
{
// Show Error
}
return View(taskDetailsViewModel);
}
In View (bound strongly to TaskDetailsViewModel)
#Html.DropDownListFor(model => model.Task.AssignedStaffMember, new SelectList(Model.StaffMembers, "ID", "FirstName", Model.Task.AssignedStaffMember))
#Html.ValidationMessageFor(model => model.Task.AssignedStaffMember)
I've said this about a million times already, but I'm still new to ASP.Net MVC (and ASP.NET in general) so sorry if this is a bit obvious...
Ok, I have a controller which sends data to two views. One view just lists data from a database, while the other allows you to edit and add records respectively (they use the same view, with different arguments passed in).
The edit view is where my question lies. I have four drop downs in the form that are populated with data from my repository (coming from 3 different tables in my database). This all works 100% absolutely fine with the code below, no errors.
My question is what is the best way to populate these drop downs? Currently, I have my view code like so, passing data through ViewData in the controller:
Edit View:
public ViewResult EditJob(int jobId)
{
IList<JobCust> customerList = jobsRepository.JobCustomers.OrderBy(Customer => Customer.CompanyName).ToList();
ViewData["customers"] = new SelectList(customerList, "CompanyName", "CompanyName");
IList<JobVehicle> vehicleRegList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleReg).ToList();
ViewData["vehicleReg"] = new SelectList(vehicleRegList, "VehicleReg", "VehicleReg");
IList<JobVehicle> vehicleTypeList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleType).ToList();
ViewData["vehicleType"] = new SelectList(vehicleTypeList, "VehicleType", "VehicleType");
IList<JobDriver> driverList = jobsRepository.JobDrivers.OrderBy(Driver => Driver.LastName).ToList();
ViewData["LastName"] = new SelectList(driverList, "LastName", "LastName");
var job = jobsRepository.Jobs.First(x => x.JobID == jobId);
return View(job);
}
Add View:
public ViewResult AddJob()
{
IList<JobCust> customerList = jobsRepository.JobCustomers.OrderBy(Customer => Customer.CompanyName).ToList();
ViewData["customers"] = new SelectList(customerList, "CompanyName", "CompanyName");
IList<JobVehicle> vehicleRegList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleReg).ToList();
ViewData["vehicleReg"] = new SelectList(vehicleRegList, "VehicleReg", "VehicleReg");
IList<JobVehicle> vehicleTypeList = jobsRepository.JobVehicles.OrderBy(Vehicle => Vehicle.VehicleType).ToList();
ViewData["vehicleType"] = new SelectList(vehicleTypeList, "VehicleType", "VehicleType");
IList<JobDriver> driverList = jobsRepository.JobDrivers.OrderBy(Driver => Driver.LastName).ToList();
ViewData["LastName"] = new SelectList(driverList, "LastName", "LastName");
return View("EditJob", new Job());
}
I have that big block of duplicate code there that is bugging me. I know there will be a better solution, but I just don't know what it is. I now want to use this same set of drop downs on another view within this controller, so I'll be duplicating this code for a third time using my current method.
Any ideas? Maybe it's something obvious I'm totally overlooking... Thanks in advance for the help.
You can create a PartialView that accepts as it's model a view model that is designed to contain the data for those four drop down lists.
Then have some service method that returns that view, this may then be called from any controller and then either passed straight to your views, or added as a child object to another view model that then passes it down to your PartialView.
// a view model class to contain the SelectLists that will be rendered as drop downs
public class DropDownViewModel
{
public SelectList Customers{get;set;}
public SelectList VehicleReg{get;set;}
public SelectList VehicleType{get;set;}
public SelectList LastName{get;set;}
}
// another view model that contains a child DropDownViewModel
// object and also the Job object. This is what the Add and Edit Views
// will be responsible for rendering
public class JobsViewModel
{
public DropDownViewModel DropDownViewModel {get;set;}
public Job Job {get;set;}
}
// a service level class that will be responsible for constructing the
// DropDownViewModel object and populating with the required data
public class DropDownService
{
public DropDownViewModel GetDropDownViewModel()
{
// data access code
}
}
Then in your controllers
public ViewResult AddJob()
{
// get the view model
DropDownService service = new DropDownService();
DropDownViewModel dropDownViewModel = service.GetDropDownViewModel();
// create the wrapping JobViewModel that will contain the DropDownViewModel
JobViewModel viewModel= new JobViewModel();
viewModel.Job = new Job();
viewModel.DropDownViewModel = dropDownViewModel;
return View(viewModel);
}
public ViewResult EditJob(int jobId)
{
// get the view model
DropDownService service = new DropDownService();
DropDownViewModel dropDownViewModel = service.GetDropDownViewModel();
// create the wrapping JobViewModel that will contain the DropDownViewModel
JobViewModel viewModel= new JobViewModel();
viewModel.Job = jobsRepository.Jobs.First(x => x.JobID == jobId);
viewModel.DropDownViewModel = dropDownViewModel;
return View(viewModel);
}
In your mark up, you will need to ask your Add/Edit views to pass the model data down to the PartialView which you can do like this:
<% Html.RenderPartial("MyPartialView", Model.DropDownViewModel); "%>
What you're looking for is a ViewModel as described in the Nerd Dinner Tutorial. It's a custom, strongly typed representation of all the data that you need in your view including data to populate dropdowns. Swaffs answer is good and pretty complete. I just wanted to give you another reference. Good luck!