ASP.Net MVC 3 ViewModel with Drop Down Lists - asp.net-mvc-3

I am developing an ASP.Net MVC 3 web application. The app currently is connected to a database that has several tables, two of which are Category(catId, Name) and Site(siteID, Name).
I wish to create a view that has two drop down lists, one for each of the tables mentioned, so that the user can select from and then run a report. To do this I have created a viewModel to represent the two drop down lists
public class ReportSiteCategorySearchViewModel
{
public SelectList categoryList { get; set; }
public SelectList siteList { get; set; }
}
Then in my controller that returns the viewModel I have the following
public ActionResult getEquipmentByCategoryAndSite()
{
ReportSiteCategorySearchViewModel viewModel = new ReportSiteCategorySearchViewModel
{
categoryList = new SelectList(categoryService.GetAllCategories().ToList(), "categoryID", "categoryTitle"),
siteList = new SelectList(siteService.GetAllSites().ToList(), "siteID", "title")
};
return View(viewModel);
}
I then pass to a view which takes this viewModel and writes out the values to the drop downs
<div>
<label for="ddlSite">Sites</label>
#Html.DropDownList("ddlSite", Model.siteList, "All Sites")
<label for="ddlCatgeory">Categories</label>
#Html.DropDownList("ddlCatgeory", Model.categoryList, "All Categories")
</div>
This works, however, I am not sure this is the best way to do it. I am just wondering is my method correct, is there a better way to do this? Ie, what if I needed 5/6 more drop down lists from other tables, should I just add to the current viewModel etc?
Any feedback would be much appreciated.
Thank You.

You can create a viewModel of type List<SelectList> In your controller, add each table (as a SelectList as you're doing) to this model. Then pass the view the model, which is a list of SelectLists.
Then you can iterate through each value in your view:
<div>
#foreach(SelectList SL in Model)
{
<label for="ddl"+SL>SL.Title</label>
#Html.DropDownList("ddl"+SL.Title, sl.list, sl.items")
}
You may need to modify your list of SelectList to include the 'Title' or 'items' field. By doing it this way you can keep adding elements to the List, and you won't need to update the view.

Related

multiple models to partial view

I am teaching myself asp .net mvc3.
I have a partial view which uses various models and lookup type db. I want to keep it strongly typed and use it in multiple places but I am not sure how to implement it.
The example below should explain it better. This question might get a bit long and I appreciate your patience.
The partial view basically gives a small description of the property. A snippet of the ‘_PropertyExcerptPartial’ is below:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>#Html.DisplayFor(model => model.Property.NoOfBedrooms) bedroom #Html.DisplayFor(model => model.FurnitureType.FurnitureTypeDescription) flat to Rent </h3>
…
</div>
I want to keep this partial view strongly typed and use it in multiple places. The model that it is strongly typed to is as follows:
public class PropertyExcerptViewModel
{
public Property Property { get; set; }
public FurnitureType FurnitureType { get; set; }
}
The two 2 database that this model looks up is as follows:
public class Property
{
public int PropertyId {get; set; }
...
public int NoOfBedrooms {get; set;}
public int FurnishedType { get; set; }
...
}
public class FurnishedType
{
public int FurnishedTypeId { get; set; }
public string FurnishedTypeDescription { get; set; }
}
The furnished type database is basically just a lookup table with the following data:
1 - Furnished
2 - Not Furnished
3 - Part Furnished
4 - Negotiable
I have many such lookups in that I only store an int value in the property database which can be used to look up the description. These databases are not linked to property database and the value of furniture type is read via a function GetFurnitureType(id). I pass stored int value of Property.FurnitureType as the id.
However, I encounter a problem when I try to use this partial view as I am not sure how to pass these multiple models from a view to partial view.
Say I am trying to create an ‘added property’ page. This page basically list the properties added by the logged in user. To facilitate this, I have created another function called GetAddedProperties(userId) that return the properties added by a particular user. In the ‘added property’ view, I can call a foreach function to loop through all the properties returned by GetAddedProperties and display the _PropertyExcerptPartial. Something like this:
<div>
#foreach (var item in //Not sure what to pass here)
{
#Html.Partial("_PropertyExcerptPartial",item)
}
</div>
However, I can’t use the partial view to display information as it will display the int value of furniture type stored in the property database and I am not sure how to get the corresponding FurnitureTypeDescription and pass it to the partial view from the ‘added property’ page.
Any help would be appreciated. Thanks a lot!
You should start by designing a real view model, not some class that you suffix its name with ViewModel and stuff your domain models inside. That's not a view model.
So think of what information you need to work with in your view and design your real view model:
public class PropertyExcerptViewModel
{
public int NoOfBedrooms { get; set; }
public string FurnishedTypeDescription { get; set; }
}
and then adapt your partial to work with this view model:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>
#Html.DisplayFor(model => model.NoOfBedrooms)
bedroom
#Html.DisplayFor(model => model.FurnitureTypeDescription)
flat to Rent
</h3>
...
</div>
OK, now that we have a real view model let's see how we could populate it. So basically the main view could be strongly typed to a collection of those view models:
#model IEnumerable<Test.ViewModels.PropertyExcerptViewModel>
<div>
#foreach (var item in Model)
{
#Html.Partial("_PropertyExcerptPartial", item)
}
</div>
and the last bit of course is the main controller that will do all the db querying and building of the view model that will be passed to the main view:
[Authorize]
public ActionResult SomeAction()
{
// get the currently logged in username
string user = User.Identity.Name;
// query the database in order to fetch the corresponding domain entities
IEnumerable<Property> properties = GetAddedProperties(user);
// Now let's build the view model:
IEnumerable<PropertyExcerptViewModel> vm = properties.Select(x => new PropertyExcerptViewModel
{
NoOfBedrooms = x.NoOfBedrooms,
FurnishedTypeDescription = GetFurnitureType(x.FurnishedType).FurnishedTypeDescription
});
// and finally pass the view model to the view
return View(vm);
}
Be careful with the lazy nature of EF if that is the ORM that you are using in order not to fall into the SELECT N + 1 trap.

Best way to bind the constant values into view (MVC3)

I have a constants values such as "Required","Optional", and "Hidden". I want this to bind in the dropdownlist. So far on what I've done is the below code, this is coded in the view. What is the best way to bind the constant values to the dropdownlist? I want to implement this in the controller and call it in the view.
#{
var dropdownList = new List<KeyValuePair<int, string>> { new KeyValuePair<int, string>(0, "Required"), new KeyValuePair<int, string>(1, "Optional"), new KeyValuePair<int, string>(2, "Hidden") };
var selectList = new SelectList(dropdownList, "key", "value", 0);
}
Bind the selectList in the Dropdownlist
#Html.DropDownListFor(model => model.EM_ReqTitle, selectList)
Judging by the property EM_RegTitle I'm guessing that the model you're using is auto-generated from a database in some way. Maybe Entity Framework? If this is the case, then you should be able to create a partial class in the same namespace as your ORM/Entity Framework entities and add extra properties. Something like:
public partial class MyModel
{
public SelectList MyConstantValues { get; set; }
}
You can then pass your SelectList with the rest of the model.
There are usually hangups from using ORM/EF entities through every layer in your MVC app and although it looks easy in code examples online, I would recommend creating your own View Model classes and using something like AutoMapper to fill these views. This way you're only passing the data that the views need and you avoid passing the DB row, which could contain other sensitive information that you do not want the user to view or change.
You can also move the logic to generate your static value Select Lists into your domain model, or into a service class to help keep reduce the amount of code and clutter in the controllers.
Hope this helps you in some way!
Example...
Your View Model (put this in your "Model" dir):
public class MyViewModel
{
public SelectList RegTitleSelectList { get; set; }
public int RegTitle { get; set; }
}
Your Controller (goes in the "Controllers" dir):
public class SimpleController : Controller
{
MyViewModel model = new MyViewModel();
model.RegTitle = myEfModelLoadedFromTheDb.EM_RegTitle;
model.RegTitleSelectList = // Code goes here to populate the select list.
return View(model);
}
Now right click the SimpleController class name in your editor and select "Add View...".
Create a new view, tick strongly typed and select your MyViewModel class as the model class.
Now edit the view and do something similar to what you were doing earlier in your code. You'll notice there should now be a #model line at the top of your view. This indicates that your view is a strongly typed view and uses the MyViewModel model.
If you get stuck, there are plenty of examples online to getting to basics with MVC and Strongly Typed Views.
You would prefer view model and populate it with data in controller.
class MyViewModel
{
public string ReqTitle { get; set; }
public SelectList SelectListItems { get; set; }
}
Then you can use:
#Html.DropDownListFor(model => model.EM_ReqTitle, model.SelectListItems)

asp.net mvc3 updated (refresh) the viewmodel in view

I send a BOOKVIEWMODEL with fields and a simple IEnumerable in view I get the this list IEnumerable in the view by a method with JSON AJAX in view and I fill my table Ristourne(View) with JQUERY it works very well but I not know how I fill (BIND or refresh) the list IEnumerable of my BOOKVIEWMODEL in the VIEW to recovered it in the Controller:
[HttpPost]
public ActionResult Create(BookViewModel _bookViewModel)
{
if (ModelState.IsValid)
{
_bookViewModel.Ristourne
return RedirectToAction("Index");
}
return View(_bookViewModel);
my bookviewmodel
public class BookViewModel
{
public String book { get; set; }
public String price { get; set; }
public IEnumerable<Ristourne> Ristourne { get; set; }
}
For the model binding to work, you need to "mimic" the convention MVC uses when generating the form fields.
I don't know the contents of the Ristourne class, but let's say it had 1 field called Foo.
In that case, when you render out the elements (from the JSON AJAX callback), make them look like this:
<input type="text" name="Model.Ristourne[0].Foo" id="Model.Ristourne[0].Foo"/>
<input type="text" name="Model.Ristourne[1].Foo" id="Model.Ristourne[1].Foo"/>
<input type="text" name="Model.Ristourne[2].Foo" id="Model.Ristourne[2].Foo"/>
And so on. Easiest thing to do is in your AJAX callback, just use a basic for loop to create the elements indexer.
Altenatively, a cheat/easy way around this problem would be to make your AJAX action return a PartialViewResult:
public PartialViewResult Book()
{
var ristournes = _repo.Get();
var model = new BooksViewModel { Ristourne = ristournes };
return PartialView(model);
}
Then the partial view:
#Html.EditorFor(model => mode.Ristourne)
Then MVC will create the form fields correctly.
I always prefer this option over dynamically generated form fields. If you want to go down this path often, you should consider something like Knockout.js and/or Upshot.

A `ViewModel` for each page (`Create.cshtml` and `Edit.cshtml`)?

Questions
There are actually two related questions:
Should I create a ViewModel for each page?
If you do not have problems in creating a single ViewModel class for the two pages (Create.cshtml and Edit.cshtml) how can I validate the ViewModel in different ways (depending on the page that is being used)
Source
ViewModel
public class ProjectViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
}
Edit.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = Model.Name;
}
#Html.EditorForModel()
Create.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = "New Project";
}
#Html.EditorForModel()
ProjectValidator.cs
public class ProjectValidator : AbstractValidator<ProjectViewModel>
{
private readonly IProjectService _projectService;
public ProjectValidator(IProjectService projectService)
{
_projectService = projectService;
RuleFor(p => p.Name)
.NotEmpty().WithMessage("required field")
/*The validation should be made only if the page is Create.cshtml. That is, if you are creating a new project.*/
.When(p => p.??) //Problem Here!!
.Must(n => !_projectService.Exist(n)).WithMessage("name already exists");
RuleFor(p => p.Url)
.NotEmpty().WithMessage("required field");
}
}
Note that if the user is editing an existing project, validation of the property name should not be done again.
ProjectController.cs > Edit method
[HttpPost]
public ActionResult Edit(Guid projectID, ProjectViewModel model)
{
var project = _projectService.Repository.Get(projectID);
if (ModelState.IsValid && TryUpdateModel(project))
{
_projectService.Repository.Attach(project);
if (_projectImageWrap.Create(project) && _projectService.Repository.Save() > 0)
return AjaxRedirect("Index");
}
return View(model);
}
Notes
If I create a ViewModel for each page, there is a duplication of code since pages have the same properties.
Add a property on the ViewModel indicating what page it is being displayed does not solve my problem as to instantiate the ViewModel, I use AutoMapper.
To validate the data, I use FluentValidator.
Thank you all for your help!
My understanding is that there isn't a 1:1 correlation between ViewModels and Views. Oftentimes you will have a View that will not require a ViewModel to go alongside with it.
You will want to create a ViewModel if and only if you need a Model absolutely paralleled and tailored to a specific View. This will not be the case 100% of the time.
When the functionality / use case /validation is different between the pages I use different models. If its the exact same besides the presence of an ID or something similar I use the same model, and its also possible to just use the same view if the differences are pretty minor.
Since your validation is different, if I were doing it I would create two different models so that I could use the out of the box DataAnnotations, with your validation though it may not be required. You could also on the edit model have a readonly property for name since its not editable any longer.
For me the same object must have the same validation on every time, in main to ensure the consistence of the object, independently if it was created or edited.
i think that you should create only one validation, and edit your "exists" method to pass to verify if it is a new object or the current object in repository.
Personally, I don't have a problem with 2 view models, especially if (as Paul Tyng suggested) you use a base class for the fields that are common to edit and create scenarios.
However, if you really only want a single view model then you would either need to:
add a flag to the view model and use the When() method in your validator. Note though that this will not generate the appropriate client-side only validation
define a second validator and invoke the appropriate one from the controller (i.e. instead of the "automatic" validation)
Provide another view Edit.cshtml which will allow the user to edit the data for a selected item.
Create another view Query.cshtml which based on the ItemName will allow the users to query the Inventory table.
Perform the calculation for the total profit (numbersold times (saleprice-purchasecost). Display the total profit.
(BONUS) Create another view Sell.cshtml that will indicate the sale of an item. Adding one to NumberSold and subtract one from NumberInventory for the selected record.

How to post the selected value of a selectlist to the controller using a view model?

This question has been asked in various forms but none of the answers seem to fit my situation. I am simply trying to retrieve the selected value of a dropdown list in my controller.
Here is my code:
ViewModel.cs
public class ViewModel
{
public ViewModel() {}
public ViewModel(Contact contact, IEnumerable<State> states)
{
this.Contact = contact;
this.States = new SelectList(states, "Id", "Name", contact.StateId);
}
public Contact Contact {get;set;}
public SelectList States {get;set;}
}
Controller.cs
[HttpPost]
public ActionResult Edit(ViewModel viewModel)
{
_contactService.UpdateContact(viewModel.Contact);
return RedirectToAction("Item", new {id = viewModel.Contact.Id});
}
View.cshtml
<button type="submit" onclick="javascript:document.update.submit()"><span>Update</span></button>//aesthic usage.
#{using (Html.BeginForm("Edit", "Controller", FormMethod.Post, new { name = "update" }))
{
#Html.HiddenFor(m => m.Contact.Id)
#Html.LabelFor(m => m.Contact.Name, "Name:")
#Html.TextBoxFor(m => m.Contact.Name)
<label for="state">State:</label>
#Html.DropDownList("state", Model.States)
}
}
Everything works as expected except that no values from the dropdownlist are passed in my posted viewModel to the controller. The edit page and all fields load correctly. The dropdowns bind correctly and have their selected values displayed properly. However, when I post I only get a "Contact" object passed to the controller. The "States" SelectList object is null.
I tried mapping a "StateId" property in my viewModel contstructor but that did not work either. What am I doing wrong?
Thanks.
I hate answering my own questions but based on the multiple issues I had coupled with the myriad of answers available I thought I would summarize my findings.
First off thanks for Filip, his answer did not exactly fix my problem but it led me in the right direction. +1
If you are creating a form for viewing and editing that requires a drop down list, here are some suggestions and gotchas. I will start with a list of parameters that I needed to fit my needs.
Strongly typed views in my view are preferable. Minimize magic strings.
View models should contain as little logic and extraneous elements as possible. There only job should be to facilitate a collection of data objects.
The drop down list should display the selected value.
The selected value should map easily back to the view model on form submit.
This may sound like an obvious and easily obtainable list but for someone new to MVC, it is not. I will revise my code from above with comments. Here is what I did.
ViewModel.cs
public class ViewModel
{
public ViewModel() {}
public ViewModel(Contact contact, IList<State> states)
{
//no need to pass in a SelectList or IEnumerable, just what your service or repository spits out
this.Contact = contact;
this.States = states;
}
public Contact Contact {get;set;}
public IList<State> States {get;set;}
}
Controller.cs //nothing really different than above
public ActionResult Edit(int id)
{
var contact = _contactService.GetContactById(id);
var states = _stateService.GetAllStates();
return View(new ViewModel(contact, states));
}
public ActionResult Edit(ViewModel viewModel)
{
_contactService.UpdateContact(viewModel.Contact);
return RedirectToAction("Edit", new {id = viewModel.Contact.Id });
}
View//thanks goes to Artirto at this post
#{using (Html.BeginForm("Edit", "Controller", FormMethod.Post))
{
#Html.HiddenFor(m => m.Contact.Id)
#Html.DropDownListFor(m => m.Contact.StateId, new SelectList(Model.States, "Id", "Name", #Model.Contact.StateId))
<input type="submit" value="Save" />
}
}
Try using #Html.DropDownListFor instead, if "state" is a part of your model you can use it like this:
#Html.DropDownListFor(m => m.Contact.StateId, Model.States, "-- Please select a State --") where m.State holds the selected value.
Also not to confuse the IEnumerable with the Model, I would put that in the ViewBag / ViewData.
It would look something like this instead:
#Html.DropDownListFor(m => m.Contact.StateId, (IEnumerable<string>)ViewBag.States, "-- Please select a State --")
And in your action that returns this view you will need to initialize the State enumerable to the ViewBag.States property.

Resources