Updated UmbracoModule to support the TransferRequest just like umbraMVCo does using query strings. Added more

unit tests for UmbracoModule, refactored the Umbraco.Web.Routing.Domains to not have static methods and created an
interface for it so that we can unit test it. Changed DocumentRequest stuff to internal. Finally got unit test working for the module
This commit is contained in:
shannon@ShandemVaio
2012-08-07 02:33:08 +06:00
parent fbf94bbf01
commit b68bcba85e
18 changed files with 371 additions and 113 deletions

View File

@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Web.Routing;
@@ -22,6 +25,87 @@ namespace Umbraco.Web
public class UmbracoModule : IHttpModule
{
/// <summary>
/// Checks if the current request should process the request as a front-end umbraco request, if this is tru
/// it then creates the DocumentRequest object, finds the document, domain and culture and stores this back
/// to the UmbracoContext
/// </summary>
/// <param name="httpContext"></param>
/// <param name="umbracoContext"> </param>
/// <param name="docLookups"> </param>
/// <param name="lastChanceLookup"> </param>
internal bool ProcessFrontEndDocumentRequest(
HttpContextBase httpContext,
UmbracoContext umbracoContext,
IEnumerable<IDocumentLookup> docLookups,
IDocumentLastChanceLookup lastChanceLookup)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
if (httpContext.Request.Url.LocalPath.InvariantStartsWith("/default.aspx")
&& !string.IsNullOrWhiteSpace(httpContext.Request.QueryString["path"]))
{
//the path is the original path that the request came in on before we've rewritten it,
//this is required because TransferRequest does not maintain the httpcontext
var path = httpContext.Request.QueryString["path"];
var qry = httpContext.Request.QueryString["qry"];
Uri uri;
try
{
uri = UriUtility.ToFullUrl(path + qry, httpContext);
}
catch (Exception ex)
{
//if this fails then the path could not be parsed to a Uri which could be something malicious
LogHelper.Error<UmbracoModule>(string.Format("Could not parse the path {0} into a full Uri", path), ex);
return false;
}
//create request based objects (one per http request)...
//create a content store
var contentStore = new ContentStore(umbracoContext);
//create the nice urls
var niceUrls = new NiceUrlProvider(contentStore, umbracoContext);
//create the RoutingContext
var routingContext = new RoutingContext(
umbracoContext,
docLookups,
lastChanceLookup,
contentStore,
niceUrls);
// create the new document request which will cleanup the uri once and for all
var docreq = new DocumentRequest(uri, routingContext);
// initialize the DocumentRequest on the UmbracoContext (this is circular dependency but i think in this case is ok)
umbracoContext.DocumentRequest = docreq;
// note - at that point the original legacy module did something do handle IIS custom 404 errors
// ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
// "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
// to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
//
// to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
// so that they point to a non-existing page eg /redirect-404.aspx
docreq.LookupDomain();
if (docreq.IsRedirect)
httpContext.Response.Redirect(docreq.RedirectUrl, true);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = docreq.Culture;
docreq.LookupDocument();
if (docreq.IsRedirect)
httpContext.Response.Redirect(docreq.RedirectUrl, true);
if (docreq.Is404)
httpContext.Response.StatusCode = 404;
// it is up to default.aspx to figure out what to display in case
// there is no document (ugly 404 page?) or no template (blank page?)
return true;
}
return false;
}
/// <summary>
/// This is a performance tweak to check if this is a .css, .js or .ico file request since
@@ -32,7 +116,7 @@ namespace Umbraco.Web
/// <returns></returns>
internal bool IsClientSideRequest(Uri url)
{
var toIgnore = new[] {".js", ".css", ".ico"};
var toIgnore = new[] { ".js", ".css", ".ico" };
return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x));
}
@@ -69,12 +153,9 @@ namespace Umbraco.Web
{
LogHelper.Debug<UmbracoModule>("Start processing request");
var uri = httpContext.Request.Url;
var lpath = uri.AbsolutePath.ToLower();
if (IsClientSideRequest(uri))
if (IsClientSideRequest(httpContext.Request.Url))
{
LogHelper.Debug<UmbracoModule>("End processing request, not transfering to handler, this is a client side file request {0}", () => uri);
LogHelper.Debug<UmbracoModule>("End processing request, not transfering to handler, this is a client side file request {0}", () => httpContext.Request.Url);
return;
}
@@ -83,8 +164,12 @@ namespace Umbraco.Web
httpContext.Response.AddHeader("X-Umbraco-Version", string.Format("{0}.{1}", GlobalSettings.VersionMajor, GlobalSettings.VersionMinor));
//create the legacy UmbracoContext
global::umbraco.presentation.UmbracoContext.Current
= new global::umbraco.presentation.UmbracoContext(httpContext);
global::umbraco.presentation.UmbracoContext.Current = new global::umbraco.presentation.UmbracoContext(httpContext);
//create the LegacyRequestInitializer
var legacyRequestInitializer = new LegacyRequestInitializer(httpContext.Request.Url, httpContext);
// legacy - initialize legacy stuff
legacyRequestInitializer.InitializeRequest();
//create the UmbracoContext singleton, one per request!!
var umbracoContext = new UmbracoContext(
@@ -93,66 +178,32 @@ namespace Umbraco.Web
RoutesCacheResolver.Current.RoutesCache);
UmbracoContext.Current = umbracoContext;
//create request based objects (one per http request)...
//create a content store
var contentStore = new ContentStore(umbracoContext);
//create the nice urls
var niceUrls = new NiceUrlProvider(contentStore, umbracoContext);
//create the RoutingContext
var routingContext = new RoutingContext(
umbracoContext,
//Does a check to see if this current request contains the information in order to process the
//request as a front-end request. If not, then its because the rewrite hasn't taken place yet.
//if we need to rewrite, then we'll cleanup the query strings and rewrite to the front end handler.
if (!ProcessFrontEndDocumentRequest(httpContext, umbracoContext,
DocumentLookupsResolver.Current.DocumentLookups,
LastChanceLookupResolver.Current.LastChanceLookup,
contentStore,
niceUrls);
// create the new document request which will cleanup the uri once and for all
var docreq = new DocumentRequest(uri, routingContext);
// initialize the DocumentRequest on the UmbracoContext (this is circular dependency but i think in this case is ok)
umbracoContext.DocumentRequest = docreq;
//create the LegacyRequestInitializer
var legacyRequestInitializer = new LegacyRequestInitializer(httpContext.Request.Url, httpContext);
// legacy - initialize legacy stuff
legacyRequestInitializer.InitializeRequest();
// note - at that point the original legacy module did something do handle IIS custom 404 errors
// ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
// "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
// to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
//
// to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
// so that they point to a non-existing page eg /redirect-404.aspx
//do not continue if this request is not a front-end routable page
if (!EnsureUmbracoRoutablePage(uri, lpath, httpContext))
LastChanceLookupResolver.Current.LastChanceLookup))
{
LogHelper.Debug<UmbracoModule>("End processing request, not transfering to handler {0}", () => uri);
return;
}
var uri = httpContext.Request.Url;
var lpath = uri.AbsolutePath.ToLower();
// legacy - no idea what this is
LegacyCleanUmbPageFromQueryString(ref uri, ref lpath);
// legacy - no idea what this is
LegacyCleanUmbPageFromQueryString(ref uri, ref lpath);
//**THERE** we should create the doc request
// before, we're not sure we handling a doc request
docreq.LookupDomain();
if (docreq.IsRedirect)
httpContext.Response.Redirect(docreq.RedirectUrl, true);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = docreq.Culture;
docreq.LookupDocument();
if (docreq.IsRedirect)
httpContext.Response.Redirect(docreq.RedirectUrl, true);
//do not continue if this request is not a front-end routable page
if (EnsureUmbracoRoutablePage(uri, lpath, httpContext))
{
RewriteToPath<Page>(HttpContext.Current, lpath, uri.Query);
//RewriteToPath<Page>(HttpContext.Current, "", docreq.Uri.Query);
}
else
{
LogHelper.Debug<UmbracoModule>("End processing request, not transfering to handler {0}", () => uri);
}
}
if (docreq.Is404)
httpContext.Response.StatusCode = 404;
TransferRequest("~/default.aspx" + docreq.Uri.Query, httpContext);
// it is up to default.aspx to figure out what to display in case
// there is no document (ugly 404 page?) or no template (blank page?)
}
/// <summary>
@@ -245,7 +296,11 @@ namespace Umbraco.Web
// fixme ?orgurl=... ?retry=...
}
TransferRequest(bootUrl, httpContext);
//RewriteToPath<Page>(HttpContext.Current, bootUrl, "", "");
//TransferRequest(bootUrl, httpContext);
httpContext.RewritePath(bootUrl);
return false;
}
@@ -291,37 +346,70 @@ namespace Umbraco.Web
return true;
}
// transfers the request using the fastest method available on the server
void TransferRequest(string path, HttpContextBase httpContext)
private static void RewriteToPath<THandler>(HttpContext context, string currentPath, string currentQuery)
where THandler : IHttpHandler
{
LogHelper.Debug<UmbracoModule>("Transfering to " + path);
var integrated = HttpRuntime.UsingIntegratedPipeline;
if ((context.CurrentHandler is THandler)) return;
// fixme - are we doing this properly?
// fixme - handle virtual directory?
// fixme - this does not work 'cos it resets the HttpContext
// so we should move the DocumentRequest stuff etc back to default.aspx?
// but, also, with TransferRequest, auth & co will run on the new (default.aspx) url,
// is that really what we want? I need to talk about it with others. @zpqrtbnk
var rewritePath = "~/default.aspx?path="
+ context.Server.UrlEncode(currentPath)
+ "&qry="
+ context.Server.UrlEncode(currentQuery);
// NOTE: SD: Need to look at how umbraMVCo does this. It is true that the TransferRequest initializes a new HttpContext,
// what we need to do is when we transfer to the handler we send a query string with the found page Id to be looked up
// after we have done our routing check. This however needs some though as we don't want to have to query for this
// page twice. Again, I'll check how umbraMVCo is doing it as I had though about that when i created it :)
if (currentPath.StartsWith(rewritePath, StringComparison.InvariantCultureIgnoreCase)) return;
integrated = false;
var isMvc = TypeHelper.IsTypeAssignableFrom<THandler>(typeof(MvcHandler));
// http://msmvps.com/blogs/luisabreu/archive/2007/10/09/are-you-using-the-new-transferrequest.aspx
// http://msdn.microsoft.com/en-us/library/aa344903.aspx
// http://forums.iis.net/t/1146511.aspx
LogHelper.Debug<UmbracoModule>("Transfering to " + rewritePath);
if (integrated)
httpContext.Server.TransferRequest(path);
if (HttpRuntime.UsingIntegratedPipeline)
{
context.Server.TransferRequest(rewritePath, true);
}
else
httpContext.RewritePath(path);
{
// Pre MVC 3
context.RewritePath(rewritePath, false);
if (isMvc)
{
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(context);
}
}
}
//// transfers the request using the fastest method available on the server
//void TransferRequest(string path, HttpContextBase httpContext)
//{
// LogHelper.Debug<UmbracoModule>("Transfering to " + path);
// var integrated = HttpRuntime.UsingIntegratedPipeline;
// // fixme - are we doing this properly?
// // fixme - handle virtual directory?
// // fixme - this does not work 'cos it resets the HttpContext
// // so we should move the DocumentRequest stuff etc back to default.aspx?
// // but, also, with TransferRequest, auth & co will run on the new (default.aspx) url,
// // is that really what we want? I need to talk about it with others. @zpqrtbnk
// // NOTE: SD: Need to look at how umbraMVCo does this. It is true that the TransferRequest initializes a new HttpContext,
// // what we need to do is when we transfer to the handler we send a query string with the found page Id to be looked up
// // after we have done our routing check. This however needs some though as we don't want to have to query for this
// // page twice. Again, I'll check how umbraMVCo is doing it as I had though about that when i created it :)
// integrated = false;
// // http://msmvps.com/blogs/luisabreu/archive/2007/10/09/are-you-using-the-new-transferrequest.aspx
// // http://msdn.microsoft.com/en-us/library/aa344903.aspx
// // http://forums.iis.net/t/1146511.aspx
// if (integrated)
// httpContext.Server.TransferRequest(path);
// else
// httpContext.RewritePath(path);
//}
#region Legacy
@@ -380,7 +468,7 @@ namespace Umbraco.Web
// used to be done in PostAuthorizeRequest but then it disabled OutputCaching due
// to rewriting happening too early in the chain (Alex Norcliffe 2010-02).
//app.PostResolveRequestCache += (sender, e) =>
//SD: changed to post map request handler so we can know what the handler actually is, this is a better fit for
//when we handle the routing
app.PostMapRequestHandler += (sender, e) =>