Custom attribute with dash in name using EditorFor/TextBoxFor/TextBox helpers - asp.net-mvc-3

I am using Knockout-JS to bind properties in my view to my view model. Knockout-JS uses a custom attribute called 'data-bind' that you have to append to controls in which you want to be bound to view model objects.
Example:
<input type='text' name='first-name' data-bind='value: firstName'/>
Notice the 'data-bind' attribute.
In my view rendering, I am having trouble rendering a textbox that has this attribute. I am aware the Html.EditorFor, Html.TextBoxFor, and Html.TextBox helpers all take an anonymous object that you can use to specify custom attributes. The only problem with this implementation is C# doesn't allow dashes as variable names, so this won't compile:
#Html.EditorFor(m => m.FirstName, new { data-bind = "value: firstName" });
The only thing I can think of is this (in view-model):
public class DataBindingInput
{
public string Value { get; set; }
public string DataBindingAttributes { get; set }
}
public class MyViewModel
{
...
public DataBindingValue firstName { get; set; }
....
}
And a view template called "DataBindingInput.cshtml":
#model DataBindingInput
<input type='text' data-binding='#Model.DataBindingAttributes' value='#Model.Value'>
The only trouble with this is I lose the automatic generation of the input name so it won't work on a post-back because the model binder has no idea how to bind it.
How can I make this work?

Thanks to Crescent Fish above, looks like you can just use underscores and MVC 3 will convert them to dashes since underscores aren't allowed in HTML attribute names.

Related

ASP.NET MVC3 Validation of nested view model object fields

I have a view model that looks like this:
public class VenueIndexViewModel : BaseViewModel
{
public VenueAddViewModel Venue;
...
}
public class VenueAddViewModel
{
...
[Required(ErrorMessage = "This field is required")]
public string State { get; set; }
...
}
In my view, I'm rendering a form with with a drop down list for this property like so:
using (var form = Html.BeginForm())
{
...
#Html.DropDownListFor(x => x.Venue.State, Model.GetStates())
#Html.ValidationMessageFor(x => x.Venue.State)
...
}
This works, but the problem is that the the Required attribute on the view model appears to be ignored. If I look at the HTML, the data-val-* attributes are missing as well.
<select id="Venue_State" name="Venue.State">...</select>
However, if I change the rendering to a textbox...
using (var form = Html.BeginForm())
{
...
#Html.TextBoxFor(x => x.Venue.State)
#Html.ValidationMessageFor(x => x.Venue.State)
...
}
I see the expected data-val-* attributes and the validation works:
<input data-val="true"
data-val-required="This field is required"
id="Venue_State" name="Venue.State" type="text" value="">
I should note that I have other view models elsewhere that use DropDownListFor with a flat view model (no nested objects) and the validation works fine there, so I'm thinking I've hit a bug in the MVC validation handling for drop down lists when using a nested view model. Can anyone confirm / advise?
As far as I know you can't have client side validation on nested objects. And a quick google search seems to confirm that.
http://forums.asp.net/t/1737269.aspx/1

UIHint does not work with IList?

I have a property in my ViewMode:
[UIHint("FileUpload")]
public IList<string> Images { get; set; }
In view Create.cshtml
#html.ValidationSummary(true)
#html.EditorForModel()
In the folder Shared/EditorTemplates/FileUpload.cshtml
<h3>Test</h3>
But the field is not displayed. Simply, nothing happens!
I did the same test with another type of field and it worked:
[UIHint("FileUpload")]
public string Test { get; set; }
What could be wrong?
How do you solve this problem?
If I manually add the code below in my Create.cshtml view, it works!
#Html.EditorFor(m => m.Images)
I do not know what to do.
Yes, UIHint doesn't work with lists. You will need a loop inside the corresponding editor template (~/Views/Shared/EditorTemplates/FileUpload.cshtml). The UIHint template is passed the model which in this case is a IList<string>.
#model IList<string>
#foreach (var item in Model)
{
...
}

Using ASP.NET MVC 3 with Razor, what's the most effective way to add an ICollection to a Create view?

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.

EditorFor is not getting the right Editor in ASP.NET MVC 3.0

I have a situation where I want to use a custom EditorTemplate with a ViewModel. So I have my ViewModel...
class Aspect {
}
class AspectViewModel {
}
then my EditorTemplate
Views
Shared
EditorTemplates
Aspect.cshtml
Aspect.cshtml
#model AspectViewModel
// other html
Then in another view that takes AspectViewModel, I call #Html.EditorFor(model => model), but it does not work. It only works if I use a hard-coded string #Html.EditorForModel("Aspect").
Any idea why it isn't being called?
You should name the editor template AspectViewModel.cshtml if it is strongly typed to AspectViewModel. Then all you have to do is:
#model AspectViewModel
#Html.EditorForModel()
or
#model SomeViewModel
#Html.EditorFor(x => x.Aspect)
where the Aspect property of SomeViewModel is of type AspectViewModel.
The convention is that the editor/display should be named as the type of the property you are calling it on and not the name of this property.
They also work greatly with collections. For example if you have the following property:
public class SomeViewModel
{
public IEnumerable<AspectViewModel> Aspects { get; set; }
}
and you use:
#model SomeViewModel
#Html.EditorFor(x => x.Aspects)
then the ~/Views/Shared/EditorTemplates/AspectViewModel.cshtml editor template wil be rendered for each element of this collection. Thanks to this you really no longer need to ever write for/foreach loops in your views.
This is because your model is AspectViewModel, but your view name is Aspect. They must match exactly.

Strongly Typed RadioButtonlist

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

Resources