i'm having issues with implementing a ViewModel class which acts as a wrapper class around a Model class called "User" generated by L2SQL. In short, I have two instances when i have to validate against the L2SQL model. One when the user first signs up, and the second when the user logs in and edits only part of his account data.
The initial "problem" was when trying to edit & validate only some the account details against the original L2SQL model from a View which only displays the few (e.g FirstName, LastName, Email), the missing ones (e.g. Password) would flair up when validation was run. The Password settings will have its own View.
I was advised here on StackOverflow that adding a wrapping ViewModel class would be the best solution. So I did, implemented below:
ViewModel code:
[Bind]
public class AccountEdit
{
public User UserAccount { get; set; }
[Required(ErrorMessage = "First name required"), StringLength(20, MinimumLength = 3, ErrorMessage = "Must be between 3 and 20 characters")]
public string FirstName { get { return UserAccount.FirstName; } set { UserAccount.FirstName = value; } }
[Required(ErrorMessage = "Last name required"), StringLength(20, MinimumLength = 3, ErrorMessage = "Must be between 3 and 20 characters")]
public string LastName { get { return UserAccount.LastName; } set { UserAccount.LastName = value; } }
[Required(ErrorMessage = "Email address required"), RegularExpression("^[a-z0-9_\\+-]+(\\.[a-z0-9_\\+-]+)*#[a-z0-9-]+(\\.[a-z0-9-]+)*\\.([a-z]{2,4})$", ErrorMessage = "Must be a valid email address")]
public string Email { get { return UserAccount.Email; } set { UserAccount.Email = value; } }
}
Controller code:
//
// GET /User/Account
public ActionResult Account()
{
string cookieUser = User.Identity.Name;
User user = userRepository.GetUserByEmail(cookieUser);
return View(user);
}
// POST /User/Account
[HttpPost]
public ActionResult Account(AccountEdit model)
{
if (ModelState.IsValid)
{
model.LastUpdated = DateTime.Now;
userRepository.Save();
}
return View(model);
}
Veiw code:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/User.Master" Inherits="System.Web.Mvc.ViewPage<Digitalent.Models.User>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Account settings
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary("Oops! Please correct the errors and try again!") %>
<p>
<label for="FirstName">First name:</label>
<%: Html.TextBoxFor(model => model.FirstName, new { #class = "textfield" })%>
<%: Html.ValidationMessageFor(model => model.FirstName) %>
</p>
<p>
<label for="LastName">Last name:</label>
<%: Html.TextBoxFor(model => model.LastName, new { #class = "textfield" })%>
<%: Html.ValidationMessageFor(model => model.LastName) %>
</p>
<p>
<label for="Email">Email address:</label>
<%: Html.TextBoxFor(model => model.Email, new { #class = "textfield" })%>
<%: Html.ValidationMessageFor(model => model.Email) %>
</p>
<p>
Email newsletter: <%= Html.CheckBoxFor(model => model.EmailNewsletter) %>
<label for="EmailNewsletter">Keep me posted with latest Digitalent happenings.</label>
</p>
<p>
<input type="submit" value="Save changes" class="button" />
</p>
<p>Change your password settings</p>
<% } %>
But now when i tried to run the app i get an error when it runs the validation:
'Object reference not set to an instance of an object.'
On the line below in my ViewModel:
'public string FirstName { get { return UserAccount.FirstName; } set { UserAccount.FirstName = value; } }'
Where am I going wrong or can anyone else tell me a better recommended approach as i'm a novice. Help please!
Change your view from:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/User.Master"
Inherits="System.Web.Mvc.ViewPage<Digitalent.Models.User>" %>
to:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/User.Master"
Inherits="System.Web.Mvc.ViewPage<Digitalent.Models.AccountEdit>" %>
Related
I've got two forms on my page. My viewmodel looks like this:
public class HomeViewModel
{
[Required(ErrorMessage = "We kind of need an email address!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string Email { get; set; }
public ContactForm ContactForm { get; set; }
}
ContactForm:
public class ContactForm
{
[Required(ErrorMessage = "We need your name, please!")]
public string Name { get; set; }
[Required(ErrorMessage = "We need your email, please!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please elaborate a little!")]
public string Message { get; set; }
}
First form action:
public ActionResult FreeConsultSubmit(HomeViewModel model)
{
if (ModelState.IsValid)
{
//do stuff
}
return PartialView("_SubmitResult", false);
}
Second Action:
public ActionResult ContactSubmit(HomeViewModel model)
{
if (ModelState.IsValid)
{
//dostuff
}
return PartialView("_SubmitContactResult", false);
}
First Ajax Form
#using (Ajax.BeginForm("FreeConsultSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FreeConsultResults"
}))
{
<div id="FreeConsultResults">
<div class="row">
<div class="small-4 columns small-centered text-center splashEmail">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "Please enter your email..." })
#Html.ValidationMessageFor(m => m.Email)
</div>
</div>
<div class="row">
<div class="small-5 columns small-centered text-center">
<input type="submit" class="hvr-border-fade splashCallToAction" value="Get Started" />
</div>
</div>
</div>
}
Second Ajax Form:
#using (Ajax.BeginForm("ContactSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ContactSubmitResults"
}))
{
<div id="ContactSubmitResults">
<div class="row">
<div class="small-12 columns small-centered">
#Html.TextBoxFor(m => m.ContactForm.Name, new { #placeholder = "Your Name" })
#Html.ValidationMessageFor(m => m.ContactForm.Name)
#Html.TextBoxFor(m => m.ContactForm.EmailAddress, new { #placeholder = "Your Email Address" })
#Html.ValidationMessageFor(m => m.ContactForm.EmailAddress)
#Html.TextAreaFor(m => m.ContactForm.Message, new { #placeholder = "Your Message", #class = "contactMessage" })
#Html.ValidationMessageFor(m => m.ContactForm.Message)
</div>
</div>
<div class="row">
<div class="small-12 columns small-centered text-center">
<a href="">
<input type="submit" class="hvr-border-fade sendMessageSmall" value="Send Message" />
</a>
</div>
</div>
</div>
}
I've got everything wired up fine, and client side validation works as it should. This may be overkill, but I also wanted to set up server side validation.
The issue is, when submitting the first form (just the email string), I check if the ModelState is valid, and it is. If you drill down into the ModelState, you see that only 1 property is being looked at.
If you submit the second form though (the ContactForm), the ModelState.IsValid returns false, and if you drill down, you see that it's looking at 4 properties (the 3 ContactForm properties, plus the string email). Since the email string is required, it fails.
I'm confused as to why it works for one, but not the other. I could just remove the server side validation, but I'd at least like to know why this is the case. I could also remove the error from the ModelState, but that doesn't seem elegant at all.
If you simply are trying to have two separate forms within one view, you're probably better off splitting the forms into separate "sub views" and child actions, and then using #Html.Action() to render them in place.
Here's an example:
Models
I'd remove the ContactForm model from HomeViewModel, and rename HomeViewModel to ConsultingForm (to match your naming convention for the contact model):
public class ConsultingForm
{
[Required(ErrorMessage = "We kind of need an email address!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string Email { get; set; }
}
public class ContactForm
{
[Required(ErrorMessage = "We need your name, please!")]
public string Name { get; set; }
[Required(ErrorMessage = "We need your email, please!")]
[EmailAddress(ErrorMessage = "This isn't an email address!")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Please elaborate a little!")]
public string Message { get; set; }
}
Controller
Add "child" actions, like those below:
public class HomeController : Controller
{
public ActionResult Home()
{
return View();
}
[ChildActionOnly, HttpGet]
public ActionResult ConsultingRequest()
{
var model = new ConsultingForm();
return View(model);
}
[ChildActionOnly, HttpGet]
public ActionResult ContactRequest()
{
var model = new ContactForm();
return View(model);
}
}
The ChildActionOnlyAttribute marks the action as a child action. From the MSDN:
A child action method renders inline HTML markup for part of a view
instead of rendering a whole view.
Views
Your first subview will be the same as you already have:
#using (Ajax.BeginForm("FreeConsultSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "FreeConsultResults"
}))
{
<div id="FreeConsultResults">
<div class="row">
<div class="small-4 columns small-centered text-center splashEmail">
#Html.TextBoxFor(m => m.Email, new { #placeholder = "Please enter your email..." })
#Html.ValidationMessageFor(m => m.Email)
</div>
</div>
<div class="row">
<div class="small-5 columns small-centered text-center">
<input type="submit" class="hvr-border-fade splashCallToAction" value="Get Started" />
</div>
</div>
</div>
}
Your second subview simply needs to remove the extra "step" in the property bindings:
#using (Ajax.BeginForm("ContactSubmit", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "ContactSubmitResults"
}))
{
<div id="ContactSubmitResults">
<div class="row">
<div class="small-12 columns small-centered">
#Html.TextBoxFor(m => m.Name, new { #placeholder = "Your Name" })
#Html.ValidationMessageFor(m => m.Name)
#Html.TextBoxFor(m => m.EmailAddress, new { #placeholder = "Your Email Address" })
#Html.ValidationMessageFor(m => m.EmailAddress)
#Html.TextAreaFor(m => m.Message, new { #placeholder = "Your Message", #class = "contactMessage" })
#Html.ValidationMessageFor(m => m.Message)
</div>
</div>
<div class="row">
<div class="small-12 columns small-centered text-center">
<a href="">
<input type="submit" class="hvr-border-fade sendMessageSmall" value="Send Message" />
</a>
</div>
</div>
</div>
}
The "wrapper", or parent view, will look something like:
<div class="whatever">
#Html.Action("ConsultingRequest", "Home")
#Html.Action("ContactRequest", "Home")
</div>
If you inspect the rendered HTML, you'll see that each form is properly bound to only its model's properties, so when you post each form, only those properties are model-bound and validated.
I have a aspx page in my view, which calls a partial view,
using (Html.BeginForm("Edit", "MyReport", FormMethod.Post, new { name = "myform", autocomplete = "off" }))
{%>
<%
Html.RenderPartial("_Form");%>
<% } %>
partial view is
<div>
<%=Html.ValidationSummary()%>
</div>
<div class="fieldRow">
<span class="fieldCell threeCol">
<label for="schedule.SavedReportName">
Name<span class="requiredField">*</span>
</label>
<%=Html.TextBox("schedule.SavedReportName", ViewData.Model.SavedReportName, new {style = "width:516px;"})%>
</span>
</div>
My model has this property
[Required(ErrorMessage = "Name is required.")]
[StringLength(60, ErrorMessage = "Maximum 60")]
public string SavedReportName { get; set; }
The problem I am getting is, I am able to see string length validation error twice.
What might be the problem?
I Have controller with following methods:
public ActionResult Create()
{
return View();
}
[Authorize]
[HttpPost]
public ActionResult Create(Tests test)
{
test.CreateDate = DateTime.Now;
test.Author = User.Identity.Name;
TestEntities db = new TestEntities();
db.AddToTests(test);
db.SaveChanges();
return RedirectToAction("CreateQuestion", new { OrderNumber = 1, idTest = test.id });
}
[Authorize]
public ActionResult CreateQuestion(int OrderNumber,int idTest)
{
return View();
}
[Authorize]
[HttpPost]
public ActionResult CreateQuestion(Questions question)
{
TestEntities db = new TestEntities();
db.AddToQuestions(question);
db.SaveChanges();
return RedirectToAction("CreateQuestion", new {id = question.id, t = question.Type});
}
The problem is Create methods works right. It get parameter and adds it to DB. But similar method CreateQuestion displays message about question is null.
What do I wrong?
CreateQuestion view
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<test.su.Models.Questions>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Создать вопрос
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Создать вопрос</h2>
<% using (Html.BeginForm("CreateQuestion","Test")) { %>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Вопрос</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Type,"Тип вопроса") %>
</div>
<% // List of question types
List<SelectListItem> QuestionTypes = new List<SelectListItem>();
SelectListItem t = new SelectListItem();
t.Text = "Вопрос с вариантами ответа (флажки или радиокнопки)";
t.Value = "0";
QuestionTypes.Add(t);
t = new SelectListItem();
t.Text = "Вопрос со свободным ответом (текстовое поле)";
t.Value = "1";
QuestionTypes.Add(t);
%>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.Type, QuestionTypes) %>
<%: Html.ValidationMessageFor(model => model.Type) %>
</div>
<%-- <div class="editor-label">
<%: Html.LabelFor(model => model.OrderNumber,"Порядковый номер вопроса") %>
<%: Html.EditorFor(model => model.OrderNumber) %>
<%: Html.ValidationMessageFor(model => model.OrderNumber) %>
</div>--%>
<div class="editor-label">
<%: Html.LabelFor(model => model.Question,"Текст вопроса") %>
</div>
<div class="editor-field">
<%: Html.TextAreaFor(model => model.Question,2,47,"") %>
<%: Html.ValidationMessageFor(model => model.Question) %>
</div>
<%: Html.HiddenFor(model => model.idTest) %>
<%: Html.ValidationMessageFor(model => model.idTest) %>
<%: Html.HiddenFor(model => model.OrderNumber ) %>
<%: Html.ValidationMessageFor( model => model.OrderNumber) %>
<p>
<input type="submit" value="Далее" />
</p>
</fieldset>
<% } %>
</asp:Content>
This is difficult to figure out without knowing the model. Someone else may provide a better answer, but here is the only thing I can think of for now:
If your Questions model looks like this:
public class Questions
{
int Id {get;set;}
string Name {get;set;}
string Description {get;set;}
}
What you can do, for now, is alter your controller to accept the individual parameters and create the object yourself. This might help you figure out which critical property in your Model is missing.
public ActionResult CreateQuestion(string Name, string Description)
{
//make the entity yourself
Questions newQuestion = new Questions()
{
Name = Name,
Description = Description
}
//your other code here
}
Now normally MVC is smart enough to bind your individual values in your form (view) to your model, but some critical value is missing and causing you issue. Once you've figured out what that is, you can actually restore your controller back to accepting only a Questions object.
Sorry I couldn't help you more.
Good Luck.
I am attempting to add validation to my application. I have some rules I need to check before allowing the information to be written to the database. I have the basic data validation added to the model, but I also need to make sure that if one field has a certain value, this other field is required. At one time the NerdDinner tutorial at asp.net covered that and I used that in the past for validation, but now I can't find that or any other example. Here is my model:
public class DayRequested
{
public int RequestId { set; get; }
[Required, DisplayName("Date of Leave")]
public string DateOfLeave { get; set; }
[Required, DisplayName("Time of Leave")]
public string TimeOfLeave { get; set; }
[Required, DisplayName("Hours Requested")]
[Range(0.5, 24, ErrorMessage = "Requested Hours must be within 1 day")]
public double HoursRequested { get; set; }
[Required, DisplayName("Request Type")]
public string RequestType { get; set; }
[DisplayName("Specify Relationship")]
public string Relationship { get; set; }
[DisplayName("Nature of Illness")]
public string NatureOfIllness { get; set; }
public bool AddedToTimesheet { get; set; }
public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(DateOfLeave))
yield return new RuleViolation("Date of Leave Required", "DateOfLeave");
if (String.IsNullOrEmpty(TimeOfLeave))
yield return new RuleViolation("Date of Leave Required", "TimeOfLeave");
if ((HoursRequested < 0.5) || (HoursRequested > 24))
yield return new RuleViolation("Hours must be in a period of one day", "HoursRequested");
if (String.IsNullOrEmpty(RequestType))
yield return new RuleViolation("Request Type is required", "RequestType");
if ((!String.IsNullOrEmpty(NatureOfIllness)) && (NatureOfIllness.Length < 3))
yield return new RuleViolation("Nature of Illness must be longer 2 characters", "NatureOfIllness");
// Advanced data validation to make sure rules are followed
LeaveRequestRepository lrr = new LeaveRequestRepository();
List<LeaveRequestType> lrt = lrr.GetAllLeaveRequestTypes();
LeaveRequestType workingType = lrt.Find(b => b.Id == Convert.ToInt32(RequestType));
if ((String.IsNullOrEmpty(Relationship)) && (workingType.HasRelationship))
yield return new RuleViolation("Relationship is Required", "Relationship");
if ((String.IsNullOrEmpty(NatureOfIllness)) && (workingType.HasNatureOfIllness))
yield return new RuleViolation("Nature of Illness is Required", "NatureOfIllness");
yield break;
}
}
My controller:
//
// POST: /LeaveRequest/Create
[Authorize, HttpPost]
public ActionResult Create(LeaveRequest leaveRequest, List<DayRequested> requestedDays)
{
if (ModelState.IsValid)
{
foreach (DayRequested requestedDay in requestedDays)
{
requestedDay.RequestId = leaveRequest.RequestId;
requestedDay.NatureOfIllness = (String.IsNullOrEmpty(requestedDay.NatureOfIllness) ? "" : requestedDay.NatureOfIllness);
requestedDay.Relationship = (String.IsNullOrEmpty(requestedDay.Relationship) ? "" : requestedDay.Relationship);
if (requestedDay.IsValid)
lrRepository.CreateNewLeaveRequestDate(requestedDay);
else
return View(new LeaveRequestViewModel(leaveRequest, requestedDays, lrRepository.GetLeaveRequestTypes()));
}
if (leaveRequest.IsValid)
lrRepository.CreateNewLeaveRequest(leaveRequest);
else
return View(new LeaveRequestViewModel(leaveRequest, requestedDays, lrRepository.GetLeaveRequestTypes()));
}
else
return View(new LeaveRequestViewModel(leaveRequest, requestedDays, lrRepository.GetLeaveRequestTypes()));
return RedirectToAction("Index", lrRepository.GetLeaveRequests(udh.employeeId));
}
ModelState.IsValid is not set to false though the code in IsValid is run and does return a RuleViolation. So I manually check IsValid it returns false. When I return to the view, the error messages do not appear. What might I be missing? Here are some snippets of the views.
Create.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create New Leave Request</h2>
<div><%= Html.ActionLink("Back to List", "Index") %></div>
<%= Html.Partial("RequestEditor", Model) %>
<div><%= Html.ActionLink("Back to List", "Index") %></div>
</asp:Content>
RequestEditor.ascx
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true) %>
<table id="editorRows">
<% foreach (var item in Model.DaysRequested)
Html.RenderPartial("RequestedDayRow", new EmployeePayroll.ViewModels.LeaveRequestRow(item, Model.LeaveRequestType)); %>
</table>
<p>Type your time to sign your request.</p>
<p><%= Html.LabelFor(model => model.LeaveRequest.EmployeeSignature) %>:
<%= Html.TextBoxFor(model => model.LeaveRequest.EmployeeSignature, new { Class="required" })%>
<%= Html.ValidationMessageFor(model => model.LeaveRequest.EmployeeSignature)%></p>
<p><input type="submit" value="Submit Request" /></p>
<% } %>
RequestedDayRow.ascx
<tbody class="editorRow">
<tr class="row1"></tr>
<tr class="row2">
<td colspan="2" class="relationship">
<%= Html.LabelFor(model => model.DayRequested.Relationship)%>:
<%= Html.TextBoxFor(model => model.DayRequested.Relationship) %>
<%= Html.ValidationMessageFor(model => model.DayRequested.Relationship)%>
</td>
<td colspan="2" class="natureOfIllness">
<%= Html.LabelFor(model => model.DayRequested.NatureOfIllness)%>:
<%= Html.TextBoxFor(model => model.DayRequested.NatureOfIllness) %>
<%= Html.ValidationMessageFor(model => model.DayRequested.NatureOfIllness)%>
</td>
<td></td>
</tr>
</tbody>
It's quite simple - you just need to apply your validation attribute to the entire model (or a child class). Then the validation attribute gets a reference to the model instead of just one property and you can perform your checks on multiple properties.
You should look at password validation for an example of how to do this.
Check out the PropertiesMustMatch validator here:
http://msdn.microsoft.com/en-us/magazine/ee336030.aspx
I have the following code in my aspx view page:
<% using (Html.BeginForm())
{
%>
<div>
CustomerCode:
<%= Html.TextBoxFor(x=> x.CustomerCode) %>
<%= Html.ValidationMessageFor(x => x.CustomerCode)%>
and this code in my model:
public class MyModel
{
[Required(ErrorMessage="customer code req")]
[StringLength(2,ErrorMessage="must be 2 u idiot")]
public string CustomerCode {get; set;}
Though if I enter more than 2 charachters in the textbox and submit the page, in the controller when I do:
if (ModelState.IsValid)
It always says its valid? What am I missing? I have put this MVC project inside a Web Forms project but the MVC project works fine, its just the validation which is not working, any ideas? Thanks.
Make sure that the controller action accepts the model as parameter:
public ActionResult SomeAction(MyModel model)
{
if (ModelState.IsValid)
{
}
return View();
}
Now if you invoke:
http://example.com/myapp/home/someaction?customercode=123
The model should not be valid.
Hmm, it works for me on a test page with the following
public ActionResult Test()
{
MyModel model = new MyModel();
return View(model);
}
[HttpPost]
public ActionResult Test(MyModel model)
{
if (ModelState.IsValid) { }
return View(model);
}
<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.CustomerCode) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.CustomerCode) %>
<%: Html.ValidationMessageFor(model => model.CustomerCode) %>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
public class MyModel
{
[Required(ErrorMessage = "customer code req")]
[StringLength(2, ErrorMessage = "must be 2 u idiot")]
public string CustomerCode { get; set; }
}