I am new to mvc and would like to add an additional field to my registration page that is simply a dropdownlist bound to a table in my model (a table of organization names and IDs). However, in my default application I see that the AccountController is using the RegisterModel model to create the register form view. This is fine, I don't want to disturb this. But I want to add a new select box on the page bound to a different model (my model with the organizations). How do I accomplish this?
I've found other posts that suggest I create a wrapper model for both my model and the RegisterModel, but this isn't working. My wrapper model looks like this:
public class RegisterPeopleModel
{
public RegisterModel reg { get; set; }
public fwfEntities fwf { get; set; }
}
And now the field validator for password is no longer working. The code in the view:
<div class="editor-field">
#Html.PasswordFor(m => m.reg.ConfirmPassword)
#Html.ValidationMessageFor(m => m.reg.ConfirmPassword)
</div>
Now renders this:
<div class="editor-field">
<input data-val="true" data-val-equalto="The password and confirmation password do not match." data-val-equalto-other="*.Password" id="reg_ConfirmPassword" name="reg.ConfirmPassword" type="password" />
<span class="field-validation-valid" data-valmsg-for="reg.ConfirmPassword" data-valmsg-replace="true"></span>
</div>
Notice that the IDs of the span and input no longer match. The form no longer works at all. This is leading me to believe I'm taking the wrong approach. Is there a better way of getting my select list on the page bound to a different model?
Thanks in advance.
Is there a better way of getting my select list on the page bound to a
different model?
If the model doesn't meet the requirements of the view you should modify this model. In ASP.NET MVC it is a good practice to design view models and pass only view models to views and not domain models (like for example EF autogenerated classes which is what this fwfEntities type seem to be). So design a view model which contains only the properties needed by your view and have your controller action query the database in order to fetch the model then map the model to a view model and finally pass this view model to the view.
Without seeing RegisterModel my guess is that ConfirmPassword has not got a required attribute for example;
[Required(ErrorMessage = "A password is required")]
public string ConfirmPassword { get; set; }
oh I believe the period in the id is replaced with an underscore so as not to cause issues with Jquery.
Related
I have an Index view in my application that shows a list of vendors. I also want to add a small form to add new items right on that page. The create form will post to the Create action. My model class contains a list of vendors, plus one property for a single vendor named NewVendor.
public IEnumerable<SoftwareVendor> Vendors { get; set; }
public SoftwareVendor NewVendor { get; set; }
The SoftwareVendor class has validation attributes. It's an Entity Framework class.
Making a form that posts to the Create action is easy:
#using (Html.BeginForm( "Create", "Vendor" )) {
#Html.ValidationSummary(true)
<fieldset>
<legend>New Software Vendor</legend>
<div class="editor-label">
#Html.LabelFor(model => model.NewVendor.Name)
</div>
<div class="editor-field">
#Html.EditorFor( model => model.NewVendor.Name )
#Html.ValidationMessageFor( model => model.NewVendor.Name )
</div>
<br />
<input type="submit" value="Create" />
</fieldset>
}
This posts just fine, and client-side validation also works. However, the default Create action takes an instance of SoftwareVendor and is looking for a key in the form collection called "Name". Instead, the above form posts "NewVendor.Name".
I can remedy this by specifying a template and field name in #Html.EditorFor.
#Html.EditorFor( model => model.NewVendor.Name, "string", "Name" )
Now the Create action is happy because the "Name" value is being received. However, the validation message is broken because it is still looking for a field named "NewVendor.Name", and there seems to be no way to override this.
<span class="field-validation-valid" data-valmsg-for="NewVendor.Name" data-valmsg-replace="true"></span>
Is there something simple I'm missing to make this work?
Here is a list of things I can do to solve this:
Have my Create action take an instance of my Index model instead of a SoftwareVendor. I still have a traditional Create view, though, and I don't want to do this.
Don't have my Create action take any parameters. Instead, manually look at the form keys and pull the name from either "Name" or "NewVendor.Name", whichever is there.
Have the Create action take both model classes and detect which one got populated properly. This is a lot like #2 but I'm checking properties for non-null values instead of checking the form collection.
Figure out how to make a model binder that will perform what #2 is doing. This seems overly complicated, and I'm going to have this problem in a number of pages, so I'm hoping for an easier way.
Use javascript to make the post instead of a form submit, so I can control the exact field names I'm posting. This works, but I'd prefer to leverage an HTML form, since that's what it's for.
Use the overload of EditorFor to specify the field name, and create the validation message manually.
Write my own extension method on HtmlHelper for a new ValidationMessageFor that can override the field name.
Of these options, #2 or #5 are the ones I think I'd choose from unless there's a better way.
Well, this worked:
#Html.EditorFor( model => model.NewVendor.Name, "string", "Name" )
#Html.ValidationMessage( "Name" )
Since my only real problem with my above code was a broken validation message, this seems to solve my problem. I'm still curious if there is a better solution overall.
Is it possible to set authorization on a specific field in MVC 3?
My initial thought (and MSDN research) indicates that the [Authorize] tag is only for controller level actions (create,edit,index,etc). I can do this on the controller action:
[Authorize(Roles = "RoleA,RoleB")]
public ActionResult Create()
{
return View(new Tracking());
}
The scenario is that two roles (RoleA and RoleB) can access the 'Edit' controller. But only RoleA can change the first field. The other role (B) can only view the field.
I would like to do something like this on a specific field:
[Required]
[Range(1, 99)]
[Authorize(Roles = "RoleA")]
public int Sequence { get; set; }
UPDATE1:
A little more research down the StackOverflow rabbit roles reveals that I need to use partial views.
So in my view I add this code:
<div>
#if (Context.User.IsInRole("RoleA"))
{
#Html.Partial("_SequenceEdit")
}
else
{
#Html.Partial("_SequenceView")
}
</div>
So if the user is RoleA they get a partial view that allows editing of the 'sequence' field. Otherwise they get a view only of the 'sequence' field.
My view only partial view looks like this:
<div class="editor-label">
#Html.LabelFor(model => model.Sequence)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.Sequence)
#Html.HiddenFor(model => model.Sequence)
#Html.ValidationMessageFor(model => model.Sequence)
</div>
I see that you've already figured out how to modify the view in order to not show a text box to users in Role B. But you should also do server-side validation to make sure only users in Role A can edit the field.
[Authorize(Roles = "RoleA,RoleB")]
[HttpPost]
public ActionResult Edit(int trackingID, Tracking newTrackingObject)
{
// grab the current version of the tracking object from your data repo
var oldTrackingObject = trackingRepo.GetByID(trackingID);
// check if the user is in role A and edit the sequence number
if(Context.User.IsInRole("RoleA"))
oldTrackingObject.Sequence = newTrackingObject.Sequence;
// continue processing the new tracking object
// after all processing is done, persist the edited tracking object back to the repo
trackingRepo.Update(oldTrackingObject);
trackingRepo.SaveChanges();
}
This will prevent users in Role B from changing the sequence field by manually editing the hidden form field (eg. with FireBug or a similar tool.)
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
i'm trying to put DropDownList validation to work.
in model:
[Required(ErrorMessage = "this field is required")]
public int ObjectTypeID { get; set; }
in view:
<div class="editor-field">
#Html.DropDownList("ObjectTypeID", string.Empty)
#Html.ValidationMessageFor(model => model.ObjectTypeID)
</div>
if the user leaves the selection empty i expect client side validation to alarm. but this does not happen.
what can be done?
The behavior of system types is that they must have a value when initalized. An integer has a value of "0". Change your model to accept a nullable int:
public int? ObjectTypeID { get; set; }
Just wondering, but why not use DropDownListFor?
For client side validation to work I think you need to turn on ClientValidationEnabled & UnobtrusiveJavaScriptEnabled in the web.config for your project, I believe you also need to reference the jquery.validate.unobtrusive.min.js script on your page?
1) You are not loading your dropdownlist
2) Use DropDownListFor in order to match validation with ddl
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.