I'm very new to MVC 5 and web programming in general so please bear with me.
I have a view (used to manage user roles) where I have three separate forms, which I more or less copied and pasted from a tutorial. In the tutorial the fields for the forms were created in the following way:
Username : #Html.TextBox("Username")
Since I wanted the styling to work for them, I changed the code to look more like the default forms in the MVC 5 template, so it ended up looking like this:
#Html.LabelFor(m => m.GetRolesUsername, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.GetRolesUsername, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.GetRolesUsername, "", new { #class = "text-danger" })
</div>
My model ManageUserRolesViewModel looks like this (note that at the top of my view I have #model ManageUserRolesViewModel):
public class ManageUserRolesViewModel
{
#region Assign Role
[Required]
[Display(Name = "Username", ResourceType = typeof(Resources))]
public string AssignRoleUsername { get; set; }
[Required]
[Display(Name = "RoleName", ResourceType = typeof(Resources))]
public string AssignRoleRole { get; set; }
#endregion
#region Get Roles
[Required]
[Display(Name = "Username", ResourceType = typeof(Resources))]
public string GetRolesUsername { get; set; }
#endregion
#region Unassign Role
[Required]
[Display(Name = "Username", ResourceType = typeof(Resources))]
public string UnassignRoleUsername { get; set; }
[Required]
[Display(Name = "RoleName", ResourceType = typeof(Resources))]
public string UnassignRoleRole { get; set; }
#endregion
}
Notice how I'm using annotations to load the name of the elements in the ViewModel directly from resources. I'm doing this for localization purposes, and the resources are returning strings in Spanish. I think this may be the root of my issue, but I'm not sure.
Then, in my controller I have the following method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult GetRoles(string UserName)
{
if (!string.IsNullOrWhiteSpace(UserName))
{
ApplicationUser user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
ViewBag.RolesForThisUser = this.UserManager.GetRoles(user.Id);
ViewBag.Roles = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
}
return View("ManageUserRoles");
}
Now, here's what happens: if I use Username : #Html.TextBox("Username"), when the method GetRoles() gets called in the controler, the UserName parameter is there and the user is successfully loaded. If instead I use the
#Html.LabelFor(m => m.GetRolesUsername, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.GetRolesUsername, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.GetRolesUsername, "", new { #class = "text-danger" })
</div>
when the method gets called, the UserName parameter is null.
My wild guess is that somewhere in the code MVC is looking for UserName or Username and finding Usuario instead, but I am not sure if this is true and in any case, I'd like to know how to solve the issue.
Assuming your form has the model defined as:
#model ManageUserRolesViewModel
and somewhere in the view:
#Html.TextBoxFor(m => m.GetRolesUsername, new { #class = "form-control" })
The action should look like this:
[HttpPost]
public ActionResult GetRoles(ManageUserRolesViewModel vm)
{
string userName = vm.GetRolesUsername;
//rest of code omitted.
}
So you do not rely on the UserName parameter and can use the view model itself.
if this does not suffice, you could do this:
#Html.TextBoxFor(x => x.GetRolesUsername, new { Name = "UserName" })
Hi Eric,
#Html.TextBox("Username")
// It creates a html input element with name "Username"
<input type="text" id=""Username" name="Username" value="" />
And
#Html.TextBoxFor(m => m.GetRolesUsername)
// It also create a html element with name as it's property name. ie, "GetRolesUsername"
<input type="text" id=""Username" name="Username" value="#Model.GetRolesUsername" />
So, if you submit your form, your browser will send parameters as,
Username = "value in the #html.TeaxtBox()",
GetRolesUsername = "value in the #html.TextBoxFor()"
So, both values will be passed to your MVC controller. Then you can decide what parameters you want to receive.
public ActionResult Submit(string Username, string GetRolesUsername)
{
// You can get Username and GetRolesUsername here
}
public ActionResult Submit(string Username)
{
// You tell your controller that I am expecting only one parameter and that is Username
}
Then there will be a main difference between `#html.TextBox()` and `#html.TextBoxFor()' is,
`#html.TextBox()` will just create a text element. But, `#html.TextBoxFor()' will create and set value to the element.
Hope this will help you.
If you have any doubts, please feel free to ask me.
**And Grand Welcome To MVC.**
I am also just a beginner in MVC :)
Related
I am trying to post multiselect option and save it in Db. I did try few options but same did not work.
Client model class:
public string OwnerName { get; set; }
public string UnitNumber { get; set; }
public int AreaId { get; set; } // This is a foreign key
public string AreaName { get; set; } // This will display dropdown list
Controller action method - I am using FormCollection to collect the AreaId from the view to post in Client model class:
public ActionResult ClientDeal(Client model, FormCollection formCollection)
{
string selectedArea = formCollection["AreaId"];
}
Here is the view with the field I am having trouble with
<div class="form-group col-md-4">
<label class="control-label col-md-8 font-weight-bold">Area</label>
<div class="col-md-10">
#Html.DropDownListFor(model => model.AreaId, Model.Areas, new { #class = "form-control selectpicker select", #multiple = "multiple" })
#Html.ValidationMessageFor(model => model.Areas, "", new { #class = "text-danger" })
</div>
</div>
Thank you in advance
this line is wrong:
#Html.DropDownListFor(model => model.AreaId, Model.Areas, new { #class = "form-control selectpicker select", #multiple = "multiple" })
You need to bind the dropdown to a collection property, have something like:
public int[] AreaIds { get; set; }
and bind it like this:
#Html.DropDownListFor(model => model.AreaIds,
There's no need to set the value Model.Areas in the helper declaration, just set in the ViewModel when you return the view:
return View(new Client { AreaIds = "what you now have in Model.Areas" }
There's no need to use FormCollection
This question already has an answer here:
MVC model validation
(1 answer)
Closed 6 years ago.
I have a form like following in my MVC application:
#using (Html.BeginForm("Register", "User", FormMethod.Post))
{
<div>
#Html.TextBoxFor(m => m.FirstName, new { placeholder = "First name", #class = "form-control", #type = "text" })
</div>
<div>
#Html.TextBoxFor(m => m.LastName, new { placeholder = "Last name", #class = "form-control", #type = "text" })
</div>
<div>
#Html.TextBoxFor(m => m.Email, new { placeholder = "Email", #class = "form-control", #type = "email" })
</div>
<div>
#Html.TextBoxFor(m => m.Password, new { placeholder = "Password", #class = "form-control", #type = "password" })
</div>
<div>
#Html.TextBoxFor(m => m.PasswordConfirm, new { placeholder = "Confirm password", #class = "form-control", #type = "password" })
</div>
<div>
#Html.DropDownListFor(model => model.SelectedCountryId, Model.Countries, new { #class="select2_single form-control select2-hidden-accessible", #tabindex = "-1" })
</div>
<div>
<input class="btn btn-default submit" type="submit" value="Register" />
</div>
}
My ViewModel looks like following:
public class UserRegistrationViewModel
{
[Required(ErrorMessage = "First name is required!")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name is required!")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email name is required!")]
public string Email { get; set; }
[Required(ErrorMessage = "Password name is required!")]
public string Password { get; set; }
[Required(ErrorMessage = "Password confirmation name is required!")]
public string PasswordConfirm { get; set; }
public int SelectedCountryId { get; set; }
[Required(ErrorMessage = "Country needs to be selected!")]
public SelectList Countries { get; set; }
}
And these are my two actions:
public ActionResult Index()
{
var model = new UserRegistrationViewModel();
var countries = Connection.ctx.Countries.OrderBy(x => x.CountryName).ToList();
model.Countries = new SelectList(countries, "CountryId", "CountryName");
return View(model);
}
[HttpPost]
public ActionResult Register(UserRegistrationViewModel model)
{
if (ModelState.IsValid)
{
var user = new Users();
user.FirstName = model.FirstName;
user.LastName =model.LastName;
user.Email = model.Email;
user.PasswordSalt = Helpers.PasswordHelper.CreateSalt(40);
user.PasswordHash = Helpers.PasswordHelper.CreatePasswordHash(model.Password, user.PasswordSalt);
user.CountryId = Convert.ToInt32(model.SelectedCountryId);
user.Active = true;
Connection.ctx.Users.Add(user);
Connection.ctx.SaveChanges();
var role = new UserRoles();
role.RoleId = 2;
role.UserId = user.UserId;
role.Active = true;
user.UserRoles.Add(role);
Connection.ctx.SaveChanges();
return RedirectToAction("Index");
}
return null;
}
Now my question here is what do I do if the model state is not valid (ie. display the error messages that I've set up in my ViewModel)???
Do I just do `return View(); or ??
I need to render those messages on my view now...
Whenever I get an invalid form being submitted, I return the View() back for them to correct the issue. Taking them to an error page where they would have to come back to the form and start again would frustrate the user. Give them back the invalid form and tell them what needs correcting.
Now, what needs correcting can be read from the ViewBag(). Or you can have inside you Model some properties that will hold your error message for the user and display them if they are not null.
In the case of an invalid model state, you can just return the current view with the model as a parameter:
if (!ModelState.IsValid)
{
return View(model);
}
EDIT: In your html, add the html elements to show the validation messages:
#Html.ValidationMessageFor(model => model.FirstName)
I have an EmailFormModel class.
public class EmailFormModel
{
[Required, Display(Name = "Your Name:")]
public string FromName { get; set; }
[Required, Display(Name = "Your Email:")]
public string FromEmail { get; set; }
[Required, Display(Name = "To Email:")]
public string ToEmail { get; set; }
public List<SelectListItem> CCEmail { get; set; }
[Required]
[AllowHtml]
public string Message { get; set; }
public EmailFormModel()
{
CCEmail = new List<SelectListItem>();
}
}
Now I need this email to have multiple CC recipients, hence why I made the property CCEmail a type of List. In my HttpGet method I am populating the list which is correctly working. In my HttpPost I am doing this:
foreach(var item in model.CCEmail)
{
message.CC.Add(new MailAddress(item.Text));
}
Now, in my View... what can I do to display these email addresses.. so that when I hit Submit they will be submitted as email addresses?
Currently in my View I have this:
<div class="form-group">
#Html.LabelFor(m => m.CCEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.ListBoxFor(m => m.CCEmail,null, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.CCEmail)
</div>
</div>
Is there another/better way to display the email addresses rather than ListBoxFor?
But when I select the email addresses.. and then hit Submit, I get an error message:
The value 'John.Doe#test.com,Test.User1#test.com' is invalid.
Those aren't the real email addresses.. the ones that I am using are valid.
Any help is appreciated.
Even though I have found an alternative solution, I am still looking for a cleaner solution. Here is what I have done.
I changed the CCEmail property to a List<string>.
So, in the HttpPost method I changed the foreach loop to this syntax:
foreach(var item in model.CCEmail)
{
message.CC.Add(new MailAddress(item));
}
Then in my view, I did this:
<div class="form-group">
#foreach(var item in Model.CCEmail)
{
#Html.LabelFor(m => m.CCEmail, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBox("CCEmail", item, htmlAttributes: new { #class = "form-control", #readonly = true } )
</div>
}
</div>
Even though this creates 2 separate textboxes, it still submits as 2 separate email addresses instead of both of them combined as what I think the error in my OP was.
Again, if you know of a simpler/cleaner solution, please post!
I want to use remote validation to check to see if a Username exists. I am using a Viewmodel to create users. While I can do this to get validation for creation or editing purposes, it will not work for both creating and editing. Here is my model:
[Required]
[Display(Name = "Homeowner Username")]
[Remote("doesUserNameExist", "Homeowners", HttpMethod = "POST", ErrorMessage = "User name already exists. Please enter a different user name.", AdditionalFields = "InitialUsername")]
Here is my edit view:
#Html.Hidden("Homeowner.InitialUsername", Model.Username)
<div class="form-group">
#Html.LabelFor(model => model.Username, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Username, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Username, "", new { #class = "text-danger" })
</div>
</div>
Here is my controller in the version that works for registration but not editing(when editing, parameters are null):
public JsonResult doesUserNameExist([Bind(Prefix = "Homeowner.Username")]string Username, [Bind(Prefix = "InitialUsername")] string InitialUsername)
{
MY CODE
}
Here is my controller that works for editing but not creating(when creating, both parameters are null):
public JsonResult doesUserNameExist([Bind(Include = "Homeowner.Username")]string Username, [Bind(Include = "InitialUsername")] string InitialUsername)
{
MY CODE
}
I have tried many variations of this but just can't get it.
I have looked here: ASP.NET MVC Binding with Remote Validation
Here:
Remote ViewModel validation of nested objects not working
And here: http://forums.asp.net/t/1652512.aspx?Compound+View+Model+object+causing+remote+validation+failure
But I seem to be missing something. Is there a way I can make this work for both editing and registering? I am pretty new at this, and would greatly appreciate any ideas!
Edit:
Perhaps this is a poor design choice(first time using view models, only been coding a few months), but I was trying to create a new homeowner and address at the same time as when I create a new application user in that role. Here is the viewmodel I am using:
public class RegisterHomeownerViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public int roles { get; set; }
public virtual Address Address { get; set; }
public virtual Homeowner Homeowner { get; set; }
}
Here is my method in the account controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> RegisterHomeowner(RegisterHomeownerViewModel model, Address address, Homeowner homeowner)
{
ApplicationDbContext db = new ApplicationDbContext();
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var role = db.Roles.Find("0");
UserManager.AddToRole(user.Id, role.Name);
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
db.Addresses.Add(address);
homeowner.UserId = user.Id;
homeowner.AddressID = address.ID;
db.Homeowners.Add(homeowner);
db.SaveChanges();
return RedirectToAction("Index", "Homeowners");
}
AddErrors(result);
}
return View(model);
}
Here is the view I am using to create those entities:
<div class="form-horizontal">
<h4>RegisterViewModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Homeowner.Username, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Homeowner.Username, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Homeowner.Username, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Email, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Email, "", new { #class = "text-danger" })
</div>
</div>
Everything does work as far as I can tell except for remote validation on Username. I can get it to give me an error when creating in the account method above, or I can get an error when editing by deleting the Prefix (which makes it so my Username is not recognized. There is obviously something I am doing wrong.
I got it. I read Stephen's post a few times and a few threads on virtual properties. I went to bed, got up, and revised my viewmodel by removing all data models. Then, for validation when creating, I compared the input to the validation properties in my view model instead of my actual model. This works for creation. Then when I edit, I compare my input against the validation properties in my actual model. It made sense when I learned you can have different validation for a viewmodel than there is in the model. This thread helped too: View Model virtual properties and drop down lists. Thanks so much! I learned a lot from this!
I'm relatively new to MVC, and I kicked off with the default MVC project template in visual studio 2013 (Express).
I modified the views to suit my needs (temporarily), then followed this tutorial to add 3 new fields to the user registration process. I added BirthDate and Email as in the tutorial, and a third one: FirstName. Everything built and published fine.
After this, I uploaded the published site to my domain here and tested in Chrome. All of the pages load correctly, but when I try to register a user, I get taken to the "Error" page. I've checked the JS console and the only entry is: event.returnValue is deprecated. Please use the standard event.preventDefault() instead.
Here is the code for my C# classes as shown in the tutorial above:
Account/Register View (New fields):
<div class="form-group">
#Html.LabelFor(m => m.Email, new { #class = "col-md-2 control-label" })
<div class="col-md-10">#Html.TextBoxFor(m => m.Email, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.FirstName, new { #class = "col-md-2 control-label" })
<div class="col-md-10">#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
</div>
Account ViewModel (Register ViewModel New Fields):
[Required]
[DataType(DataType.Date)]
public DateTime BirthDate { get; set; }
[Required]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
Identity Model (ApplicationUser Method):
public class ApplicationUser : IdentityUser
{
public DateTime BirthDate { get; set; }
public string Email { get; set; }
public String FirstName { get; set; }
}
Account Controller (Post:Register Method):
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName, BirthDate = model.BirthDate, Email = model.Email, FirstName = model.FirstName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Please could you let me know if you think you know what is causing the error page (I'll leave it live so you can see for yourself) or if you know how I can find out the specific error. Any help will be greatly appreciated. Thanks.
Update 1: I get taken to the same error pag on logging in rather than being told a username is not in database. Is this meant to happen?
Update 2: It runs fine locally.