Asp.net mvc3 razor with multiple submit buttons - asp.net

I'm using MVC3 Razor. I have 2 submit buttons setup on my view but the problem I'm having is that both submit buttons cause the validation of the model. I want to hook up individual submit buttons with specific input controls for validation.

I know this is a few months old but the solutions here seemed needlessly complex and there's no accepted answer yet. If you name your inputs the same but give them different values, you can get that value in your controller just by including a string with the name of the input as a variable. This is how I solved this problem:
View:
<input type="submit" id="EnterprisePush" name="btnSubmit" value="Push" />
<input type="submit" id="EnterprisePull" name="btnSubmit" value="Pull" />
Controller:
[HttpPost]
public ActionResult EnterpriseAdmin(int id, string btnSubmit, FormCollection collection)
{
switch (btnSubmit) {
case "Push":
/* Do Something here */
break;
case "Pull":
/* Do Something else here */
break;
}

The browser is always going to submit the entire form regardless of which submit button you press.
The best solution would be to have two submit buttons with the same value for the name attribute and different values for the value attributes.
When you submit the form, the value of the button will be submitted as well. In your action which handles that form submission, you check to see the value of the button and perform the correct validation based on that.
In your form you would have something like this:
<button type="submit" name="Command" value="command1">Do Command #1</button>
<button type="submit" name="Command" value="command2">Do Command #2</button>
Your Form Model would look like this:
public class MyFormModel() {
public string Command {get;set;}
public string SomeOtherVal {get;set;}
}
Your controller\action would look like this:
public ActionResult HandleFormSubmit(MyFormModel model) {
if (model.Command == "command1") {
// do something
} else if (model.Command == "command2") {
// do something else
}
}

Firstly, you can disable client validation on your cancel button simply by adding the CSS class 'cancel' to it. See: Disable client-side validation in MVC 3 "cancel" submit button
Secondly, as well testing the submit element's form name as described above, you can use a custom action selector. Here's mine, which I originally took from the blog post shown in the comment:
/// <summary>
/// Used to vary an action method based on which button in a form was pressed. This
/// is useful but is an anti-pattern because it couples the controller to names
/// used in the form elements.
/// </summary>
/// <remarks>
/// See the example at http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form.aspx
/// </remarks>
public class AcceptButtonAttribute : ActionMethodSelectorAttribute
{
public string ButtonName { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var req = controllerContext.RequestContext.HttpContext.Request;
return !string.IsNullOrEmpty(req.Form[this.ButtonName]);
}
}
In your controller:
[HttpPost]
[ActionName("Edit")]
[AcceptButton(ButtonName = "Cancel")]
public ActionResult Edit_Cancel(MyModel model)
{
return RedirectToAction("Index");
}
[HttpPost]
[AcceptButton(ButtonName = "Save")]
public ActionResult Edit(MyModel model)
{
// do real work here
}
Note that you need the [ActionName("Edit")] attribute to tell MVC that although using a different method name, it is for the Edit action.
And in your View:
<input type="submit" name="Save" value="Save" />
<input type="submit" name="Cancel" value="Cancel" class="cancel" />

My solution was to do two things. Say we have a Save button and another Add Something button. When user clicks on Save we want client validation and server validation to be performed. For later button we don't want any validation to take place.
Temporarily disable client validation for second button (on click):
<input type="submit" name="submit-button" value="Save" />
<input type="submit" name="submit-button" value="Add Something" onclick="document.forms[0].noValidate = true; document.forms[0].submit();" />
Good thing about it is when JavaScript is disabled the client validation would never have taken place anyway.
Take care of server side
Similar to what Bryan is saying when you click any submit button within a form, the entire form and the clicked submit button value is posted. You can differentiate which button was clicked by the name posted. In example above when user clicks on Save button and we read Request.Form["submit-button"] in controller post action we get "Save". If user clicked on Add Something we would get "Add Something". This is the way HTML is supposed to work.
Now to get around having magic strings all over the place I usually have a public static class within the controller, like so:
public class HomeController
{
public static class Buttons
{
public const string Save = "Save";
public const string AddSomething = "Add something";
}
// Action methods
}
So you can use these for rendering form:
<input type="submit" name="submit-button" value="#HomeController.Buttons.Save" />
And you can easily read the button clicked in controller:
[HttpPost]
public ActionResult Index(Model viewModel)
{
var buttonClicked = Request.Form["submit-button"];
switch (buttonClicked) {
case HomeController.Buttons.Save:
return Save(viewModel);
case HomeController.Buttons.AddSomething:
return AddSOmething(viewModel);
}
return View();
}
In Save method you first ask if ModelState.IsValid and return view model if not but in AddSomething method we will clear any errors:
public ActionResult AddSomething(Model viewModel)
{
ModelState.Clear();
// your code to add something to model
return View(viewModel);
}
This was you keep everything clean, tidy and testable. And you can introduce a constant for submit-button html name attribute. It might be possible to do all the constants with T4MVC too. A similar solution applies to when you need a "auto postback" combo box, except you need a hidden field that is set via onchange event of the select element.
Hope this helps.

Just use this code as a template:
#{
var nextButtonVal = "Next >>";
var backButtonVal = "<< Back";
if (IsPost) {
if(Request["navigate"].Equals(backButtonVal)){Response.Redirect("~/pageFoo");}
if(Request["navigate"].Equals(nextButtonVal)){Response.Redirect("~/pagebar");}
}
}
<input type="submit" value="#backButtonVal" title="Back" name="navigate"/>
<input type="submit" value="#nextButtonVal" title="Next" name="navigate"/>

One final thing I would do is instead of using intelligent strings, use an enum to determine the value for each input tag. Using razor syntax:
#Enum.GetName(typeof(YourEnumType), yourEnum.WhateverValue)
then in your controller:
public ActionResult DoSomethingBasedOnEnumValue(string enumValue)
{
YourEnumType localVar = (YourEnumType)Enum.Parse(typeof(YourEnumType), enumValue);
switch(localVar)
{
case YourEnumType.Action1:
//do something
break;
case YourEnumType.Action2:
//do something else
break;
}
return View();
}

If you want to have separate action for delete, try this.
add a delete action in the controller and mark it as HttpDelete,
[HttpDelete]
public ActionResult Edit(int id, string foo) {
...
}
And in the view,
button name should be X-HTTP-Method-Override and value should be DELETE
<button name="X-HTTP-Method-Override" value="DELETE" formnovalidate="formnovalidate" class="cancel">Delete</button>
note: all most all the browsers don't allow for other HTTP methods, like HEAD, PUT, or DELETE. but by add a header to the HTTP request, X-HTTP-Method-Override, that is supposed to be interpreted by the service and acted upon regardless of the actual HTTP method used. So above code will add a header to the request like X-HTTP-Method-Override: DELETE. and .net framework will do the rest of the things and direct you to delete action.

Submit button name don't come to server side if in all from this situation you are will be use [Remote] attribute for validation model property.

Related

How to POST from one Page Handler to another?

I am working on a ASP.NET Core 2.0 project using Razor Pages (not MVC).
I have the following flow:
User fills out form and POSTs
This page's POST handler validates the info and returns Page() if there are issues. If not, handler saves data to database.
From here, I want the handler to POST to a different page's POST handler with the validated and saved data from Step 2.
How do I POST to another page from within a page handler? Is this the appropriate way to do this kind of thing? The reason I don't want to RedirectToPage() is because I don't want the final page in the sequence to be navigable via GET. The final page should not be accessible via a direct link but should only return on a POST.
I thought about validating/saving the data and setting a boolean "IsValid" and returning the page, checking for that IsValid, and immediately POSTing to the final page via JS. However this feels dirty.
Set the "asp-page" property of the form to your other page. Then set values in the standard way.
<form method="post" asp-page="/pathto/otherpage">
Select Example:<select name="DataForOtherPage">
Then in your controller, bind the value...
[BindProperty]
public string DataForOtherPage { get; set; }
You don't need to cross post!
If possible, you should avoid the cross-post. Do it all under the original action. The action can return a different view by specifying the view name in the View call.
If the target of the cross-post contains complicated logic that you don't want to duplicate, extract it to a common library, and call it from both actions.
For example, instead of
ActionResult Action1()
{
if (canHandleItMyself)
{
return View("View1");
}
else
{
return //Something that posts to action2
}
}
ActionResult Action2()
{
DoSomethingComplicated1();
DoSomethingComplicated2();
DoSomethingComplicated3();
DoSomethingComplicated4();
return View("View2");
}
Do something like this:
class CommonLibrary
{
static public void DoSomethingComplicated()
{
DoSomethingComplicated1();
DoSomethingComplicated2();
DoSomethingComplicated3();
DoSomethingComplicated4();
}
}
ActionResult Action1()
{
if (canHandleItMyself)
{
return View("View1");
}
else
{
CommonLibrary.DoSomethingComplicated();
return View("View2");
}
}
ActionResult Action2()
{
CommonLibrary.DoSomethingComplicated();
return View("View2");
}
If you really want to cross-post
If you insist on using a cross-post, you will have to render a page that does the post, e.g.
<HTML>
<BODY>
<IMG Src="/Images/Spinner.gif"> <!-- so the user doesn't just see a blank page -->
<FORM name="MyForm" Action="Action2" Method="Post">
<INPUT type="hidden" Name="Argument1" Value="Foo">
<INPUT type="hidden" Name="Argument2" Value="Bar">
</FORM>
<SCRIPT type="text/javascript>
document.getElementById("MyForm").submit(); //Automatically submit
</SCRIPT>
</BODY>
</HTML>

Passing data from View to Controller in ASP.NET MVC

I have a model class Person
public class Person
{
public string name { get; set; }
public string area { get; set; }
}
Now, in my Index view, I want to pass values from view to controller by taking value name property from user and area ="foo".I know how I can take values from user by like below
#using (Html.BeginForm())
{
#Html.Label("Name")
#Html.TextBoxFor(m=>m.name)
<input type="submit" value="Name" />
}
Now, I want area ="foo" in views.I tried to google the problem,I did not find the solution.
This is general problem.Do not answer like ,set value area="foo" in controller.
Please help me and don't downvote without commenting so that I can improve my question in future.
Thanks
Add a hidden field to your form with name "area" and set the value as whatever you want. When your form is posted, the hidden field value will be also posted to your action method.
#using (Html.BeginForm())
{
#Html.Label("Name")
#Html.TextBoxFor(m=>m.name)
<input type="hidden" name="area" value="foo" />
<input type="submit" value="Name" />
}
Now you can get this in your HttpPost action method
[HttpPost]
public ActionResult Create(Person model)
{
// check for model.name and model.area.
// TO DO : Save and redirect
}
You should remember that, people can always update the hidden field value in the browser using some tools like firebug or so. If it is a sensitive information (Price of an item in a shopping portal) , don't read like this from client. Read it from server.

Asp.net MVC cancel button with multiple redirect choices

I currently have a form with a submit and cancel button. Depending on some logic, each time the page loads, I want that same cancel button to redirect to different other pages in the application. This is the code I have at the moment in my aspx view that changes the location.href based on my property
<% if (Model.MyProperty.Equals("something"))
{ %>
<input class="btnCancel" type="button" value="" onclick="location.href='<%: Url.Action("MyAction","MyController", new {Area="MyArea"},null)%>'" />
<% } %>
<% else if (Model.MyProperty.Equals("somethingelse"))
{ %>
<input class="btnCancel" type="button" value="" onclick="location.href='<%: Url.Action("MyOtherAction","MyOtherController", new {Area="SomeOtherArea"},null)%>'" />
<% } %>
Is this the correct and elegant way to do this? I would rather reduce the multiple IF-ELSE conditions if there was a way to do it.
Thanks for your time.
The way I've always handled multiple redirect options is by setting the href value in the controller action.
The View is generic, but the controller action is specific to the context of the page your rendering. So in your model, make a property called CancelUrl. Now, in the controller action, set it to the link you want it to go to.
model.CancelUrl = Url.Action("action", "controller");
This way, all you have to do in your View is say
Text
You can create a cancel method that takes your property as a parameter and redirect appropriately within the controller. This logic should probably not be in your view anyway as views should have almost 0 logic anyway
I would put the property that will be used to decide the cancel action in the view model (as you already have), alongside any other required properties.
For example:
public class IndexModel
{
//any other properties you need to define here
public string MyProperty { get; set; }
}
Then your view would look similar to:
#model IndexModel
#using (Html.BeginForm())
{
//other information you may want to submit would go here and in the model.
#Html.HiddenFor(m => m.MyProperty)
<button type="submit" name="submit" value="submit">submit</button>
<button type="submit" name="cancel" value="cancel">cancel</button>
}
And finally, your post action should decide the next action that should be returned:
[HttpPost]
public ActionResult Index(IndexModel model)
{
if (!string.IsNullOrEmpty(Request["submit"]))
{
if (ModelState.IsValid)
{
//any processing of the model here
return RedirectToAction("TheNextAction");
}
return View();
}
if (model.MyProperty.Equals("something"))
{
return RedirectToAction("MyAction", "MyController", new { area = "MyArea" });
}
else //assumes the only other option is "somethingelse"
{
return RedirectToAction("MyOtherAction", "MyOtherController", new { area = "SomeOtherArea" });
}
}

repost form to a different action (not from my site) and add additional data

I would like to do this because I need to post to a foreign site, and would like to avoid setting a hidden input inside the form because the user might change the value inside of it (or somebody else might do it for him)
my html:
<form action="<%=Url.Action("prepare") %>" >
<input type="submit" value="submit" />
</form>
and my Action
[HttpPost]
public ActionResult Prepare()
{
if(Request.IsAuthenticated)
{
//post to "http://example.com/do"
//and add to the request userId = User.Identity.Name
return //the result of the repost
}
else
{
return RedirectToAction("youneedtobeloggedin",);
}
}
If you want to keep everything server side, you can take a look at here. It's a simple way to perform POSTs programmatically. In this way you'll handle external POSTs all server side. Please beware that this will make your server perform the POST.
In your controller you can have this Action:
[HttpGet]
public string MyActionPostingToRemote()
{
string postResult = HttpPost (remoteUrl, remotePostQueryString);
return postResult;
}
The HttpPost is the function you can find in my link.
Cross-site calls are not allowed by modern browsers. Because of security.

Html.ValidationMessage renders on all fields, regardless if they're valid or not

My model is correctly validated. If I take a peak in the validation results during debug, I will see that everything is correct. However, all my validation results will show, even if only one is invalid. Again, during debug, only one field is correctly showing up in the validation results, but when my view is rendered all our displayed:
[HttpPost]
public ActionResult Create(Widget widget)
{
if (widge.Valid)
{
// Save to db
}
retun View(widget);
}
My view:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" Inherits="System.Web.Mvc.ViewPage<Widget>" %>
// took out a lot of html here
<form action="Create" method="post">
<input name="Widget.City" value="<%= Model.City == null ? "" : Model.City%>" />
<%= Html.ValidationMessage("Widget.City")%>
<input name="Widget.Department" value="<%= Model.Department == null ? "" : Model.Department %>" />
<%= Html.ValidationMessage("Widget.Department")%>
<button type="submit">Save</button>
</form>
Let us say City and Department are set to NotNull in my model and I correctly put in a City, but leave Department blank. Again, it will show it is invalid on the controller, with the property Department having a problem, yet in my view I'll get "may not be null" messages for both properties. If I have 4 properties and 4 ValidationMessage tags in my view, even if one property is not valid ... all 4 will show. What's the deal?
If I'm not mistaken, I think you want to be using
Html.ValidationMessageFor(model => model.City)
Not what you're using currently in your view.
Also... Since you're using a strongly typed view, you should be checking ModelState.IsValid to determine whether or not you should save your Widget. That is if you're using data annotations on your view model.
What are you using as your input to the [HttpGet] action of the view?
Since you're using the format Widget.Property the view expects the model to have a Widget property containing the data for the widget.. i.e it is looking for Model.Widget.Property but your model only contains Model.Property. Based on the code posted here, you're only passing the widget back to the view and it will interpret that as having a NULL Widget property, thus triggering all NotNull validation.
A solution here is to assign the Widget post model (what you're accepting in the [HttpPost] action) to a Widget property of the of the model you're passing back to the view.
public class CreateViewModel
{
public Widget Widget { get; set; }
}
[HttpPost]
public ActionResult Create(Widget widget)
{
if (widge.Valid)
{
// Save to db
}
var viewModel = new CreateModel() { Widget = widget };
retun View( viewModel );
}
Hopefully I articulated that correctly.. Its been a long day = )

Resources