How to do validation from a viewmodel - asp.net-mvc-4

I have a model with some validations below
public class RequestABook
{
[Required(ErrorMessage = "Name Required")]
[DisplayName("Book Name")]
public string BookName { get; set; }
[Required(ErrorMessage = "Zipcode Required")]
[DisplayName("Zipcode")]
public string ZipCode { get; set; }
[Required(ErrorMessage = "Contact Name Required")]
[DisplayName("Contact Name")]
public string ContactName { get; set; }
[Required(ErrorMessage = "Email Id Required")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required(ErrorMessage = "Book Description Required")]
public string BookDescription { get; set; }
[Required(ErrorMessage = "You need to check one answer")]
public string Answer { get; set; }
}
I have a view model here
public class RequestViewModel
{
public RequestABook MyTestViewModel { get; set; }
}
I have my main page here that loads
#model BookRequestValidation.Models.RequestViewModel
#{
ViewBag.Title = "RequestABook";
}
<h2>RequestABook</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Request Book</legend>
<div class="editor-label">
#Html.LabelFor(m => m.MyTestViewModel.BookName)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.MyTestViewModel.BookName)
#Html.ValidationMessageFor(m => m.MyTestViewModel.BookName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.MyTestViewModel.ZipCode)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.MyTestViewModel.ZipCode)
#Html.ValidationMessageFor(m => m.MyTestViewModel.ZipCode)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.MyTestViewModel.ContactName)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.MyTestViewModel.ContactName)
#Html.ValidationMessageFor(m => m.MyTestViewModel.ContactName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.MyTestViewModel.Email)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.MyTestViewModel.Email)
#Html.ValidationMessageFor(m => m.MyTestViewModel.Email)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.MyTestViewModel.BookDescription)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.MyTestViewModel.BookDescription)
#Html.ValidationMessageFor(m => m.MyTestViewModel.BookDescription)
</div>
<div id="HCBudget" class="validation">
<label for="budgethealth">Budget Health</label>
#Html.RadioButton("Answer", "Red")
#Html.RadioButton("Answer", "Yellow")
#Html.RadioButton("Answer", "Green")
#Html.ValidationMessageFor(m => m.MyTestViewModel.Answer)
</div>
<input type="submit" value="Request Book" />
</fieldset>
}
Question: How do you guys handle validation with models used in a viewmodel.
Before I used this in a viewmodel everything was working well. By the time I used a viewmodel
validation stopped working.
Here is what the post action looks like.
public ActionResult RequestABook()
{
return View();
}
[HttpPost]
public ActionResult RequestABook(RequestABook quote)
{
return View();
}

It would help greatly if you posted your POST action. However, generally, I can say that validation is only run on related class instances if they are non-null. So, unless a value is posted for at least one of the properties on MyTestViewModel, it will not be instantiated by the modelbinder (MyTestViewModel will be null), and validation on its properties will not be run.
You can fix this scenario by always instantiating the MyTestViewModel property, either via the constructor of your view model or, probably better, using a custom getter and setter:
Constructor
public class RequestViewModel
{
public RequestViewModel()
{
MyTestViewModel = new RequestABook();
}
...
}
Custom Getter and Setter
private RequestABook myTestViewModel;
public RequestABook MyTestViewModel
{
get
{
if (myTestViewModel == null)
{
myTestViewModel = new RequestABook();
}
return myTestViewModel;
}
set { myTestViewModel = value; }
}

Related

How to solve empty model trouble using both Ajax.BeginForm and Html.BeginForm in MVC

I am new to MVC and stuck in passing modal to controller.
I have read many similar threads in SO, to no avail.
Here, I have a view for entering order details.
User will enter order item details (using ajax.BeginForm) and when he clicks save, whole order will be saved in backend (using Html.BeginForm). Ajax.BeginForm is working properly and passing + displaying records properly. But Html.BeginForm is passing model as nothing.
Here is my code ...
My Models
public class OrderItemsModel
{
public string SrNo { get; set; }
public int? ItemCode { get; set; }
public string ItemName { get; set; }
public Decimal? Qty { get; set; }
public Decimal? Rate { get; set; }
public Decimal? Amount { get; set; }
}
public class OrderModel
{
public string OrderNumber { get; set; }
public string OrderDate { get; set; }
public int? CustomerCode { get; set; }
public string CustomerName { get; set; }
public string Note { get; set; }
//List of items selected in Order
public List<OrderItemsModel> ItemsSelected { get; set; }
}
Extract from My View
#model OrderApplication.Models.OrderModel
#{
ViewBag.Title = "Index";
Model.ItemsSelected = ViewBag.getlist;
}
#using (Ajax.BeginForm("UpdateItemList", "Order", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "selectedtable" }))
{
<h2 style="margin-left:5%;">Order Entry</h2>
//Order No & Date
<div class="row">
<div class="col-sm-6">
<div class="col-sm-3">
#Html.LabelFor(model => model.OrderNumber, "OrderNo"):
</div>
<div class="col-sm-3">
#Html.TextBoxFor(model => model.OrderNumber, new { #class = "form-control", #readonly = "readonly" })
</div>
</div>
<div class="col-sm-6">
<div class="col-sm-3">
#Html.LabelFor(model => model.OrderDate, "Date"):
</div>
<div class="col-sm-3">
#Html.TextBoxFor(model => model.OrderDate, new { #class = "form-control" })
</div>
</div>
</div>
<br />
//Table of entries
<div id="selectedtable">
#Html.Partial("_selectTable", Model);
</div>
<br />
}
#*Main Save*#
<div class="row">
<div class="col-sm-12">
<div class="col-sm-3">
#using (Html.BeginForm("SaveData", "Order", new { order = Model, id = "loginform", #class = "justify-content-center" }))
{
<input type="submit" value="Save Order" class="btn btn-success" />
}
</div>
<div class="col-sm-3">
<input type="button" class="btn btn-success" value="Clear Form" onclick="location.href='#Url.Action("Clear", "Order")'" />
</div>
</div>
</div>
My Controller
public class OrderController : Controller
{
public List<OrderItemsModel> dd = new List<OrderItemsModel>() ;
[HttpPost]
public ActionResult SaveData(OrderModel order, string id)
{
if (order == null) //order is always Nothing
{
return View(order);
}
if (order.CustomerCode == 0)
{
return View(order);
}
return View(order);
}
}
}
You shouldn't use both Ajax.BeginForm and Html.BeginForm, as it won't work. Please, check this post as might be of help to decide which one you wish to choose:
https://forums.asp.net/t/1757936.aspx?When+to+use+Html+BeginForm+vs+ajax+BeginForm
If you still want to use Html.BeginForm, just move the using sentence to replace your Ajax.BeginForm at the top of the page, so the form covers all fields, and the model won't be empty.

Not updating ID

i am new to asp.net and i have a question. I have created a simple form fro sending sms, however my id always stays null. Can you please advise on what am i doing wrong? I used exactly the same form in my other page and it worked fine.
#model MessagingWebApplication.ViewModel.MessageViewModel
#{ ViewBag.Title = "New";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>New Sms</h2>
#using (Html.BeginForm("MessageStatus", "Message"))
{
#Html.ValidationSummary()
<div class="form-group">
#Html.LabelFor(m => m.Message.Reciever)
#Html.TextBoxFor(m => m.Message.Reciever, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Message.Reciever)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Message.Sender)
#Html.TextBoxFor(m => m.Message.Sender, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Message.Sender)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Message.Body)
#Html.TextBoxFor(m => m.Message.Body, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Message.Body)
</div>
#Html.HiddenFor(m => m.Message.Id)
#Html.AntiForgeryToken()
<button type="submit" class="btn btn-primary">Send</button>
}
My model
public class Message
{
[Key]
public int Id { get; set; }
[Required]
public string Sender { get; set; }
[Required]
public string Reciever { get; set; }
[Required]
public string Body { get; set; }
}
Method used for sending
public ActionResult Send(Message message)
{
var viewModel = new MessageViewModel
{
Message = new Message()
};
_context.Add(message);
_context.SaveChanges();
_messageSender.Send(message);
return View("MessageStatus", viewModel);
}
here _context.SaveChanges(); i get SqlException: Cannot insert the value NULL into column 'Id', table 'MyDatabase.dbo.Messages'; column does not allow nulls. INSERT fails.
My issue was that i had failed when updating the database so i just followed this How to delete and recreate from scratch an existing EF Code First database

How to pass a custom login failed notification to the view in MVC4

I want to pass an error login notification in the view which I coded my self, but I do not know how. I want to put it in the #Html.ValidationMessageFor(model => model.UserName) and #Html.ValidationMessageFor(model => model.Password) or a separate label (am I correct that I will use #Html.ValidationMessage() instead of #Html.ValidationMessageFor()?)
here is my model:
public class User
{
public int UserId { get; set; }
[Required]
[Display(Name = "User Name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
here is my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(User p)
{
if (ModelState.IsValid)
{
User item = db.Authenticate(p);
if (item != null) // if item is not null, the login succeeded
{
return RedirectToAction("Main", "Home");
}
}
string error = "Incorrect user name or password."; // I don't know how to pass this
return View(); //login failed
}
here is my view:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>User</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Password)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Password)
#Html.ValidationMessageFor(model => model.Password)
</div>
<p>
<input type="submit" value="Login" />
</p>
</fieldset>
}
You can add the custom error message to the Model state dictionary using the AddModelError method. The validationSummary /ValidationMessageFor helper methods reads the validation errors from model state dictionary when it gets called.
The first parameter is the key for the error message. If you pass string.empty as the value the custom error message you are passing will be rendered by the ValidationSummary helper method
ModelState.AddModelError(string.Empty,"Incorrect user name or password.");
return View(p);
If you want to render the error message by the input element (The one ValidationMessageFor renders), you may pass the property name as the key value when calling the AdddModelError method.
ModelState.AddModelError(nameof(User.Password),"Incorrect password");
return View();
We can use AddModelError method for handling custom error message
ModelState.AddModelError(nameof(User.UserName),"Incorrect UserName");
ModelState.AddModelError(nameof(User.Password),"Incorrect password");
return View();

asp.net mvc 4 dropdownlist does not return a value

I have 2 models - Question and Category -
public class Question
{
[ScaffoldColumn(false)]
public int QuestionId { get; set; }
[Required]
public string QuestionText { get; set; }
[Required]
public string AnswerA { get; set; }
[Required]
public string AnswerB { get; set; }
[Required]
public string AnswerC { get; set; }
[Required]
public string AnswerD { get; set; }
[Required]
public int Correct { get; set; }
[ForeignKey("Category")]
[Display(Name = "Category")]
[Required]
public int categoryId;
//Navigation property
public virtual Category Category { get; set; }
}
public class Category
{
[ScaffoldColumn(false)]
public int CategoryId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Question> Question { get; set; }
}
In my QuestionController, I have added code to be able to access the available categories for a dropdownlist in the view -
private void PopulateCategoryDropDownList(object selectedCategory = null)
{
var categoryQuery = from c in db.Categories
orderby c.Name
select c;
ViewBag.categoryId = new SelectList(categoryQuery, "CategoryId", "Name", selectedCategory);
}
And I have the following methods for create -
// GET: /Question/Create
public ActionResult Create()
{
PopulateCategoryDropDownList();
return View();
}
//
// POST: /Question/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Question question)
{
try
{
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid)
{
db.Questions.Add(question);
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException dex)
{
ModelState.AddModelError("",dex.Message);
}
PopulateCategoryDropDownList(question.Category.CategoryId);
return View(question);
}
My view for creating a new question is as follows -
#model Quiz.Models.Question
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Question</legend>
<div class="editor-label">
#Html.LabelFor(model => model.QuestionText)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.QuestionText)
#Html.ValidationMessageFor(model => model.QuestionText)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerA)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerA)
#Html.ValidationMessageFor(model => model.AnswerA)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerB)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerB)
#Html.ValidationMessageFor(model => model.AnswerB)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerC)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerC)
#Html.ValidationMessageFor(model => model.AnswerC)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AnswerD)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AnswerD)
#Html.ValidationMessageFor(model => model.AnswerD)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Correct)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Correct)
#Html.ValidationMessageFor(model => model.Correct)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.categoryId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.categoryId,(SelectList)ViewBag.categoryId)
#Html.ValidationMessageFor(model => model.categoryId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
So, the issue is that although the question can be created, the categoryId from the dropdownlist is always null.
I have tried a bunch of things, ranging from attempting to access the dropdownlist directly to creating a different viewmodel. However, none of them work as required. Also, my code follows the tutorials available online. I'm not able to figure out what is different.
Please do help me find the mistake in my code.
We might have to narrow things down, I have a feeling something is going wrong with the models being read correctly from ViewBag. Try replacing your Create actions for a moment with the following, where your custom ViewBag filling function has been removed:
public ActionResult Create() {
ViewBag.categoryId = new SelectList(db.Categories, "CategoryId", "Name");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Question question) {
try {
var errors = ModelState.Values.SelectMany(v => v.Errors);
if (ModelState.IsValid) {
db.Questions.Add(question);
db.SaveChanges();
return RedirectToAction("Index");
}
} catch (DataException dex) {
ModelState.AddModelError("",dex.Message);
}
ViewBag.categoryId = new SelectList(db.Categories, "CategoryId", "Name", question.Category.CategoryId);
return View(question);
}
Does this run correctly?
If this doesn't work it must be a model-binding issue. The last thing I can think of trying is change the ViewBag calls to effect the field Category instead of CategoryId. Also update your view when making the DropDownList.

Using Automapper to Edit selected fields of a model MVC4

I am trying to update selected fields using a viewmodel(OApplyIDViewModel) of the original model(OApply). When I run changes are not effected. I will appreciate help from anyone who is experienced with this. I do not get any error. The form submits and redirects.
I have this at global.asax
AutoMapper.Mapper.CreateMap<OApplyIDViewModel, OApply>();
This is ViewModel
public class OApplyIDViewModel
{
[Key]
public int OAId { get; set; }
[Display(Name = "Identification")]
[Required(ErrorMessage = "Identification Required")]
public int IdId { get; set; }
[Display(Name = "Identification Number")][Required(ErrorMessage="ID Number Required")]
public string AIdentificationNo { get; set; }
[Display(Name = "Licence Version(5b)")]
[RequiredIf("IdId", Comparison.IsEqualTo, 1, ErrorMessage = "Version(5b) Required")]
public string ALicenceVersion { get; set; }
public int CountryId { get; set; }
[RequiredIf("IdId",Comparison.IsNotEqualTo,1, ErrorMessage="Country Required")]
[Display(Name = "Your Electronic Signature Date Seal")]
[Required(ErrorMessage="Date Signature Seal Required")]
public DateTime SigDate { get; set; }
[ScaffoldColumn(false)]
[Display(Name = "Last Updated on")]
public DateTime UDate { get; set; }
[ScaffoldColumn(false)]
[Display(Name = "Last Updated by")]
public String UDateBy { get; set; }
}
This is at Controller
//GET
public ActionResult ClientEditID(int id)
{
var model = new OApplyIDViewModel();
OApply oapply = db.OApply.Find(id);
if (model == null )
{
return HttpNotFound();
}
ViewBag.CountryId = new SelectList(db.Countries, "CountryId", "CountryName", model.CountryId);
ViewBag.IdId = new SelectList(db.PhotoIds, "IdId", "IdName", model.IdId);
return View();
}
[HttpPost]
public ActionResult ClientEditId(OApplyIDViewModel oapply, int Id)
{
if (!ModelState.IsValid)
{
return View(oapply);
}
var onlineid = db.OApply.Where(x => x.OAId == Id).FirstOrDefault();
Mapper.Map<OApplyIDViewModel,OApply>(oapply);
oapply.UDateBy = Membership.GetUser().ProviderUserKey.ToString();
oapply.UDate = DateTime.Now;
db.Entry(onlineid).State= EntityState.Modified;
db.SaveChanges();
ViewBag.CountryId = new SelectList(db.Countries, "CountryId", "CountryName", oapply.CountryId);
ViewBag.IdId = new SelectList(db.PhotoIds, "IdId", "IdName", oapply.IdId);
return RedirectToAction("HomeAccount");
}
This is the View
#using (Html.BeginForm("ClientEditId", "OApply", FormMethod.Post, new { enctype = "multipart/form-data", #class = "stdform" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>OnlineApplication</legend>
#Html.HiddenFor(model => model.OAId)
<div class="related">
<div class="editor-label">
#Html.LabelFor(model => model.IdId, "IdType")
</div>
<div class="editor-field">
#Html.DropDownList("IdId", String.Empty)
#Html.ValidationMessageFor(model => model.IdId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AIdentificationNo)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.AIdentificationNo)
#Html.ValidationMessageFor(model => model.AIdentificationNo)
</div>
<div class="requiredfields">
<div class="editor-label">
#Html.LabelFor(model => model.ALicenceVersion)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ALicenceVersion)
#Html.ValidationMessageFor(model => model.ALicenceVersion)
</div>
</div>
<div class="country">
<div class="editor-label">
#Html.LabelFor(model => model.CountryId)
</div>
<div class="editor-field">
#Html.DropDownList("CountryId")
#Html.ValidationMessageFor(model => model.CountryId)
</div>
</div></div>
<div class="editor-label">
#Html.LabelFor(model => model.SigDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SigDate)
#Html.ValidationMessageFor(model => model.SigDate)
</div>
<p>
<input type="submit" value="Save" />
</p>
There is something wrong here:
var onlineid = db.OApply.Where(x => x.OAId == Id).FirstOrDefault();
-> Mapper.Map<OApplyIDViewModel,OApply>(oapply);
oapply.UDateBy = Membership.GetUser().ProviderUserKey.ToString();
oapply.UDate = DateTime.Now;
db.Entry(onlineid).State= EntityState.Modified;
The line with the arrow returns a new OApply instance. It does not update onlineid. In fact, it has no idea about onlineid. Try the following.
Mapper.Map<OApplyIDViewModel,OApply>(oapply, onlineid);
Now it will modify onlineid instead of returning one. However, you should ignore the mapping for the primary key if it is an identity (auto-incrementing) one.
AutoMapper.Mapper.CreateMap<OApplyIDViewModel, OApply>()
.ForMember(dest => dest.OAId, opt => opt.Ignore());
I am not sure if OAId is your primary key or not. You are not following naming conventions and probably some other conventions too, at all.
I have made corrections in your code :
public ActionResult ClientEditID(int id)
{
OApply oapply = db.OApply.Find(id);
->if (oapply == null )
{
return HttpNotFound();
}
->var model = Mapper.Map<OApply, OApplyIDViewModel>(oapply);
ViewBag.CountryId = new SelectList(db.Countries, "CountryId", "CountryName", model.CountryId);
ViewBag.IdId = new SelectList(db.PhotoIds, "IdId", "IdName", model.IdId);
->return View(model);
}
Your HttpPost is mostly valid, except that you put data into ViewBag before you use RedirectToAction(). That data will be lost. Instead, use TempData dictionary. Check msdn.