inside my ASP.NET MVC controller, I've got a method that requires an HttpRequest object. All I have access to is an HttpRequestBase object.
Is there anyway I can somehow convert this?
What can/should I do??
You should always use HttpRequestBase and HttpResponseBase in your application as opposed to the concrete versions which are impossible to test (without typemock or some other magic).
Simply use the HttpRequestWrapper class to convert as shown below.
var httpRequestBase = new HttpRequestWrapper(Context.Request);
Is it your method, so you can re-write it to take HttpRequestBase? If not, you can always get the current HttpRequest from HttpContext.Current.HttpRequest to pass on. However, I often wrap access to the HttpContext inside a class like mentioned in ASP.NET: Removing System.Web Dependencies for better unit testing support.
You can just use
System.Web.HttpContext.Current.Request
The key here is that you need the full namespace to get to the "correct" HttpContext.
I know it's been 4 years since this question was asked, but if this will help somebody, then here you go!
(Edit: I see that Kevin Hakanson already gave this answer...so hopefully my response will help those people who just read answers and not comments.) :)
To get HttpRequest in ASP.NET MVC4 .NET 4.5, you can do the following:
this.HttpContext.ApplicationInstance.Context.Request
Try to use/create a HttpRequestWrapper using your HttpRequestBase.
Typically when you need to access the HttpContext property in a controller action, there is something you can do better design wise.
For example, if you need to access the current user, give your action method a parameter of type IPrincipal, which you populate with an Attribute and mock as you wish when testing. For a small example on how, see this blog post, and specifically point 7.
There is no way to convert between these types.
We had a similar case. We rewrote our classes/web services methods so that they use HttpContextBase, HttpApplicationStateBase, HttpServerUtilityBase, HttpSessionStateBase... instead of the types of close name without the "Base" suffix (HttpContext, ... HttpSessionState). They are a lot easier to handle with home-made mocking.
I feel sorry you couldn't do it.
This is an ASP.Net MVC 3.0 AsyncController which accepts requests, converts the inbound HttpRequestBase MVC object to a System.Web.HttpWebRequest. It then sends the request asynchronously. When the response comes back, it converts the System.Web.HttpWebResponse back into an MVC HttpResponseBase object which can be returned via the MVC controller.
To answer this question explicitly, I guess you'd only be interested in the BuildWebRequest() function. However, it demonstrates how to move through the whole pipeline - converting from BaseRequest > Request and then Response > BaseResponse. I thought sharing both would be useful.
Through these classes, you can have an MVC server which acts as a web proxy.
Hope this helps!
Controller:
[HandleError]
public class MyProxy : AsyncController
{
[HttpGet]
public void RedirectAsync()
{
AsyncManager.OutstandingOperations.Increment();
var hubBroker = new RequestBroker();
hubBroker.BrokerCompleted += (sender, e) =>
{
this.AsyncManager.Parameters["brokered"] = e.Response;
this.AsyncManager.OutstandingOperations.Decrement();
};
hubBroker.BrokerAsync(this.Request, redirectTo);
}
public ActionResult RedirectCompleted(HttpWebResponse brokered)
{
RequestBroker.BuildControllerResponse(this.Response, brokered);
return new HttpStatusCodeResult(Response.StatusCode);
}
}
This is the proxy class which does the heavy lifting:
namespace MyProxy
{
/// <summary>
/// Asynchronous operation to proxy or "broker" a request via MVC
/// </summary>
internal class RequestBroker
{
/*
* HttpWebRequest is a little protective, and if we do a straight copy of header information we will get ArgumentException for a set of 'restricted'
* headers which either can't be set or need to be set on other interfaces. This is a complete list of restricted headers.
*/
private static readonly string[] RestrictedHeaders = new string[] { "Accept", "Connection", "Content-Length", "Content-Type", "Date", "Expect", "Host", "If-Modified-Since", "Range", "Referer", "Transfer-Encoding", "User-Agent", "Proxy-Connection" };
internal class BrokerEventArgs : EventArgs
{
public DateTime StartTime { get; set; }
public HttpWebResponse Response { get; set; }
}
public delegate void BrokerEventHandler(object sender, BrokerEventArgs e);
public event BrokerEventHandler BrokerCompleted;
public void BrokerAsync(HttpRequestBase requestToBroker, string redirectToUrl)
{
var httpRequest = BuildWebRequest(requestToBroker, redirectToUrl);
var brokerTask = new Task(() => this.DoBroker(httpRequest));
brokerTask.Start();
}
private void DoBroker(HttpWebRequest requestToBroker)
{
var startTime = DateTime.UtcNow;
HttpWebResponse response;
try
{
response = requestToBroker.GetResponse() as HttpWebResponse;
}
catch (WebException e)
{
Trace.TraceError("Broker Fail: " + e.ToString());
response = e.Response as HttpWebResponse;
}
var args = new BrokerEventArgs()
{
StartTime = startTime,
Response = response,
};
this.BrokerCompleted(this, args);
}
public static void BuildControllerResponse(HttpResponseBase httpResponseBase, HttpWebResponse brokeredResponse)
{
if (brokeredResponse == null)
{
PerfCounters.ErrorCounter.Increment();
throw new GriddleException("Failed to broker a response. Refer to logs for details.");
}
httpResponseBase.Charset = brokeredResponse.CharacterSet;
httpResponseBase.ContentType = brokeredResponse.ContentType;
foreach (Cookie cookie in brokeredResponse.Cookies)
{
httpResponseBase.Cookies.Add(CookieToHttpCookie(cookie));
}
foreach (var header in brokeredResponse.Headers.AllKeys
.Where(k => !k.Equals("Transfer-Encoding", StringComparison.InvariantCultureIgnoreCase)))
{
httpResponseBase.Headers.Add(header, brokeredResponse.Headers[header]);
}
httpResponseBase.StatusCode = (int)brokeredResponse.StatusCode;
httpResponseBase.StatusDescription = brokeredResponse.StatusDescription;
BridgeAndCloseStreams(brokeredResponse.GetResponseStream(), httpResponseBase.OutputStream);
}
private static HttpWebRequest BuildWebRequest(HttpRequestBase requestToBroker, string redirectToUrl)
{
var httpRequest = (HttpWebRequest)WebRequest.Create(redirectToUrl);
if (requestToBroker.Headers != null)
{
foreach (var header in requestToBroker.Headers.AllKeys)
{
if (RestrictedHeaders.Any(h => header.Equals(h, StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
httpRequest.Headers.Add(header, requestToBroker.Headers[header]);
}
}
httpRequest.Accept = string.Join(",", requestToBroker.AcceptTypes);
httpRequest.ContentType = requestToBroker.ContentType;
httpRequest.Method = requestToBroker.HttpMethod;
if (requestToBroker.UrlReferrer != null)
{
httpRequest.Referer = requestToBroker.UrlReferrer.AbsoluteUri;
}
httpRequest.UserAgent = requestToBroker.UserAgent;
/* This is a performance change which I like.
* If this is not explicitly set to null, the CLR will do a registry hit for each request to use the default proxy.
*/
httpRequest.Proxy = null;
if (requestToBroker.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
{
BridgeAndCloseStreams(requestToBroker.InputStream, httpRequest.GetRequestStream());
}
return httpRequest;
}
/// <summary>
/// Convert System.Net.Cookie into System.Web.HttpCookie
/// </summary>
private static HttpCookie CookieToHttpCookie(Cookie cookie)
{
HttpCookie httpCookie = new HttpCookie(cookie.Name);
foreach (string value in cookie.Value.Split('&'))
{
string[] val = value.Split('=');
httpCookie.Values.Add(val[0], val[1]);
}
httpCookie.Domain = cookie.Domain;
httpCookie.Expires = cookie.Expires;
httpCookie.HttpOnly = cookie.HttpOnly;
httpCookie.Path = cookie.Path;
httpCookie.Secure = cookie.Secure;
return httpCookie;
}
/// <summary>
/// Reads from stream into the to stream
/// </summary>
private static void BridgeAndCloseStreams(Stream from, Stream to)
{
try
{
int read;
do
{
read = from.ReadByte();
if (read != -1)
{
to.WriteByte((byte)read);
}
}
while (read != -1);
}
finally
{
from.Close();
to.Close();
}
}
}
}
It worked like Kevin said.
I'm using a static method to retrieve the HttpContext.Current.Request, and so always have a HttpRequest object for use when needed.
Here in Class Helper
public static HttpRequest GetRequest()
{
return HttpContext.Current.Request;
}
Here in Controller
if (AcessoModel.UsuarioLogado(Helper.GetRequest()))
Here in View
bool bUserLogado = ProjectNamespace.Models.AcessoModel.UsuarioLogado(
ProjectNamespace.Models.Helper.GetRequest()
);
if (bUserLogado == false) { Response.Redirect("/"); }
My Method UsuarioLogado
public static bool UsuarioLogado(HttpRequest Request)
Related
I am using a WCF Data Services class that exposes an entity framework model via the OData protocol like so:
public class Service : EntityFrameworkDataService<MyEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.UseVerboseErrors = true;
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
}
I consume this service through a service reference in a web solution. I am having problems including all the navigation properties for the entity. I cannot use the following syntax because I do not know what type of entity the user may be requesting:
I CANNOT USE
MyEntities.Customer.Expand("Address");
or
MyEntities.Customer.Include("Address");
What I am currently doing is building a URI string with the $expand=Entity1,Entity2 syntax and then executing that against my service as follows:
public static QueryOperationResponse<object> GetList(string entitySetName, params string[] preloads)
{
StringBuilder stringBuilder = new StringBuilder();
string queryString = string.Empty;
object result = null;
Uri dataAccessURI;
stringBuilder.Append(ServiceReferenceURI.AbsoluteUri);
stringBuilder.Append(entitySetName);
if (preloads != null)
{
for (int i = 0; i <= preloads.Length - 1; i++)
{
queryString = i == 0 ? "?$expand=" : ",";
stringBuilder.AppendFormat("{0}{1}", queryString, preloads[i]);
}
}
dataAccessURI = new Uri(stringBuilder.ToString());
try
{
result = TitanEntities.Execute<object>(dataAccessURI, "GET", true);
}
catch (Exception ex)
{
// log any errors to the console
WriteConsoleMessage(ex.Message, DataAccessEventType.Error);
}
return (QueryOperationResponse<object>)result;
resulting URI string is similar to this:
http://192.168.0.196/Service.svc/AliquotPreparation?$expand=Aliquot,AliquotPrepBatch,AnalysisPreparationMethod,Unit,Employee,Unit,PreparationMethod,State
To me this is a crappy implementation. It is all I could come up with right now though. The problem is, if there are A LOT of navigation properties the $expand command gets too long and the URI reaches it's character limit!
So how can I implement this through a service reference? I would greatly appreciate someone's help!!!
How to use many #Html.AntiForgeryToken() in one page?
When I put it doesn't work on the remote host, only locally!
I tried to use different strings foreach forgery token
#Html.AntiForgeryToken("logoff_forgery") but when I add [ValidateAntiForgeryToken(Salt = "logoff_forgery")] in the controller, I get this following error
'System.Web.Mvc.ValidateAntiForgeryTokenAttribute.Salt'
'The 'Salt' property is deprecated.
To specify custom data to be embedded within the token,
use the static AntiForgeryConfig.AdditionalDataProvider property.'
D:\projects\codesan\app\CodeSan\CodeSan\Controllers\AccountController.cs
289 35 CodeSan
Does anyone know how to use the static AntiForgeryConfig.AdditionalDataProvider ? If yes please share it with me.
As it states in the description Salt property is deprecated.
Here is a simple implementation for IAntiForgeryAdditionalDataProvider
public class MyAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(HttpContextBase context)
{
return GenerateTokenAndSaveItToTheDB();
}
public bool ValidateAdditionalData(HttpContextBase context, string additionalData)
{
Guid token = Guid.TryParse(additionalData, out token) ? token : Guid.Empty;
if (token == Guid.Empty) return false;
return GetIfTokenIsFoundInTheDBAndNotExpired(token);
}
private string GenerateTokenAndSaveItToTheDB()
{
var newToken = Guid.NewGuid().ToString();
//save it to the db
return newToken;
}
}
And you simply register it in the Global.asax.cs
protected void Application_Start()
{
AntiForgeryConfig.AdditionalDataProvider = new MyAntiForgeryAdditionalDataProvider();
}
We're using ASP.Net Web API to generate a feed and it includes the ability to do paging.
myfeed.com/afeed?page=2
My boss says "let's also allow users to use 'paged', because that's what WP uses." In addition, we're also using pageIndex in some of our older feeds. So what I'd like to do is accept all three.
myfeed.com/afeed?page=2
myfeed.com/afeed?paged=2
myfeed.com/afeed?pageIndex=2
I'd like to do is be able to write a clean Web API method, such as
public Foo Get(int page = 1)
{
//do some stuff
return foo;
}
without cluttering the method with page 'plumbing'. So I tried creating an ActionFilter
public override void OnActionExecuting(HttpActionContext actionContext)
{
object pageParam = new object(); //query["page"]
if (pageParam == null)
{
var altPageParam = GetPageParamUsingAlternateParams(actionContext);
if (altPageParam != null){}
//SetPageParam here
}
base.OnActionExecuting(actionContext);
}
private object GetPageParamUsingAlternateParams(HttpActionContext actionContext)
{
object result = new object();
object pageIndexParam = new object(); //Query["pageIndex"]
object pagedParam = new object(); ////Query["paged"]
if (pagedParam != null)
result = pagedParam;
else if (pageIndexParam != null)
result = pageIndexParam;
return result;
}
I didn't finish. As I was looking for the best way to get the query params, I stumbled into a big mistake!
OnActionExecuting is executed after int page = 1. Sure, I could override it in an ActionFilter, but that would lead to confusion down the road. I really want to be able to do a simple flow through the URI query parameters that goes from
page -> paged -> pageIndex -> default value in method
I have found a lot of articles on custom binding to a an object. Also, I found articles about "parameter binding", however those dealt with FromUri and FromBody. I didn't find anything that I felt had a direct parallel to what I'm facing.
You could achieve what you want by defining 3 different GET method with parameters matched with the query segment of the Url like the code snippet below:
public class ProductsController : ApiController
{
//Matched api/products?page=1
public IHttpActionResult Get(int page)
{
return GetPagedData(page);
}
//Matched api/products?paged=1
public IHttpActionResult GetPaged(int paged)
{
return GetPagedData(paged);
}
//Matched api/products?pagIndex=1
public IHttpActionResult GetPageIndex(int pageIndex)
{
return GetPagedData(pageIndex);
}
//Do the real paging here
private IHttpActionResult GetPagedData(int page =1)
{
return Ok("Data Pages");
}
}
I previously had a Web API controller that looked like this:
public IQueryable<ApiDesignOverview> GetList(
string brandIds = "",
string categoryIds = "",
string query = "",
string categoryOp = "or")
I heard that the OData NuGet package now supports the $inlinecount OData parameter, so I tried to add it using the instructions from http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options - I don't want to have to use OData wholesale as that would entail a large amount of re-architecturing of the app, so I went for the PageResult<T> option.
So now my controller looks like this:
public PageResult<ApiDesignOverview> GetList(
ODataQueryOptions<ApiDesignOverview> options,
string brandIds = "",
string categoryIds = "",
string query = "",
string categoryOp = "or")
My problems are now:
How do I mock a ODataQueryOptions for unit testing?
If they can't be mocked, how do I create one? I need a ODataQueryContext to construct one, which requires a Microsoft.Data.Edm.IEdmModel, which requires... what? I can't find any documentation for this.
Really, it would be better if I could remove the ODataQueryOptions from the controller signature like before. Is this possible?
If you do not (or cannot as in my case) want to change away from using ODataQueryOptions and PageResult, here is how you can create an ODataQueryOptions instance for unit tests:
//arrange
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/MyProject/api/Customers?$filter=CustomerID eq 1");
var controller = new CustomersController
{
Request = request
};
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
var opts = new ODataQueryOptions<Customer>(new ODataQueryContext(modelBuilder.GetEdmModel(),typeof(Customer)), request);
//act
var result = controller.Get(opts);
//assert
Assert.AreEqual(1, result.Items.First().CustomerID);
If you prefer returning IQueryable and yet want support for $inlinecount, it is still possible to do that by modyifying QueryableAttribute.
public class InlineCountQueryableAttribute : QueryableAttribute
{
private static MethodInfo _createPageResult =
typeof(InlineCountQueryableAttribute)
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(m => m.Name == "CreatePageResult");
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
HttpRequestMessage request = actionExecutedContext.Request;
HttpResponseMessage response = actionExecutedContext.Response;
IQueryable result;
if (response.IsSuccessStatusCode
&& response.TryGetContentValue<IQueryable>(out result))
{
long? inlineCount = request.GetInlineCount();
if (inlineCount != null)
{
actionExecutedContext.Response = _createPageResult.MakeGenericMethod(result.ElementType).Invoke(
null, new object[] { request, request.GetInlineCount(), request.GetNextPageLink(), result }) as HttpResponseMessage;
}
}
}
internal static HttpResponseMessage CreatePageResult<T>(HttpRequestMessage request, long? count, Uri nextpageLink, IEnumerable<T> results)
{
return request.CreateResponse(HttpStatusCode.OK, new PageResult<T>(results, nextpageLink, count));
}
}
Notice, that I am using reflection to create PageResult. You can instead return an object of your liking that can be formatted by the formatter that you use. An anonymous object with results and count will work too if you are using the Json formatter.
In the latest ODataController there is an AllowedQueryOptions that solves this.
public class MyOdataController : ODataController
{
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Product> Get()
{
return Products.AsQueryable();
}
}
If you don't want any context or an example of why I need this, then skip to The question(s) at the bottom!
In a bid to keep things tidy I initially built my application without JavaScript. I am now attempting to add a layer of unobtrusive JavaScript on the top of it.
In the spirit of MVC I took advantage of the easy routing and re-routing you can do with things like RedirectToAction().
Suppose I have the following URL to kick off the sign up process:
http://www.mysite.com/signup
And suppose the sign up process is two steps long:
http://www.mysite.com/signup/1
http://www.mysite.com/signup/2
And suppose I want, if JavaScript is enabled, the sign up form to appear in a dialog box like ThickBox.
If the user leaves the sign up process at step 2, but later clicks the "sign up" button, I want this URL:
http://www.mysite.com/signup
To perform some business logic, checking the session. If they left a previous sign up effort half way through then I want to prompt them to resume that or start over.
I might end up with the following methods:
public ActionResult SignUp(int? step)
{
if(!step.HasValue)
{
if((bool)Session["SignUpInProgress"] == true)
{
return RedirectToAction("WouldYouLikeToResume");
}
else
{
step = 1;
}
}
...
}
public ActionResult WouldYouLikeToResume()
{
if(Request.IsAjaxRequest())
{
return View("WouldYouLikeToResumeControl");
}
return View();
}
The logic in WouldYouLikeToResume being:
If it's an AJAX request, only return the user control, or "partial", so that the modal popup box does not contain the master page.
Otherwise return the normal view
This fails, however, because once I redirect out of SignUp, IsAjaxRequest() becomes false.
Obviously there are very easy ways to fix this particular redirect, but I'd like to maintain the knowledge of the Ajax request globally to resolve this issue across my site.
The question(s):
ASP.NET MVC is very, very extensible.
Is it possible to intercept calls to RedirectToAction and inject something like "isAjaxRequest" in the parameters?
OR
Is there some other way I can detect, safely, that the originating call was an AJAX one?
OR
Am I going about this the completely wrong way?
As requested by #joshcomley, an automated answer using the TempData approach:
This assumes that you have a BaseController and your controllers are inheriting from it.
public class AjaxianController : /*Base?*/Controller
{
private const string AjaxTempKey = "__isAjax";
public bool IsAjax
{
get { return Request.IsAjaxRequest() || (TempData.ContainsKey(AjaxTempKey)); }
}
protected override RedirectResult Redirect(string url)
{
ensureAjaxFlag();
return base.Redirect(url);
}
protected override RedirectToRouteResult RedirectToAction(string actionName, string controllerName, System.Web.Routing.RouteValueDictionary routeValues)
{
ensureAjaxFlag();
return base.RedirectToAction(actionName, controllerName, routeValues);
}
protected override RedirectToRouteResult RedirectToRoute(string routeName, System.Web.Routing.RouteValueDictionary routeValues)
{
ensureAjaxFlag();
return base.RedirectToRoute(routeName, routeValues);
}
private void ensureAjaxFlag()
{
if (IsAjax)
TempData[AjaxTempKey] = true;
else if (TempData.ContainsKey(AjaxTempKey))
TempData.Remove(AjaxTempKey);
}
}
To use this, make your controller inherit from AjaxianController and use the "IsAjax" property instead of the IsAjaxRequest extension method, then all redirects on the controller will automatically maintain the ajax-or-not flag.
...
Havn't tested it though, so be wary of bugs :-)
...
Another generic approach that doesn't require using state that I can think of may requires you to modify your routes.
Specifically, you need to be able to add a generic word into your route, i.e.
{controller}/{action}/{format}.{ajax}.html
And then instead of checking for TempData, you'd check for RouteData["ajax"] instead.
And on the extension points, instead of setting the TempData key, you add "ajax" to your RouteData instead.
See this question on multiple format route for more info.
This worked for me.
Please note that this doesn't require any session state which is a potential concurrency issue:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (this.Request.IsAjaxRequest)
{
if (filterContext.Result is RedirectToRouteResult)
{
RedirectToRouteResult rrr = (RedirectToRouteResult)filterContext.Result;
rrr.RouteValues.Add("X-Requested-With",Request.Params["X-Requested-With"]);
}
}
}
}
Perhaps you can add a AjaxRedirected key in the TempData property before doing the redirection?
One way to transfer state is to add an extra route parameter i.e.
public ActionResult WouldYouLikeToResume(bool isAjax)
{
if(isAjax || Request.IsAjaxRequest())
{
return PartialView("WouldYouLikeToResumeControl");
}
return View();
}
and then in the Signup method:
return RedirectToAction("WouldYouLikeToResume", new { isAjax = Request.IsAjaxRequest() });
// Don't forget to also set the "ajax" parameter to false in your RouteTable
// So normal views is not considered Ajax
Then in your RouteTable, default the "ajax" parameter to false.
Or another way to go would be override extension points in your BaseController (you do have one, right?) to always pass along the IsAjaxRequest state.
..
The TempData approaches are valid too, but I'm a little allergic of states when doing anything that looks RESTful :-)
Havn't tested/prettify the route though but you should get the idea.
I would just like to offer what I believe is a MUCH better answer than the current accepted one.
Use this:
public class BaseController : Controller
{
private string _headerValue = "X-Requested-With";
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var ajaxHeader = TempData[_headerValue] as string;
if (!Request.IsAjaxRequest() && ajaxHeader != null)
Request.Headers.Add(_headerValue, ajaxHeader);
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (Request.IsAjaxRequest() && IsRedirectResult(filterContext.Result))
TempData[_headerValue] = Request.Headers[_headerValue];
}
private bool IsRedirectResult(ActionResult result)
{
return result.GetType().Name.ToLower().Contains("redirect");
}
}
Then make all your controllers inherit from this.
What it does:
Before an action executes this checks to see if there is a value in TempData. If there is then it manually adds its value to the Request object's header collection.
After an action executes it checks if the result was a redirect. If it was a redirect and the request was an Ajax Request before this action was hit then it reads the value of the custom ajax header that was sent and stores it in temp data.
This is better because of two things.
It is shorter and cleaner.
It adds the request header to the Request object after reading the temp data. This allows Request.IsAjaxRequest() to work normally. No calling a custom IsAjax property.
Credit to: queen3 for his question containing this solution. I did modify it to clean it up a bit but it is his solution originally.
The Problem is in the Client-Cache.
To overcome this, just add a cachebreaker
like "?_=XXXXXX" to Location Url in the 302 Response.
Here is my working Filter. Regisiter it in the GlobalFilter Collection.
I added the Location Header to the Redirected Response, so the client script can get the destination url, in the ajax call. (for Google-Analytics)
public class PNetAjaxFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
if(request.QueryString["_"] == "ajax")
{
filterContext.HttpContext.Request.Headers["X-Requested-With"] = "XMLHttpRequest";
request.QueryString.Remove("_");
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
//public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var context = filterContext.HttpContext;
if (!context.Request.IsAjaxRequest())
return;
var request = context.Request;
String noCacheQuery = String.Empty;
if (request.HttpMethod == "GET")
{
noCacheQuery = request.QueryString["_"];
}
else if (context.Response.IsRequestBeingRedirected)
{
var pragma = request.Headers["Pragma"] ?? String.Empty;
if (pragma.StartsWith("no-cache", StringComparison.OrdinalIgnoreCase))
{
noCacheQuery = DateTime.Now.ToUnixTimestamp().ToString();
}
else
{
//mode switch: one spezial cache For AjaxResponse
noCacheQuery = "ajax";
}
}
if (!String.IsNullOrEmpty(noCacheQuery))
{
if (context.Response.IsRequestBeingRedirected)
{
var location = context.Response.RedirectLocation;
if (location.Contains('?'))
location += "&_=" + noCacheQuery;
else
location += "?_=" + noCacheQuery;
context.Response.RedirectLocation = location;
}
else
{
var url = new UriBuilder(request.Url);
if (url.Port == 80 && url.Scheme == Uri.UriSchemeHttp)
url.Port = -1;
else if(url.Port == 443 && url.Scheme == Uri.UriSchemeHttps)
url.Port = -1;
if(!String.IsNullOrEmpty(url.Query))
url.Query = String.Join("&", url.Query.Substring(1).Split('&').Where(s => !s.StartsWith("_=")));
context.Response.AppendHeader("Location", url.ToString());
}
}
}
}
And here the jQuery:
var $form = $("form");
var action = $form.attr("action");
var $item = $("body");
$.ajax({
type: "POST",
url: action,
data: $form.serialize(),
success: function (data, status, xhr) {
$item.html(data);
var source = xhr.getResponseHeader('Location');
if (source == null) //if no redirect
source = action;
$(document).trigger("partialLoaded", { source: source, item: $item });
}
});