Time for yet another stupid question, adding to a long line of them.
I'm a newbie with Struts 2, having spent years using old 1.X. Struts 2 actions can be roughly equivalent to Struts 1 DispatchActions, simply by adding methods (and defining them in struts.xml).
So, suppose I have this method:
public String create() throws Exception {
// insert create logic here
}
What I want is to have create() do double duty. If create() is called without being passed any parameters, it returns INPUT (and displays the JSP form page), otherwise it processes the form data and returns SUCCESS (and displays a default page).
The only way I have now is to check and see if any values are in the request, which seems silly to me. If I could say "if I call this via HTTP GET show the form, if I call this via HTTP POST, process then redirect to the default".
Like I said, I'm probably being pretty dumb here, but any help would be appreciated.
What you are looking for is to use the same action to show a form and then (after submit) process the form.
public class MyAction {
#SkipValidation
public String execute() throws Exception {
return INPUT; // shows the form
}
public void validate() {
// do your validations here...
}
public String submit() throws Exception {
// process the form
// redirect somewhere
}
}
If your action is mapped as "myaction", then your form should submit to "myaction!submit" (that's Dynamic Method Invocation, which invokes your submit() method).
You'll need to create a custom interceptor if you want to enforce that the execute and submit methods are invoked only by HTTP GET and POST methods (respectively).
Related
Using ASP.NET MVC 4, I have a controller that accepts a JSON structure:
public class SomeDto {
...
public Boolean IsUnicornAlive { get; set; }
...
}
[HttpPost]
public ActionResult DoSomething(SomeDto dto) {
...
}
Now if this POST request comes in without Content-Type specified:
{
...
"IsUnicornAlive":true
...
}
the action still gets called but IsUnicornAlive property would be false. Essentially this is a problem on the side that created the request, but still I would expect ASP to not treat it silently and not call my action with defaulted values. The most appropriate handling in this case would be a 4xx error, 415 ideally. What would be the easiest way to implement it? Or is there a way to know that default values were used inside the controller action?
Note: this question is not about ASP.NET WebApi which does not have this issue. This is specifically about MVC.
How about having a custom ModelBinder for SomeDTO and parsing the incoming data yourself and deciding whether to deduce the values, add a modelstate error, etc...
Look at the ModelState property of the Controller. For example ModelState.Keys.Count == 0 when the default value was used but you might find something even nicer in that object.
If I have a controller:
[HttpPost]
public ReturnType ControllerMethod(CustomModel c)
{
...
}
A third party is posting data to this method:
abc-xyz=testdata
One way, would be to use:
Request.Params["abc-xyz"]
However, in the case where the Request could contain malicious code, if any Request Parameter contained some problematic code, IIS would throw an HttpRequestValidationException as soon as Request.Params[""] is called.
Now, rather than turn off that validation everywhere, I'd like to map the posted data to my model. That way, if the "potentially malicious code" is contained in any parameter that isn't "abc-xyz", my application won't throw a HttpRequestValidationException. It will however, check the used paramaters, and throw a HttpRequestValidationException if the accessed data is potentially malicious.
How do I do that if the posted data has a dash/hyphen in the name?
I've tried a few variations including:
public class CustomModel
{
[Required]
public string abc_xyz
}
You should write your own ModelBinder in this case that transforms the values from the request into the ones needed for CustomModel.
To over come the request validation, you can turn it off with the ValidateInputAttribute on your controller action.
After reading about XSS attacks I have applied the AntiForgeryToken to my website using the ValidateAntiForgeryTokenWrapperAttribute described in this article:
http://weblogs.asp.net/dixin/archive/2010/05/22/anti-forgery-request-recipes-for-asp-net-mvc-and-ajax.aspx
It seems to work well however I've come across a problem when using Remote Validation in MVC3. I have a ValidationController which contains all of the common validation within my site and if I apply the ValidateAntiForgeryTokenWrapperAttribute to it then the remote validation no longer works and I get an 'A required anti-forgery token was not supplied or was invalid.' exception logged in Elmah. I've tried debugging this and it doesn't even hit the controller action before throwing the exception. I assume this happens because the remote validation doesn't know to pass the AntiForgeryToken to the controller - has anyone else had this problem or knows whether the two are not meant to be used together?
It also made me question whether I should be using the ValidateAntiForgeryTokenWrapperAttribute on every controller or not, any thoughts?
In the remote attribute do as below:
[Remote("MyValidationMethod","MyController", HttpMethod = "POST", AdditionalFields = "__RequestVerificationToken")]
public object MyField { get; set; }
the AdditionalFields property can accept comma separated fields names in the form; the __RequestVerificationToken is the name of the hidden field which contains the AntiForgeryToken.
I haven't used Remote Validation. However I had similar experience with AntiForgeryToken. When I had it applied for all the actions in my controller. Later, I removed it from all the actions and applied to only those actions which were sending data back to database (insert/update/delete).
As it seems you have applied AntiForgeryToken validation attribute to entire controller, it will always create a new token value every time an action is executed and so when response goes back to client for remote validation action the value of token is different than what is on the form which gets submitted later for other actions.
You can remove AntiForgeryToken attribute from the controller and use it with other action apart from remote validation action or wherever you really need it.
//Instead of this
[ValidateAntiForgeryToken]
public class mycontroller
{
//...
}
//Do something like this
public class mycontroller
{
public ActionResult myotheraction ()
{ }
[ValidateAntiForgeryToken]
public ActionResult valdaitionaction ()
{ }
}
When I do Ajax.BeginForm posts to my actions returning a partial view I send error info in a ViewData item.
Currently in order to handle all errors I must wrap all of the method in a try catch statement.
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Save(int id, FormCollection form)
{
MyModel model;
try
{
...do stuff...
}
catch(Exception ex)
{
...log...
ViewData["ResultInfo"] = new ResultInfo(false, Resource.SAVE_NOT_SAVED, someErrorMessage);
}
return PartialView("Folder/SomeView", model);
}
I would like to do this with a custom HandleError attribute, but I realize that there must be many gotchas waiting to bite. Has anyone tried and want to share their experiences?
EDIT:
I've ended up doing error handling in a controller base class.
This ErrorHandlingController has 2 methods; RegisterErrorHandler and RegisterModel. If the error handler is found registered when an error is found in the base class OnException I just add the ResultInfo and marks the error as handled and uses the view and error caption I've set in RegisterErrorHandler.
This way it's very easy to get the model to the error handler and it's natural to use Resources directly since the error handler is registered as the first row inside the method as opposed to an attribute outside it.
I think the answer to this question will help you along: How to handle model state errors in ajax-invoked controller action that returns a PartialView.
I'm developing a wizard type solution in MVC2 and I'd like to prevent users from going to step 2 directly, however, I'd still like it to show up in the URL.
Also, because progress can be saved at any time, I'd still like the ability to programmatically go to Step2. How can I do this?
[HttpGet]
public ActionResult Step1() {
return View("Step1View");
}
[HttpPost]
public ActionResult Step1(Stuff s) {
return RedirectToAction("Step2", new { S = s });
}
[HttpGet] //<-- how do I stop users going directly here
public ActionResult Step2(Stuff s) {
return View();
}
[HttpPost]
public ActionResult Step2(Stuff2 s) {
return RedirectToAction("Step3");
}
I haven't tried this myself but if I were to I'd give some consideration to ActionFilters. I'd create some context object that describes the wizard data and the steps through that wizard (maybe wrapping a model or two in some fashion).
This wizard context would be self 'validating' in the sense that I can ask what the next valid step is.
Then, with that, I load it up from the action filter and then if the current action is not valid for that step I redirect.
Of course, I can do that without the action filter and just have that as pre-amble to the method I'm looking at. Personally, I'd do this first, then play with action filters to try and make it look a little neater if I had time.
Action filters are the way to go
First of all this will heavily depend on the storage system of your temporary saved data. Action filter should check this store and see whether step 1 data exists. If it doesn't you can always add an error to ModelState thus make it invalid. Your Step 2 code would therefore look like this:
[HttpGet]
[CheckExistingData]
public ActionResult Step2(Stuff s)
{
if (!this.ModelState.IsValid)
{
return RedirectToAction("Step1");
}
return View(s);
}
So. Your filter should check for existing data and either:
fill up Stuff parameter or
add model state error and keep parameter null
Data consolidation
The way that you've written redirect to action (in step 1 POST) to just provide a complex object is not a good way of doing it. You should consolidate data storage, so no matter where your user came to step 2, filter would always work the same. In your Step 1 POST you should save data to this particular storage and just redirect to Step 2.
You have two scenarios of getting to Step 2:
From step 1
From anywhere after data has been saved
This way, your step 2 would work the same for both scenarios. And you could create an arbitrary number of steps in your wizard, and the same process would still work the same.
I have finished developing a highly reusable wizard that by just doing:
return Navigate();
from the actions, the wizard knows what to do (this is possible if you implement a wizard pattern). Navigate() being a method defined on a base WizardController class.
The reason this works is that, in essence, step info gets serialized to the page with each request (AJAX or not), and is deserialized when the controller reads the response in the OnActionExecuting method.
The framework uses WizardStep attributes to know which action corresponds to which wizard step, and the controller is decorated with a WizardOptions attribute that dictates how the Wizard will allow itself to be navigated. EG:
[WizardStepOptions(WizardNavigatorRules.LeapBackOnly, WizardButtonRules.Both, WizardCompleteRules.DisableNavigation)]
public class MembershipFormController : WizardController<ESregister.Models.TheSociety.RegistrationData>
{
[WizardStep(1, "Start")]
public override ActionResult Start()
{
return Navigate();
}
It works a dream. If in the course of your wizard's use, you need to prune or add steps, you just define which steps are to be displayed using the Range property, also defined on the base class WizardController:
[WizardStep(2, "Category")]
public ActionResult Category()
{
return Navigate();
}
[HttpPost]
public ActionResult Category(int ? Category)
{
if (Category == null)
{
ModelState.AddModelError("Category", "You must fill in a Category!");
return Navigate();
}
if (Category == 3)
{
Range = new List<int> { 1, 2, 7, 8 };
}
else
{
Range = DefaultRange();
}
return Navigate();
}
The wizard framework implements PRG automatically. You only need to provide HttpPost in a case like the above where you need to, for example, prune the steps range depending on user input.
It also provides navigation controls as follows:
<% StepManager stepManager = (StepManager)TempData["stepManager"];
Html.WizardNavigator(stepManager); %>
Html.WizardButtons(stepManager, WizardButtonLocation.Top); %>
Where the WizardNavigator shows / provides links to the different steps (links if allowed) and WizardButtons are the Start, Next, Continue, Previous and Confirm buttons.
It is working in production.
I have included all this detail to show what is possible and that the suggested solution does work.