I have the following ADO Model
Student
Id,Name
and
Course
Id,Name,Student_ID
I have made the following view for it
#model Tuple<MvcApplication4.Models.Course, MvcApplication4.Models.Student >
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Course</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item1.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item1.Name)
#Html.ValidationMessageFor(model => model.Item1.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item1.S_ID, "Student")
</div>
<fieldset>
<legend>Student</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item2.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item2.Name)
#Html.ValidationMessageFor(model => model.Item2.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item2.Class)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item2.Class)
#Html.ValidationMessageFor(model => model.Item2.Class)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
</fieldset>
}
And the controller for it is as
public ActionResult Create()
{
return View();
}
//
// POST: /Default3/Create
[HttpPost]
public ActionResult Create(Tuple<Student ,Course > t)
{
try
{
// TODO: Add insert logic here
db.Students.AddObject(t.Item1);
db.SaveChanges();
t.Item2.S_ID = t.Item1.Id;
db.Courses.AddObject(t.Item2);
db.SaveChanges();
return RedirectToAction("Copy");
}
catch
{
return View();
}
}
But When i click the Creat button it gives the following Error
Server Error in '/' Application.
No parameterless constructor defined for this object.
The Tuple<X, Y> class doesn't have a default constructor so you will need to write a custom model binder if you want this to work. Another possibility is to use a custom view model which is what I would recommend you:
public class MyViewModel
{
public Course Course { get; set; }
public Student Student { get; set; }
}
and then:
public ActionResult Create()
{
return View(new MyViewModel());
}
//
// POST: /Default3/Create
[HttpPost]
public ActionResult Create(MyViewModel model)
{
try
{
// TODO: Add insert logic here
db.Students.AddObject(t.Student);
db.SaveChanges();
t.Course.S_ID = t.Student.Id;
db.Courses.AddObject(t.Course);
db.SaveChanges();
return RedirectToAction("Copy");
}
catch
{
return View(model);
}
}
and finally:
#model MvcApplication4.Models.MyViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Course</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Student.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Student.Name)
#Html.ValidationMessageFor(model => model.Student.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Student.S_ID, "Student")
</div>
<fieldset>
<legend>Student</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Course.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Course.Name)
#Html.ValidationMessageFor(model => model.Course.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Course.Class)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Course.Class)
#Html.ValidationMessageFor(model => model.Course.Class)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
MVC is pretty smart, but it can't really figure out how to create a new instance of Tuple and create new instances of the items, then assign the proper items to it. That's just too complex of a task.
The error you get is because a Tuple doesn't have a default parameterless constructor, and requires new items to be passed to it in the constructor, something that MVC can't do.
You will have to break this down, and create your tuple in your controller action from a viewmodel that contains your items as members.
You need to pass the model to your view.
Example:
return View(model);
This seems to have resolved the issue for me as an alternative and it is working now:
[HttpGet]
public ActionResult Create()
{
Course course = new Course();
Student student = new Student();
var tuple = new Tuple<Course,Student>(course,student);
return View(tuple);
}
[HttpPost]
public ActionResult Create(Tuple<Course,Student> tuple){ do something ...}
I tried several other approaches include some that have been suggested here but, didn't resolve the problem. I just posted this to help someone else who might want to use Tuple though, use it only if you don't have another alternative.
I got it to work after a few minutes of digging and some thinking. Here's a quick example of what I did:
GET action:
[HttpGet]
public ActionResult Update(int id = 0)
{
ProductDto product = _productService.FindByID(id);
SupplierDto supplier = _supplierService.FindByProductID(productId: product.ProductID);
return View(model: new Tuple<ProductDto, SupplierDto>(product, supplier));
}
POST action:
[HttpPost]
public JsonResult Update(int id = 0, ProductDto Item1, SupplierDto Item2)
{
// Get the product name
string productName = Item1.ProductName;
// Get the supplier name
string supplierName = Item2.SupplierName;
...
return Json(new { success = true });
}
View:
#model Tuple<ProductDto, SupplierDto>
#{
ViewBag.Title = "add title later ... ";
AjaxOptions options = new AjaxOptions { ... };
}
#using (Ajax.BeginForm("Update", "Product", options, htmlAttributes: new { #id = "update-form" }))
{
<fieldset>
<legend>Update Product</legend>
<div class="display-label">
#Html.LabelFor(model => model.Item1.ProductName)
</div>
<div class="display-field">
#Html.EditorFor(model => model.Item1.ProductName)
</div>
...
<div class="display-label">
#Html.LabelFor(model => model.Item2.SupplierName)
</div>
<div class="display-field">
#Html.EditorFor(model => model.Item2.SupplierName)
</div>
</fieldset>
<div class="submit-button">
<button type="submit" class="button">Update details</button>
<div>
}
You should bind prefix in parameters
Controller:
public ActionResult ThisMethod([Bind(Prefix = "Item1")] AccountViewModel model)
{
// toDo
}
View:
#model Tuple<AccountViewModel>
#Html.EditorFor(model => model.Item1.Firstname)
#DarinDimitri's solution is right but there is a way with Tuple as well. If you just change these code below you will get tuple models.
[HttpPost]
public ActionResult Create(Course Item1, Student Item2)
{
try
{
// TODO: Add insert logic here
db.Students.AddObject(Item2);
db.SaveChanges();
Item1.S_ID = Item2.Id;
db.Courses.AddObject(Item1);
db.SaveChanges();
return RedirectToAction("Copy");
}
catch
{
return View();
}
Related
I have a Form where I successfully use unobtrusive-validation with the [remote] annotation.
I have other fields in the Form with [required] annotation in the model but I don't want client-side validation for these fields.
I only want server-side validation for [required] fields
I haven't found a solution and I wonder if it's easily feasible?
EDIT :
A part of my code
Part of my model :
I would like the first field : "Email" use Unobtrusive-validation and the second one : "PasswordHash" only use server-side validation even if it has [required] annotation.
Finally, i don't want an AJAX error message for all my form 's fields.
[Required(ErrorMessageResourceType = typeof(Project.Resources.Models.Accounts.User), ErrorMessageResourceName = "EMAIL_REQUIRED")]
[Display(Name = "EMAIL_DISPLAY", ResourceType = typeof(Project.Resources.Models.Accounts.User))]
[Remote("EmailExists", "User", "An account with this email address already exists.")]
public string Email { get; set; }
[Required(ErrorMessageResourceType = typeof(Project.Resources.Models.Accounts.User), ErrorMessageResourceName = "PASSWORDHASH_REQUIRED")]
[DataType(DataType.Password)]
[Display(Name = "PASSWORDHASH_DISPLAY", ResourceType = typeof(Project.Resources.Models.Accounts.User))]
public string PasswordHash { get; set; }
Part of the action in Controller
Server-side validation :
[HttpPost]
public ActionResult Register(User user)
{
if (ModelState.IsValid )
{
DataContext.User.Add(user);
DataContext.SaveChanges();
return RedirectToAction("Index");
}
return View(user);
}
Ajax validation :
public JsonResult EmailExists(string email)
{
User user = DataContext.User.SingleOrDefault(x => x.Email == email);
return user == null ?
Json(true, JsonRequestBehavior.AllowGet) :
Json(
string.Format("an account for address {0} already exists.",
email), JsonRequestBehavior.AllowGet);
}
Part of the view :
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PasswordHash)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PasswordHash)
#Html.ValidationMessageFor(model => model.PasswordHash)
</div>
<p>
<input type="submit" name="op" id="edit-submit" value="#Project.Resources.Views.User.Register.SUBMIT_FORM" class="form-submit art-button" />
</p>
}
When I click on the sumbit button i would like a server-side validation.
And for some specific fields like Email i would like an Ajax Validation.
there might be better ways, but one way to accomplish this is to do the following
#using (Html.BeginForm("Register", "Home", FormMethod.Post, new { id = "registerForm" }))
{
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PasswordHash)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PasswordHash)
#Html.ValidationMessageFor(model => model.PasswordHash)
</div>
<p>
<input type="submit" name="op" id="edit-submit" value="#Project.Resources.Views.User.Register.SUBMIT_FORM" class="form-submit art-button" />
</p>
}
<script type="text/javascript">
// add the rules for the fields that you want client-side validation on
// leave out the fields that you dont want client-side validation. they will be validated
// on server side.
$("#registerForm").validate({
rules: {
Email: { required: true }
},
messages: {
Email: { required : "Email is required" }
}
});
</script>
I am trying to create what I feel is a very simple form submission using a ViewModel. I have worked on this off and on all day and for some reason cannot understand why when my app gets to my HttpPost action my EmailViewModel is empty. I get a "NullReference Exception Occurred" "Object reference not set to an instance of an object" error.
Can you take a look at my code and tell me where I am being crazy?
Here is my httpPost action:
[HttpPost]
public ActionResult SendStudentAnEmail(EmailViewModel email)
{
Debug.Write(email.Subject); // First NullReferenceException
Debug.Write(email.Body);
Debug.Write(email.Email);
etc. . .
My ViewModel:
namespace MyApp.ViewModels
{
public class EmailViewModel
{
public string Email { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
}
}
and My View:
#model MyApp.ViewModels.EmailViewModel
#{
ViewBag.Title = "SendStudentAnEmail";
}
<h2>SendStudentAnEmail</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>EmailViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Subject)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Subject)
#Html.ValidationMessageFor(model => model.Subject)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Body)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Body)
#Html.ValidationMessageFor(model => model.Body)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Thank you.
*UPDATE*
If I change my HttpPost Action to use FormCollection, I can use the values just fine, I can even re-cast the FormCollection values back to my EmailViewModel. Why is this?
[HttpPost]
public ActionResult SendStudentAnEmail(FormCollection emailFormCollection)
{
Debug.Write(emailFormCollection["email"]);
Debug.Write(emailFormCollection["subject"]);
Debug.Write(emailFormCollection["body"]);
var email = new EmailViewModel
{
Email = emailFormCollection["email"],
Subject = emailFormCollection["subject"],
Body = emailFormCollection["body"]
};
. . . . then the rest of my code works just how I wanted. . .
Why do I have to cast from FormCollection over to my EmailViewModel? Why isn't it giving me the NullReference Exception if I attempt to simply push an EmailViewModel into my Action?
Your EmailViewModel class has a property called Email of type string. And your controller action takes an argument called email of type EmailViewModel. This confuses the default model binder. So either rename the property inside the view model or the action argument:
[HttpPost]
public ActionResult SendStudentAnEmail(EmailViewModel model)
{
Debug.Write(model.Subject);
Debug.Write(model.Body);
Debug.Write(model.Email);
...
}
I have a problem trying to edit. I work with Areas for better management the application.The problem is in the areas called "Administrator".
Next is the controller code (OfficeControlle), I use a session variable has been previously created and functions to edit the model data I get.
public ActionResult Edit()
{
decimal id;
id = (decimal)Session["consul"];
CAMPUS_UNIVERSITY campus_university = db. CAMPUS_UNIVERSITY.Single(s => s.Idoffice == id);
ViewData.Model = db.OFFICE.Single(c => c.Idoffice == id);
ViewBag.University = db.UNIVERSITY.Single(u => u.IdUniversity == campus_university.IdUniversity);
ViewBag.campus = db.CITY_CAMPUS.Single(u => u.IdCity == campus_university.Idcitycampus);
return View(sede_universidad);
}
[HttpPost]
public ActionResult Edit(CAMPUS_UNIVERSITY campus_university, OFFICE office)
{
if (ModelState.IsValid)
{
db.CAMPUS_UNIVERSITY.Attach(campus_university);
db.ObjectStateManager.ChangeObjectState(campus_university, EntityState.Modified);
db.SaveChanges();
db. OFFICE.Attach(office);
db.ObjectStateManager.ChangeObjectState(office, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.IdCitycampus = new SelectList(db.CITY_CAMPUS, "IdCity", "Name", campus_university.IdCitycampus);
ViewBag.IdConsultorio = new SelectList(db.OFFICE, "Idoffice", "addressoffice", campus_university.Idoffice);
ViewBag.IdUniversidad = new SelectList(db.UNIVERSITY, "IdUniversity", "Name", campus_university.IdUniversity);
return View(campus_university);
}
Next is the view code
#model RolMVC3.Models.CAMPUS_UNIVERSITY
#{
ViewBag.Title = "Edit";
}
<h2>edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<h2> #ViewBag.University.Name - #ViewBag.Campus.Name </h2>
<fieldset>
<legend>OFFICE</legend>
#Html.HiddenFor(model => model.IdUniversity)
#Html.HiddenFor(model => model.IdCitycampus)
#Html.HiddenFor(model => model.Idoffice)
<div class="editor-label">
#Html.LabelFor(model => model.addresscampus)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.addresscampus)
#Html.ValidationMessageFor(model => model.addresscampus)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.phonecampus)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.phonecampus)
#Html.ValidationMessageFor(model => model.phonecampus)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.emailcampus)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.emailcampus)
#Html.ValidationMessageFor(model => model.emailcampus)
</div>
<fieldset>
<legend>OTHER DATE</legend>
#Html.Partial("_office", Model.OFFICE)
</fieldset>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("back", "Index")
</div>
The problem appears when I press the "Save" button, get the following error:
The parameters dictionary contains a null entry for parameter 'Id' of non-nullable type ''System.Decimal' ' for method ''System.Web.Mvc.ActionResult Index(System.Decimal)' in ''RolMVC3.Areas.Administrator.Controllers.OfficeController''
When you are redirecting here:
return RedirectToAction("Index");
make sure that you pass the id in the query string as it looks like your Index action requires it as parameter:
return RedirectToAction("Index", new { id = campus_university.IdUniversity });
I'm assuming that your Index action method looks something like this (please correct me if I am wrong):
public ActionResult Index(decimal id)
{
// Your method's code
}
So where you do return RedirectToAction("Index"); you are trying to redirect to an action method that takes no parameters. But in this case there is an action method that requires a parameter, namely id. So when redirecting you need to change your code and supply this id parameter.
This is what you could do:
return RedirectToAction("Index", new { id = /* put your id here */ });
I have created a custom validator in my asp.net mvc3 application like this:
{
if (customerToValidate.FirstName == customerToValidate.LastName)
return new ValidationResult("First Name and Last Name can not be same.");
return ValidationResult.Success;
}
public static ValidationResult ValidateFirstName(string firstName, ValidationContext context)
{
if (firstName == "Nadeem")
{
return new ValidationResult("First Name can not be Nadeem", new List<string> { "FirstName" });
}
return ValidationResult.Success;
}
and I have decorated my model like this:
[CustomValidation(typeof(CustomerValidator), "ValidateCustomer")]
public class Customer
{
public int Id { get; set; }
[CustomValidation(typeof(CustomerValidator), "ValidateFirstName")]
public string FirstName { get; set; }
public string LastName { get; set; }
}
my view is like this:
#model CustomvalidatorSample.Models.Customer
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
#using (#Html.BeginForm())
{
#Html.ValidationSummary(false)
<div class="editor-label">
#Html.LabelFor(model => model.FirstName, "First Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName, "Last Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
</div>
<div>
<input type="submit" value="Validate" />
</div>
}
But validation doesn't fire. Please suggest solution.
Thanks
How do you know the validation doesn't fire? Are you setting a break point in your controller?
You are not displaying any validation errors in your view. You need to add the following lines to the view.
#Html.ValidationMessageFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.LastName)
You will want to remove the custom validation from the class. Leave it on the properties though.
I have a problem with the following code. Basically when I call updatemodel it fails but I dont get an inner exception. If i change the drop down to a text box it works.
class:
public class Member
{
[Key]
public int MemberID { get; set; }
[StringLength(50),Required()]
public string Name { get; set; }
[Range(0,120)]
public short Age { get; set; }
[Required(),MaxLength(1)]
public string Gender {get; set;}
[Required()]
public virtual Team Team { get; set; }
}
controller methods:
[HttpGet]
public ActionResult ViewMember(int id)
{
try
{
var m = db.GetMember(id);
var maleItem = new SelectListItem{Text = "Male", Value= "M", Selected=m.Gender == "M"};
var femaleItem = new SelectListItem{Text = "Female", Value="F", Selected=m.Gender == "F"};
List<SelectListItem> items = new List<SelectListItem>();
items.Add(maleItem);
items.Add(femaleItem);
var genders = new SelectList(items, "Value", "Text");
ViewBag.Genders = genders;
return View(m);
}
catch (Exception ex)
{
return View();
}
}
[HttpPost]
public ActionResult ViewMember(Member m)
{
try
{
var member = db.GetMember(m.MemberID);
m.Team = db.GetTeam(member.Team.TeamID);
UpdateModel(member);
db.Save();
return RedirectToAction("ViewMembers", new { id = member.Team.TeamID });
}
catch (Exception ex)
{
return View();
}
}
and finally cshtml code:
#model GreenpowerAdmin.Models.Member
#using GreenpowerAdmin.Helpers
#{
ViewBag.Title = "ViewMember";
}
<h2>ViewMember</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Member</legend>
#Html.HiddenFor(model => model.MemberID)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Team.Name)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.Team.Name)
#Html.ValidationMessageFor(model => model.Team.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Age)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Age)
#Html.ValidationMessageFor(model => model.Age)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
#Html.DropDownList("Gender", ViewBag.Genders as SelectList)
#Html.ValidationMessageFor(model => model.Gender)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "ViewMembers", new { id=Model.Team.TeamID })
</div>
When I click the save button the updatemodel line fails. As said above, it doesn't fail if I use a text box for gender. Does anyone know what is making it fail or how to debug it?
Try declaring the MemberID property as a nullable integer:
[Key]
public int? MemberID { get; set; }