I want to get some options (say payment method cash, credit card etc.) and bind these to radio buttons. I believe there is no RadioButtonList in MVC 3.
Also, once radios are bound I want to show the previously selected option to the user while editing the answer.
As always you start with a model:
public enum PaiementMethod
{
Cash,
CreditCard,
}
public class MyViewModel
{
public PaiementMethod PaiementMethod { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and finally a view:
#model MyViewModel
#using (Html.BeginForm())
{
<label for="paiement_cash">Cash</label>
#Html.RadioButtonFor(x => x.PaiementMethod, "Cash", new { id = "paiement_cash" })
<label for="paiement_cc">Credit card</label>
#Html.RadioButtonFor(x => x.PaiementMethod, "CreditCard", new { id = "paiement_cc" })
<input type="submit" value="OK" />
}
And if you want some more generic solution which encapsulates this in a helper you may find the following answer helpful.
This is how I like to bind RadioButtonLists. The view model has a collection of my strongly typed objects. For example, maybe PaymentOptions is a code table. Along with the collection is a SelectedPaymentOptionKey (or Selected*Id if you prefix your primary keys with Id). Initially this key will just be default 0, but on postback, it will hold the value of the selected item.
public class PaymentSelectionVM
{
public ICollection<PaymentOption> PaymentOptions { get; set; }
public int SelectedPaymentOptionKey { get; set; }
}
public ViewResult PaymentSelection()
{
var paymentOptions = db.PaymentOptions.ToList();
return View(
new PaymentSelectionVM {
PaymentOptions = paymentOptions,
//This is not required, but shows how to default the selected radiobutton
//Perhaps you have a relationship between a Customer and PaymentOption already,
//SelectedPaymentOptionKey = someCustomer.LastPaymentOptionUsed.PaymentOptionKey
// or maybe just grab the first one(note this would NullReferenceException on empty collection)
//SelectedPaymentOptionKey = paymentOptions.FirstOrDefault().PaymentOptionKey
});
}
Then in the View:
#foreach (var opt in Model.PaymentOptions)
{
#*Any other HTML here that you want for displaying labels or styling*#
#Html.RadioButtonFor(m => m.SelectedPaymentOptionKey, opt.PaymentOptionKey)
}
The m.SelectedPaymentOptionKey serves two purposes. First, it groups the Radio buttons together so that the selection is mutually exclusive(I would encourage you to use something like FireBug to inspect the generated html just for your own understanding. The wonderful thing about MVC is the generated HTML is fairly basic and standard so it shouldn't be hard for you to eventually be able to predict the behavior of your views. There is very little magic going on here.). Second, it will hold the value of the selected item on postback.
And finally in the post handler we have the SelectedPaymentOptionKey available:
[HttpPost]
public ActionResult PaymentSelection(PaymentSelectionVM vm)
{
currentOrder.PaymentOption = db.PaymentOptions.Find(vm.SelectedPaymentOptionKey);
....
}
The advantage of this over using SelectListItems is you have access to more of the object's properties in the case that you are displaying a grid/table and need to display many values of the object. I also like that there are no hard coded strings being passed in the Html helpers as some other approaches have.
The disadvantage is you get radio buttons which all have the same ID, which is not really a good practice. This is easily fixed by changing to this:
#Html.RadioButtonFor(m => m.SelectedPaymentOptionKey, opt.PaymentOptionKey, new { id = "PaymentOptions_" + opt.PaymentOptionKey})
Lastly, validation is a bit quirky with most all of the radio button techniques I've seen. If I really needed it, I would wire some jquery up to populate a hidden SelectedPaymentOptionsKey whenever the radio buttons are clicked, and place the [Required] or other validation on the hidden field.
Another workaround for the validation problem
ASP.NET MVC 3 unobtrusive validation and radio buttons
This looks promising but I haven't had a chance to test it:
http://memoriesdotnet.blogspot.com/2011/11/mvc-3-radiobuttonlist-including.html
You should bind your options to SelectList in ViewModel and set Selected attribute to true for previously selected option
Related
I have model which has data.
The i create form with only on button, i don't use the model's data in there.
When i press the button it goes correctly to the HTTPPOST method but the model is completely empty.
HTML:
#model ViewModels.RequestDeletionViewObject
#using (Html.BeginForm())
{
<input type="submit" value="submit"/>
}
The RequestDeletionViewObject:
public class RequestDeletionViewObject : ViewModelBase
{
public TreeGridData NodeFilespacesData { get; set; }
public Dictionary<long, string> EmailList{ get; set; }
}
Controller:
public ActionResult RequestDel()
{
return View(_businessLogic.GetData());
}
[HttpPost]
public ActionResult RequestDel(RequestDeletionViewObject model)
{
return View(_businessLogic.GetData());
}
Please help me, I have done similar thing in many other places and it worked there, but not here, i don't know what am i missing?
Thanks
You don't have any form controls in your form. A form will only post data that is in a form control (textbox, hidden field, checkbox, etc..)
It doesn't matter what data you send to the view, it will only post back data in form controls within the form.
I'm experimenting with ASP.NET MVC3 and want to simply populate a dropdown list with data I get from a LINQ2SQL class, like so
controller (I know, Linq doesn't belong in the controller)
var allUsers = (from u in _userDataContext.Users
select u).ToList();
ViewBag.allUsers = allUsers.ToList();
return View();
view:
<select id="drop_heroes">
#foreach (var u in ViewBag.allUsers)
{
<option value="#u.pk_userid">#u.email</option>
}
</select>
That works fine, but I would like to use Razor #Html.Dropdownlist to create the same dropdown list, but can't find any info to make this work with Linq data.
I know, Linq doesn't belong in the controller
Then why are you using it in a controller? Anyway, at least it's fine that you know it.
Here's an example. As always in an ASP.NET MVC application you start by defining a view model which will represent the data that you need in the view. So in your case you need to display a dropdown so you define a list of users and a selected user id:
public class MyViewModel
{
public string SelectedUserId { get; set; }
public IEnumerable<SelectListItem> Users { get; set; }
}
then you define a controller action which will populate this view model from your repository and handle it to the view:
public ActionResult Index()
{
var model = new MyViewModel
{
Users = _userDataContext.Users.ToList().Select(x => new SelectListItem
{
Value = x.pk_userid.ToString(),
Text = x.email
})
}
return View(model);
}
and finally you will have a view which will be strongly typed to your view model and use HTML helpers to generate the dropdownlist:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedUserId, Model.Users)
<button type="submit">OK</button>
}
Things to notice:
Usage of view models
Usage of a strongly typed view
Usage of strongly typed HTML helpers to generate markup such as form elements and input fields
Getting rid of weakly typed structures such as ViewBag
If you follow these simple rules you will see how much easier your life as an ASP.NET MVC developer will become.
I'm attempting to add a classic Accept Terms and Conditions checkbox on the log on page of an MVC application.
If the user accepts the Terms and Conditions, but fails to log on for some other reason (bad password etc), then I want the Accept T&Cs checkbox not to be checked, so the user is forced to accept the T&Cs on every log on attempt.
The problem is that using Html.CheckboxFor(), after a postback the checkbox retains its previous value, despite the value of the bound Model property.
Here's the code, stripped down to essentials. If you run this code up, check the checkbox, and click the button, you'll be returned to the form with the checkbox still checked, even though the bound model property is false.
The Model:
namespace Namespace.Web.ViewModels.Account
{
public class LogOnInputViewModel
{
[IsTrue("You must agree to the Terms and Conditions.")]
public bool AcceptTermsAndConditions { get; set; }
}
}
The validation attribute:
public class IsTrueAttribute : ValidationAttribute
{
public IsTrueAttribute(string errorMessage) : base(errorMessage)
{
}
public override bool IsValid(object value)
{
if (value == null) return false;
if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
return (bool)value;
}
}
The View:
#model Namespace.Web.ViewModels.Account.LogOnInputViewModel
#using (Html.BeginForm()) {
#Html.CheckBoxFor(x => x.AcceptTermsAndConditions)
<input type="submit" value="Log On" />
}
The Controller:
[HttpGet]
public ActionResult LogOn(string returnUrl)
{
return View(new LogOnInputViewModel { AcceptTermsAndConditions = false });
}
[HttpPost]
public ActionResult LogOn(LogOnInputViewModel input)
{
return View(new LogOnInputViewModel { AcceptTermsAndConditions = false });
}
I saw the suggestion on asp.net to add a #checked attribute to the CheckboxFor. I tried this, making the view
#model Namespace.Web.ViewModels.Account.LogOnInputViewModel
#using (Html.BeginForm()) {
#Html.CheckBoxFor(x => x.AcceptTermsAndConditions, new { #checked = Model.AcceptTermsAndConditions })
<input type="submit" value="Log On" />
}
And I saw the same behaviour.
Thanks for any help/insights!
Edit: Although I want to override the posted back value, I wish to retain the message if validation of AcceptTermsAndConditions fails (there is a validation attribute on AcceptTermsAndConditions requiring it to be true), so I can't use ModelState.Remove("AcceptTermsAndConditions") which was the otherwise sound answer #counsellorben gave me. I've edited the code above to include the validation attribute - apologies to #counsellorben for not being clearer originally.
You need to clear the ModelState for AcceptTermsAndConditions. By design, CheckBoxFor and other data-bound helpers are bound first against the ModelState, and then against the model if there is no ModelState for the element. Add the following to your POST action:
ModelState.Remove("AcceptTermsAndConditions");
I'm using Entity Framework Code First to generated my database, so I have an object defined like the following:
public class Band
{
public int Id { get; set; }
[Required(ErrorMessage = "You must enter a name of this band.")]
public string Name { get; set; }
// ...
public virtual ICollection<Genre> Genres { get; set; }
}
Now I'm looking at a create view for this and the default scaffolding isn't adding Genres to my form, which from past experience is about what I expect.
Looking online I've found Using ASP.NET MVC v2 EditorFor and DisplayFor with IEnumerable<T> Generic types which seems to come closest to what I want, but doesn't seem to make sense with Razor and possibly MVC 3, per ASP.NET MVC 3 Custom Display Template With UIHint - For Loop Required?.
At present I've added the listing of genres to the ViewBag and then loop through that listing in my create view:
#{
List<Genre> genreList = ViewBag.Genres as List<Genre>;
}
// ...
<ul>
#for (int i = 0; i < genreList.Count; i++)
{
<li><input type="checkbox" name="Genres" id="Genre#(i.ToString())" value="#genreList[i].Name" /> #Html.Label("Genre" + i.ToString(), genreList[i].Name)</li>
}
</ul>
Outside of not yet handling cases where the user has JavaScript disabled and the checkboxes need to be re-checked, and actually updating the database with this information, it does output the genres as I'd like.
But this doesn't feel right, based on how good MVC 3 has become.
So what's the most effective way to handle this in MVC 3?
I don't send lists into my View via the ViewBag, instead I use my viewmodel to do this. For instance, I did something like this:
I have an EditorTemplate like this:
#model IceCream.ViewModels.Toppings.ToppingsViewModel
<div>
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x =x> x.Name, new { #readonly="readonly"})
#Html.CheckBoxFor(x => x.IsChecked)
</div>
which I put in my Views\IceCream\EditorTemplates folder. I use this to display some html for allowing the user to "check" any particular topping.
Then in my View I've got something like this:
#HtmlEditorFor(model => model.Toppings)
and that will use that result in my EditorTemplate being used for each of the toppings in the Toppings property of my viewmodel.
And then I've got a viewmodel which, among other things, includes the Toppings collection:
public IEnumerable<ToppingsViewModel> Toppings { get; set; }
Over in my controller, among other things, I retrieve the toppings (however I do that in my case) and set my viewmodel's property to that collection of toppings. In the case of an Edit, where toppings may have been selected previously, I set the IsChecked member of the TopingsViewModel and it'll set the corresponding checkboxes to checked.
Doing it this way provided the correct model binding so that when the user checked a few toppings, the underlying items in the collection reflected those selections. Worked well for me, hope it's helpful for you.
I have two classes, Vat and Product. Product has a property of IVat. I am trying to use editor templates in MVC to display a dropdown list of all the Vat objects when creating/editing a Product. For the dear life of me I cannot get this working.
I have the following code which displays the dropdown but it does not set the Vat for the Product when the form gets submitted.
Controller:
IList<IVatRate> vatRates = SqlDataRepository.VatRates.Data.GetAllResults();
ViewBag.VatRates = new SelectList(vatRates, "Id", "Description");
Add.cshtml
#Html.EditorFor(model => model.VatRate.Id, "VatSelector", (SelectList)ViewBag.VatRates)
VatSelector.cshtml
#model SelectList
#Html.DropDownList(
String.Empty /* */,
(SelectList)ViewBag.Suppliers,
Model
)
I would be grateful if anyone can shed some light on this or even point me to a good example on the web somewhere...I have been stuck with this for quite a few days now.
I would use strongly typed views and view models as it makes things so much easier rather than ViewBag.
So start with a view model:
public class VatRateViewModel
{
public string SelectedVatRateId { get; set; }
public IEnumerable<IVatRate> Rates { get; set; }
}
then a controller:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = new VatRateViewModel
{
Rates = SqlDataRepository.VatRates.Data.GetAllResults()
};
return View(model);
}
[HttpPost]
public ActionResult Index(VatRateViewModel model)
{
// model.SelectedVatRateId will contain the selected vat rate id
...
}
}
View:
#model VatRateViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(
x => x.SelectedVatRateId,
new SelectList(Model.Rates, "Id", "Description")
)
<input type="submit" value="OK" />
}
And if you wanted to use editor template for the VatRateViewModel you could define one in ~/Views/Shared/EditorTemplates/VatRateViewModel.cshtml:
#model VatRateViewModel
#Html.DropDownListFor(
x => x.SelectedVatRateId,
new SelectList(Model.Rates, "Id", "Description")
)
and then whenever somewhere you have a property of type VatRateViewModel you could simply:
#Html.EditorFor(x => x.SomePropertyOfTypeVatRateViewModel)
which would render the corresponding editor template.