I have a basic model
public class SpeakerConsent
{
public string FieldLabel { get; set; }
public List<SelectListItem> OptionValues { get; set; }
}
My razor page currently looks like the following
#for (var i = 0; i < Model.OptionValues.Count(); i++)
{
#Html.RadioButtonFor(x => Model.OptionValues[i].Selected, Model.OptionValues[i].Value )
}
I might have 4 or 5 items in my OptionValues.
Question : How can i bind to the Selected property of the SelectListItem so when the model is posted i can identify which on of the radio buttons has been selected?
With your current code, the HTML markup generated for the input element will be like below
<input data-val="true" name="OptionValues[0].Selected" type="radio" value="201">
the name is Selected and the value is a number. The Selected property of the SelectListItem is bool type. Model binding will not work as expected when you post this form data.
IMHO, The easiest way to handle a scenario like this is to use editor templates.
I will first create a class which represents the data I want to use with the radio buttons.
public class MyOption
{
public bool IsSelected { set; get; }
public int Id { set; get; }
public string Text { set; get; }
}
Now in my main view model, I will add a collection type property
public class SpeakerConsent
{
public List<MyOption> Options { set; get; }
}
Now in your ~/Views/Shared/ directory, create a folder called EditorTemplates and then create a view called MyOption.cshtml in that. You can have the HTML markup you want to use to render the radio button in that. Here I am keeping the Id property in a hidden field along with the radio button and label.
#model YourNamespace.MyOption
<div>
<span>#Model.Text</span>
#Html.HiddenFor(g=>g.Id)
#Html.RadioButtonFor(b => b.IsSelected, true)
</div>
Now in your GET action, you can populate this Options property of yout SpeakerConsent view model object and send to the view.
public ActionResult Create()
{
var vm = new SpeakerConsent();
vm.Options = new List<MyOption>
{
new MyOption { Id=1, Text="Seattle"},
new MyOption { Id=2, Text="Detroit"},
new MyOption { Id=31, Text="Kerala"},
};
return View(vm);
}
Now in your main view, you can simply call the EditorFor helper method.
#model YourNamespace.SpeakerConsent
#using (Html.BeginForm("Index", "Home"))
{
#Html.EditorFor(a=>a.Options)
<button type="submit">Save</button>
}
Make sure to check the HTML markup generated by this code. notice the
name attribute value of the inputs.
When the form is submitted, you can read the Options property value, loop through them and read the Id and IsSelected property value and use them as needed
[HttpPost]
public ActionResult Create(SpeakerConsent model)
{
// check model.Options
// to do : return something
}
Related
I have a view model that is used to display a form on one view, and then is also used to represent the POST data to an action. The action then displays another view model that contains much of the same data from the first view model. However, the first view model has several "display only" properties that are also required on the second view model (for display only on the second view also).
I am wondering what the best way to pass this "display only" data to the second view would be. Currently, the best solution I have come up with is to have a bunch of hidden form fields that contain the display only property values, and then the model gets auto-populated for the action that handles the form POST. However, using hidden form fields seems very "hackish", and there seems like there should be a better solution to passing this data to another view The action doesn't need the display only information, it is only accessing it to populate the properties of the second view model that is passed to the second view.
Let me just explain my question with code, as what I am after is probably better understood through code than words.
Models:
public class SearchFilters
{
// ...
}
public class SearchResult
{
public int Id { get; set; }
public bool Selected { get; set; }
public string SomeDisplayValue1 { get; set; }
public string SomeDisplayValue2 { get; set; }
// ...
}
public class ResultsViewModel
{
public IList<SearchResult> Results { get; set; }
// ...
}
public class DoSomethingWithSelectedResultsViewModel
{
public IList<SearchResult> SelectedResults { get; set; }
public string SomeOtherProperty { get; set; }
// ...
}
Controller:
[HttpPost]
public ActionResult Results(SearchFilters filters)
{
ResultsViewModel results = new ResultsViewModel();
// ...
return new View(results);
}
[HttpPost]
public ActionResult DoSomethingWithSelectedResults(ResultsViewModel model)
{
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
View: Results.cshtml
#model ResultsViewModel
#using (Html.BeginForm("DoSomethingWithSelectedResults", "Search"))
{
<table>
for (int i = 0; i < Model.Results.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model.Results[i].Selected)
#* I would like to eliminate these hidden inputs *#
#Html.HiddenFor(m => Model.Results[i].Id)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue1)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue2)
</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue1)</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue2)</td>
<tr>
}
</table>
<button type="submit">Do Something With Selected Results</button>
}
As far as I know, one of the best way to pass data from View to another View through a Controller is to use ViewBag, ViewData or TempData. As an example, you can pass the data retrieved from View I as shown below:
TempData[DataToBePassed] = model.CustomData;
And then retrieve this data in View II similar to that:
#if(TempData[DataToBePassed] != null)
{
var dataFromFirstView = TempData[DataToBePassed];
}
For more information take a look at When to use ViewBag, ViewData, or TempData in ASP.NET MVC 3 applications.
You could put the model in the TempData property of the controller, that way it's automatically available in the next request.
More here
Found what I was looking for, I just hadn't worked with MVC enough yet to know about it. The Controller.UpdateModel method does exactly what I was looking for.
Example (using the code from the question):
[HttpPost]
public ActionResult DoSomethingWithSelectedResults()
{
// Load initial model data here, in this case I had simply cached the results in
// temp data in the previous action as suggested by Emeka Awagu.
ResultsViewModel model = (ResultsViewModel)TempData["results"];
// Call UpdateModel and let it do it's magic.
UpdateModel(model);
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
Using this method I was able to eliminate all the hidden form fields and did not have to write any custom copy logic, since UpdateModel deals with it automatically.
Note: I did have to implement some custom model binders to get things to work correctly with dictionaries and collections (see here, here, and here).
I've got a view that looks like this:
With this view I'm filtering records which are in a database. Each "filter" is a SearchViewModel which has a class definition like this:
public class SearchViewModel
{
//Property has a property called "SqlColumnName"
public Property Property { get; set; }
public Enums.SearchOperator Operator { get; set; }
public string Value { get; set; }
}
I'm now trying to find a solution how to build a actionlink to this site and passing in a List<SearchViewModel>().
So I'm trying to accomplish something like this:
http://url/Index?property=Typ&Operator=2&Value=4&property=ServerName&Operator=1&Value=server
I've tried to solve my problem with this http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ but this guy is calling the Action with a POST and i need to call it with a GET request.
So how to pass a list of objects to a mvc controller action with an actionlink?
EDIT
I think i need to say, that POST request is not an option because i need that link in an onClick event of a div element.
You could construct a link with the following parameters that should work:
http://url/Index?%5B0%5D0.property=Typ&%5B0%5D0.Operator=2&%5B0%5D0.Value=4&%5B1%5D0.property=ServerName&%5B1%5D0.Operator=1&%5B1%5D0.Value=server
Also notice that there are restrictions in the length of query string parameters. Those restrictions vary between the browsers. So it's probably a better approach to simply generate a link with an id that would allow you to retrieve the corresponding collection on the server (from your datastore):
http://url/Index?id=123
ActionLink helper method renders an anchor tag. It is ok to pass few query string items via a link. Remember Query string has a limit about how much data you can pass and it varies from browser to browser.
What you should be doing is a form posting. You can do a form posting on a click event on a div with the help of a little javascript.
Let's create a new view model for our search page
public class SearchVm
{
public List<SelectListItem> Operators { set; get; }
public List<SearchViewModel> Filters { set; get; }
}
public class SearchViewModel
{
//Property has a property called "SqlColumnName"
public Property Property { get; set; }
public SearchOperator Operator { get; set; }
public string Value { get; set; }
}
So in your GET action, You will send a list of SearchViewModel to the view.
public ActionResult Index()
{
var search = new SearchVm
{
Filters = new List<SearchViewModel>
{
new SearchViewModel {Property = new Property {SqlColumn = "Name"}},
new SearchViewModel {Property = new Property {SqlColumn = "Age"}},
new SearchViewModel {Property = new Property {SqlColumn = "Location"}}
}
};
//Convert the Enums to a List of SelectListItem
search.Operators= Enum.GetValues(typeof(SearchOperator)).Cast<SearchOperator>()
.Select(v => new SelectListItem
{
Text = v.ToString(),
Value = ((int)v).ToString()
}).ToList();
return View(search);
}
And in your view, which is strongly typed to your SearchVm view model, We will manipulate the form field names so that the model binding will work when the form is submitted.
#model SearchVm
#using (Html.BeginForm())
{
var i = 0;
foreach (var criteria in Model.Filters)
{
<label>#criteria.Property.SqlColumn</label>
#Html.HiddenFor(f => f.Filters[i].Property.SqlColumn)
#Html.DropDownList("Filters[" + i+ "].Operator",Model.Operators)
#Html.TextBoxFor(f=>f.Filters[i].Value)
i++;
}
<div id="someDiv">Search button using Div</div>
<input type="submit" value="Search" />
}
And your HttpPost action method to handle the form submit.
[HttpPost]
public ActionResult Index(SearchVm model)
{
foreach(var f in model.Filters)
{
//check f.Property.SqlColumn, f.Value & f.Operator
}
// to do :Return something useful
}
If you want the form to be submitted on the click event on the div, Listen to the click event on the specific div and call the submit method on the form the div resides in.
<script>
$(function () {
$("#someDiv").click(function(e) {
$(this).closest("form").submit();
});
});
</script>
How do I create drop down list using data annotation?
I would like to achieve markup generated by
#Html.DropDownListFor(x=>x.ContactType, Model.ContactTypeOptions)
to be set so I can use and it would generate dropdown list:
#Html.EditorForModel(Model)
My current model is:
public class ContactModel
{
public string ContactType { get; set; }
public IList<SelectListItem> ContactTypeOptions
{
get
{
return new List<SelectListItem>()
{
new SelectListItem(){Text = "Options"}
};
}
}
[Required(AllowEmptyStrings = false)]
[MinLength(15)]
[DataType(DataType.MultilineText)]
public string Message { get; set; }
}
Updated
I do not want to use partial view.
You could try something like this:
public class ContactModel
{
[UIHint("_DropDownList")]
public SelectList ContactType { get; set; }
}
Set (in your controller) ContactType.Items to be your list of options and ContactType.SelectedValue to be your initially selected value.
Then define a partial view _DropDownList.cshtml:
#model SelectList
#Html.DropDownListFor(m => m.SelectedValue, Model)
You should then be able to use #Html.EditorFor(m => m.ContactType) and get your dropdown. And you can reuse it anywhere!
You might even get this behaviour out of the box nowadays when you do #Html.EditorFor(m => m.Property) where m.Property is a SelectList. Not sure on that one.
If you're looking to just use EditorForModel() on your ContactModel, then you can just create an editor template called ContactModel.cshtml and do:
#model ContactModel
#Html.DropDownListFor(m => m.ContactType,
new SelectList(Model.ContactTypeOptions, Model.ContactType))
Note that this should be called as #Html.EditorForModel() in a view already typed to a ContactModel- the object passed in as a parameter in the overload EditorForModel(Object) is for additional view data, not for the model object. EditorForModel always renders an editor template for the current view model.
In view I have and I am not able to validate the below mvc code
#Html.DropDownList("PriorityId", (IEnumerable<SelectListItem>)#ViewBag.Priority, "--Please Select One--", new { #id = "ddlPriority", #class = "dropdown" })
In Controller I have
private void PriorityDropdownList()
{
IEnumerable<SelectListItem> priorityDropdownlist =_PriorityObj.PriorityDropdownlistGet();
if (priorityDropdownlist != null)
{
ViewBag.Priority = priorityDropdownlist;
}
}
Try to avoid using ViewBag / ViewData for sending data to your view. Use strongly typed views & ViewModels with data annotations as required for validation.
public class CreateIssueVM
{
[Required]
public string Name { set;get;}
public List<SelectListItem> Priorities { get; set; }
[Required]
public int SelectedPriority { set;get;}
}
Here i have decorated the Name and SelectedPriority properties with Required attributes. That means, user needs to enter these, otherwise, it will bring up the validation message of form submit.
In your GET action , set the collection property value and send that to the view.
public ActionResult Create()
{
var vm=new CreateIssueVM();
vm.Priorities =GetPriorities();
return View(vm);
}
Assuming GetPriorities method returns a collection of SelectListItem for the priorities.
public List<SelectListItem> GetPriorities()
{
List<SelectListItem> prioList=new List<SelectListItem>();
prioList.Add(new SelectListItem { Text = "High", Value = "1" });
prioList.Add(new SelectListItem { Text = "Low", Value = "2" });
// to do :read from db and add to list instead of hardcoding
return prioList;
}
Now in our view, which is strongly typed to CreateIssueVM
#model CreateIssueVM
#using(Html.BeginForm())
{
#Html.DropDownListFor(x=>x.SelectedPriority,
new SelectList(Model.Priorities ,"Value","Text"),"select")
#Html.ValidationMessageFor(x=>x.SelectedPriority)
<input type="submit" />
}
When you submit the form, you can check the form is valid or not by checking the ModelState.IsValid property.
[HttpPost]
public ActionResult Create(CreateIssueVM model)
{
if(ModelState.IsValid)
{
// save and redirect
}
vm.Priorities =GetPriorities();
return View(vm);
}
If you want to implement client side validation as well, you may simply include the relevant jquery unobtrusive validation scripts in the view and enable the client side validation in web.config.
I have a List of about 20 items I want to display to the user with a checkbox beside each one (a Available property on my ViewModel).
When the form is submitted, I want to be able to pass the value of each checkbox that is checked back to my controller method via the Selections property on my ViewModel.
How would I go about doing this using the Form Helper class in MVC? Is this even possible?
PS: I don't want a listbox where the user can just highlight multiple items.
Model:
public class MyViewModel
{
public int Id { get; set; }
public bool Available { get; set; }
}
Controller:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = Enumerable.Range(1, 20).Select(x => new MyViewModel
{
Id = x
});
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<MyViewModel> model)
{
...
}
}
View ~/Views/Home/Index.cshtml:
#model IEnumerable<AppName.Models.MyViewModel>
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="OK" />
}
Editor template ~/Views/Home/EditorTemplates/MyViewModel.cshtml:
#model AppName.Models.MyViewModel
#Html.HiddenFor(x => x.Id)
#Html.CheckBoxFor(x => x.Available)
The best thing to do would be to create a template that can be reused. I have some code at home that I can post later tonight.
Maybe check SO for similar posts in the mean time.
Dynamic list of checkboxes and model binding
This blog post also could help;
http://tugberkugurlu.com/archive/how-to-handle-multiple-checkboxes-from-controller-in-asp-net-mvc-sample-app-with-new-stuff-after-mvc-3-tools-update