#Url.Action results 404 error when called from form - asp.net-mvc

I work on ASP.NET MVC application. Table is being populated using jQuery, but it doesn't matter now.
The problem is this:
When I make a call with form tags, I get an error 404 Not Found. By inspecting I confirmed that model is bound OK (it's populated).
When I comment out form tags, I can make a call to method, but with model being null (like it's not bound).
Here is some of code I use:
#model PayrollModel
#*#using (Html.BeginForm("Index", "Request", FormMethod.Post, new { id = "frmPersonView" }))
{*#
<table data-dataurl="#Url.Action("PayrollData", "Payroll")" id="tblData" class="table hover table-bordered table-striped"></table>
#*}*#
Here is my controller method:
[HttpGet]
public ActionResult PayrollData(PayrollModel model)
{
//do something
}
Edited:
I found out why this happens:
Request URL:
http:// localhost/CT.ISVDV.WebUI/Payroll/PayrollData?sEcho=1&iColumns=5&sColumns=&iDisplayStart=0&iDisplayLength=10&mDataProp_0=FIRSTNAME&mDataProp_1=LASTNAME&mDataProp_2=RIGHTGROUPNAME&mDataProp_3=DISABILITYTYPENAME&mDataProp_4=AMOUNT&iSortCol_0=0&sSortDir_0=asc&iSortingCols=1&bSortable_0=true&bSortable_1=true&bSortable_2=true&bSortable_3=true&bSortable_4=true&SelectedTypes%5B3%5D=4&SelectedTypes%5B4%5D=5&SelectedTypes%5B5%5D=6&SelectedTypes%5B6%5D=7&SelectedTypes%5B7%5D=8&SelectedTypes%5B8%5D=9&SelectedTypes%5B9%5D=10&SelectedTypes%5B10%5D=11&SelectedTypes%5B11%5D=12&SelectedTypes%5B12%5D=13&SelectedTypes%5B13%5D=14&SelectedTypes%5B14%5D=15&SelectedTypes%5B15%5D=16&SelectedTypes%5B16%5D=17&SelectedTypes%5B17%5D=18&SelectedTypes%5B18%5D=19&SelectedTypes%5B19%5D=20&SelectedTypes%5B20%5D=21&SelectedTypes%5B21%5D=22&SelectedTypes%5B22%5D=23&SelectedTypes%5B23%5D=24&SelectedTypes%5B24%5D=25&SelectedTypes%5B25%5D=26&PayrollDate=01.12.2014&PayrollSearch.RIGHTGROUPIDList.Value%5B0%5D=4&PayrollSearch.RIGHTGROUPIDList.Value%5B1%5D=5&PayrollSearch.RIGHTGROUPIDList.Value%5B2%5D=6&PayrollSearch.RIGHTGROUPIDList.Value%5B3%5D=7&PayrollSearch.RIGHTGROUPIDList.Value%5B4%5D=8&PayrollSearch.RIGHTGROUPIDList.Value%5B5%5D=9&PayrollSearch.RIGHTGROUPIDList.Value%5B6%5D=10&PayrollSearch.RIGHTGROUPIDList.Value%5B7%5D=11&PayrollSearch.RIGHTGROUPIDList.Value%5B8%5D=12&PayrollSearch.RIGHTGROUPIDList.Value%5B9%5D=13&PayrollSearch.RIGHTGROUPIDList.Value%5B10%5D=14&PayrollSearch.RIGHTGROUPIDList.Value%5B11%5D=15&PayrollSearch.RIGHTGROUPIDList.Value%5B12%5D=16&PayrollSearch.RIGHTGROUPIDList.Value%5B13%5D=17&PayrollSearch.RIGHTGROUPIDList.Value%5B14%5D=18&PayrollSearch.RIGHTGROUPIDList.Value%5B15%5D=19&PayrollSearch.RIGHTGROUPIDList.Value%5B16%5D=20&PayrollSearch.RIGHTGROUPIDList.Value%5B17%5D=21&PayrollSearch.RIGHTGROUPIDList.Value%5B18%5D=22&PayrollSearch.RIGHTGROUPIDList.Value%5B19%5D=23&PayrollSearch.RIGHTGROUPIDList.Value%5B20%5D=24&PayrollSearch.RIGHTGROUPIDList.Value%5B21%5D=25&PayrollSearch.RIGHTGROUPIDList.Value%5B22%5D=26&tblData_length=10&
Up to this point, everything is right, but then i also passes some strange values that are not model properties. This happens only if I use form tags, and I need those.
DXScript=1_157%2C1_89%2C1_88%2C17_28%2C17_2%2C1_149%2C1_86%2C1_141%2C1_96%2C17_7%2C1_139%2C1_98%2C1_97%2C17_8%2C1_155%2C1_125%2C1_156%2C1_118%2C17_9%2C1_148%2C1_147%2C1_132%2C17_27%2C1_142%2C1_93%2C1_119%2C1_99%2C1_151%2C1_126%2C17_13%2C1_108%2C1_115%2C1_137%2C1_92%2C1_152%2C1_128%2C17_15%2C1_129%2C1_120%2C17_11%2C1_131%2C1_140%2C1_134%2C17_18%2C1_145%2C17_20%2C1_143%2C1_138%2C1_146%2C1_150%2C17_23%2C17_26%2C1_95%2C5_5%2C5_4%2C4_11%2C4_10%2C4_6%2C4_7%2C4_9%2C17_14%2C4_12%2C1_107%2C1_110%2C4_13%2C4_14%2C1_106%2C1_124%2C17_12%2C1_144%2C7_48%2C1_91%2C7_50%2C17_19%2C1_100%2C1_103%2C1_111%2C17_0%2C1_114%2C1_101%2C17_1%2C1_102%2C17_3%2C1_104%2C1_116%2C17_5%2C1_130%2C1_113%2C17_16%2C17_17%2C1_112%2C17_24%2C1_117%2C10_2%2C10_1%2C10_3%2C10_4%2C17_4%2C9_15%2C9_12%2C9_10%2C17_21%2C9_14%2C9_11%2C9_13%2C8_10%2C8_17%2C8_24%2C8_26%2C8_9%2C8_12%2C8_13%2C8_18%2C17_22%2C8_21%2C8_23%2C8_22%2C8_16%2C8_19%2C8_20%2C8_14%2C8_15%2C8_25%2C8_11%2C6_12%2C17_25&DXCss=%2FCT.ISVDV.WebUI%2Fimg%2Ffavicon.ico%2C%2FCT.ISVDV.WebUI%2FContent%2Fjvectormap%2Fjquery-jvectormap-1.2.2.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fbootstrap.min.css%2C%2FCT.ISVDV.WebUI%2FContent%2Ffont-awesome.min.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fionicons.min.css%2C%2FCT.ISVDV.WebUI%2FContent%2FiCheck%2Fall.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fcolorpicker%2Fbootstrap-colorpicker.min.css%2C%2FCT.ISVDV.WebUI%2FContent%2Ftimepicker%2Fbootstrap-timepicker.min.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fmorris%2Fmorris.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fdatepicker%2Fdatepicker3.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fdaterangepicker%2Fdaterangepicker-bs3.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fbootstrap-wysihtml5%2Fbootstrap3-wysihtml5.min.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fcss%2Fselect2.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fselect2-bootstrap.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fdatatables%2FdataTables.bootstrap.css%2C%2FCT.ISVDV.WebUI%2FContent%2FAdminLTE.css%2C%2FCT.ISVDV.WebUI%2FContent%2FSite.css%2C%2FCT.ISVDV.WebUI%2FContent%2Fjquery.bootstrap-touchspin%2Fjquery.bootstrap-touchspin.css%2C1_9%2C1_11%2C0_844%2C0_842%2C1_8%2C0_691%2C0_694%2C0_682%2C1_4%2C0_684%2C0_805%2C0_698%2C4_2%2C0_700%2C5_1%2C0_782%2C0_762%2C0_764%2C7_1%2C7_0%2C1_1%2C0_657%2C0_869%2C0_871%2C0_777%2C8_2%2C0_779%2C8_0%2C0_794%2C6_2%2C0_796&_=1417596051723
How can I prevent this from happening?

Your formatting is off. Try mix single quotes with double ones:
data-dataurl='#Url.Action("PayrollData", "Payroll")'
PayrollData expects a PayrollModel model and you're providing none. There's no reasonable way to send a whole model using Url.Action so consider sending only an ID.
data-dataurl='#Url.Action("PayrollData", "Payroll", new { id = model.ID })'

I think you are very confused on how it all works, especially model binding.
Normally you would have something like this (Lets say we are working only have Payroll controller)
[HttpGet]
public ActionResult Index()
{
// here you would get your model that displays some data on that page
PayrollIndexModel model = fetchModelData();
return View(model)
}
and its view
#model PayrollIndexModel
#Model.PageTitle
#using (Html.BeginForm("Request", "Payroll", FormMethod.Post, new { id = "frmPersonView" }))
{
<table data-dataurl="#Url.Action("PayrollData", "Payroll")" id="tblData" class="table hover table-bordered table-striped"></table>
}
then if you click Save on the form you would call the one below where PayrollModel is binded to your model class and later you can do things with it.
[HttpPost]
public ActionResult Request(PayrollModel model)
{
// save stuff to database
}
To get this one working
#Url.Action("PayrollData", "Payroll")
You have to create actions this way as you are requesting data by GET method and not sending anything to the server:
[HttpGet]
public ActionResult PayrollData()
{
PayrollModel model = fetchModelData();
return View(model)
}
However, I see that you use DataTables library and last time I used it I remember that Datatable is expecting JSON object. So here you should send JSON.
[HttpGet]
public ActionResult PayrollData()
{
PayrollModel model = fetchModelData();
return Json(model)
}
Your model converted to JSON should be formatted in the way that JQuery DataTable can parse it (understand it) such as:
{ "iTotalRecords":11,
"iTotalDisplayRecords":11,
"sEcho":1,
"aaData":[{"FirstName":"Dan","LastName":"Callahan","PhoneNumber":"(123) 555-5552","Age":35,"Birthday":"\/Date(218955600000)\/"},
{"FirstName":"Tom","LastName":"Gun","PhoneNumber":"(123) 555-5559","Age":59,"Birthday":"\/Date(-534538800000)\/"},
{"FirstName":"James","LastName":"Halk","PhoneNumber":"(123) 555-5554","Age":21,"Birthday":"\/Date(660027600000)\/"},
{"FirstName":"Jarold","LastName":"Interface","PhoneNumber":"(123) 555-5556","Age":39,"Birthday":"\/Date(86932800000)\/"},
{"FirstName":"Kevin","LastName":"Kentucky","PhoneNumber":"(123) 555-5551","Age":40,"Birthday":"\/Date(92638800000)\/"},
{"FirstName":"Justin","LastName":"Michaels","PhoneNumber":"(123) 555-5555","Age":27,"Birthday":"\/Date(470898000000)\/"},
{"FirstName":"Erich","LastName":"Milton","PhoneNumber":"(123) 555-5558","Age":54,"Birthday":"\/Date(-370724400000)\/"},
{"FirstName":"Mike","LastName":"Peterson","PhoneNumber":"(123) 555-5550","Age":24,"Birthday":"\/Date(568184400000)\/"},
{"FirstName":"Jason","LastName":"Ralph","PhoneNumber":"(123) 555-5557","Age":27,"Birthday":"\/Date(468306000000)\/"},
{"FirstName":"John","LastName":"Thompson21","PhoneNumber":"(123) 555-5545","Age":27,"Birthday":"\/Date(473317200000)\/"}
]}
To make your life easier check this library which does a lot of tedious work for you:
https://github.com/ALMMa/datatables.mvc
Good Luck.

Related

ASP.NET MVC: unable to post array of values

I'm working at an ASP.NET MVC website using C#.
On a page I try to send back a complete model to the controller. In this model most inputs are unique variables, and there is an array.
In the model, the array is defined like this:
public class SimulazioneModelComplete
...
public SoaValueModel[] GareSoaSec { get; set; }
The razor / html code (very simplified!) is like this
#model SimulazioneModelComplete
#using (Html.BeginForm("CreaEsito", "Simulazioni", FormMethod.Post, new { id = "CreaEsito", name = "CreaEsito", enctype = "multipart/form-data" }))
{
...
#foreach (SoaModel soa in ViewBag.Categorie)
<input type="checkbox" name="GareSoaSec[#counter].IdSoa" id="GareSoaSec[#counter].IdSoa" value="#soa.id" />
...
}
The user posts data clicking a button:
<button onclick="Save()">SAVE</button>
The onclick javascript is like this:
function Save()
{
$.post("/Abbonamenti/Simulazioni/CreaEsito", $("CreaEsito").serialize(), function (data) { });
};
The CreaEsito method is like this:
[HttpPost]
[AuthorizedOnly(Roles = "Administrator, Agent, Esiti")]
public ActionResult CreaEsito(SimulazioneModelComplete model, SoaValueModel[] GareSoaSec)
I try in a lot of ways, but I never have a complete model, with both the simple variables and the array of objects. It seems like the "link" made by "name" doesn't work properly, and I don't understand why.
It should work well (I do the same in other pages, and everything works well), but this time it doesn't work. When I look the model sent back to the controller, the unique variables are ok, the array is empty, length is zero.
I've lost all Friday and Saturday, now is 10PM and I'm still fighting, without any solution.
Can someone help?

SurfaceController generate incorrect URL?

A Form is posted to a SurfaceController 'Submit' action. After saving to the database, it redirects to another action 'LeadContact', in the same controller (using RedirectToAction()), passing in 'Id' as a paramter. The model is then populated and passed to the 'LeadContact' view.
Not sure if I'm doing this correctly, but when 'LeadContact' renders in the browser, the URL shows as
http://localhost:50656/umbraco/Surface/HealthInsurance/LeadContact?leadOptionId=70`
while I'm expecting it to be
http://localhost:50656/HealthInsurance/LeadContact?leadOptionId=70
In short it adds /umbraco/SurfaceContact' into url.
Can you please advise how I can correct it and what I'm doing wrong ?
public ActionResult Submit(FormCollection form)
{
//Some logic and later redirect to another action 'LeadContact'
return RedirectToAction("LeadContact", new { leadOptionId = _id});
}
public ActionResult LeadContact(int leadOptionId)
{
MyViewModel model = new MyViewModel();
//Lines of code to populate data into model
return View("LeadContact", model);
}
Thanks for your help and sharing.
Check your project properties, under Web you most likely have a virtual path specified.

MVC sending data from View to Controller

I am quite new to MVC 3.
I know how to send a strongly typed object from a Controller to a View. What I have now, is a View which contains a table/form which consists of that data.
The user can change that data whilst they're are in that View (html page).
When they click on "Save", how do I send the data from the View back to the Controller so that I can update my database.
Do I overload the Controller method so that it accepts a parameter of the model type? Can you please provide some source code.
(Please do not show code of persisting data to a database, I know how to do that part).
Thank you very much for helping me.
I would also prefer using #Html.BeginForm()
I like creating an action method made for my post data. So let's say you have a UserViewModel:
public class UserViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then a UserController:
public class UserController
{
[HttpGet]
public ActionResult Edit(int id)
{
// Create your UserViewModel with the passed in Id. Get stuff from the db, etc...
var userViewModel = new UserViewModel();
// ...
return View(userViewModel);
}
[HttpPost]
public ActionResult Edit(UserViewModel userViewModel)
{
// This is the post method. MVC will bind the data from your
// view's form and put that data in the UserViewModel that is sent
// to this method.
// Validate the data and save to the database.
// Redirect to where the user needs to be.
}
}
I'm assuming you have a form in your view already. You'll want to make sure that the form posts the data to the correct action method. In my example, you'd create the form like so:
#model UserViewModel
#using (Html.BeginForm("Edit", "User", FormMethod.Post))
{
#Html.TextBoxFor(m => m.Name)
#Html.HiddenFor(m => m.Id)
}
The key to all this is the model binding that MVC does. Make use of the HTML helpers, like the Html.TextBoxFor I used. Also, you'll notice the top line of the view code I added. The #model tells the view you'll be sending it a UserViewModel. Let the engine do work for you.
Edit: Good call, did that all in Notepad, forgot a HiddenFor for the Id!
In MVC, the act of scraping out data from POST or GET HttpRequests is referred to as Model Binding - there are plenty of SO questions relating to this.
Out of the box, MVC will bind your Get and Post variables based on convention, e.g. a form field with the name 'FormName' will be bound back to a parameter on your controller with the same name.
Model binding also works for objects - MVC will instantiate an object for your controller, and set the properties with the same name as your form.

Calling Html.BeginForm and specifying action

I have been trying to do the following but whatever I try I just keep getting different errors:
#using (Html.BeginForm(Url.Action(this.ViewContext.RouteData.Values["action"] as string)))
This for example produces:
<form action="/adminTests/create?Length=22" method="post">
Has anyone figured out how to do this?
You are using a wrong overload. It should be:
#using (Html.BeginForm(new { action = ViewContext.RouteData.Values.GetRequiredString("action") }))
or:
#using (Html.BeginForm(ViewContext.RouteData.Values.GetRequiredString("action"), ViewContext.RouteData.Values.GetRequiredString("controller"))))
or if you want to generate a form POSTing to the current url use simply (note that this will include any query string parameters):
#using (Html.BeginForm())
For full list of available overloads consult the documentation.
Perhaps try this:
#using (Html.BeginForm("action", "controller"))
This uses the correct overload and has a simpler (imo) syntax.
Edge case answer
If you are posting back to an action of the same name, but you loaded the form with more data from the route you have to do one more thing. In your controller, you need to remove the extra routing values.
RouteData.Values.Remove("CompanyId");
Let me explain.
In my case I was loading the Add Contact form passing it the company id in the route, e.g., contact/add/123 to the Add action public IActionResult Add(int companyId). The Add/Edit contact form is then loaded with a new ViewModel with the CompanyId populated. var model = new ContactViewModel { CompanyId = companyId };
The route now serves no purpose, but if you try to use Html.BeginForm("add", "contact") the form will still have an action of contact/add/123, which will cause the routing to fail.
You can work around this by taking the id on the add and ignoring it (yuk!). Or by passing parameters rather than using routing (probably best), but sometimes there are external reasons that's not an option and you have to work with the situation you have.
The Action in one go for easier reading
[HttpGet]
[Route("add/{companyId}")]
public IActionResult Add(int companyId)
{
var model = new ContactViewModel { CompanyId = companyId };
RouteData.Values.Remove("companyId");
return PartialView("_ContactModal", model);
}
[HttpPost]
[Route("add")]
public IActionResult Add(ContactViewModel model)
{
...
}

Get and post methods with different names

I am using ASP.NET MVC 3.
I have an action method called New. When form validation succeeds then I want Create to handle the request.
public ActionResult New()
{
// Code
}
[HttpPost]
public ActionResult Create()
{
// Code
}
I have 2 questions regarding this. Firstly, how do I modify my Html.BeginForm to handle the above? I tried the following but I get an error, it is looking for the physical file Create (News is my controller name):
#using (Html.BeginForm("Create", "News"))
{
<!-- Code -->
}
Secondly, how do I prevent a user from typing in /News/Create in the URL. Create should handle only my post requests coming from New.
UPDATE:
I managed to get something working. The original URL looks like this:
http://localhost:33947/News/New
After I click the submit button and validation fails then the URL looks like:
http://localhost:33947/News/Create
Why does it do this? I want it to stay:
http://localhost:33947/News/New
Here is my action method code:
public ActionResult New()
{
return View();
}
[HttpPost]
public ActionResult Create(NewsViewModel newsViewModel)
{
if (!ModelState.IsValid)
{
return View("New", newsViewModel);
}
return View("Index");
}
#using (Html.BeginForm("Create", "News", FormMethod.Post))
You cannot prevent user from typing address manually, but you can add another Create action marked with [HttpGet] attribute which will show user the error page. Anyway, your current Create action method will not handle such requests. Probably user will get standard File not found error in this case.
UPDATE: Html.BeginForm("Create", "News") means to submit the form to action "Create" of the controller "News", so it works as it should. If you want to submit to /News/New, replace it with Html.BeginForm("New", "News").
Regarding your second question, instead of:
if (!ModelState.IsValid)
{
return View("New", newsViewModel);
}
you should use :
if (!ModelState.IsValid)
{
return RedirectToAction("new");
}
Now this has the undesired side affect that your Modelstate is lost. (no validation errors are shown in your form.
To fix this add the ModelStateToTempData attribute to both your new and create action.
This attribute come as part of http://mvccontrib.codeplex.com/ .

Resources