how to get Razor form to use route - asp.net-mvc-3

I have a Razor form
using (Html.BeginForm("TermList", "Course", FormMethod.Get))
{
<div style="text-align:right;display:inline-block; width:48%; margin-right:25px;">
#Html.DropDownList( "id", (SelectList) ViewBag.schoolId)
</div>
<input type="submit" value="Choose school" />
}
I expected this form to post to URI of:
http://localhost:56939/Course/TermList/764
instead the route looks like:
http://localhost:56939/Course/TermList?id=764
the route is not being used. I'd like to do away with the parameter
?id=764

The reason ?id=764 is appended to the URL is because you're using FormMethod.Get. Any values you are going to pass along with your form will be added in the querystring. You need to use FormMethod.Post.
#using(Html.BeginForm("TermList", "Course", FormMethod.Post))
{
... Your form stuff here ...
}
This will result in a form action of http://localhost:56939/Course/TermList/
If you want to post to http://localhost:56939/Course/TermList/764 you need to pass the id parameter in the Html.BeginForm statement:
#using(Html.BeginForm("TermList", "Course", new { #id = 764 }, FormMethod.Post))
{
... Your form stuff here ...
}
Obviously instead of hard coding 764 just use whatever variable it is that stores that value.

Related

ASP MVC-3 : Problems updating the data of an AJAX form after making a post

I have the following problem when updating a for via AJAX after it is submitted. For some reason some hidden fields that are on the HTML that is returned are not being updated, which is weird because when I run the debugger they appeared to have the correct value.
This is the relevant part of my form
<div id="itemPopUpForm">
#{Html.EnableClientValidation();}
#Html.ValidationSummary()
<div id="formDiv">
#{ Html.RenderPartial("ItemData", Model, new ViewDataDictionary() { { "Machines", ViewBag.Machines }, { "WarehouseList", ViewBag.WarehouseList }, { WebConstants.FORM_ID_KEY, #ViewData[WebConstants.FORM_ID_KEY] } }); }
</div>
</div>
Then the partial view contains hidden fields like these which are the ones not being updated
#using (Html.BeginForm("Index", "Item", FormMethod.Post, new { id = "frmItem", name = "frmItem" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Item.SodID)
#Html.HiddenFor(model => Model.Item.ItemID) //The itemID needs updating when an item is copied
#Html.HiddenFor(model => model.Item.Delivery.DeliveryAddressID, new { #id = "delAddressID" })
And this is the javascript method that updates the form
function ajaxSave() {
if (!itemValid()) return;
popup('ajaxSplash');
$.ajax({
type: "POST",
url: '#Url.Action("Index")',
data: $("#frmItem").serialize(),
success: function (html) {
console.log(html);
$("#formDiv").html(html);
initItemPage();
alert("Item was saved successfully");
},
error: function () { popup('ajaxSplash'); onFailure(); }
});
}
The action Index returns the Partial View "ItemData" and when I check the Item Model it does have the correct value, but when I see the html returned it is still set to 0.
If you intend to modify a model property in your POST action don't forget to remove it from ModelState first, otherwise HTML helpers will use the originally posted value when rendering:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// remove the value from modelstate
ModelState.Remove("Item.ItemID");
// update the value
model.Item.ItemID = 2;
return PartialView(model);
}
I'm having the same problem and it seems like the helper HiddenFor evaluates with required unobtrusive validation even if in the model one does not annotate the property with [Required].
The HTML rendered by #Html.HiddenFor(m=>m.Step) is :
<input data-val=​"true" data-val-number=​"The field Step must be a number." data-val-required=​"The Step field is required." id=​"Step" name=​"Step" type=​"hidden" value=​"2">​
Hence, it is why it works if we remove it from the ModelState.
Removing the property from the ModelState seems to me like a hack. I would prefer to use
<input type="hidden" id="Step" name="Step" value="#Model.Step" />
instead of the Html.HiddenFor helper.
You can also implement you own HiddenFor helper.

MVC3 - Pass back a model from RenderPartial

I have a page in MVC3 with a model of "pageModel".
In this page I have:
#{ Html.RenderPartial("_subPage", Model.subModel); } (Pagemodel.submodel)
In my controller I am doing:
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Results(pagemodel model, string frmAction)
{
}
The page loads fine the first time, but when I postback into the httpPost action, model.submodel is always null.
My question is, how do I return an updated model from the RenderPartial (if at all). I can get my model INTO the partial, but not back!
The problem with partials is that they do not preserve the navigational context. This means that any input fields that you might have put inside this partial will have incorrect names and the default model binder will not be able to retrieve the values back when you POST. Your HTML will look like this:
<input type="text" name="Prop1" value="property 1 value" />
<input type="text" name="Prop2" value="property 2 value" />
whereas the correct is:
<input type="text" name="subModel.Prop1" value="property 1 value" />
<input type="text" name="subModel.Prop2" value="property 2 value" />
In order to achieve this correct markup I would recommend you using editor templates.
So you replace:
#{ Html.RenderPartial("_subPage", Model.subModel); }
with:
#Html.EditorFor(x => x.subModel)
and then you move your _subPage.cshtml partial into ~/Views/Shared/EditorTemplates/SubModelType.cshtml where SubModelType is the type of the subModel property:
#model SubModelType
#Html.EditorFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop2)
Now when you look at the generated HTML the corresponding input field names should be prefixed with subModel and inside the POST controller action the model.subModel property will this time be properly initialized and populated from the values that were entered by the user in the input fields.
you'll need to change your partialview to accept the top level model, i.e:
#{ Html.RenderPartial("_subPage", Model); }
which would then render your properties in the partialview with the correct property names i.e. :
<input type="text" name="subModel.MyProperty" value="somevalue" />
It would also mean that your returned model in the HttpPost action will have to correct navigational relationship intact.
this is just one of those caveats related to viewmodels and hierarchies. Oh, btw, in mvc3, you don't need the verbose [AcceptVerbs(HttpVerbs.Post)] for posts. You can simply use [HttpPost]
You can also perform the following.
#Html.RenderPartial(
"_subPage",
Model.subModel,
new ViewDataDictionary
{
TemplateInfo = new TemplateInfo
{
HtmlFieldPrefix = "subModel"
}
});
Your partial view will remain as is, using the #model SubModel

ActionMethod param is 0 every time

I've got a form (here's some of its code):
#model Discussion
#{
ViewBag.Title = "Edit Discussion";
Layout = "~/Views/Shared/_App.cshtml";
}
#using (Html.BeginForm("Update", "Discussion", FormMethod.Post, new { id = Model.discussionId, #class = "update" }))
{
... rest of the view code
}
My related controller's update method has:
[HttpPost]
[ValidateInput(false)]
public ActionResult Update(FormCollection col, int id = 0)
{
... rest of code
}
when the form is submitted, I keep getting an id of 0 instead of the ID I'm seeing from the model that when I debug is clearly there inside being used during the form rendering. IT's just when I submit, it's not passing the id correctly.
Am I doing something wrong in terms of syntax here? the new{} in the Html.BeginForm I guess I don't understand how the new anonymous type is matching up to this id when the form is submitted when it's a collection of id and a class here as you can see.
When you do the new {id = Model.discussionId ...} you are setting the id attribute of the form element. To get the id to come across properly you would have to make it an input of the form. So inside the form you would put a hidden input element with something like this: #Html.Hidden("id", Model.discussionId)
So your current form is rendering something like this:
<form id="theDiscussionId" class="update" action="/Discussion/Update">
... rest of the view code
</form>
And you need it to be like this:
<form class="update" action="/Discussion/Update">
<input type="hidden" name="id" value="theDiscussionId" />
... rest of the view code
</form>

Setting form method to get without specifying controller and action

I have a partial view which I need to re-use:
div class="selectDate">
#using (Html.BeginForm("ViewTransactionLog", "Profile", FormMethod.Get))
{
<div class="selectDateLabel">Date:</div>
<div>
#Html.TextBox("start", range.Start, new { #class = "pickDate" }) to #Html.TextBox("end", range.End, new { #class = "pickDate" })
</div>
<div>
<input type="submit" value="Go" />
</div>
}
</div>
This is the code for picking 2 dates. As the data is lightweight, I wish to pass it through the Get method. I also wish to generalize it and put it into its own cshtml; however, Html.BeginForm expects the controller name and action name to be given if I wish to use the Get method. Is there anyway to avoid this so I could just move the code into a partial view of its own?
Assuming you want the form to post back to the current controller and action, you should be able to use an extension method:
public static MvcForm BeginForm<TModel>(
this HtmlHelper<TModel> html,
FormMethod formMethod)
{
string controller = (string)html.ViewContext.RouteData.Values["controller"];
string action = (string)html.ViewContext.RouteData.Values["action"];
return html.BeginForm(action, controller, formMethod);
}

Why does Html.BeginForm generate empty action?

I have a controller in an area called Admin
public class SiteVisitController : Controller
{
public ViewResult ReadyForCompletion() { ... }
public ViewResult CompleteAndExport() { ... }
}
and a view (ReadyForCompletion.cshtml) that has posts back to a different controller action on the same class
#using (Html.BeginForm( "CompleteAndExport", "SiteVisit" ))
{
<input type="submit" value="Complete & Export" />
}
The generated HTML for this form has a blank action:
<form action="" method="post"> <input type="submit" value="Complete & Export" />
</form>
I want to know why this has a blank action? For more info, I also added in a
#Url.RouteUrl(new { controller = "ReadyForCompletion", action = "SiteVisit", area = "Admin" })
which also printed out an empty string. Also, if I use an empty Html.BeginForm() it generates the correct action.
Registered routes are
context.MapRoute(
"Admin_manyParams",
"Admin/{controller}/{action}/{id}/{actionId}",
new { action = "Index", id = UrlParameter.Optional, actionId = UrlParameter.Optional }
);
I believe your problem is caused by having consecutive optional parameters. I was not able to replicate your problem until I changed the route to contain two optional parameters.
See: This article which explains the problem
For those of you encountering this issue using ASP.NET Core the root cause is the same, though the solution is slightly different. I first saw this in Core using multiple default values when calling .MapRoutes(). E.g.
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Foo", action = "Bar" }
);
The workaround is to place the default values into the string template:
routes.MapRoute(
name: "default",
template: "{controller=Foo}/{action=Bar}/{id?}"
);
YMMV.

Resources