ASP.NET MVC View User Control - how to set IDs? - asp.net

I need a dropdown list on my page that will allow a user to select their state. Since this is probably a control that will be used elsewhere, I thought it would be a good idea to create an MVC View User Control that could be reused.
I was thinking the control would look something like this:
<select name="" id="">
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
</select>
And the code in my view would be something like:
<%= Html.RenderPartial("StateDropdownControl") %>
My question is, what's the best way to set the name and id on the control? I'd want to make sure I could have multiple instances of this control on one page, if needed. Also, I'd want to be able to send in the state that should be selected by default.
Would I do it with ViewData somehow?

Corey is on to the right solution. I think declaring specific Model objects for your view makes the views VERY simple and as a side bonus makes them dirt easy to test.
So instead of just passing the ID as the object, you'd probably want to create your own Model object to pass in.
It could look something like this:
public class StateDropDownPresentationModel
{
public string DropDownID { get; set; }
public string SelectedState { get; set; }
}
Obviously, keep adding whatever you need to this model to make your view correct.
Then, you could call it like this:
<%= Html.RenderPartial("/someDirectory/SomeControl.ascx", new StateDropDownPresentationModel { DropDownID = "MyID", SelectedState = "IL" } %>
Then just make sure you put in checks for things like ID being null/blank (that should probably throw an error) and SelectedState being null/blank.

Well you can pass object data to the RenderPartial method in conjunction to the User Control to render, so you could easily do the following:
<%= Html.RenderPartial("/someDirectory/SomeControl.ascx", "MyID") %>
and in the UserControl do the following:
<select name="<%=ViewData.Model%>" id="<%=ViewData.Model%>">
....
Just to be sure, a better way to handle it is to make a simple DTO (data transfer object) to hold that information so you can pass more information to your user control, that you will inevitably need.
Example:
class ComboData
{
string ID {get;set;}
string CssClass {get;set;}
//Other stuff here
}
<%
var comboData = new ComboData {ID = "myID", CssClass = "comboStyle" }
%>
<%= Html.RenderPartial("/someDirectory/SomeControl.ascx", comboData) %>
<select name="<%=ViewData.Model.ID%>" id="<%=ViewData.Model.ID%>" class="<%=ViewData.Model.CssClass%>">
....
Make sure you set the Code behind for the user control to be a generic of type ComboData for this example.

Take a look at the Html.DropDownList helper method. It has a number of overloads that allow you to pass the list data and set the selected item. the simplest version just sets the name of the select.
<%= Html.DropDownList("SelectStates"); %>
If there is a value in the ViewData["SelectStates"] that is of type MultiSelectList then the list will be automatically populated.

Related

Is using Viewbag for remembering past form inputs a bad idea?

I have a small asp.net core mvc application that basically consists of a form that a user can input some constraints into, and then get a filtered list of data depending on those constraints.
The controller action for filtering data basically looks like this:
[HttpPost]
public async Task<IActionResult> Query(QueryModel query)
{
var customers = await _context.Customers.AsQueryable().FilterCustomerList(query);
return View("Index", customers);
}
Now, my issue is that I would like the inputs in the fields to persist after entering them and being redirected to the view again. Right now they are currently just reset.
One way of doing this that I found was using viewBag. An example for a single query attribute is this:
public async Task<IActionResult> Query(QueryModel query)
{
var customers = await _context.Customers.AsQueryable().FilterCustomerList(query);
ViewBag.Name = query.Name;
return View("Index", customers);
}
and then the inpuit html elelment would look like:
<div class="col-md-4">
<input name="Name" type="text" placeholder="First name" value="#ViewBag.Name"class="form-control">
</div>
And this makes sure that if something has been entered into a field, it will now be entered into the field when after the query has been submitted.
But when I read up on ViewBag, I understand that a lot of .net developers have an aversion to it. It's not safe, the compiler can't catch errors in it easily etc.
Also, If I were to add all the input fields in my form to the viewbag, I would need a lot of lines of ViewBag.Attribute = query.SomeAttribute (20-30). Which seems like a code-smell too.
Is there any nicer way to do what I am trying to here?
You haven't included your QueryModel class and that class could be a key point to a cleaner approach.
You see, usually the user data, POSTed to your action is bound to the model, from there it's rendered on the form and is POSTed again. The model binding is where an input of a specific name is bound to a model member of the same name.
Thus, there's no need for viewbags.
More formally:
The Model
public class QueryModel
{
[your-validators-in-attributes, e.g. Required or MaxLength
there can be multiple validators]
public string Name { get; set; }
}
The controller:
[HttpPost]
async Task<IActionResult> Query(QueryModel query)
{
// query.Name is there, bound from the view
}
The View:
#model .....QueryModel
<div>
#Html.TextBoxFor( m => m.Name, new { placeholder = "a placeholder" } )
</div>
The html helper does two things
renders an input of the given name (Name in this case)
sets its value depending on the actual value from the model
In newer ASP.NETs you can achieve similar result by using tag helpers, where instead of Html.TextBoxFor(...) you write
<input asp-for="Name" />
These two approaches, using html helpers or using tag helpers are equivalent. In both cases there's no need for view bags.

Accessing models from view in MVC 2 Timesheet application?

I am trying to create a timesheet application in MVC 2, but I feel like I am still struggling to grasp the model/view relationships and all that.
The problem I have is, I want to let the user report a new time segment in a create view. But I want to have dropdownlists populated with Projects, Tasks, and Consultants from the model.
Basically the database structure looks like this:
(table) TimeSegments
TimeSegmentID
Hours
Date
ConsultantID (FK)
TaskID (FK)
ProjectID (FK)
(table) Projects
ProjectID
ProjectName
(table) Tasks
TaskID
TaskName
(table) Consultants
ConsultantID
ConsultantName
This design may be extended in future, right now I want to get basic functionality working before I complicate it further.
Now, I am passing the entire model to the create view (actually a viewmodel based on it, just to simplify some coding, but it might as well have been the entire model).
The problem is, normally when I have done similar things with a create view, I would have created a new object in the controller and passed that to the view. In this case it would have been the TimeSegment object, since it is a new time segment that should be created in the database. Then I could just submit it and update the database in the controller. However, if I only pass a new TimeSegment object to the view, I can't populate the dropdownlists with Projects, Tasks and Consultants.
And oppositely, if I only pass the entire model, how would I bind a textbox to a new TimeSegment to be updated in the database?
I feel like I need to send both a new TimeSegment object and the entire model to do this, but then I have no idea how I would access it (there's only that one "Model" to access from the view). Also, back in the controller after a submit, how would the controller know what to update?
I'm sure I'm just thoroughly confused still by the MVC way of thinking, but I would really appreciate it if someone could clarify this for me and tell me (as pedagogically as possible) what to do to solve this.
Okay, I will give it a shot.
MVC is not hard, but you do have to alter your way of thinking a bit. In MVC you have the Models (your data layer[s]), the Views and the Controllers.
Before we continue, I make the assumptions with my examples below that you are using LINQ to SQL for you data access layer (Model), and I have labeled it as dc.
The Controllers fetch and format the data out of the Models and hand it off to the Views to display. So lets start with your first view which would be the view to create a TimeSegment.
[HttpGet]
public ActionResult CreateTimeSegment() {
return View(new TimeSegmentView {
Consultants = dc.Consultants.ToList(),
Projects = dc.Projects.ToList(),
Tasks = dc.Tasks.ToList()
});
}
This action will create a TimeSegmentView object and pass that to the View as its Model. Keep in mind that this action is decorated with [HttpGet]. TimeSegmentView` is a container class for the objects you need to pass to the view to create your UI and it looks like this:
public class TimeSegmentView {
public IList<Consultant> Consultants { get; set; }
public IList<Project> Projects { get; set; }
public IList<Task> Tasks { get; set; }
public TimeSegment TimeSegment { get; set; }
}
NOTE: I'm not using the TimeSegment property yet, it's further down...
In the view make sure you have it inherit from TimeSegmentView. Assuming that you're following the default MVC project structure and with me taking the liberty to add a Views folder into the Models folder your full reference would look like this:
<%# Page Inherits="System.Web.Mvc.ViewPage<PROJECTNAME.Models.Views.TimeSegmentView>" %>
Now you've typed the view to that object and you can now interact with its properties. So, you can build a form such as:
<form action="/" method="post">
<p>
<label>Hours</label>
<input name="TimeSegment.Hours" />
</p>
<p>
<label>Date</label>
<input name="TimeSegment.Date" />
</p>
<p>
<label>Consultant</label>
<select name="TimeSegment.ConsultantID">
<% foreach (Consultant C in Model.Consultants) { %>
<option value="<%=C.ConsultantID%>"><%=C.ConsultantName%></option>
<% }; %>
</select>
</p>
<p>
<label>Project</label>
<select name="TimeSegment.ProjectID">
<% foreach (Project P in Model.Projects) { %>
<option value="<%=P.ProjectID%>"><%=P.ProjectName%></option>
<% }; %>
</select>
</p>
<p>
<label>Task</label>
<select name="TimeSegment.TaskID">
<% foreach (Task T in Model.Tasks) { %>
<option value="<%=T.TaskID%>"><%=T.TaskName%></option>
<% }; %>
</select>
</p>
</form>
As you can see it created 3 select fields and just performed loops in each of them to build up their values based off of the model.
Now, taking a submission of this form, we'll need to get the data and add it to our database with:
[HttpPost]
public RedirectToRouteResult CreateTimeSegment(
[Bind(Prefix = "TimeSegment", Include = "Hours,Date,ConsultantID,ProjectID,TaskID")] TimeSegment TimeSegment) {
dc.TimeSegments.InsertOnSubmit(TimeSegment);
dc.SubmitChanges();
return RedirectToAction("EditTimeSegment", new {
TimeSegmentID = TimeSegment.TimeSegmentID
});
}
Okay, first notice that I've named the action the same, but this one has an [HttpPost] decoration. I'm telling the action that I'm sending it a TimeSegment object and that I want it to bind the properties in the Include clause (this is mostly for security and validation). I then take the TimeSegment object I've passed in, add it to the data context, submit the changes and redirect. In this case I'm redirecting to another action to edit the object I just created passing in the new TimeSegmentID. You can redirect to what ever, this just felt appropriate to me...
[HttpGet]
public ActionResult EditTimeSegment(
int TimeSegmentID) {
return View(new TimeSegmentView {
Consultants = dc.Consultants.ToList(),
Projects = dc.Projects.ToList(),
Tasks = dc.Tasks.ToList(),
TimeSegment = dc.TimeSegments.Single(t => t.TimeSegmentID == TimeSegmentID)
});
}
In the edit action your doing the same thing as in the create action by building a new TimeSegmentView object and passing it to the view. The key difference here is that you're now populating the TimeSegment property. Your form would look something like this (shortened from above):
<form action="/<%=Model.TimeSegment.TimeSegmentID%>" method="post">
<p>
<label>Hours</label>
<input name="TimeSegment.Hours" value="<%=Model.TimeSegment.Hours%>" />
</p>
</form>
And your receiving action on the controller would look like this:
[HttpPost]
public RedirectToRouteResult EditTimeSegment(
int TimeSegmentID) {
TimeSegment TS = dc.TimeSegments.Single(t => t.TimeSegmentID == TimeSegmentID);
TryUpdateModel<TimeSegment>(TS, "TimeSegment", new string[5] {
"Hours", "Date", "ConsultantID", "ProjectID", "TaskID"
});
dc.SubmitChanges();
return RedirectToAction("EditTimeSegment", new {
TimeSegmentID = TimeSegment.TimeSegmentID
});
}
Lastly, if you want to display a list of TimeSegments you can do something like this:
[HttpGet]
public ActionResult ListTimeSegments() {
return View(new TimeSegmentsView {
TimeSegments = dc.TimeSegments.ToList()
});
}
And TimeSegmentsView looks like this:
public class TimeSegmentsView {
public IList<TimeSegment> TimeSegments { get; set; }
}
And in the View you'd want to do this:
<table>
<% foreach (TimeSegment TS in Model.TimeSegments) { %>
<tr>
<td><%=TS.Hours%></td>
<td><%=TS.Date%></td>
<td><%=TS.Project.ProjectName%></td>
<td><%=TS.Consultant.ConsultantName%></td>
<td><%=TS.Task.TaskName%></td>
</tr>
<% }; %>
</table>
I hope this is enough to give you a start. It's by no means complete, but its 5 AM and I haven't slept yet, so this will have to do for now (from me). Feel free to name your actions what you want, you don't have to stick to my naming conventions.
I would suggest however that you change the naming of the properties of your tables. For example when your writing the expressions like in the table above you'll have to do TS.Project.ProjectName and that's redundant. You're already accessing the Project property of TS through their relationship so you know you're only going to work with a Project. This then makes ProjectName a pointless blob of text re-describing the object your working with. Instead just use Name, and turn your expression to TS.Project.Name.
Anyway, just a suggestion, do what you like better. I'm passing out, so good night and happy Thanksgiving!
UPDATE
The process with collections is essentially the same as far as the controller side is conserned. It's the client side and the JavaScript that's more difficult to get going, so I'll assume that you have something established on that end.
So, here's how the controller would work. You pass in an array of TimeSegment and the model binder is smart enough to figure it out through the Prefix of your form elements.
<form action="/<%=Model.TimeSegment.TimeSegmentID%>" method="post">
<p>
<label>Hours</label>
<input name="TimeSegment[0].Hours" />
<!-- Notice the array in the prefix -->
</p>
<p>
<label>Hours</label>
<input name="TimeSegment[1].Hours" />
<!-- Notice the array in the prefix -->
</p>
</form>
And the controller:
[HttpPost]
public RedirectToRouteResult CreateTimeSegments(
[Bind(Prefix = "TimeSegment", Include = "Hours,Date,ConsultantID,ProjectID,TaskID")] TimeSegment[] TimeSegments) {
dc.TimeSegments.InsertAllOnSubmit(TimeSegments);
dc.SubmitChanges();
return RedirectToAction("ListTimeSegments");
}
And that's it. Of course you'll want to validate or do other stuff before sending to the database, but that's roughly all there is to it.
UPDATE 2
I believe you can do an IList<TimeSegment> instead of TimeSegment[] without issues, but as far as if it's better, that's up for debate. The way I look at it the browser still sends a virtual array to the server so having the action receive an array feels natural, but its up to you what you want to use.
So, a generic list action would look like this:
[HttpPost]
public RedirectToRouteResult CreateTimeSegments(
[Bind(Prefix = "TimeSegment", Include = "Hours,Date,ConsultantID,ProjectID,TaskID")] IList<TimeSegment> TimeSegments) {
dc.TimeSegments.InsertAllOnSubmit(TimeSegments);
dc.SubmitChanges();
return RedirectToAction("ListTimeSegments");
}
Keep in mind that I haven't used this (meaning the IList) before so I can't guarantee it will work, just speculating...
UPDATE 3
About what you want to do with the Consultant, it sound a lot like what I do with Cookies. I have a BaseView class which is the type used by the Site.Master and then all other views extend from it. In the BaseView I have a Cookie property which is always populated by each controller action. I then use that property to get the id of the currently authorized user.
So, in code it looks like this (using examples from one of my apps):
public class BaseView {
// Don't confuse with an HttpCookie, this is an object in my database...
public Cookie Cookie { get; set;}
}
public class EmployeeView : BaseView {
public Employee Employee { get; set; }
}
And with this, say I want to add a note to an employee, my form would look like this where I pass in a hidden field which is where your ConsultantID comes into play.
<form>
<input type="hidden" name="Note.AuthorId" value="<%=Model.Cookie.EmployeeId%>" />
<!-- other fields -->
</form>
Hope this helps.

How to get data of EditorFor with nested viewmodels

Here is my situation -
I have two nested view models:
<%=Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel)%><br />
Which sit within their parent (StructureViewModel), I can populate the nested ViewModels easily and pass it through to the main View:
Within the Controller - Example
var moveDepartment = new StructureViewModel();
moveDepartment.DisplayEntitiesWithRadioboxesViewModel = fullDepartmentList.Select(x => new DisplayEntityViewModel
{
Id = x.Id,
Path = x.Path,
PathLevel = x.PathLevel,
Description = x.Description,
});
return View(moveDepartment);
EditorTemplete - Example
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Site.Areas.Administration.ViewModel.DisplayEntityViewModel>>" %>
<table class="aligncenter"><%
if (Model != null)
{
foreach (var entity in Model)
{%>
<tr class="tRow">
<td style="text-align:left; text-indent:<%=Html.Encode(entity.PathLevel)%>em">
<%=Html.Encode(entity.Description)%>
<%=Html.RadioButton("radiobutton",entity.Id)%>
</td>
</tr><%
}
}%>
</table>
namespace Site.Areas.Administration.ViewModel
{
public class DisplayEntityViewModel
{
public int Id { get; set; }
public string Path { get; set; }
public string PathLevel { get; set; }
public string Description { get; set; }
}
}
However when I try to pull back this information the nested ViewModels are null:
[HttpPost]
public ActionResult Move(StructureViewModel StructureViewModel)
When I hover over StructureViewModel it only contains data set at the parent ViewModel. For example: a hidden value can been seen but DisplayEntitiesWithRadioboxesViewModel = null.
The only way I know how to access the DisplayEntitiesWithRadioboxesViewModel is to use FormCollection and iterate throught the FormCollection and pull out the information I need from the nested ViewModels.
This however just doesn't seem right, as I have found at I then have to re-populate the DisplayEntitiesWithRadioboxesViewModel with the values from the FormCollection, if for example an error has occured and the user needs to be sent back to the same View.
I have tried searching the web/books but cannot find a solution.
Is there a better way?
Thanks in advance for any help.
And why did you use an EditorFor for a
simple dropdown, which is easily to
use with DropDownFor
This has now been altered to use the DropDownFor.
what is the Key of the
DisplayEntitiesWithRadioboxesViewModel
value in FormCollection
{string[3]}
[0] = "DisplayEntitiesWithRadioboxesViewModel.radiobutton"
[1] = "Action"
[2] = "OldParentId"
Clare :-)
Your problem is pretty common and somewhat easy to fix once you understand how it works.
Right now you have a view model that has a property which is an IEnumerable<T> (doesn't matter what the generic parameter is).  You are trying to pass the items to the view and populate the IEnumerable<T> with the same values when the response comes back, using the values originally written to the page, and augmented with the selected item (at least from the code you have posted anyway, it would help for you to state your exact intention in the question).  The problem you have here is that you must send those values to the page in a way in which they can be returned.
Let me just say now that you probably should NOT be using this technique.  It is typically a much better idea to return the selection only and generate the list again if you need to server side.
From the looks of things, you want to return the whole list and then look for the item that is selected, which is after all the point of a drop down or radio button group.  In order to get the selection back, the parameter to your controller action must have properties which match the variables passed back in.  In this case, it looks like you are using the parameter name radiobutton for all of your radio buttons (the same hold true for drop down list, only it uses the name of the list).  Which ever one is selected, the value associated with it is returned with that name.  The MVC framework takes care of trying to find the appropriate action which has as many names specified as possible.  
What you need to use for your action parameter is a new class that contains a property for all of the field names being submitted back to the server!  Or of course you could simply add the radiobutton property to your StructureViewModel too.  In fact, you'll notice that it is trying to set that value already, only it doesn't currently exist on your view model.  You still will not receive the original list back however, but thats okay, because even if you did receive the original list back, you have no identifier on it to let you know which item was selected!
Hopefully this helps you understand what is going on, if you have more questions, please ask.
I would recommend you using strongly typed helpers everywhere so that you don't have to worry about naming your controls. Here's how to proceed:
Models:
public class DisplayEntityViewModel
{
public int Id { get; set; }
public string Path { get; set; }
public string PathLevel { get; set; }
public string Description { get; set; }
}
public class StructureViewModel
{
public IEnumerable<DisplayEntityViewModel> DisplayEntitiesWithRadioboxesViewModel { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var moveDepartment = new StructureViewModel();
moveDepartment.DisplayEntitiesWithRadioboxesViewModel = new[]
{
new DisplayEntityViewModel
{
Id = 1,
Path = "some path 1",
PathLevel = "some path level 1",
Description = "some description 1"
},
new DisplayEntityViewModel
{
Id = 2,
Path = "some path 2",
PathLevel = "some path level 2",
Description = "some description 2"
},
};
return View(moveDepartment);
}
[HttpPost]
public ActionResult Index(StructureViewModel StructureViewModel)
{
return View(StructureViewModel);
}
}
Main View (~/Views/Home/Index.aspx):
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<SomeNs.Models.StructureViewModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<table class="aligncenter">
<%= Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel) %>
</table>
<input type="submit" value="Go" />
<% } %>
</asp:Content>
Editor Template (~/Views/Home/EditorTemplates/DisplayEntityViewModel.ascx)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ToDD.Models.DisplayEntityViewModel>" %>
<tr class="tRow">
<td style="text-align:left; text-indent:<%=Html.Encode(Model.PathLevel)%>em">
<%= Html.Encode(Model.Description) %>
<!-- Remember that you need to place input fields for each property
that you expect to get back in the submit action
-->
<%= Html.HiddenFor(x => x.Description) %>
<%= Html.TextBoxFor(x => x.Path) %>
</td>
</tr>
Now submit the form and everything should be bound correctly. An important thing to note is that the editor template is strongly typed to DisplayEntityViewModel and not IEnumerable<DisplayEntityViewModel> as in your case. When in your main view you write:
<%= Html.EditorFor(x => x.DisplayEntitiesWithRadioboxesViewModel) %>
the framework automatically detects that the property is a collection and will call the editor template for each item of this collection so you no longer need to loop through the elements which makes your code more elegant.
UPDATE:
Using dropdown lists is also very easy: checkout this answer.
Can you tell me how the EditorFor looks exactly? And why did you use an EditorFor for a simple dropdown, which is easily to use with DropDownFor.
what is the Key of the DisplayEntitiesWithRadioboxesViewModel value in FormCollection
If I understand it correctly, you have a View, with some parent-info, and at the same time multiple iterations of these 2 fields in the same view. Is that right?
Then I know how to fix this.

ASP.NET MVC 2 - Simple Search Page

I just started ASP.NET MVC (coming from WebForms) and I'm struggling with some really basic concepts.
I want to create a single page that uses a textbox for date input. I would like the date input to be passed to the index of my controller which looks like this:
public ActionResult Index(int month,
int day,
int year){
var visitors = visitorRepoistory.FindVisitorsByDate(month, day, year).ToList();
return View("Index", visitors);
}
Up to this point I have used scaffolding with strongly typed views so everything was kind of glued together for me.
What would/should my view look like? Would I use an actionlink (this is a get request after all, right?) and not a submit button.
Thanks.
I thought about this for a while before trying to come up with an answer. What threw me initially was the concept of turning a single text input string into it's month, day, and year components. In ASP.NET MVC, it would be much easier to just accept the string for the date. This way, your code changes to:
public ActionResult Index(string date) {
try
{
DateTime dtDate = DateTime.Parse(date);
var visitors = visitorRepoistory.FindVisitorsByDate(dtDate.month,
dtDate.day, dtDate.year).ToList();
return View("Index", visitors);
}
catch (FormatException)
{
//String was not a valid date/time
}
}
Are there ways to split it up into 3 ints? I'm sure. But to me, this would be the easiest/quickest way to the goal.
So in the view, you'll have your form looking something like this:
<% using(Html.BeginForm("VisitorSearchController", "Index")) { %>
Enter a date: <%= Html.TextBox("date") %>
<input type='submit' value='Search' />
<% } %>
Where "VisitorSearchController" is the name of the controller you want to post back to. Of course, "Index" is the method you're posting to. I'd stick with the submit button for now unless you're trying to get a LinkButton equivalent on the page. But you can save the "prettying up" part after functionality, right?
Edit: Added view code to the answer.
EDIT after comment:
Simplest way is to make the search input page post (not get) back to some other method, parse the date out, then redirect to the Action you have specified.
If you want to do it through get, then you can use some Javascript trickeration to link to whatever they type in, but I recommend the former.

How to bind Lists of a custom view model to a dropDownList an get the selected value after POST in ASP.NET MVC?

I have following problem. In my view model I defined some list properties as follows:
public class BasketAndOrderSearchCriteriaViewModel
{
List<KeyValuePair> currencies;
public ICollection<KeyValuePair> Currencies
{
get
{
if (this.currencies == null)
this.currencies = new List<KeyValuePair>();
return this.currencies;
}
}
List<KeyValuePair> deliverMethods;
public ICollection<KeyValuePair> DeliveryMethods
{
get
{
if (this.deliverMethods == null)
this.deliverMethods = new List<KeyValuePair>();
return this.deliverMethods;
}
}
}
This view model is embedded in another view model:
public class BasketAndOrderSearchViewModel
{
public BasketAndOrderSearchCriteriaViewModel Criteria
{
[System.Diagnostics.DebuggerStepThrough]
get { return this.criteria; }
}
}
I use 2 action methods; one is for the GET and the other for POST:
[HttpGet]
public ActionResult Search(BasketAndOrderSearchViewModel model){...}
[HttpPost]
public ActionResult SubmitSearch(BasketAndOrderSearchViewModel model){...}
In the view I implement the whole view model by using the EditorFor-Html Helper which does not want to automatically display DropDownLists for List properties!
1. Question: How can you let EditorFor display DropDownLists?
Since I could not figure out how to display DropDownLists by using EditorFor, I used the DropDownList Html helper and filled it through the view model as follows:
public IEnumerable<SelectListItem> DeliveryMethodAsSelectListItem()
{
List<SelectListItem> list = new List<SelectListItem>();
list.Add(new SelectListItem()
{
Selected = true,
Text = "<Choose Delivery method>",
Value = "0"
});
foreach (var item in this.DeliveryMethods)
{
list.Add(new SelectListItem()
{
Selected = false,
Text = item.Value,
Value = item.Key
});
}
return list;
}
My 2. question: As you can see I pass my view model to the action metho with POST attribute! Is there a way to get the selected value of a DropDownList get binded to the passed view model? At the moment all the DropDownList are empty and the selected value can only be fetched by the Request.Form which I definitely want to avoid!
I would greatly appreciate some ideas or tips on this!
For those like me that got to this post these days I'd recommend you to fully download the tutorial from http://www.asp.net/mvc/tutorials/mvc-music-store-part-1 which covers this and most of the common techniques related with .NET MVC applications.
Anyway Really usefull your post and answers man (If I could vote you I would :)
Let's try to take on this one:
Answer to Question 1: How can you let EditorFor display DropDownLists?
When you call Html.EditorFor() you can pass extra ViewData values to the EdiorTemplate View:
<%: Html.EditorFor(model => Model.Criteria, new { DeliveryMethods = Model.DeliveryMethods, Currencies = Model.Currencies}) %>
Now you have ViewData["DeliveryMethods"] and ViewData["Currencies"] initialized and available inside your EditorTemplate.
In your EditorTemplate you somehow need to call and convert those entries into DropDowns / SelectLists.
Assuming you've got an ascx file of type System.Web.Mvc.ViewUserControl<BasketAndOrderSearchCriteriaViewModel> you could do the following:
<%: Html.LabelFor(model => model.DeliveryMethods) %>
<%: Html.DropDownList("SelectedDeliveryMethod", new SelectList(ViewData["DeliveryMethods"] as IEnumerable, "SelectedDeliveryMethod", "Key", "value", Model.SelectedDeliveryMethod)) %>
Same goes for the Currencies.
<%: Html.LabelFor(model => model.Currencies) %>
<%: Html.DropDownList("SelectedCurrency", new SelectList(ViewData["Currencies"] as IEnumerable, "SelectedDeliveryMethod", "Key", "value", Model.SelectedCurrency)) %>
This setup will make your DeliveryMethodAsSelectListItem() obsolete and you can use any kind of list. Means you are not bound to KeyValuePairs. You'll just need to adjust your call on Html.DropDownList() from now on.
As you can see, I have introduced some new properties to your BasketAndOrderSearchCriteriaViewModel:
Model.SelectedDeliveryMethod
Model.SelectedCurrency
They are used to store the currently selected value.
Answer to Question 2: Is there a way to get the selected value of a DropDownList get binded to the passed view model?
In the EditorFor template we are passing the newly created Model.SelectedDeliveryMethod and Model.SelectedCurrency properties as the SelectedValue Parameter (See 4th Overload of the DropDownList Extension Method).
Now that we have the View doing it's job: How can we get the currently selected value inside the POST Action?
This is really easy now:
[HttpPost]
public ActionResult SubmitSearch(BasketAndOrderSearchViewModel model)
{
...
var selectedDeliveryMethod = model.Criteria.SelectedDeliveryMethod;
var selectedCurrency model.Criteria.SelectedDeliveryMethod;
...
}
Note: I don't have an IDE to test it right now, but it should do the trick or at least show you in which direction to go.

Resources