Prevent RedirectToAction() if AJAX request - asp.net

Take for instance the following code for Edit action in my controller:
// POST: /Admin/Text/Edit/5
[HttpPost]
[ValidateInput(false)]
public virtual ActionResult Edit(TextViewModel editing)
{
if (!ModelState.IsValid)
return View(editing);
Text existing = _repository.Find(editing.Id);
if (TryUpdateModel(existing)) {
_repository.Update(existing);
if(Request.IsAjaxRequest())
return Content(bool.TrueString);
else
return RedirectToAction(pndng.Admin.Text.List());
}
else return View(editing);
}
What I want this action to do is handle both classic (non AJAX form) and AJAX (jquery) POSTs. In case of AJAX POST it is very likely that the request is coming from an inline edit form. In this case, the action should just return the Content=Ok result. In case we have been editing the model in a form page and performed a classic postback we want to redirect the user back to the content List (see RedirectToAction()).
However, the thing bothering me is the if..else clause. I would like to abstract this away into an action filter/attribute. I would like to leave in the redirection call (as this is the default) but have the action filter to act upon it if it detects that the request was in fact AJAX request, meaning stop the redirection and just return the ContentResult or JsonResult.
Also feel free to respond if you think my workflow is wrong.

You should be to implement a custom ResultFilter to do what you want, by implementing IResultFilter. something like:
public class AjaxOverrideFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Result is RedirectResult)
filterContext.Result = new ContentResult {Content = "Ok"};
}
}
And then decorate that action with [AjaxOverrideFilter].
This should override the result if its an ajax request and the result type was a redirect... At least, this should give you a push in the right direction. I'm not wildly convinced that this is a great architectural approach though...

Related

Accessing actual request in Message Handlers and Action filters in Web API

This is more of a design related question, and any help/pointers in the right direction is highly appreciated.
I am working on a ASP.NET Web API2 application, and have an Authorization filter and other Action filters. Within these filters, I need to access the Request object that comes as a part of the HttpPost request body.
I use the following code to read the request body content and deserialize into the desired object. It works fine.
//actionContext is HttpActionContext
var requestContent = actionContext.Request.Content.ReadAsStringAsync();
var request = JsonSerializer.GetObject<BaseOTARequestModel>(requestContent.Result);
To serve a particular request, I am deserializing the request content twice (I have 2 filters). Once the request reaches the controller action, it is deserialized again by the Web API framework. I feel guilty that every request is deserialized 3 times, and have a feeling there is a better way to handle this.
How can I avoid deserializing the request multiple times in a request?
I took this on as a challenge and came up with this solution. Here's a base filter attribute class:
public abstract class BaseOTARequestFilterAttribute : ActionFilterAttribute
{
private HttpActionContext _actionContext;
protected BaseOTARequestModel RequestModel
{
get
{
if (_actionContext.Request.Properties.ContainsKey("RequestModel"))
{
return _actionContext.Request.Properties["RequestModel"] as BaseOTARequestModel;
}
return null;
}
set
{
_actionContext.Request.Properties["RequestModel"] = value;
}
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_actionContext = actionContext;
if (RequestModel == null)
{
//actionContext is HttpActionContext
var requestContent = actionContext.Request.Content.ReadAsStringAsync();
RequestModel = JsonSerializer.GetObject<BaseOTARequestModel>(requestContent.Result);
}
}
}
This base class handles your deserialization and uses the Request.Properties collection to store it. (OK, I know a Web API is stateless but this state only exists during the execution of the request so fine imho.)
Your various attributes should all inherit this base class and can use the derialized object as follows:
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
var data = RequestModel;
// etc.
}
This may not be the most elegant solution, but I believe it works. Interested to hear the views of others.

Re-route all requests dynamically based on some condition

I have an ASP.Net MVC 5 web site and need to dynamically re-route all incoming requests to a specific Controller and Action under certain circumstances.
For example, if the database does not exist, I would like to re-route all incoming requests to a specific Action, such as SetupController.MissingDatabase:
public class SetupController : Controller
{
public ActionResult MissingDatabase()
{
return View();
}
}
I would like to check for this condition (database existence) for each and every request. It would be best to perform the check early on in the pipeline, rather than at the top of each Action, or in each Contoller. Of course, if the incoming request is being routed to SetupController.MissingDatabase I don't need to perform the check or re-route the request.
What is the best way to accomplish this?
Specifically, where in the ASP.Net MVC 5 pipeline is the best place to perform such a check, and how would I re-route the incoming request?
You can create an action filter to do that and register it in the application level so that it will be executed for all the incoming requests.
public class VerifySetupIsGood : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var dbExists=VerifyDataBaseExists();
if ( ! dbExists)
{
var values = new Dictionary<string, string> {{"action", "MissingDatabase"},
{"controller", "Setup"}};
var routeValDict = new RouteValueDictionary(values);
//redirect the request to MissingDatabase action method.
context.Result = new RedirectToRouteResult(routeValDict);
}
}
}
Assuming VerifyDataBaseExists() executes your code to check the db exists or not and return a boolean value. Now register this action filter in the Application_Start event in global.asax.cs
protected void Application_Start()
{
//Existing code goes here
GlobalFilters.Filters.Add(new VerifySetupIsGood());
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
//Existing code goes here
}
You can update the action filter to check for a subset of requests as well if needed. You may get the request url from the object of ActionExecutingContext and use it to do your checks.

Executing code before any action

I have the following requirement:
On every request to my web page, regardless of which action the user is trying to invoke, I need to call some code that checks if a resource is in place. If it is, then everything is fine and the action method should be called as normal.
However, if this resource is not available, I want all requests to return a separate page asking the user to select another resource from a list of available ones.
So is it possible to have one method run before any action method that have the option of cancelling the call to the action method, and doing something else instead?
Look at global action filters (available since asp.net mvc 3): http://msdn.microsoft.com/en-us/library/gg416513%28v=vs.98%29.aspx
Basically, in your Global.asax, you can register the filter globally during your application startup (in Application_Start()) with:
GlobalFilters.Filters.Add(new MyActionFilterAttribute());
You can then override the OnActionExecuting method, and set the Result property with a RedirectToRouteResult.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (IsMyResourceAvailable())
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "Controller", "YourControllerName" },
{ "Action", "YourAction" }
});
}
base.OnActionExecuting(filterContext);
}
MVC provides several hooks to do this.
In a base controller, you can override Controller.OnActionExecuting(context) which fires right before the action executes. You can set context.Result to any ActionResult (such as RedirectToAction) to override the action.
Alternatively, you can create an ActionFilterAttribute, and exactly like above, you override the OnActionExecuting method. Then, you just apply the attribute to any controller that needs it.

How do you interrupt/intercept MVC Actions using ActionFilters?

Feel free to close this one if it s a duplicate. I couldn't find an answer.
I wish to be able to place a System.Web.ActionFilterAttribute on an Action Method and override the OnActionExecuting method to insert business logic which determines if the Action should be fulfilled.
Can the ActionExecutingContext be used to cancel the executing Action Method and do one of the following:
Send an HTTP Status Code (and the corresponding <customError> page).
Execute a different Action Method within the same Controller.
Send an HTTP Status Code (and the
corresponding <customError> page)
Almost:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.StatusCode = 500;
}
Execute a different Action Method
within the same Controller.
Yes:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ViewResult() { ViewName = "SomeOtherAction" };
}
You can always redirect to another controller/action in an action filter.
See here for an example.

Redirecting users from edit page back to calling page

I am working on a project management web application. The user has a variety of ways to display a list of tasks. When viewing a list page, they click on task and are redirected to the task edit page.
Since they are coming from a variety of ways, I am just curious as to the best way to redirect the user back to the calling page. I have some ideas, but would like to get other developers input.
Would you store the calling url in session? as a cookie? I like the concept of using an object handle the redirection.
I would store the referring URL using the ViewState. Storing this outside the scope of the page (i.e. in the Session state or cookie) may cause problems if more than one browser window is open.
The example below validates that the page was called internally (i.e. not requested directly) and bounces back to the referring page after the user submits their response.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.UrlReferrer == null)
{
//Handle the case where the page is requested directly
throw new Exception("This page has been called without a referring page");
}
if (!IsPostBack)
{
ReturnUrl = Request.UrlReferrer.PathAndQuery;
}
}
public string ReturnUrl
{
get { return ViewState["returnUrl"].ToString(); }
set { ViewState["returnUrl"] = value; }
}
protected void btn_Click(object sender, EventArgs e)
{
//Do what you need to do to save the page
//...
//Go back to calling page
Response.Redirect(ReturnUrl, true);
}
}
This message my be tagged asp.net but I think it is a platform independent issue that pains all new web developers as they seek a 'clean' way to do this.
I think the two options in achieving this are:
A param in the url
A url stored in the session
I don't like the url method, it is a bit messy, and you have to remember to include the param in every relevent URL.
I'd just use an object with static methods for this. The object would wrap around the session item you use to store redirect URLS.
The methods would probably be as follows (all public static):
setRedirectUrl(string URL)
doRedirect(string defaultURL)
setRedirectUrl would be called in any action that produces links / forms which need to redirect to a given url. So say you had a projects view action that generates a list of projects, each with tasks that can be performed on them (e.g. delete, edit) you would call RedirectClass.setRedirectUrl("/project/view-all") in the code for this action.
Then lets say the user clicks delete, they need to be redirected to the view page after a delete action, so in the delete action you would call RedirectClass.setRedirectUrl("/project/view-all"). This method would look to see if the redirect variable was set in the session. If so redirect to that URL. If not, redirect to the default url (the string passed to the setRedirectUrl method).
I agree with "rmbarnes.myopenid.com" regarding this issue as being platform independent.
I would store the calling page URL in the QueryString or in a hidden field (for example in ViewState for ASP.NET). If you will store it outside of the page scope (such as Session, global variable - Application State and so on) then it will not be just overkill as Tom said but it will bring you trouble.
What kind of trouble? Trouble if the user has more than one tab (window) of that browser open. The tabs (or windows) of the same browser will probably share the same session and the redirection will not be the one expected and all the user will feel is that it is a bug.
My 2 eurocents..
I personally would store the required redirection info in an object and handle globally. I would avoid using a QueryString param or the like since they could try bouncing themselves back to a page they are not supposed to (possible security issue?). You could then create a static method to handle the redirection object, which could read the information and act accordingly. This encapsulates your redirection process within one page.
Using an object also means you can later extend it if required (such as adding return messages and other info).
For example (this is a 2 minute rough guideline BTW!):
public partial class _Default : System.Web.UI.Page
{
void Redirect(string url, string messsage)
{
RedirectionParams paras = new RedirectionParams(url, messsage);
RedirectionHandler(paras); // pass to some global method (or this could BE the global method)
}
protected void Button1_Click(object sender, EventArgs e)
{
Redirect("mypage.aspx", "you have been redirected");
}
}
public class RedirectionParams
{
private string _url;
public string URL
{
get { return _url; }
set { _url = value; }
}
private string _message;
public string Message
{
get { return _message; }
set { _message = value; }
}
public RedirectionParams(string url, string message)
{
this.URL = url;
this.Message = message;
}
}

Resources