Skipping IIS Custom Error pages

By default, IIS7 intercepts 4xx and 5xx status responses with its own custom error pages. At work, we have a custom redirection module that checks if the status is 401 Unauthorized and spits javascript to redirect to the log in page. We use javascript in order to preserve # fragment in the return url.

The issue was 401. We set 401 to the response to send a meaninful response. The body contains a javascript redirection chunk. But it is intercepted by IIS7, so the user is not redirected but only see an dumb IIS 401 error page.

dumb_iis7_errror_page

 

 

After some googling, I found two ways to handle the issue. One is to let all response ignore IIS custom error pages. You can do that by setting existingResponse=”PassThrough” in the web.config.

<configuration>
  <system.webServer>
    <httpErrors existingResponse="PassThrough" />
  </system.webServer>
</configuration>

 

The other is to set response.TrySkipIisCustomErrors = true, and then the only that response will be passed through without being intercepted by IIS7 custom error pages.

The second option was appropriate, as we want to pass through only for redirection module.

public void OnEndRequest(HttpContextBase context)
{
    if (context.Response.StatusCode != 401)
        return;

    var response = context.Response;
    response.TrySkipIisCustomErrors = true;
    response.Status = response.Status;
    response.StatusCode = (int)HttpStatusCode.Unauthorized;
    response.TrySkipIisCustomErrors = true;
    response.ClearContent();
    response.RedirectLocation = null;
    response.Write(_buildClientRedirectionResponse.GetRedirectionScript());
    response.End();
}

For me, TrySkipIisCustomErros = true didn’t work until you set a value to response.StatusCode. It seems that response.TrySkipIisCustomErrors = true and response.Status = response.Status should be set together.

With the second option, you can benefit from Custom error pages and a temporal pass through for 401 for redirection. Hope this helps.

Skipping IIS Custom Error pages

Keeping site root url clean without query string

References

SEO is becoming more and more important issue these days, and your site root url can lose its SEO rank if there are too many different query string attached such as http://www.yoursite.com/?a=b&b=c and http://www.yoursite.com/default.aspx?wt=129. It will be nice you can keep the site root clean lik http://www.yoursite.com

My company employed Scrum from this year and in March sprint, the above was one of the user stories I worked on.

The implementation logic is like this

  • If the url has any query string, store query string values into cookie and redirect it to site root without query string.
  • If there is no query string, then check if cookie has any query string. If the cookie exists, then manually populate Request.QueryString object with the values from cookie.

One tricky bit is Request.QueryString is read only property. So you need to find a private value that the property uses inside. I could do that using .Net reflector and it is _queryString. Using Reflection, you can set the value of a private variable.

The followings are the codes I wrote for this. Of course, I could not write this without the help from the above links.

        protected override void OnInit(EventArgs e)
        {
            if (!IsPostBack)
            {
                if (Request.QueryString.Count > 0)
                    RedirectToCleanSiteRootUrl();

                if(Request.Cookies[“QS”]!= null)
                    PopulateQueryStringFromCookie();
            }

            base.OnInit (e);
        }

        private void RedirectToCleanSiteRootUrl()
        {
            HttpCookie cookie = new HttpCookie(“QS”);
            foreach (string key in Request.QueryString.AllKeys)
            {
                cookie.Values[key] = Request.QueryString[key];
                cookie.Expires = DateTime.Now.AddDays(1d);
            }
            Response.Cookies.Add(cookie);

            PageUtility.RedirectPermanent(“/”);
        }

        private void PopulateQueryStringFromCookie()
        {
            NameValueCollection collection = (NameValueCollection) Request.GetType().GetField(“_queryString”,
                BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Request);
            PropertyInfo readOnlyInfo = collection.GetType().GetProperty(“IsReadOnly”,
                BindingFlags.NonPublic | BindingFlags.Instance);
            readOnlyInfo.SetValue(collection,false,null);

            collection.Add(Request.Cookies[“QS”].Values);
            Response.Cookies[“QS”].Expires = DateTime.Now.AddDays(-1d);
        }

Keeping site root url clean without query string

add a reference to assembly ‘System.Web.Extensions…

I was adding a ajax-enabled control on a page and suddenly having “… You must add a reference to assembly ‘System.Web.Extensions…” error. It complained that my website assembly does not have a reference to ‘System.Web.Extensions’ and it was true. Definitely, I added the reference to my project, but the compiled dll does not have it.

I googled a little bit. Some suggest that I need to install Microsoft Ajax framework 1.0. I did it long time ago, but I re-installed it. The problem persisted. One good document was Configuring ASP.Net Ajax. I noticed <control> section in the web.config

The <controls> Element

The <controls> element registers ASP.NET AJAX namespaces in the System.Web.Extensions assembly and maps the asp tag prefix as an alias for these namespaces. The controls in the ASP.NET AJAX namespaces can be referenced in a Web page with syntax such as the following:

First, I didn’t have ScriptManager. This was a silly mistake.

<asp:ScriptManager ID="ScriptManager1" runat="server" />

Second, I didn’t register tagPrefix for “asp”. I find this strange. asp prefix is the default one, and I wondered why I bother to do. It turned out that if you don’t have tagPrefix for “asp”, your web project does not reference system.web.extensions and it will break. So, please do.

<system.web>
  <pages>
    <controls>
      <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </controls>
  </pages>
</system.web>
add a reference to assembly ‘System.Web.Extensions…

default button for asp.net user control (.net 1.1)

  406                 control.BasePage.RegisterStartupScript(“DefaultButtonForLogin”, String.Format(@”

  407 <script language=””javascript””>

  408 <!–

  409 function handler(e)

  410 {{

  411     if ((e && ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13))) || (event && event.keyCode == 13))

  412     {{

  413         document.forms[0].action = ‘{0}’;

  414         {1};

  415         return false;

  416     }}

  417 }}

  418 if (!!document.forms[0][‘{2}’])

  419     document.forms[0][‘{2}’].onkeydown = handler;

  420 if (!!document.forms[0][‘{3}’])

  421     document.forms[0][‘{3}’].onkeydown = handler;

  422 //–>

  423 </script>”,

  424                     secureURL,

  425                     control.BasePage.GetPostBackClientEvent(control.LoginButton, “”),

  426                     control.PasswordTextBox.ClientID,

  427                     control.EmailTextBox.ClientID));

특히 if (!!document.forms[0][‘{2}’]) 부분이 중요하다. !!는 truthy or falsy를 해주는 표현인데, asp.net control들이 visible=false 일 경우, 서버단에서는 control이 null이 아니지만, rendering이 되지않기 때문에, javascript에서는 null이거나 undefined 에러가 나는 경우가 많다. 그러므로, 반드시 null 또는 undefined 체크를 해줄 것.

default button for asp.net user control (.net 1.1)

CS1595 ‘…’ is defined in multiple places error when you use a user control

CS1595: ‘_ASP.FormsLoginPart_ascx’ is defined in multiple places; using definition from ‘c:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\recruitment_selfservice\858213b9\9a24bb64\tyrklreb.dll’

I have this error often, because some .net webcontrols have the same file name, though they belong to different namespaces. I googled this a little bit and there are a few solutions.

  • change webcontrol .ascx name. asp.net compiler does not like same webcontrol file name.
    ex) AnotherCurrentDate.ascx
  • use classname attribute in control directive
    ex) <%@ Control Language=”c#” AutoEventWireup=”false” Codebehind=”CurrentDate.ascx.cs” Classname=”AnotherCurrentDate” Inherits=”MyProj.CurrentDate” %>

The idea is from velocity reviews thread

CS1595 ‘…’ is defined in multiple places error when you use a user control