How to redirect category page to first child in Umbraco without a ThreadAbortException

766 views Asked by At

(I'm about to answer this question as well, because I've found the solution, but Google didn't really help, so hopefully this will gain some PageRank for the search terms I was using.)

We have a big Umbraco site with several sections, but most locales don't have section homepages. So if the structure looks like:

- Homepage
  - Section1
    - Page1-1
    - Page1-2
  - Section2
    - Page2-1
    - Page2-2

and so on, then going to ~/section1/ would redirect you to ~/section1/page1-1/ (and, likewise ~/section2/ redirects you to ~/section2/page2-1/).

At the moment, we use a macro that checks a property in the locale homepage and then redirects:

var node = Model.AncestorOrSelf("SiteHome");
var useCSSV2 = node.GetProperty("useCSSV2").Value;
if (useCSSV2 == "1")
{
    Response.Redirect(Model.Children.First().Url);
}

We're seeing a bunch of occasions where macros don't load properly, with errors like

Error loading MacroEngine script (file: PrimaryNavigationSwitcher.cshtml)

displaying instead. Looking at the UmbracoTraceLog, I can see things like:

2014-11-25 00:11:28,226 [5] WARN umbraco.macro - [Thread 39] Error loading MacroEngine script (file: PrimaryNavigationSwitcher.cshtml, Type: ''. Exception: System.Threading.ThreadAbortException: Thread was being aborted.
at System.Threading.Thread.AbortInternal()
at System.Threading.Thread.Abort(Object stateInfo)
at System.Web.HttpResponse.AbortCurrentThread()
at System.Web.HttpResponseWrapper.Redirect(String url)
at ASP._Page_macroScripts_SecondLevelPageRedirection_cshtml.Execute() in d:\webroot\www.mysite.com\macroScripts\SecondLevelPageRedirection.cshtml:line 8
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy()
at System.Web.WebPages.WebPage.ExecutePageHierarchy()
at System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage)
at umbraco.MacroEngines.RazorMacroEngine.ExecuteRazor(MacroModel macro, INode currentPage)
at umbraco.MacroEngines.RazorMacroEngine.Execute(MacroModel macro, INode currentPage)
at umbraco.macro.loadMacroScript(MacroModel macro)
at umbraco.macro.renderMacro(Hashtable pageElements, Int32 pageId)

(where line 8 of SecondLevelPageRedirection.cshtml is the Response.Redirect).

That problem and the ThreadAbortException itself are strongly suggesting to me that Response.Redirect is the problem here and I should be using some other means of performing this redirect. (And even if this weren't a problem I'd prefer to avoid the performance impact of a bunch of exceptions being thrown.)

How should we be performing this redirect to have the same effect (so anyone going to ~/section1/ will be redirected to ~/section1/page1-1/ and so on), without having to add an umbracoRedirect or umbracoInternalRedirectId to each node and without having these damn ThreadAbortExceptions thrown all the time?

1

There are 1 answers

0
Owen Blacker On

As detailed in a handful of places (notably Why Response.Redirect causes System.Threading.ThreadAbortException? here on Stack Overflow and PRB: ThreadAbortException Occurs If You Use Response.End, Response.Redirect, or Server.Transfer on the MSKB), Response.Redirect(string) is only present in ASP.Net for backwards compatibility.

To quote from Joel Fillmore in his answer to the Stack Overflow question linked above:

The correct pattern is to call the Redirect overload with endResponse=false and make a call to tell the IIS pipeline that it should advance directly to the EndRequest stage once you return control:

Response.Redirect(url, false);  
Context.ApplicationInstance.CompleteRequest();

This blog post from Thomas Marquardt provides additional details, including how to handle the special case of redirecting inside an Application_Error handler.

Note that code after the Context.ApplicationInstance.CompleteRequest call will execute, so may need handling separately.

Incidentally, as the problem stems from Response.End, which includes the code

InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));

then Server.Transfer will have precisely the same problem. There is more information at Is Response.End() considered harmful?