How to submit entire form to MVC Controller Action using DataTables Ajax - ajax

I'm revisiting a problem I had early on with a page that contains quite a few input and select controls in a form. I prefer that my Controller Action uses the View Model to determine the input values, but the model's members are always empty when submitted. Here is my undesirable WORKING code, briefly:
VIEW MODEL:
public class MyViewModel
{
[Display(Name = "Project Name")]
public string ProjectName { get; set; }
[Display(Name = "Project Country")]
public string ProjectCountry { get; set; }
public IEnumerable<SelectListItem> Countries { get; set; }
}
RAZOR VIEW:
#model My_Web_App.Models.MyViewModel
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myform" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.ProjectName)
<div class="col-md-9">
#Html.EditorFor(model => model.ProjectName)
</div>
</div>
<!-- Project Country -->
<div class="form-group">
#Html.LabelFor(model => model.ProjectCountry)
<div class="col-md-4">
#Html.DropDownListFor(
x => x.ProjectCountry,
new SelectList(Model.Countries, "Value", "Text"))
</div>
</div>
<table id="mytable">
<thead>
<tr>
<th>Data1</th>
<th>Data2</th>
<th>Data3</th>
</tr>
</thead>
<tbody></tbody>
</table>
<button id="btnSubmit" type="submit">Submit</button>
}
<script type="text/javascript">
$('#btnSubmit').click(function (event) {
event.preventDefault();
oTable = $('#mytable').dataTable({
"ajax": {
"url": "/MyController/MyJsonResult/",
"type": "POST",
"data": {
ProjectName: $('#ProjectName').val(),
ProjectCountry: $('#ProjectCountry').val()
}
}
});
return false;
});
</script>
CONTROLLER:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult MyJsonResult()
{
string projectName = Request["ProjectName"];
string projectCountry = Request["ProjectCountry"];
... DO SOMETHING ...
return Json(new { data = dosomethingresult }, JsonRequestBehavior.AllowGet);
}
The above code works in that I can get the View's form variables and use them to determine the results to return. Obviously I've left out a lot of code, a lot of form variables, and a lot of controller logic. I don't want to convert strings to integers, worry about whether a POST var is empty or null, etc. There are so many form variables I would like MVC to take care of this for me. For example:
PREFERRED CONTROLLER ACTION:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult MyJsonResult(MyViewModel model)
{
string projectName = model.ProjectName;
string projectCountry = model.ProjectCountry;
... DO SOMETHING ...
return Json(new { data = dosomethingresult }, JsonRequestBehavior.AllowGet);
}
The problem with the preferred controller action above is that the contents of model are always null when using DataTables Ajax.
model is initialized with valid data if I make the Ajax call outside of the DataTables initialization. :
$('#btnSubmit').click(function (event) {
event.preventDefault();
$.ajax({
url: "/MyController/MyJsonResult/",
type: "POST",
dataType: "json",
data: $('#myform').serialize()
});
return false;
}
The data setting just above correctly serializes all of the form fields and MVC successfully creates the View Model object for me to use in the Controller Action. Unfortunately, "data": $('#myform').serialize() in the DataTables Ajax does not work. The model is uninitialized (nulls and zeros) when the Controller Action is called.
How can I pass my entire form to the Controller Action using DataTables Ajax so that my View Model object contains correct data from the View's form?
I have been searching for a solution for a long time. Way too long. :S

Try this in your Razor view:
#using (Html.BeginForm("MyJsonResult", "MyController", FormMethod.Post, new { id = "myform" }))

Related

Saving multiple partial views from one main page

Here is my requirement :
I am designing a page to add a vehicle to the database :
Normal vehicle information [Model - Inventory]
Some other features [Model - IList]
Here is my index.cshtml page
#model Model.ViewModel.VehicleViewModel
<div>
<div class='col-md-12'>
<div class="form-group">
<input id="mainFormSubmit" type="button" value="Save" class="btn btn-default" />
</div>
</div>
#{Html.RenderPartial("~/Views/Shared/_InventoryPartial.cshtml", Model.InventoryVM);}
#{Html.RenderPartial("~/Views/Shared/_StandardFeaturePartial.cshtml", Model.StandardFeatures);}
</div>
<script type="text/javascript">
$('#mainFormSubmit').click(function () {
$('#InventoryForm').submit();
$("#StandardFeatureForm").submit();
});
</script>
This is my view model class
public class VehicleViewModel
{
public InventoryViewModel InventoryVM { get; set; }
public IList<StandardFeature> StandardFeatures { get; set; }
}
The Inventory partial view [_InventoryPartial.cshtml]
#model Model.ViewModel.InventoryViewModel
#{
var options = new AjaxOptions() { HttpMethod = "Post" };
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Ajax.BeginForm("InventorySave", "AddVehicle", options, new { id = "InventoryForm" }))
{
<fieldset>
<legend>Inventory Info</legend>
<div class='col-md-6'>
<!-- VIN input-->
<div class="form-group">
#Html.LabelFor(x => x.VIN, new { #class = "col-md-4 control-label" })
<div class="col-md-7">
#Html.TextBoxFor(x => x.VIN, new { #class = "form-control", #placeholder = "VIN" })
</div>
</div>
</div>
</fieldset>
}
The standard feature partial view [_StandardFeaturePartial.cshtml]
==
#model IEnumerable<Model.DomainModel.StandardFeature>
#{
var options = new AjaxOptions() { HttpMethod = "Post" };
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Ajax.BeginForm("StandardFeatureSave", "AddVehicle", options, new { id = "StandardFeatureForm" }))
{
When I am clicking on index page SAVE button, only
$('#InventoryForm').submit();
$("#StandardFeatureForm").submit();
last one(StandardFeatureForm) is executing.
Please let me know if this process is correct, and what could be the reason of this issue.
You should not call the submit method twice. Depending of the browser you can face different issues :
the form submission causes the browser to navigate to the form action and the submission
of the first may prevent the submission of the second
The browser could detected there are two requests and discards the
first submit.
In your case it will be easier to wrap your two partial views inside a unique form.
#using (Ajax.BeginForm("InventorySave", "AddVehicle", FormMethod.Post, new { id = "InventoryForm" }))
{
#{Html.RenderPartial("~/Views/Shared/_InventoryPartial.cshtml", Model.InventoryVM);}
#{Html.RenderPartial("~/Views/Shared/_StandardFeaturePartial.cshtml", Model.StandardFeatures);}
}
However when the partial views render they are not generating the correct name attributes for the larger modelModel.ViewModel.VehicleViewModel you want to use :
public void InventorySave(VehicleViewModel vehicleViewModel) {}
In this case you should use EditorTempmlate instead of partial views. It's simple to do from your partial views and this post should help you :Post a form with multiple partial views
Basically, drag your partials to the folder ~/Shared/EditorTemplates/
and rename them to match the model name they are the editor templates
for.
Finally something like :
#model Model.ViewModel.VehicleViewModel
#using (Html.BeginForm("InventorySave", "AddVehicle", FormMethod.Post, new { id = "InventoryForm" }))
{
#Html.EditorFor(m => m.InventoryVM);
#Html.EditorFor(m => m.StandardFeatures});
}
The Ajax.BeginForm helper already has a submit event associated to it which creates an Ajax POST request. When you are manually submitting your form using $('#InventoryForm').submit();, you're calling both and the submit events which can have strange side effects.
There are a few ways around this. Here is one solution
Change your forms to a regular HTML form using the Html.BeingForm helper.
Amend your script to create ajax requests and use the form data
$('#InventoryForm').submit(function(e) {
e.preventDefault();
$.post($(this).attr("action"), $(this).serialize(), function(r) {
//Do something
});
});
$('#StandardFeatureForm').submit(function(e) {
e.preventDefault();
$.post($(this).attr("action"), $(this).serialize(), function(r) {
//Do something
});
});
Hope this helps

Pass FormCollection to controller with ajax

How can I pass form collection to controller with jquery or ajax on change for drop down list?
Can I call an action in my controller and give it my form if I don't use submit button?
#using (Html.BeginForm("Create","Order", FormMethod.Post, new {#class = "form-horizontal", #name="forma"})) {
#Html.ValidationSummary(true)
#Html.Partial("_CreateOrEdit", Model)
<div class="form-actions no-margin-bottom">
<input type="submit" value=#Html.LocalizeString("String_Save") class="btn btn-primary" >
#Html.LocalizeString("String_BackToList")
</div>
}
and part of that partial is (it has many more fields but I think they don't matter for question)
<div class="control-group">
<div class="control-label">
#Html.LocalizedLabelFor(model => model.BuyerId)
</div>
<div class="controls">
#Html.DropDownListFor(model => model.BuyerId, ((IEnumerable<SalesAgent.Core.Entities.Buyer>)ViewBag.PossibleBuyers).Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.Name),
Value = option.Id.ToString(),
Selected = (Model != null) && (option.Id == Model.BuyerId)
}), #Html.LocalizeString("String_Choose"),new {#class="searchable" })
#Html.ValidationMessageFor(model => model.BuyerId)
when I change dropdown list I want to call an action from my controller and pass it my current form data. How do I do that?
var input = $(':input');
$.ajax({
type: "POST",
data: input,
url: "URL",
success: function (items) {
//TODO
}
});
In controller I recive it to a FormCollection and then I can do what I need with form data.
I think like this...
$("#BuyerId").change(function() {
var model = {
// name of model member : value of model member
// BuyerId : $("#BuyerId").val(),
// ....your form data :)))
}
$.ajax({
url : //your method of controller url,
type: "POST",
data: {model : model} // name of model object what send to your controller
});
});

This property cannot be set to a null value

As I am newbie to asp.net mvc 3, I have no good knowledge of it's internal process. I have two action to create new category as Get and Post method:
public ActionResult Create()
{
List<Category> cat = this.display_children(null, 0);
List<SelectListItem> items = new SelectList(cat, "ID", "CategoryName").ToList();
items.Insert(0, (new SelectListItem { Text = "root", Value = "" }));
ViewBag.ParentCategoryID = items;
return View();
}
[HttpPost]
public ActionResult Create(Category category)
{
if (ModelState.IsValid)
{
db.Categories.AddObject(category);
db.SaveChanges();
return RedirectToAction("Index");
}
List<Category> cat = this.display_children(null, 0);
List<SelectListItem> items = new SelectList(cat, "ID", "CategoryName").ToList();
items.Insert(0, (new SelectListItem { Text = "root", Value = "" }));
ViewBag.ParentCategoryID = items;
return View(category);
}
Below is View:
#model IVRControlPanel.Models.Category
#{
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()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Category</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CategoryName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CategoryName)
#Html.ValidationMessageFor(model => model.CategoryName)
</div>
<div class="editor-label">
#Html.Label("Select Category")
</div>
<div>
#*#Html.DropDownList("CategoryList",new SelectList(ViewBag.Categories))*#
#Html.DropDownList("ParentCategoryID", ViewBag.ParentCategoryID as SelectList)
#Html.ValidationMessageFor(model => model.ParentCategoryID)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Problem:
When I click the create button without filling up the category-name field following exception is thrown:
This property cannot be set to a null value.
The exception is thrown only when visual studio is debugging mode and when I continue debugging then only error is shown in validation message. Here, What actually have to be is that Error should be shown without throwing exception which is alright while not in debugging mode. I have following category table column in database and use model first approach Entity framework:
ID -> primary key and identity , integer
CategoryName -> Non nullable, varchar(50)
ParentCategoryID -> Nullable
I have not good at asp.net mvc 3 and can not figured what might be the problems.
In your actions replace:
ViewBag.ParentCategoryID = items;
with:
ViewBag.Categories = items;
and in your view:
#Html.DropDownListFor(x => x.ParentCategoryID, ViewBag.Categories as SelectList)
The DropDownList helper needs 2 arguments: the first one represents a scalar property that will hold the selected value and the second argument a collection with the available items. They should be different.
Luckily I found solution for this. Actually, Problem was due to PreBinding validation. I was searching and found same issue at this link explained nicely.
I have made partial class for Category as below:
public partial class Category{
}
public class TestEntityValidation{
[Required]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public String CategoryName { get; set; }
}
Here, Converting empty string to null is set to false which have solved my problem at DisplayFormat attributes.

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

MVC3 Client Side Validation not working with an Ajax.BeginForm form

I have the following form in a partial view
#model PartDetail
#using (Ajax.BeginForm("Create", "Part", null, new AjaxOptions { UpdateTargetId = "update_panel"}))
{
<h3>New #Model.PartType</h3>
<p> Serial Number:
<strong>
#Html.EditorFor(model => model.SerialNumber)
#Html.ValidationMessageFor(model => model.SerialNumber)
</strong>
</p>
<p> Name:
<strong>
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</strong>
</p>
...... more relatively boiler plate code here......
<p>
<input type="submit" class="btn primary" value="Save Part"/>
</p>
}
With a model of
public class PartDetail
{
public string DateCreated { get; set; }
[StringLength(255, MinimumLength = 3)]
public string Description { get; set; }
public Guid ID { get; set; }
public string IsActive { get; set; }
public string Manufacturer { get; set; }
public IEnumerable<SelectListItem> Manufacturers { get; set; }
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
public string PartType { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string SerialNumber { get; set; }
}
And I reference (in parent views of my partial view)
<script src="#Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"> </script>
And nothing gets validated. If I type nothing into the Serial Number text box and press submit it saves it to the database with no problems.
Try adding an OnBegin callback to the AjaxOptions and this should work.
function validateForm() {
return $('form').validate().form();
}
#using (Ajax.BeginForm("Create", "Part", null, new AjaxOptions { UpdateTargetId = "update_panel", OnBegin = "validateForm" }))
...
If this doesn't work for you, an alternative solution may be to submit the form using jQuery. This would be my preferred solution.
<div id="result"></div>
#using (Html.BeginForm())
{
<h3>New #Model.PartType</h3>
<p>Serial Number:
<strong>
#Html.EditorFor(model => model.SerialNumber)
#Html.ValidationMessageFor(model => model.SerialNumber)
</strong>
</p>
<p> Name:
<strong>
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</strong>
</p>
...... more relatively boiler plate code here......
<p>
<input type="submit" class="btn primary" value="Save Part"/>
</p>
}
jQuery/JS function to submit the form
$(function () {
$('form').submit(function () {
$.validator.unobtrusive.parse($('form')); //added
if ($(this).valid()) {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
$('#result').html(result);
}
});
}
return false;
});
});
In this case order of javascript file including process is important. Your order of including these file should be
jquery.js
jquery.validate.js
jquery.validate.unobtrusive.js
When I tried with re-ordering it works fine for me. Try it.
Check your root Web.config of the solution/project. Does it contain the following lines?
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
It not, add them.
you need to modified your controller action little bit to work validation in Ajax request
you catch RenderHtml of partial view and apply your validation on it for e.g
//In Controller
public string Create(PartDetail model)
{
string RenderHtml = RenderPartialViewToString("PartailViewName", model);
return RenderHtml;
}
protected string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
you must be pass ViewName and Model to RenderPartialViewToString method. it will return you view with validation which are you applied in model.

Resources