Reference DropDownList selected value from enclosing Form - asp.net-mvc

I'm just getting started with MVC5 (from WebForms), and dropdownlist bindings are giving me some fits.
I'd like to get this working using a GET request back to the page, with a selected value parameter. I'm hopeful that I can specify the route arguments in the form itself, so I'd like to reference the DDL's SelectedValue.
<p>
#using (Html.BeginForm("Index", "Profile", FormMethod.Get, new { id = WHATDOIPUTHERE} )) {
#Html.AntiForgeryToken()
#Html.DropDownList("ApplicationID", new SelectList(ViewBag.ApplicationList, "ApplicationID", "ApplicationName", ViewBag.SelectedApplicationId), new {onchange = "this.form.submit();"})
}
</p>
I can make it work with a POST form, but that requires a second controller method so I end up with
public ActionResult Index(long? id) {
ConfigManager config = new ConfigManager();
//handle application. default to the first application returned if none is supplied.
ViewBag.ApplicationList = config.GetApplications().ToList();
if (id != null) {
ViewBag.SelectedApplicationId = (long)id;
}
else {
ViewBag.SelectedApplicationId = ViewBag.ApplicationList[0].ApplicationID; //just a safe default, if no param provided.
}
//handle profile list.
List<ProfileViewModel> ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId) select new ProfileViewModel(p)).ToList();
return View(ps);
}
//POST: Profile
//read the form post result, and recall Index, passing in the ID.
[HttpPost]
public ActionResult index(FormCollection collection) {
return RedirectToAction("Index", "Profile", new {id = collection["ApplicationId"]});
}
It would be really nice to get rid of the POST method, since this View only ever lists child entities.
What do you think?

You can update your GET action method parameter name to be same as your dropdown name.
I also made some small changes to avoid possible null reference exceptions.
public ActionResult Index(long? ApplicationID) {
var config = new ConfigManager();
var applicationList = config.GetApplications().ToList();
ViewBag.ApplicationList = applicationList ;
if (ApplicationID!= null) {
ViewBag.SelectedApplicationId = ApplicationID.Value;
}
else
{
if(applicationList.Any())
{
ViewBag.SelectedApplicationId = applicationList[0].ApplicationID;
}
}
var ps = new List<ProfileViewModel>();
ps = (from p in config.GetProfilesByApp((long)ViewBag.SelectedApplicationId)
select new ProfileViewModel(p)).ToList();
return View(ps);
}

Related

How to pass a list in a get request

I have an url that works for passing a List of strings
/Home/Index?Person%5B0%5D=Myname&Person%5B1%5D=Yourname
Unencoded it is
/Home/Index?Person[0]=Myname&Person[1]=Yourname
The Action Method is
public ActionResult(List<string> person)
{
...
}
The Parameter List person will be correctly filled with the values Myname and Yourname.
I need to redirect to this url using RedirectToAction
I would usually do
RedirectToAction("Index","Home",new {Parameter1=value1})
But obviously I cant use Person%5B0%5D as a parameter name, because it has ivalid characters.
How can I create such a link or should I use a different URL - scheme?
hi i worked on your query and finally got the result just check this code find weather it work with your query.
Controller code :
public ActionResult showString()
{
try
{
IEnumerable<string> persons = new[] { "myname", "urname" };
var values = new RouteValueDictionary(
persons
.Select((sampleper, index) => new {sampleper, index })
.ToDictionary(
key => string.Format("[{0}]", key.index),
value => (object)value.sampleper
)
);
return RedirectToAction("details", values);
}
catch (Exception)
{
throw;
}
}
and another action method is details with list as parameter
public ActionResult details(IEnumerable<string> persons)
{
ViewBag.person = persons;
return View();
}
it also works if you pass the link as
http://localhost:2266/Home/details?%5B0%5D=myname&%5B1%5D=urname
the view of details action method is
#{
ViewBag.Title = "details";
}
<h2>details</h2>
<ul>
#foreach (var i in ViewBag.person)
{
<li>#i</li>
}
</ul>

mvc controlling two dropdownlist to filter data

hello everyone I want to ask a question abuout mvc dropdownlist. I am trying to filter the datas to their places or their codes while dropdowns selected index changed. I can do it when I use one dropdown but when I use more than one dropdown I cannot get the results separately.
As you see in the picture I have two dropdownlist.
public ActionResult Index(int? id,int? ddwList)
{
Repository<Order> _ro = new Repository<Order>();
IEnumerable<SelectListItem> _orderSelectListItem = _ro.All().AsEnumerable().Select(s => new SelectListItem
{
Text = s.code,
Value = s.id.ToString()
});
ViewData["ddOrder"] = _orderSelectListItem;
Repository<Workshop> _rw = new Repository<Workshop>();
IEnumerable<SelectListItem> _workshopSelectListItem = _rw.All().AsEnumerable().Select(s => new SelectListItem
{
Text = s.name,
Value = s.id.ToString()
});
ViewData["ddwList"] = _workshopSelectListItem;
Repository<ClothShipment> _rcs = new Repository<ClothShipment>();
IEnumerable<MyClothShipment> _myClothShipment = null;
if (id != null)
{
int? idOrd = _rcs.Find(w => w.orderId == id).orderId;
//int? idWork = _rcs.Find(w => w.workshopId == id).workshopId;
if (idOrd != null)
{
_myClothShipment = _rcs.All().Where(w => w.orderId == id).Select(s => new MyClothShipment
{
id = s.id,
amount = s.amount,
orderName = s.order.code,
clothName = s.clothList.name,
workshopName = s.workshop.name,
shipDate = s.shipDate
});
}
//else if(idWork != null){
// _myClothShipment = _rcs.All().Where(w => w.workshopId == id).Select(s => new MyClothShipment
// {
// id = s.id,
// amount = s.amount,
// orderName = s.order.code,
// clothName = s.clothList.name,
// workshopName = s.workshop.name,
// shipDate = s.shipDate
// });
//}
}
else {
_myClothShipment = _rcs.All().Select(s => new MyClothShipment
{
id = s.id,
amount = s.amount,
orderName = s.order.code,
clothName = s.clothList.name,
workshopName = s.workshop.name,
shipDate = s.shipDate
});
}
return View(_myClothShipment);
}
my view is here
<div id="sample_editable_2_length" class="dataTables_length">
<label>
#Html.DropDownList("ddwList",(IEnumerable<SelectListItem>)ViewData["ddwList"],"Atölye Seçiniz",new {#id="StateDropDown1", #class = "span15 chosen"})
</label>
</div>
my view is here
<div id="sample_editable_2_length" class="dataTables_length">
<label>
#Html.DropDownList("ddwList",(IEnumerable<SelectListItem>)ViewData["ddwList"],"Atölye Seçiniz",new {#id="StateDropDown1", #class = "span15 chosen"})
</label>
</div>
<div id="sample_editable_1_length" class="dataTables_length">
<label>
#*<select class="m-wrap small" name="sample_editable_1_length" size="1" aria-controls="sample_editable_1">
</select>*#
#Html.DropDownList("ddOrder",(IEnumerable<SelectListItem>)ViewData["ddOrder"],"Sipariş Kodu Seçiniz",new {#id="StateDropDown", #class = "span15 chosen"})
</label>
</div>
and here is my script code
$("#StateDropDown").change(function (e) {
var controllerName = '#ViewContext.RouteData.Values["Controller"].ToString()';
var actionName = '#ViewContext.RouteData.Values["Action"].ToString()';
var _id = $("#StateDropDown").val();
var _url = "/" + controllerName + "/" + actionName + "/" + _id;
window.location.href =_url
});
$("#StateDropDown1").change(function (e) {
var controllerName = '#ViewContext.RouteData.Values["Controller"].ToString()';
var actionName = '#ViewContext.RouteData.Values["Action"].ToString()';
var _id = $("#StateDropDown1").val();
var _url = "/" + controllerName + "/" + actionName + "/" + _id;
window.location.href = _url
});
I am filling the dropdowns when on page load from database and getting all the data to show with dropdowns I want to filter the data that shown... And with this code one of my dropdown works I am taking the id of selected item (Index(int? id)) in here but when I try to use both of them separately it doesnt work how can I make both of them work. What should I do ? The second parameter always comes null or if I use different parameter except "id" it is again coming null ? and also I tried to take parameter as string but it also came null... Thank you for your helps.
To explain what your code is doing:
When your select a value from your first select, you are passing its value to the Index method (e.g. /Index/1) so the value of parameter id is 1 but no value has been passed to parameter ddwList so it is null. When you select a value from the second select you are passing its value to the index method (e.d. /Index/5) so the value of parameter id is 5 but no value has been passed to parameter ddwList so again it is null.
Assuming you want to display a table based on the selections of both selects, then you need to construct the url as /Index?id=1&ddwList=5. Therefore, remove the change events from your selects and add a button that constructs the query in its click event. However, the way you are doing this is reloading the whole page each time. I suggest you consider loading the table from a partial view using a jQuery.get() method to avoid a complete page load each time. For example
public ActionResult Index()
{
// Build the 2 select lists only
return View();
}
Index view
// Add the two #Html.DropdownFor()...
<button id="LoadTable">Load Table</button>
<div id="TablePlaceholder"></div>
and the script
$('#LoadTable').click(function() {
var id1 = // the value of the first select
var id2 = // the value of your second select
$.get('#Url.Action("Table")', { id: id1, ddwList: id2 }, function(data) {
$('#TablePlaceHolder').html(data);
});
}
and the partial result
public ActionResult Table(int? id, int? ddwList)
{
// Build your table view based on values of id and ddwList
return PartialView();
}
If I understand this, you are able to use one of the drop downs at a time and that drop down successfully sends it's value to your controller while the other drop down always sends null. If that's the case, that is working correctly with the way you made your dropdownlists with an option label.
As explained in the msdn documentation:http://msdn.microsoft.com/en-us/library/dd492256(v=vs.118).aspx
optionLabel Type: System.String The text for a default empty item.
This parameter can be null.
So if you want both of the drop down lists to be usable at the same time, you'll need to remove the events for .change and add a form with those dropdowns AND a submit button within to use both values at the same time.
OR
Do not use an option label meaning the first option of the dropDownList will be used as it's initial/default value. Here is a link to the msdn docs showing the different ways to format the Html.DropDownList helper: http://msdn.microsoft.com/en-us/library/system.web.mvc.html.selectextensions.dropdownlist(v=vs.118).aspx
I hope I understood you correctly!

Rewriting the URL for a Controllers Action Method

I have a controller called Person and it has a post method called NameSearch.
This method returns RedirectToAction("Index"), or View("SearchResults"), or View("Details").
The url i get for all 3 possibilities are http://mysite.com/Person/NameSearch.
How would i change this to rewrite the urls to http://mysite.com/Person/Index for RedirectToAction("Index"), http://mysite.com/Person/SearchResults for View("SearchResults"), and http://mysite.com/Person/Details for View("Details").
Thanks in advance
I'm assuming your NameSearch function evaluates the result of a query and returns these results based on:
Is the query valid? If not, return to index.
Is there 0 or >1 persons in the result, if so send to Search Results
If there is exactly 1 person in the result, send to Details.
So, more of less your controller would look like:
public class PersonController
{
public ActionResult NameSearch(string name)
{
// Manage query?
if (string.IsNullOrEmpty(name))
return RedirectToAction("Index");
var result = GetResult(name);
var person = result.SingleOrDefault();
if (person == null)
return RedirectToAction("SearchResults", new { name });
return RedirectToAction("Details", new { id = person.Id });
}
public ActionResult SearchResults(string name)
{
var model = // Create model...
return View(model);
}
public ActionResult Details(int id)
{
var model= // Create model...
return View(model);
}
}
So, you would probably need to define routes such that:
routes.MapRoute(
"SearchResults",
"Person/SearchResults/{name}",
new { controller = "Person", action = "SearchResults" });
routes.MapRoute(
"Details",
"Person/Details/{id}",
new { controller = "Person", action = "Details" });
The Index action result will be handled by the default {controller}/{action}/{id} route.
That push you in the right direction?

ASP.NET MVC: Server Validation & Keeping URL paramters when returning the view

I currently have the following code for the POST to edit a customer note.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditNote(Note note)
{
if (ValidateNote(note))
{
_customerRepository.Save(note);
return RedirectToAction("Notes", "Customers", new { id = note.CustomerID.ToString() });
}
else
{
var _customer = _customerRepository.GetCustomer(new Customer() { CustomerID = Convert.ToInt32(note.CustomerID) });
var _notePriorities = _customerRepository.GetNotePriorities(new Paging(), new NotePriority() { NotePriorityActive = true });
IEnumerable<SelectListItem> _selectNotePriorities = from c in _notePriorities
select new SelectListItem
{
Text = c.NotePriorityName,
Value = c.NotePriorityID.ToString()
};
var viewState = new GenericViewState
{
Customer = _customer,
SelectNotePriorities = _selectNotePriorities
};
return View(viewState);
}
}
If Validation fails, I want it to render the EditNote view again but preserve the url parameters (NoteID and CustomerID) for something like this: "http://localhost:63137/Customers/EditNote/?NoteID=7&CustomerID=28"
Any ideas on how to accomplish this?
Thanks!
This action is hit by using a post. Wouldn't you want the params to come through as part of the form rather than in the url?
If you do want it, I suppose you could do a RedirectToAction to the edit GET action which contains the noteId and customerId. This would effectively make your action look like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditNote(Note note)
{
if (ValidateNote(note))
{
_customerRepository.Save(note);
return RedirectToAction("Notes", "Customers", new { id = note.CustomerID.ToString() });
}
//It's failed, so do a redirect to action. The EditNote action here would point to the original edit note url.
return RedirectToAction("EditNote", "Customers", new { id = note.CustomerID.ToString() });
}
The benefit of this is that you've removed the need to duplicate your code that gets the customer, notes and wotnot. The downside (although I can't see where it does it here) is that you're not returning validation failures.

ASP.NET MVC: How to maintain TextBox State when your ViewModel is a Collection/List/IEnumerable

I am using ASP.NET MVC 2 Beta. I can create a wizard like workflow using Steven Sanderson's technique (in his book Pro ASP.NET MVC Framework) except using Session instead of hidden form fields to preserve the data across requests. I can go back and forth between pages and maintain the values in a TextBox without any issue when my model is not a collection. An example would be a simple Person model:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
But I am unable to get this to work when I pass around an IEnumerable. In my view I am trying to run through the Model and generate a TextBox for Name and Email for each Person in the list. I can generate the form fine and I can submit the form with my values and go to Step2. But when I click the Back button in Step2 it takes me back to Step1 with an empty form. None of the fields that I previously populated are there. There must be something I am missing. Can somebody help me out?
Here is my View:
<% using (Html.BeginForm()) { %>
<% int index = 0;
foreach (var person in Model) { %>
<fieldset>
<%= Html.Hidden("persons.index", index.ToString())%>
<div>Name: <%= Html.TextBox("persons[" + index.ToString() + "].Name")%></div>
<div>Email: <%= Html.TextBox("persons[" + index.ToString() + "].Email")%></div>
</fieldset>
<% index++;
} %>
<p><input type="submit" name="btnNext" value="Next >>" /></p>
<% } %>
And here is my controller:
public class PersonListController : Controller
{
public IEnumerable<Person> persons;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
persons = (Session["persons"]
?? TempData["persons"]
?? new List<Person>()) as List<Person>;
// I've tried this with and without the prefix.
TryUpdateModel(persons, "persons");
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
Session["persons"] = persons;
if (filterContext.Result is RedirectToRouteResult)
TempData["persons"] = persons;
}
public ActionResult Step1(string btnBack, string btnNext)
{
if (btnNext != null)
return RedirectToAction("Step2");
// Setup some fake data
var personsList = new List<Person>
{
new Person { Name = "Jared", Email = "test#email.com", },
new Person { Name = "John", Email = "test2#email.com" }
};
// Populate the model with fake data the first time
// the action method is called only. This is to simulate
// pulling some data in from a DB.
if (persons == null || persons.Count() == 0)
persons = personsList;
return View(persons);
}
// Step2 is just a page that provides a back button to Step1
public ActionResult Step2(string btnBack, string btnNext)
{
if (btnBack != null)
return RedirectToAction("Step1");
return View(persons);
}
}
As far as I can tell, this is not supported in ASP.NET MVC 2 Beta, nor is it supported in ASP.NET MVC 2 RC. I dug through the MVC source code and it looks like Dictionaries are supported but not Models that are IEnumerable<> (or that contain nested IEnumerable objects) and it's inheritors like IList<>.
The issue is in the ViewDataDictionary class. Particularly, the GetPropertyValue method only provides a way to retrieve property values from dictionary properties (by calling GetIndexedPropertyValue) or simple properties by using the PropertyDescriptor.GetValue method to pull out the value.
To fix this, I created a GetCollectionPropertyValue method that handles Models that are collections (and even Models that contain nested collections). I am pasting the code here for reference. Note: I don't make any claims about elegance - in fact all the string parsing is pretty ugly, but it seems to be working. Here is the method:
// Can be used to pull out values from Models with collections and nested collections.
// E.g. Persons[0].Phones[3].AreaCode
private static ViewDataInfo GetCollectionPropertyValue(object indexableObject, string key)
{
Type enumerableType = TypeHelpers.ExtractGenericInterface(indexableObject.GetType(), typeof(IEnumerable<>));
if (enumerableType != null)
{
IList listOfModelElements = (IList)indexableObject;
int firstOpenBracketPosition = key.IndexOf('[');
int firstCloseBracketPosition = key.IndexOf(']');
string firstIndexString = key.Substring(firstOpenBracketPosition + 1, firstCloseBracketPosition - firstOpenBracketPosition - 1);
int firstIndex = 0;
bool canParse = int.TryParse(firstIndexString, out firstIndex);
object element = null;
// if the index was numeric we should be able to grab the element from the list
if (canParse)
element = listOfModelElements[firstIndex];
if (element != null)
{
int firstDotPosition = key.IndexOf('.');
int nextOpenBracketPosition = key.IndexOf('[', firstCloseBracketPosition);
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(element).Find(key.Substring(firstDotPosition + 1), true);
// If the Model has nested collections, we need to keep digging recursively
if (nextOpenBracketPosition >= 0)
{
string nextObjectName = key.Substring(firstDotPosition+1, nextOpenBracketPosition-firstDotPosition-1);
string nextKey = key.Substring(firstDotPosition + 1);
PropertyInfo property = element.GetType().GetProperty(nextObjectName);
object nestedCollection = property.GetValue(element,null);
// Recursively pull out the nested value
return GetCollectionPropertyValue(nestedCollection, nextKey);
}
else
{
return new ViewDataInfo(() => descriptor.GetValue(element))
{
Container = indexableObject,
PropertyDescriptor = descriptor
};
}
}
}
return null;
}
And here is the modified GetPropertyValue method which calls the new method:
private static ViewDataInfo GetPropertyValue(object container, string propertyName) {
// This method handles one "segment" of a complex property expression
// First, we try to evaluate the property based on its indexer
ViewDataInfo value = GetIndexedPropertyValue(container, propertyName);
if (value != null) {
return value;
}
// If the indexer didn't return anything useful, continue...
// If the container is a ViewDataDictionary then treat its Model property
// as the container instead of the ViewDataDictionary itself.
ViewDataDictionary vdd = container as ViewDataDictionary;
if (vdd != null) {
container = vdd.Model;
}
// Second, we try to evaluate the property based on the assumption
// that it is a collection of some sort (e.g. IList<>, IEnumerable<>)
value = GetCollectionPropertyValue(container, propertyName);
if (value != null)
{
return value;
}
// If the container is null, we're out of options
if (container == null) {
return null;
}
// Third, we try to use PropertyDescriptors and treat the expression as a property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container).Find(propertyName, true);
if (descriptor == null) {
return null;
}
return new ViewDataInfo(() => descriptor.GetValue(container)) {
Container = container,
PropertyDescriptor = descriptor
};
}
Again, this is in the ViewDataDictionary.cs file in ASP.NET MVC 2 RC. Should I create a new issue to track this on the MVC codeplex site?

Resources