Model Validation with View Model which is inherited from Business Object - asp.net-mvc-4

In MVC4 app, I have some Business Objects which am inheriting as view model. I want to use DataAnnotations in view model rather than specifying it in Business Object. Is that possible.
Business Objects
public class Country
{
[Display(Name = "Country")]
public virtual int CountryId { get; set; }
public virtual string CountryName { get; set; }
public virtual string CountryCode { get; set; }
}
public class State : Country
{
[Display(Name = "State")]
public virtual int StateId { get; set; }
public virtual string StateName { get; set; }
public virtual string StateCode { get; set; }
}
public class City : State
{
public virtual int CityId { get; set; }
[Display(Name = "City")]
public virtual string CityName { get; set; }
}
View Model
public class Regional
{
public Regional()
{
Countries = new Collection<Country>();
States = new Collection<State>();
City = new City();
}
public Collection<Country> Countries { get; set; }
public Collection<State> States { get; set; }
[Required(ErrorMessage = "Required")]
public City City { get; set; }
}
Here in the above code, I am using data annotations for City Property.
The problem here am facing is,even if I don't have this required annotation the form getting validated for Country and State but not validating for City Name.
When I add the required annotation, it is still getting validated only for country and state but not city name.
Any ideas?
My View
#using (Html.BeginForm(Actions.AddCity, Controllers.AdminUtility, FormMethod.Post))
{
<div class="alert-error">
#Html.ValidationSummary()
</div>
<div class="row-fluid">
<fieldset class="form-horizontal">
<legend class="header">Add City</legend>
<div class="control-group">
#Html.LabelFor(m => m.City.CountryId, new { #class = "control-label" })
<div class="controls">
#Html.DropDownListFor(m => m.City.CountryId, new SelectList(Model.Countries, "CountryId", "CountryName"),"", new { #class = "chosen", data_placeholder = "Choose Country..." })
</div>
</div>
<div class="control-group">
#Html.LabelFor(m => m.City.StateId, new { #class = "control-label" })
<div class="controls">
#Html.DropDownListFor(m => m.City.StateId,new SelectList(Model.States, "StateId","StateName"), "",new { #class = "chosen", data_placeholder = "Choose State..." })
</div>
</div>
<div class="control-group">
#Html.LabelFor(m => m.City.CityName, new { #class = "control-label" })
<div class="controls">
#Html.TextBoxFor(m => m.City.CityName)
</div>
</div>
<div class="control-group">
<div class="controls">
<input type="submit" />
</div>
</div>
</fieldset>
</div>
}

Your validation is saying that the class is required. You want the CityName to be required, so in your scenario, no, you have to specify the attributes on the domain model.
Just a note - I don't think your domain model is very clear. City, State, and Country don't inherit from one another, they are just related. A State is in a country, it isn't a country... Also, you should always validate your domain objects since they may be populated from multiple sources. Your ViewModel would then have additional validation that is specific to that view -- maybe it has some additional data used for ui processing that doesn't need to be passed on to your domain, etc.

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.

Getting some input from user and insert multiple table with Entity Framework

I want to add some information into my database. The question is how can I post my inputs for 3 table at the same time? My tables are below. Let me clear that.
I have teachers and working hours and working days. I want that firstly I select the teacher name from dropdownlist,then select the day from dropdownlist and write working hours for example "09:00 - 17:00".
After that I submit these information I expectation is that seeing all these information can be added into database seperately and relationally.
Sample scenario: John Reese Friday 09:00-17:00
Harold Finch Monday 11:00-15:00
I am able to pull the teacher's names from database but at the same time in the same page I want to see that day's names. After all these selecetions as I mentioned above, I wanna add all these informations.
My create controller
public ActionResult Create()
{
var myTeacherList = (from teacher in db.Teachers.ToList()
select new SelectListItem
{
Text = teacher.Firstname + teacher.Lastname,
Value = teacher.Id.ToString(),
}).ToList();
var myDayNameList = (from day in db.WeekDays.ToList()
select new SelectListItem
{
Text = day.Name,
Value = day.Id.ToString(),
}).ToList();
ViewBag.TeacherId = myTeacherList;
ViewBag.DayId = myDayNameList;
return View();
}
My Create Controller
<div class="form-horizontal">
<h4>Appointment</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.TeacherId, "Teacher Name", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m=>m.Teacher.Id,(List<SelectListItem>)ViewBag.TeacherId, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TeacherId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Hours,"Working Hour", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Hours, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Hours, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Teacher.cs
namespace LanguageSchool.Models
{
using System;
using System.Collections.Generic;
public partial class Teacher
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Teacher()
{
this.Appointments = new HashSet<Appointment>();
this.Classes = new HashSet<Class>();
this.Languages = new HashSet<Language>();
}
public int Id { get; set; }
public string Description { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public System.DateTime DateOfStart { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Appointment> Appointments { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Class> Classes { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Language> Languages { get; set; }
}
}
Appointment.cs
namespace LanguageSchool.Models
{
using System;
using System.Collections.Generic;
public partial class Appointment
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Appointment()
{
this.WeekDays = new HashSet<WeekDay>();
}
public int Id { get; set; }
public int TeacherId { get; set; }
public string Hours { get; set; }
public virtual Teacher Teacher { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WeekDay> WeekDays { get; set; }
}
}
WeekDay.cs
namespace LanguageSchool.Models
{
using System;
using System.Collections.Generic;
public partial class WeekDay
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public WeekDay()
{
this.Class_WeekDay = new HashSet<Class_WeekDay>();
this.Appointments = new HashSet<Appointment>();
}
public int Id { get; set; }
public string Name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Class_WeekDay> Class_WeekDay { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Appointment> Appointments { get; set; }
}
}
We need to make a ViewModel that contains all those properties so that the moment we do a POST request, they are all bound and we could access them for saving.
But first we need to modify your models and insert a class for the many to many table.
We need to remove Weekday from Appointments and
Appointments from Weekday.
Then replace them with AppointmentWeekday. Be sure to run Migrations/Update-Database after this first step.
public class Appointment{
...
// REMOVE public virtual ICollection<WeekDay> WeekDays { get; set; }
// Add this
public virtual ICollection<AppointmentWeekday> AppointmentWeekdays {get;set;}
}
public class Weekday{
...
// REMOVE public virtual ICollection<Appointment> Appointments { get; set; }
// Add this
public virtual List<AppointmentWeekday> AppointmentWeekdays {get;set;}
}
// Add this
public class AppointmentWeekday{
public int AppointmentId {get;set;}
[ForeignKey("AppointmentId")]
public virtual Appointment Appointment {get;set;}
public int WeekdayId {get;set;}
[ForeignKey("WeekdayId")]
public virtual Weekday Weekday {get;set;}
}
Make the View Model with the necessary properties, I named it TeacherAppointmentViewModel.
public class TeacherAppointmentViewModel{
public int TeacherId {get;set;}
public int DayId {get;set;}
public string Hours {get;set;}
}
Instantiate this in your controller and pass it to the view.
public ActionResult Create()
{
var myTeacherList = (from teacher in db.Teachers.ToList()
select new SelectListItem
{
Text = teacher.Firstname + teacher.Lastname,
Value = teacher.Id.ToString(),
}).ToList();
var myDayNameList = (from day in db.WeekDays.ToList()
select new SelectListItem
{
Text = day.Name,
Value = day.Id.ToString(),
}).ToList();
ViewBag.TeacherId = myTeacherList;
ViewBag.DayId = myDayNameList;
// instantiate
TeacherAppointmentViewModel tvm = new TeacherAppointmentViewModel();
// pass to the view
return View(tvm);
}
Make your view use TeacherAppointmentViewModel.
#model TeacherAppointmentViewModel
Edit view, use the code below.
<div class="form-horizontal">
<h4>Appointment</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.TeacherId, "Teacher Name", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m=>m.TeacherId,(List<SelectListItem>)ViewBag.TeacherId, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.TeacherId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.WeekdayId, "Day", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(m=>m.WeekdayId,(List<SelectListItem>)ViewBag.WeekdayId, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.WeekdayId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Hours,"Working Hour", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Hours, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Hours, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
Use controller action below, we need to assign the properties to Appointment and AppointmentWeekday then add to the db;
[HttpPost]
public ActionResult Create(TeacherAppointmentViewModel tvm){
// create appointment
Appointment a = new Appointment();
// assign teacher id and hours from viewmodel
a.TeacherId = tvm.TeacherId;
a.Hours = tvm.Hours;
// save appointment
db.Appointments.Add(a);
db.SaveChanges();
// create appointmentweekday
AppointmentWeekday aw = new AppointmentWeekday();
// assign properties
// since we've saved the appointment, we could use a.AppointmentId
aw.WeekdayId = tvm.WeekdayId;
aw.AppointmentId = a.AppointmentId; // appointment from earlier
// save appointmentweekday
db.AppointmentWeekdays.Add(aw);
db.SaveChanges();
}

How to post HttpPostedFileBase with Model data from MVC

How to upload file with HttpPostedFileBase with 3 other Model data
and save it to database.When i try to save data employee model value
become null and give object reference error.I have a Create view link
for New employee on Get Page. I am getting object refrence error when
try to save.
EmployeeModelClass.cs
public class EmployeeViewModel
{
public Employee Employee { get; set; }
public HttpPostedFileBase File { get; set; }
public List<Employee> EmployeeList { get; set; }
public IEnumerable<Region> Regions { get;set; }
public IEnumerable<City> Cities { get; set; }
}
EmployeClass
public class Employee
{
[Key]
public int EmployeeId { get; set; }
public string Name { get; set; }
[DataType(DataType.DateTime)]
public DateTime? BirthDate { get; set; }
[Display(Name = "Region")]
public int RegionId { get; set; }
public City City { get; set; }
[Display(Name = "City")]
public int CityId { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
public string Confirm Password { get; set; }
public int ImageSize { get; set; }
public string FileName { get; set; }
public byte[] ImageData { get; set; }
}
Get Request for Create.And here i am redirect from Get.chtml view.
[HttpGet]
public ActionResult Create()
{
var viewmodel = new EmployeeViewModel
{
Regions = _context.Regions.ToList(),
Cities = _context.Cities.ToList(),
};
return View("Create", viewmodel);
}
Post Request for Create .Here my employee model value becoming null and on submit give object refrence null error.
[HttpPost]
public ActionResult Create(HttpPostedFileBase file,Employee emp)
{
}
Create.chtml
#model EmployeeManagement.ViewModel.EmployeeViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (#Html.BeginForm("Create", "Employee", FormMethod.Post, new {
enctype = "multipart/form-data" }))
{
<div class = "form-group">
#Html.LabelFor(m => m.Employee.Name)
#Html.TextBoxFor(m => m.Employee.Name, new { #class = "form-control"
})
</div>
<div class = "form-group">
#Html.LabelFor(m => m.Employee.BirthDate)
#Html.TextBoxFor(m => m.Employee.BirthDate, new { #class = "form-
control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Employee.RegionId)
#Html.DropDownListFor(m => m.Employee.RegionId, new
SelectList(Model.Regions, "RegionId", "RegionName"), "Please Select
Region", new { #onchange = "BindCity()", #class = "form-control" })
#Html.ValidationMessageFor(m => m.Employee.RegionId)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Employee.CityId)
#Html.DropDownListFor(m => m.Employee.CityId, new
SelectList(Model.Cities, "CityId", "CityName"), "Please Select
City", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Employee.CityId)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Employee.Password)
#Html.PasswordFor(m => m.Employee.Password, new { #class = "form-
control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Employee.ConfirmPassword)
#Html.PasswordFor(m => m.Employee.ConfirmPassword, new { #class =
"form-control" })
</div>
<div>
#Html.TextBoxFor(m=>m.File, new { type = "file",name="File" })
#Html.ValidationMessageFor(m=>m.File.FileName)
</div>
<p><button type="submit" class="btn btn-primary">Save</button></p>
}
Please someone help how to upload image with model data at
post.
The problem in your Post Create method you are expecting Employee object but you are binding EmployeeViewModel with view so use below line it's resolve your issue.
public ActionResult Create(HttpPostedFileBase file,EmployeeViewModel emp)

How to validate an IEnumerable is not null or empty using DataAnnotations?

I'm trying validate an IEnumerable<long> selectedItems if does not null or empty but I can't do this, it needs to has one or more options selected because its a multiple option. How could I do this ?
trying.
Model
public class EmpresaModel{
public IEnumerable<SelectListItem> formasPagto { get; set; }
[Required(ErrorMessage = "Choose one or more options")]
public IEnumerable<long> selectedItems { get; set; }
}
HTML
<div class="form-group">
<label for="#Html.IdFor(model => model.selectedItems)" class="cols-sm-2 control-label">Formas de pagamento disponíveis <img src="~/Imagens/required.png" height="6" width="6"></label>
#Html.ListBoxFor(model => model.selectedItems, Model.formasPagto, new { Class = "form-control", placeholder = "Selecione as formas de pagamento disponíveis", #multiple = true})
#Html.ValidationMessageFor(model => model.formasPagto)
</div>

How to do validation from a viewmodel

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; }
}