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>
Related
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" });
}
}
I have a MVC3 view that enables the user to create a couple different things. Within the parent view the forms to do so are broken up via jquery ui tabs like the following:
<div id="tabs">
<ul>
<li>New Thing 1</li>
<li>Different New Thing</li>
</ul>
<div id="tabs-1">#Html.Action("CreateNewThing", "NewThingController")</div>
<div id="tabs-2">#Html.Action("CreateDifferentThing", "DifferentThing")</div>
<div></div>
</div>
<script type="text/javascript">
$(function () {
$("#tabs").tabs();
});
</script>
Within the partial view I have:
#model NewThingViewModel
#using (Html.BeginForm("CreateNewThing", "NewThingController", FormMethod.Post, new { id = "frmCreateNewThing" }))
{
...
with input fields, a submit button, etc. This seems to work well: it renders everything and posts just fine to the right controller action method.
However I'm now wiring in the validation and I've got an issue.
In the controller it is rendering the view like so:
public ActionResult CreateNewThing(NewThingViewModel model)
{
... initializing model fields, drop downs etc.
return PartialView("CreateNewThing", model);
}
I have a seperate post method like so:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateNewThing(NewThingViewModel newThingViewModel, FormCollection collection)
{
.....
}
Sample Model:
public class NewThingViewModel
{
[Required]
[StringLength(50)]
[Display(Name = "Display Name:")]
public string DisplayName { get; set; }
}
The trouble is, when the page first comes up the fields marked as [Required] through DataAnnotations in the model are showing up red as well as the validation summary showing them invalid when the page initially shows. I.E. it's acting like it's already been posted before the user gets to enter anything on the initial load or even put anything in the text boxes.
I know the first non-post CreateNewThing is firing because I can catch it in the debugger and I know the 2nd one does not on the initial load.
What would cause my validations to fire on the Get?
Is it due to the way Html.Action works and the fact that I'm rendering partial views onto another view?
I'm using UnobtrusiveJavaScriptEnabled and ClientValidationEnabled = true in web.config.
I can't find anyone else that has run into this particular problem. Every other example just seems to work, then again I don't find an example where the view is broken into three partials contained within jquery ui tabs.
How do I fix this?
Options:
Do I need to manually manipulate the Model.IsValid as a workaround?
Use a different mechanism to render the partial views on the parent view instead of Html.Action?
Use some javascript/jquery to catch the validation and stop it?
Don't have method parameters on your GET controller action. You can initialize an empty model and pass it to the view but you dont need a model to be passed into the method
You're passing in an "empty" model (which I assume has default values set for your required properties), when you should be passing in null.
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.
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.
Right now, I have an action associated with just the POST from the Index page in my MVC app. But I want a generic handler action that handles all page posts and within the POST Handler action, I want to know the view that I just came from. What I want is below: Any ideas?
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GenericPostHandler(NewAccountInfo info)
{
try
{
string currentview = //how to get View for which to handle the POST?
Persist();
return RedirectToAction(StepManager.GetNextStep(currentView));
}
catch
{
return View();
}
}
I suppose you could write routes that will forward all requests to a single action in one particular controller but this will then be active for all HTTP commands, either GET or POST or otherwise.
What you are trying to achieve is very much against the spirit of MVC. Could you tell us probably what is the idea behind this requirement?
I will try to guess. You wish to perform some kind of preprocessing on each post, right? Maybe an authorization check, activity log etc. If so, you could implement your own ActionFilter and decorate with this attribute all of your controllers. Then all calls will be intercepted by this filter and you can do there whatever you need - then pass the request through to its normal handler (action) or route it elsewhere.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GenericPostHandler(NewAccountInfo info, string fromViewName)
{
...
}
In Views:
<% var actionUrl = Url.RouteUrl(new {
controller = "yourcontroller",
action = "genericposthandler",
fromviewname = "whereyoucamefrom"
}); %>
<form action="<%= actionUrl %>" method="post">
...
</form>
produces url like /yourcontroller/genericposthandler?fromviewname=whereyoucamefrom