UrlRewriter+HttpModule+Session problem - c#

I need to write a custom "UrlRewriter" using a HttpModule, in the moment of "rewriting" I need access to the Session and has followed the advice from another SO thread:
Can I access session state from an HTTPModule?
Everything works, except the RewritePath/Redirect part. I don't get any exceptions, but the browser takes forever to load. Is this really the best way to build a urlrewriter like this?
using System;
using System.Web;
using System.Web.SessionState;
using System.Diagnostics;
namespace MyCompany.Campaigns
{
public class CampaignRewriteModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.PostAcquireRequestState += new EventHandler(Application_PostAcquireRequestState);
application.PostMapRequestHandler += new EventHandler(Application_PostMapRequestHandler);
}
void Application_PostMapRequestHandler(object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
if (app.Context.Handler is IReadOnlySessionState || app.Context.Handler is IRequiresSessionState)
{
return;
}
app.Context.Handler = new MyHttpHandler(app.Context.Handler);
}
void Application_PostAcquireRequestState(object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
MyHttpHandler resourceHttpHandler = HttpContext.Current.Handler as MyHttpHandler;
if (resourceHttpHandler != null)
{
HttpContext.Current.Handler = resourceHttpHandler.OriginalHandler;
}
Debug.Assert(app.Session != null);
string path = HttpUtils.Path();
if (!CampaignCodeMethods.IsValidCampaignCode(path)) return;
string domain = HttpUtils.Domain();
CampaignCode code = CampaignManager.RegisterCode(path, domain.Equals(Config.Instance.Domain.ToLower()) ? null : domain);
if (code != null)
{
//app.Context.RewritePath(code.CampaignCodePath.Path, false);
app.Context.Response.Redirect(code.CampaignCodePath.Path, true);
}
}
public void Dispose() { }
public class MyHttpHandler : IHttpHandler, IRequiresSessionState
{
internal readonly IHttpHandler OriginalHandler;
public MyHttpHandler(IHttpHandler originalHandler)
{
OriginalHandler = originalHandler;
}
public void ProcessRequest(HttpContext context)
{
throw new InvalidOperationException("MyHttpHandler cannot process requests.");
}
public bool IsReusable
{
get { return false; }
}
}
}
}

I think I know what it is. Your module is executed on ALL requests and assigns a handler that throws an error unless there is a valid campaign code (where a rewrite/redirect occurs).
But because this is not just for your "handler campaign code" url it is causing an error to be thrown, which is causing you to be redirected to your error page, which is being caught by the module, which is assigning the handler, which is throwing an error, which is redirecting... I think you get where I'm going ;)
Otherwise I'd try a few things:
Setup Fiddler and check for an infinite redirect loop
Put a breakpoint on app.Context.Response.Redirect - make sure your not in an infinite loop
Put a breakpoint on MyHttpHandler.ProcessRequest - make sure it's not being called and the exception swallowed

I wrote a simple URL rewriter module that did something similar. The url rewriting is done in BeginRequest by comparing the requested url to a list of known urls. If we find a mach we use HttpContext.RewritePath to change the requested url.
This appears to work well with no serious side effects.
I notice that you use Response.Redirect instead of Context.RewritePath. Using Redirect will cause the users browser to request a new page with the new url. Is this really what you want? The user will then see the new url in his browser. If this really is what you want you could use an alternative approach where you use a custom 404 page not found error handler to redirect the user to the appropriate page.
If you set up IIS to redirect all 404 errors to a new page, say Custom404.aspx, that you have set up. In this page you can check the requested url to see if the url should be rewritten. If it should you can simply set the Response.Status to "301 Moved Permanently" and write a header with the name "Location" and the new url as the value. If the url should not be rewritten you can just output the standard 404 page not found error.
This last approach works well, but as with your Response.Redirect approach the user will see the new url in his browser. Using Context.RewritePath allows you to serve a different page than the one requested.

Is your URL rewriter handling requests that aren't for an actual page? If they are, then I don't think you can access Session... the last URL rewriter that I had written was there to handle 404 errors, and I remember digging around and finding (somewhere, can't remember where) that you don't get access to Session if that request is not for an actual .aspx page.

I'm thinking the problem may be inside this block:
if (code != null)
{
//app.Context.RewritePath(code.CampaignCodePath.Path, false);
app.Context.Response.Redirect(code.CampaignCodePath.Path, true);
}
Try putting a breakpoint in the if statement and see if continually gets hit.

I think there should be a call to 'return' after you reset it to the original handler, else you will continually rewrite the path.
Thinking about it, that's probably why the page is loading forever! :)

Related

Losing Cookies and Session Variables during AuthorizeCore

I'm currently working on getting a test environment stood up (it is currently called DEV) and am experiencing some weird issues.
When you first come to the site, we have an agreement page. Hitting the "I Agree" button will force the user through an Action to check to see if they are a member of the site already or not. We do use a demo mode also, but that is not part of the issue.
The issue I'm currently experiencing is the following. Initially in the Action, we create a Cookie called "siteaccept". Once that is created, we determine if the site is in demo mode or not, then move on to getting the user (actual user or demo user). Once the user is found, we log their Id in a Cookie called "cntPOC", and also create a Session variable by the same name with the same data (original developers wrote much of this convoluted logic which I want to change before someone asks why keep a Session and Cookie). We then do a RedirectToAction to the Action to bring up the main page of the site.
Here is where the issue comes into play. The main page of the site's Action has a CustomAuthorizeAttribute decoration on it. In our CustomAuthorizeAttribute class, we have OnAuthorizion and AuthorizeCore being overrode. OnAuthorizion fires off first, however, it uses base.OnAuthorization. Once that is called, AuthorizeCore is called. In AuthorizeCore, we check for the "siteaccept" Cookie, followed by a check on the "cntPOC" Session variable. If both are there, we return true, otherwise false if either fails.
On not only my local environment but the DBA's, this works without a hitch. I see our Cookies and Session variable. However, on our DEV environment, both the Cookies and Session variable are missing. We have IE 11 configured to allow Cookies, yet we cannot get them once we leave the Action and proceed into the CustomAuthorizeAttribute.
I did find I can find the Cookie today if I check HttpContext.Current.Response instead of HttpContext.Current.Request, but that is the incorrect way to do it obviously.
Below is my code. I'm fairly certain since the code works on my local environment, it should be fine in our DEV environment. Also a quick note, our production environment does work, so the code obviously functions. It's a question now of why does the DEV environment not.
MainController.cs
[HttpPost]
public ActionResult Index(FormCollection frmCollection)
{
try
{
Response.Cookies.Remove("bracmisaccept");
HttpCookie cookie = new HttpCookie("bracmisaccept");
cookie.Value = "true";
Response.Cookies.Add(cookie);
...
//Demo Mode
var poc = new HttpCookie("cntPOC");
cookie.Value = "7578";
Response.Cookies.Add(poc);
Session["cntPOC"] = 7578;
return RedirectToAction("ApplicationSelection");
}
catch (Exception ex)
{
logger.LogError("Main Index", ex);
return PartialView(#"../Error/ExceptionHandling");
}
}
[CustomAuthorizeAttribute]
public ActionResult ApplicationSelection()
{
return View();
}
CustomAuthorizeAttribute.cs
public string RedirectUrl = "~/Main/SessionTimeout";
public string CookieExpiredRedirectUrl = "~/Main/Index";
public string AjaxRedirectUrl = "~/Error/AjaxError";
private bool _isAuthorized;
private bool _isCookieExpired;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (HttpContext.Current.Request.Cookies["siteaccept"] == null)
{
_isAuthorized = false;
_isCookieExpired = true;
return false;
}
if (HttpContext.Current.Session["cntPOC"] == null)
{
_isAuthorized = false;
return false;
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!_isAuthorized)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.End();
}
else
{
if(_isCookieExpired)
filterContext.RequestContext.HttpContext.Response.Redirect(CookieExpiredRedirectUrl);
else
filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl);
}
}
}
I'm fairly certain the code is fine, but I did read in a few articles that AuthorizeCore may or may not have the Cookies and Session variables at times. I just wanted to find out if I'm wasting my time with changing the code or if it's the box we have this site on. The server is super locked down, so yeah, kind of annoying...
Edit: I have yet to figure out how to fix this yet, however, I did find if I do a publish on this code, I can enter into the site properly. I still cannot run localhost to inspect the site, but a publish fixes a few minor issues of whether things will work on this site.

How to make form page invalid?

I am creating pages in asp.net. I created WebForm called Form.aspx. Now I want all request *.htm request to load this pages where I will do what I need (maybe not the best approach but it works as needed for me). So I created something like this:
routes.MapPageRoute(null, "{file}.htm", "~/Pages/Form.aspx");
routes.MapPageRoute(null, "{folder}/{file}.htm", "~/Pages/Form.aspx");
Now everything like http://example.com/whatever.htm or http://example.com/whatever/whatever.htm is redirected to my Form.aspx. But this Form.aspx doesn't have any meaning on it’s own. So following page is nonsense http://example.com/Pages/Form.aspx. How can I make it invalid? So it would show to me something like "Error HTTP 404.0 – Not Found". I want the same behaviour as if I would wrote "http://example.com/doesntexist.aspx". I don't want to do any redirection (only if no other option exists). I so far tried only something like this (which doesn't work):
routes.MapPageRoute(null, "Pages/Form.aspx", "~/doesntexist.aspx");
It doesn't do anything... Any help appreciated.
In Global.asax add this code:
...
protected void Application_BeginRequest(object sender, EventArgs e)
{
string requestPath = Request.RawUrl.Trim().ToLower();
HttpApplication app = sender as HttpApplication;
if (!IsLocalRequest(app.Context.Request))
{
if (requestPath.IndexOf("form.aspx") > 0)
{
throw new HttpException(404, "Error HTTP 404.0 – Not Found.");
}
}
}
// This method determines whether request came from the same IP address as the server.
public static bool IsLocalRequest(HttpRequest request)
{
return request.ServerVariables["LOCAL_ADDR"] == request.ServerVariables["REMOTE_ADDR"];
}
...

Response.Redirect issue with Asp.net async

I'm new to asp.net 4.5 async and am running into the following with calling response.redirect within an async method. The issue is that the response just "hangs" Has anyone else experienced similar issues with attempting an redirect with async? This code will work in a brand new project, but, does not work with a new page in our existing code. I made sure to gut out everything I could out of our web.config and removed our master page. Hitting a brick wall...any ideas? Thanks!
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(PageLoadAsync));
}
private async Task PageLoadAsync()
{
var data = await GetData();
if (data == HttpStatusCode.OK)
Response.Redirect("http://www.google.com");
}
private async Task<HttpStatusCode> GetData()
{
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://www.google.com");
return response.StatusCode;
}
}
This code will work in a brand new project, but, does not work with a new page in our existing code.
I assume your existing site has already been upgraded to .NET 4.5.
The first thing to check is that httpRuntime.targetFramework is set to 4.5. This is not set by default when you upgrade.
Edit from comments:
Another thing to check (just in case) is that Page.Async is set to true.
In this case, the solution was to call Response.Redirect("http://www.google.com", false), which explicitly passes false for the endResponse parameter. The default value of true is only for backwards-compatibility reasons as described here.
The hack I used is:
I used a static dictionary as var d= new Dictionary<string, bool>(); in the class where my API calling method is written.
I put the code line client.timeout = new System.TimeSpan(0,0,60); for API sending the request.
When API is timed out, it throws the TaskTimeoutException, in the TaskTimeoutExceptioncatch block write code as d.Add("timeout", true);
Now, I created the custom action filter and applied the following code:
public class MyCustomActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if(MyApiClass.d.ContainsKey("timeout") && d["timeout"])
{
throw new Exception();
}
}
}
I applied the [MyCustomActionFilter ] on the action.
When action is executed and enter the custom filter it throws Exception by checking the dictionary entry.
If timeout would have occurred then dictionary entry will be true, so, on the basis of that, we check the entry and throws the exception. Now, we have Application_Error() in Global.asax.cs that catches the exception.
In the Application_Error() we have written the code for redirect to the required page.
NOTE: In step 4 you can create your custom exception to provide more precise detail for logging.

Sitecore custom 404 handler in production

I picked up the following code from Stackoverflow->a blog re handling custom 404 in Sitecore (which acutally does a 302 redirect to 404 page with status 200 which gets picked up by google as soft 404).
While this works totally fine in our local test servers, the moment we drop it in production the site goes haywire and takes AGES e.g. 8-9 minutes to load and stuff.
public class ExecuteRequest : Sitecore.Pipelines.HttpRequest.ExecuteRequest
{
protected override void RedirectOnItemNotFound(string url)
{
var context = System.Web.HttpContext.Current;
try
{
// Request the NotFound page
var domain = context.Request.Url.GetComponents(
UriComponents.Scheme | UriComponents.Host,
UriFormat.Unescaped);
var content = WebUtil.ExecuteWebPage(
string.Concat(domain, url));
// The line below is required for IIS 7.5 hosted
// sites or else IIS is gonna display default 404 page
context.Response.TrySkipIisCustomErrors = true;
context.Response.StatusCode = 404;
context.Response.Write(content);
}
catch (Exception ex)
{
Log.Error(string.Format("Falling back to default redirection behavior. Reason for error {0}", ex), ex);
// Fall back to default behavior on exceptions
base.RedirectOnItemNotFound(url);
}
context.Response.End();
}
}
P.S: I then replaced ExecuteRequest with my custom one in web.config.
If you have experienced similar thing or know of any issue re this please do shed some light.
Thanks in advance
There is a setting in Sitecore, with which you can get rid of the 302 redirect:
<setting name="RequestErrors.UseServerSideRedirect" value="true" />
With this settings, the url stays the same and the status code is 404. If you want to have some additional logic (like showing a Sitecore item as error page), there is a Shared Source module called Error Manager on the Sitecore Market Place.
Hope that helps.
Check if the server is able to access the hostname of your website.
Servers often do not have access to a DNS and therefore are unable to resolve hostnames. In order for your 404 handler to work, the application needs to be able to access its own hostname to request the 404 page.
To be sure this works, edit the hosts file of the server and add an entry for your hostname there, pointing it to 127.0.0.1
You can resolve it with creating new resolver. It is good solution when you want to give to an user error page in right language. But there some differences in IIS 7.0 and 7.5.
Add processor to your sitecore configuration:
<processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel"/>
<processor type="Project.Error404Resolver, Project" />
Processor resolving it:
For IIS 7.0:
public class Error404Resolver : Sitecore.Pipelines.HttpRequest.HttpRequestProcessor
{
public override void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
if(Sitecore.Context.Item == null && !args.Context.Request.Url.AbsolutePath.StartsWith("/sitecore")
{
args.Context.Response.Clear();
SiteContext site = Sitecore.Context.Site;
if(site != null)
{
Item item404Page = Sitecore.Context.Database.GetItem(site.RootPath + "website/error/404");
if(item404Page != null)
{
Sitecore.Context.Item = item404Page;
args.Context.Response.StatusCode = (int) System.Net.HttpStatusCode.NotFound;
}
}
}
}
}
For IIS 7.5:
public class Error404Resolver : Sitecore.Pipelines.HttpRequest.HttpRequestProcessor
{
public override void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
if(Sitecore.Context.Item == null && !args.Context.Request.Url.AbsolutePath.StartsWith("/sitecore")
{
args.Context.Response.Clear();
SiteContext site = Sitecore.Context.Site;
if(site != null)
{
Item item404Page = Sitecore.Context.Database.GetItem(site.RootPath + "website/error/404");
if(item404Page != null)
{
WebClient webClient = new WebClient();
webClient.Encoding = args.Context.Request.ContentEncoding;
webClient.Headers.Add("User-Agent", args.Context.Request.UserAgent);
string page = webClient.DownloadString(LinkManager.GetItemUrl(item404Page));
args.Context.Response.StatusCode = (int) System.Net.HttpStatusCode.NotFound;
args.Context.Response.Write(page);
args.Context.Response.TrySkipIisCustomErrors = true;
args.Context.Response.End();
}
}
}
}
}
Whit this you will render error page in current page without redirect and returns to a browser code 404.
I have the same issue at a customer I currently work at (looks like the code was pasted) and actually the reason is pretty obvious: If you execute this call with a url that is not registered in the Sitecore sites config (but accessible via IIS), you will also run through this code. Unfortunately, the WebUtil.ExecuteWebPage call is executed with the wrong url as well, hence you end up stuck in a loop.
Actually you should see a lot of these messages in your log: Falling back to default redirection behavior. Reason for error {0}, probably with timeouts.
If you really want to use your custom handler, you should check if you are in the right site context before calling WebUtil.ExecuteWebPage.

Awesomnium Post Parameters

currently i am working with Awsomnium 1.7 in the C# environment.
I'm just using the Core and trying to define custom post parameters.
Now, i googled a lot and i even posted at the awsomnium forums, but there was no answer.
I understand the concept, but the recent changes just dropped the suggested mechanic and examples.
What i found:
http://support.awesomium.com/kb/general-use/how-do-i-send-form-values-post-data
The problem with this is, that the WebView Class does not contain "OnResourceRequest" Event anymore.
So far, i have implemented the IResourceInterceptor and have the "OnRequest"-Function overwritten
public ResourceResponse OnRequest(ResourceRequest request)
is the signature, but i have no chance to reach in there in order to add request headers.
Anyone here any idea? I tried to look in the documentation, but i didn't find anything on that.....
You need to attach your IResourceInterceptor to WebCore, not WebView. Here's a working example:
Resource interceptor:
public class CustomResourceInterceptor : ResourceInterceptor
{
protected override ResourceResponse OnRequest(ResourceRequest request)
{
request.Method = "POST";
var bytes = "Appending some text to the request";
request.AppendUploadBytes(bytes, (uint) bytes.Length);
request.AppendExtraHeader("custom-header", "this is a custom header");
return null;
}
}
Main application:
public MainWindow()
{
WebCore.Started += WebCoreOnStarted;
InitializeComponent();
}
private void WebCoreOnStarted(object sender, CoreStartEventArgs coreStartEventArgs)
{
var interceptor = new CustomResourceInterceptor();
WebCore.ResourceInterceptor = interceptor;
//webView is a WebControl on my UI, but you should be able to create your own WebView off WebCore
webView.Source = new Uri("http://www.google.com");
}
HotN's answer above is good; in fact, it's what I based my answer on. However, I spent a week searching for this information and putting together something that will work. (The answer above has a couple of issues which, at the very least, make it unworkable with v1.7 of Awesomium.) What I was looking for was something that would work right out of the box.
And here is that solution. It needs improvement, but it suits my needs at the moment. I hope this helps someone else.
// CRI.CustomResourceInterceptor
//
// Author: Garison E Piatt
// Contact: {removed}
// Created: 11/17/14
// Version: 1.0.0
//
// Apparently, when Awesomium was first created, the programmers did not understand that someone would
// eventually want to post data from the application. So they made it incredibly difficult to upload
// POST parameters to the remote web site. We have to jump through hoops to get that done.
//
// This module provides that hoop-jumping in a simple-to-understand fashion. We hope. It overrides
// the current resource interceptor (if any), replacing both the OnRequest and OnFilterNavigation
// methods (we aren't using the latter yet).
//
// It also provides settable parameters. Once this module is attached to the WebCore, it is *always*
// attached; therefore, we can simply change the parameters before posting to the web site.
//
// File uploads are currently unhandled, and, once handled, will probably only upload one file. We
// will deal with that issue later.
//
// To incoroprate this into your application, follow these steps:
// 1. Add this file to your project. You know how to do that.
// 2. Edit your MainWindow.cs file.
// a. At the top, add:
// using CRI;
// b. inside the main class declaration, near the top, add:
// private CustomResourceInterceptor cri;
// c. In the MainWindow method, add:
// WebCore.Started += OnWebCoreOnStarted;
// cri = new CustomResourceInterceptor();
// and (set *before* you set the Source value for the Web Control):
// cri.Enabled = true;
// cri.Parameters = String.Format("login={0}&password={1}", login, pw);
// (Choose your own parameters, but format them like a GET query.)
// d. Add the following method:
// private void OnWebCoreOnStarted(object sender, CoreStartEventArgs coreStartEventArgs) {
// WebCore.ResourceInterceptor = cri;
// }
// 3. Compile your application. It should work.
using System;
using System.Runtime.InteropServices;
using System.Text;
using Awesomium.Core;
using Awesomium.Windows.Controls;
namespace CRI {
//* CustomResourceInterceptor
// This object replaces the standard Resource Interceptor (if any; we still don't know) with something
// that allows posting data to the remote web site. It overrides both the OnRequest and OnFilterNavigation
// methods. Public variables allow for run-time configuration.
public class CustomResourceInterceptor : IResourceInterceptor {
// Since the default interceptor remains overridden for the remainder of the session, we need to disable
// the methods herein unless we are actually using them. Note that both methods are disabled by default.
public bool RequestEnabled = false;
public bool FilterEnabled = false;
// These are the parameters we send to the remote site. They are empty by default; another safeguard
// against sending POST data unnecessarily. Currently, both values allow for only one string. POST
// variables can be combined (by the caller) into one string, but this limits us to only one file
// upload at a time. Someday, we will have to fix that. And make it backward-compatible.
public String Parameters = null;
public String FilePath = null;
/** OnRequest
** This ovverrides the default OnRequest method of the standard resource interceptor. It receives
** the resource request object as a parameter.
**
** It first checks whether or not it is enabled, and returns NULL if not. Next it sees if any
** parameters are defined. If so, it converst them to a byte stream and appends them to the request.
** Currently, files are not handled, but we hope to add that someday.
*/
public ResourceResponse OnRequest(ResourceRequest request) {
// We do nothing at all if we aren't enabled. This is a stopgap that prevents us from sending
// POST data with every request.
if (RequestEnabled == false) return null;
// If the Parameters are defined, convert them to a byte stream and append them to the request.
if (Parameters != null) {
var str = Encoding.Default.GetBytes(Parameters);
var bytes = Encoding.UTF8.GetString(str);
request.AppendUploadBytes(bytes, (uint)bytes.Length);
}
// If either the parameters or file path are defined, this is a POST request. Someday, we'll
// figure out how to get Awesomium to understand Multipart Form data.
if (Parameters != null || FilePath != null) {
request.Method = "POST";
request.AppendExtraHeader("Content-Type", "application/x-www-form-urlencoded"); //"multipart/form-data");
}
// Once the data has been appended to the page request, we need to disable this process. Otherwise,
// it will keep adding the data to every request, including those that come from the web site.
RequestEnabled = false;
Parameters = null;
FilePath = null;
return null;
}
/** OnFilterNavigation
** Not currently used, but needed to keep VisualStudio happy.
*/
public bool OnFilterNavigation(NavigationRequest request) {
return false;
}
}
}

Categories