Receiving a NullReference Exception error during HttpPost Action when using a ViewModel - asp.net-mvc-3

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

Related

Can I restrict client-side validation to specific fields?

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>

MVC3 custom server-side data annotation validation not working in partial view

I am trying to add custom data validation via data annotations. I am only concerned about server-side validation at this point. Everything I have seen here and elsewhere references client-side validation. I've stripped down all my code, ran a few test cases and I can get this working just fine on a regular view but as soon as the form is in a partial view, the code no longer breaks in the method to override IsValid.
In either case I can see the custom attribute being initialized. When the form is in a regular view I can see the override method being executed upon submitting the form, but when in a partial view the code never gets executed and it goes right to the HttpPost action.
I have spent the better parts of two days trying to figure this out and am at a loss. Any help would be GREATLY appreciated.
Note:
The code below does return the same view when it enters the HttpPost action. I have it like this for testing purposes. I know my override is never getting called from the partial view and thus IsValid is always true.
View showing form where the validation works
#model eRecruitBoard.ViewModels.HomeIndexViewModel
#{
ViewBag.Title = "eRecruitBoard";
}
#*<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>*#
<h2>Login/homepage</h2>
<br /><br />
<div class="errorMessage">
#Html.DisplayFor(m => m.LoginErrorMsg)
</div>
<br />
#using (Html.BeginForm("Index", "Home")) {
<div id="loginControlBox">
<fieldset>
<legend>Welcome to eRecruitBoard</legend>
<div class="editor-label">
#Html.LabelFor(m => m.UserName)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.Password)
</div>
<div class="editor-field">
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</div>
<div class="editor-label">
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe)
</div>
<div class="editor-label">
#Html.EditorFor(m => m.Date)
#Html.ValidationMessageFor(m => m.Date)
</div>
<p>
<input type="submit" value="Log In" />
</p>
</fieldset>
</div>
}
<div>
#Html.Action("BlankForm", "TestForm")
</div>
Partial View (these script calls also come in via _Layout, but I had them here for testing as well)
#model eRecruitBoard.ViewModels.TestFormViewModel
<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>
<script src="#Url.Content("~/Scripts/MicrosoftAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcAjax.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/MicrosoftMvcValidation.js")" type="text/javascript"></script>
#using (Html.BeginForm("NewActivity2", "TestForm", FormMethod.Post))
{
<fieldset>
<legend>Test Form</legend>
<br />
#Html.LabelFor(m => m.Date)
#Html.EditorFor(m => m.Date)
#Html.ValidationMessageFor(m => m.Date)
<input id="activityTimelineSubmit" type="submit" value="Submit" />
</fieldset>
}
ViewModel (for partial view)
namespace eRecruitBoard.ViewModels
{
public class TestFormViewModel
{
[Required]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
[NonFutureDate()]
[Display(Name = "Date")]
public DateTime Date { get; set; }
}
}
Controller (for partial view)
namespace eRecruitBoard.Controllers
{
public class TestFormController :BaseController
{
public ActionResult BlankForm()
{
var viewModel = new TestFormViewModel
{
Date = DateTime.Today
};
return PartialView("_TestForm", viewModel);
}
[HttpPost]
public ActionResult NewActivity2(DateTime Date)
{
if (!ModelState.IsValid)
return RedirectToAction("Index", "Home");
else
return RedirectToAction("Index", "Home");
}
}
}
Validation code
using System;
using System.ComponentModel.DataAnnotations;
namespace eRecruitBoard.WebLibrary.Validation
{
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class NonFutureDateAttribute : ValidationAttribute //public sealed class
{
public NonFutureDateAttribute(): base("Activity can only be saved for today or dates in the past.")
{
}
public override bool IsValid(object value)
{
DateTime dateToCheck = (DateTime)value;
return (dateToCheck <= DateTime.Today);
}
}
}
If you RedirectToAction you lose all validation. You have to return PartialView(model) in your POST action. That would require changing the parameter type of NewActivity2 to TestFormViewModel instead of DateTime.
Update your code with example of how you display the partial view (#Html.Partial() or javascript) if it still doesnt work.

Can't add file upload capability to generated MVC3 page

I'm new to MCV and I'm learning MVC3. I created a model and a controller and view was generated for me. The generated code makes perfect sense to me. I wanted to modify the generated view and controller so that I could upload a file when I "create" a new record. There is a lot of good information out there about how to do this. Specifically I tried this: http://haacked.com/archive/2010/07/16/uploading-files-with-aspnetmvc.aspx
The problem is that even when I select a file (not large) and submit, there are no files in the request. That is, Request.Files.Count is 0.
If I create the controller and and view from scratch, in the same project (no model), the example works just fine. I just can't add that functionality to the generated page. Basically, I'm trying get the Create action to also send the file. For example, create a new product entry and send the picture with it.
Example Create view:
#model Product.Models.Find
#{
ViewBag.Title = "Create";
}
<h2>Create</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("Create", "Find", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Find</legend>
<input type="file" id="file" />
<div class="editor-label">
#Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Example Controller:
[HttpPost]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
if (Request.Files.Count > 0 && Request.Files[0] != null)
{
//Not getting here
}
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(find);
}
This will create the record just fine but there are not files associated with the Request.
I've also tried a controller action like this:
[HttpPost]
public ActionResult Create(HttpPostedFileBase file)
{
if (file.ContentLength > 0)
{
//Not getting here
}
return RedirectToAction("Index");
}
I'm wondering if maybe you can't post a file at the same time as posting form fields? If that is the case, what are some patterns for creating a new record and associating a picture (or other file) with it?
Thanks
Create a ViewModel which has properties to handle your image and Product deatils
public class ProductViewModel
{
public string ImageURL { set;get;}
public string Title { set;get;}
public string Description { set;get;}
}
And in your HTTPGET Action method, return this ViewModel object to your strongly typed view
public ActionResult Create()
{
ProductViewModel objVM = new ProductViewModel();
return View(objVM);
}
And in your View
#model ProductViewModel
<h2>Add Product</h2>
#using (Html.BeginForm("Create", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(m => m.Title) <br/>
#Html.TextBoxFor(m => m.Description ) <br/>
<input type="file" name="file" />
<input type="submit" value="Upload" />
#Html.HiddenFor(m => m.ImageURL )
}
Now in your HttpPost action method, accept this ViewModel and File
[HttpPost]
public ActionResult Create(HttpPostedFileBase file, ProductViewModel objVM)
{
if(file==null)
{
return View("Create",objVM);
}
else
{
//You can check ModeState.IsValid if you have to check any model validations and do further processing with the data here.
//Now you have everything here in your parameters, you can access those and save
}
}
You will have to create a ViewModel for Product (maybe ProductViewModel) and add a HttpPostedFileBase field with the same name as the field of the form and use that instead of the Product in the action of the controller.
A ViewModel is nothing but a model used for specific views. Most of the times, with extra data to generate the view or to decompose and form the model on the controller action.
public ProductViewModel
{
public string Cod { get; set; }
// All needed fields goes here
public HttpPostedFileBase File{ get; set; }
/// Empty constructor and so on ...
}

inconvenient when editing models

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 */ });

ado.net mvc3 tuple using in model and single views

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

Resources