воскресенье, 30 августа 2009 г.

ActionScript Mouse Events and Delegation

If you’ve used Flash MovieClips events onRollOver, onRollOut, onRelease,onReleaseOutside or onPress you’ve probably encountered the annoying little Flash shortcoming, that you can’t define mouse event-handlers on MovieClips contained inside MovieClips that have a mouse event defined.

Suppose you have something like this:

this.onRelease = doSomething;
this.createEmptyMovieClip(”childMC”,this.getNextHighestDepth());
var childMC:MovieClip = this["childMC"];
//Add some code to put something inside childMC
childMC.onRollOver = doSomethingElse;

The code in the function doSomethingElse will never be reached, because all mouse events are “caught” by childMC’s parent MovieClip.

A workaround that I use is to check in the parent functions whether the events apply to children.

Here’s an example for the onRelease event:

this.onRelease = function()
{

if (childMC.hitTest(_root._xmouse, _root._ymouse, true)) {

doSomethingElse();

}

}

The MovieClip function hitTest is used to test whether the mouse is hovering above the childMC when released. hitTest takes as arguments the x and y coordinates of the mouse relative to the _root MovieClip and a boolean that specifies whether to do the hitTest using the actual MovieClip shape (true) or just it’s bounding box (false).

Delegation

Sometimes you may want to base the logic in a mouse handling event on data that’s stored in another MovieClip, of course you can expose the decisions variables by making them public variables and accessing them through some relative path like _parent.var1 or this.childMC.var1 but a cleaner solution might be using Flash delegate creator available in Flash 8 professional. I learned how to use it throughthis Delegate tutorial. With delegates you can define mouse event handlers that execute in a object scope other then the MovieClip object scope on which they are defined! This how I used it for example. I had a ‘parent’ MovieClip showing a JPG image. In the MovieClip was ‘child’ MovieClip called zoomer, that when pressed should open a new browser window containing a high-res version of the JPG. Isensed that it would be better program design to have the knowledge about the image low-res and high-res version in the ‘parent’ MovieClip. Without use of a Delegate I should have either copied the knowledge of the high-res JPG to the child MovieClip or exposed that knowledge through the parents public interface. In my humble opinion the delegate approach is the cleanest, really leaving all the data and logic concerning JPG’s in the parent MovieClip.

Here’s how I used the delegate functionality:

import mx.utils.Delegate;
zoomer.onRelease = zoomer.onReleaseOutside = Delegate.create(this, handleOnRelease);

Windows Authentication using Form Authentication

Learn how to authenticate a Windows account using Forms Authentication.

ASP.NET 4.0: WebForm Routing

NOTE: This is a quick introduction,. I’ll drill into this more in a future post.

One of the things we’re adding in ASP.NET 4.0 is built-in support for using Routing together with WebForms. Now, I’m totally aware that this is possible to do already and that a number of people have posted ways of doing this with the Web Routing feature which shipped with ASP.NET 3.5 SP1. We’re just adding some features to make it easier to do…

So, what are we adding?

  1. WebFormRouteHandler, this is a simple HttpHandler which you use in defining your routes…it essentially takes care of passing data into the page you’re routing to.
  2. Adding HttpRequest.RequestContext and Page.RouteData (this is really just a proxy onto the HttpRequest.RequestContext.RouteData object). Makes it easier to access information passed from the Route.
  3. ExpressionBuilders:
    1. RouteUrl (in System.Web.Compilation.RouteUrlExpressionBuilder), a simple way to create a URL corresponding to your route URL format within an ASP.NET Server Control
    2. RouteValue (again, in System.Web.Compilation.RouteValueExpressionBuilder), a simple way to extract information from the RouteContext.
  4. RouteParameter, a DataSource parameter which lets you easily pass data contained in RouteContext into a query for a DataSource…like FormParameter.

So, how does all this fit together?

WebFormRouteHandler

First, let’s define the new WebForm route…as follows:

public class Global : System.Web.HttpApplication

{

protected void Application_Start(object sender, EventArgs e)

{

RouteTable.Routes.Add("SearchRoute", new Route("search/{searchterm}", new WebFormRouteHandler("~/search.aspx")));

RouteTable.Routes.Add("UserRoute", new Route("users/{username}", new WebFormRouteHandler("~/users.aspx")));

}

}

As you can see, we simply map the Route onto a physical page (in the first string, “~/search.aspx”), we’ve also said that the parameter ‘searchterm’ should be extracted from the URL and passed into the page.

We also support an additional parameter for WebFormRouteHandler, the signature is as follows:

WebFormRouteHandler(string virtualPath, bool checkPhysicalUrlAccess);

The ‘checkPhysicalUrlAccess’ parameter specifies whether the RouteHandler should check the perrmissions for the physical page you’re routing to (in the case above, search.aspx) as well as the incoming Url…this defaults to ‘true’.

HttpRequest.RequestContext and Page.RouteData

Now that you’re in the physical page, how do you go about accessing the information which the Routing system has extracted from the Url (or, an object which some other object has added to the RouteData)?

As I mentioned before, we’ve added two methods…HttpRequest.RequestContext and Page.RouteData. Where Page.RouteData is just a convenience method onto HttpRequest.RequestContext.RouteData

This lets us do the following:

protected void Page_Load(object sender, EventArgs e)

{

string searchterm= Page.RouteData.Values["searchterm"] as string;

Response.Write(searchterm);

}

Here, we’re just extracting the value we passed in for ‘searchterm’ defined in the Route above.

Given the url http://localhost/search/scott/ , this would output the word ‘scott’ into the page…

So, nice…that’s how we’d do it in code…but what about Markup?

RouteUrl and RouteValue expression builders

ExpressionBuilders have been described as a ‘hidden gem of ASP.NET’, and that’s a shame…because they’re actually pretty cool. We’ve implemented two new expression builders for WebForm routing. They’re used as follows:

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="<%$RouteUrl:SearchTerm=Bob%>">Link to searchasp:HyperLink>

Here we’re using the RouteUrl expressionbuilder to work out our Url based on a route parameter…the reason for doing this is to save having to hard code in our Url structure into our markup…this of course lets us change the URL structure later.

Based on the route we defined previously, this would generate the Url: ‘http://localhost/search/bob’, one thing to note is that we automatically work out the correct route based on the input parameters…you can also name the route in this expressionbuilder which lets you define a specific route url definition to use.

RouteValue is used like so:

<asp:Label ID="Label1" runat="server" Text="<%$RouteValue:SearchTerm%>" />

Pretty simple right? Yes, but it avoids you having to mess around with the more complex Page.RouteData[“x”] in your markup…

In this case we’d get the value ‘scott’ inserted into our label’s text element.

RouteParameter

Well, just use it like FormParameter e.g.,

<asp:sqldatasource id="SqlDataSource1" runat="server" connectionstring="<%$ ConnectionStrings:MyNorthwind %>"

selectcommand="SELECT CompanyName,ShipperID FROM Shippers where CompanyName=@companyname"

<selectparameters>

<asp:routeparameter name="companyname" RouteKey=”searchterm” />

selectparameters>

asp:sqldatasource>

I this case we extract the Route parameter ‘searchterm’ and pass it into the select statement.

Well, that’s it for this whistle-stop tour of our new ASP.NET 4.0 WebForm routing feature…

Deploying ASP.NET MVC Application Under IIS6

When deploying an ASP.NET MVC application under Windows Server 2003 and IIS6 (and Windows Server 2008 Classic Mode), you’ll get 404 page not found error on every page, except the index of the site. This is because IIS6 doesn’t not support routing.

There are many solutions to fix this, I’ll just explain my favorite, which is extension-less url.

So lets show IIS6 how to handle extension-less URL. For this, we’ll use the Wildcard Mapping feature.

  • Open Internet Information Services (IIS) Manager. (run inetmgr)
  • Right Click » Properties on your website.
    1
  • Open the Home Directory tab.
  • Click on the Configuration button.
  • Open the Mappings tab of the Application Configuration window.2
  • In the Wildcard application maps section, click on the Insert button.
  • Browse to %windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll.
  • uncheck “Verify that file exists”.
    3
  • That’s it! Now try your ASP.NET MVC Application it should work nicely!

IP Address Location In ASP.NET

There are a lot of paid as well as free services that can find Visitor Geographical information like country, region, city, latitude, longitude, ZIP code, time zone etc from I.P Addresses. http://iplocationtools.com/ip_location_api.php is one such free IP Geolocation API that returns geographical data in three formats: XML, JSON and CSV. In this article, we will consume this API and fetch visitor geographical information in XML format in the simplest possible way, using LINQ To XML.

Page Meta Keyword & Description – Search Engine Optimization feature

Upto Visual Studio 2008, one can set the Title of the page declaratively or through program using Page.Title. However, as more and more web traffic is happening through search engines, Page’s Title, Keyword and description become more important. Although the Keyword feature was exploited and hence many search engines today ignore it, Page Description is something still major search engines such as Google, Bing use for identifying and indexing pages based on content.

The new feature in ASP.NET 4.0 allows users to programmatically set the Page Description and Keywords as follows:-

protected void Page_Load(object sender, EventArgs e)
{
this.Page.Title = "My ASP.NET Blog";
this.Page.MetaKeywords = "ASP.NET, Web Development, Blog, ASP.NET Blog";
this.Page.MetaDescription = "This Blog contains posts related to ASP.NET and Web Development";
}

The above code appends the following markup

<meta name="keywords" content="ASP.NET, Web Development, Blog, ASP.NET Blog" /> <meta name="description" content="This Blog contains posts related to ASP.NET and Web Development" />

And the way it works is that, if the meta tags are already present in the HTML markup, whatever is set in the code behind will fill up the “content” part alone if the “name” tag is matching.

Although this looks simple, it is very useful in cases where you want to set these dynamically based on a condition / criteria. So far, these were set statically in the HTML. Now with Page Class level access, these can be set dynamically.

There are many more enhancements to Webforms such as Routing improvements, setting ClientID etc., which we will examine in the future posts.

Controlling View State using the ViewStateMode Property

One of the most complained thing in ASP.NET Webform is the growing viewstate which becomes a concern for performance. While earlier you can set the EnableViewState property to true or false, post that, all the controls, by default inherit and even if you set it to enabled at control level, the behaviour was inconsistent.With ASP.NET 4.0, the ViewStateMode property helps to determine for every control, whether the ViewState should be enabled, disabled or inherited accordingly. Ex.-

<asp:Panel ID="pnlViewState" runat="server" ViewStateMode="Disabled"> Disabled: <asp:Label ID="label1" runat="server" Text="Value set in markup" ViewStateMode="Inherit" /><br /> Enabled: <asp:Label ID="label2" runat="server" Text="Value set in markup" ViewStateMode="Enabled" /> <hr />

In the code-behind

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
label1.Text = "Value set in code behind";
label2.Text = "Value set in code behind";
}
}

When you run the above page, you can find that the intial value for both the labels is set to “Value set in code behind” whereas after clicking on the button (postback), the value of label1 changes to “Value set in markup” whereas the value of label2 remains unchanged. As you can see, the Panel which holds both these lables has ViewStateMode set to Disabled and label1 is inherting the mode (this is the default if not specified) and label2 has it enabled. That is the reason label2 maintains viewstate while label1 loses it.While it is arguably possible using the simple EnableViewState property earlier, it was never consistent. Considering the fact that in most of our performance sessions, we talk about disabling viewstate and then enabling it at control level while it doesnt work, this ViewStateMode is a welcome architectural change to improve performance.

PHP versus ASP.NET – Windows versus Linux – Who’s the fastest ?

PHP versus ASP.NET – Windows versus Linux – Who’s the fastest ?

Export DataGrid to Excel

In this Article we are going to read and understand how in a web application we can export a grid data in the excel file. As many times in real time programming we generate reports in the grid format to display to the user.

For example

1 .The list of commodities purchased this month

2. Reports of SMS Sent.

3. Reports of invites. etc.and user might want to save this list for the future use. In Excel format then we need to export this grid to the excel.

Prerequisites:

1. To view an Excel workbook file's contents, you must have installed Microsoft Excel (alone or with MS-Office) on your system.

2. Microsoft Visual Studio (VS) must be installed on the (I haven't tested it on the Prior versions)

Let's start with creating an application in VS2008 (You can even go for VS2005 or VS2010)

Following the steps to we are going to follow.

  1. Create a new project in VS2008 as name it as "ExporttoExcel" in the C# category.
  2. Place a gridview on the default.aspx page and rename it to grdtoexport.
  3. And place a button which will export the grid to excel.
  4. Now lets create a datatable which will bind the grid.

The Code will look like:

protected void Page_Load(object sender, EventArgs e)

{

//creating a table for the grid use namespace System.Data;

DataTable dt = new DataTable ();

//adding columns to the datatale

try

{

dt.Columns.Add("Srno");

dt.Columns.Add("Name");

}

catch { }

//adding values to the datatable

for (int i = 1; i <= 10; i++)

{

DataRow dr = dt.NewRow();

dr[0] = i;

dr[1] = "Meetu Choudhary " + i.ToString();

dt.Rows.Add(dr);

}

//binding databale to the grid

grdtoexport.DataSource = dt;

grdtoexport.DataBind();

}

Writing a ExportToExcel class

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Data;

using System.Configuration;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Text;

using System.IO;

namespace ExportToExcel

{

///

/// Summary description for ExportToExcel

///

public class ExportToExcel

{

public ExportToExcel()

{

//

// TODO: Add constructor logic here

//

}

public void ExportGridView(GridView GridView1, String strFileName)

{

PrepareGridViewForExport(GridView1);

//string attachment = "attachment; filename=Contacts.xls";

HttpContext.Current.Response.ClearContent();

HttpContext.Current.Response.Buffer = true;

HttpContext.Current.Response.AddHeader("content-disposition", "attachment;filename=" + strFileName);

HttpContext.Current.Response.ContentType = "application/ms-excel";

HttpContext.Current.Response.Charset = "";

//System.Web.UI.Page.EnableViewState = false;

StringWriter sw = new StringWriter();

HtmlTextWriter htw = new HtmlTextWriter(sw);

GridView1.RenderControl(htw);

HttpContext.Current.Response.Write(sw.ToString());

HttpContext.Current.Response.End();

}

private void PrepareGridViewForExport(Control gv)

{

LinkButton lb = new LinkButton();

Literal l = new Literal();

string name = String.Empty;

for (int i = 0; i <>

{

if (gv.Controls[i].GetType() == typeof(LinkButton))

{

l.Text = (gv.Controls[i] as LinkButton).Text;

gv.Controls.Remove(gv.Controls[i]);

gv.Controls.AddAt(i, l);

}

else if (gv.Controls[i].GetType() == typeof(DropDownList))

{

l.Text = (gv.Controls[i] as DropDownList).SelectedItem.Text;

gv.Controls.Remove(gv.Controls[i]);

gv.Controls.AddAt(i, l);

}

else if (gv.Controls[i].GetType() == typeof(CheckBox))

{

l.Text = (gv.Controls[i] as CheckBox).Checked ? "True" : "False";

gv.Controls.Remove(gv.Controls[i]);

gv.Controls.AddAt(i, l);

}

if (gv.Controls[i].HasControls())

{

PrepareGridViewForExport(gv.Controls[i]);

}

}

}

/*Use this commented function in all the pages where the above export function is used

//public override void VerifyRenderingInServerForm(Control control)

//{

//}*/

}

}

Calling the Function to export on button click

protected void btnexport_Click(object sender, EventArgs e)

{

ExportToExcel ex = new ExportToExcel();

ex.ExportGridView(grdtoexport, "Client.xls");

}

You can download the code from here

Export Image to Excel using c#

Here am trying to show how we can export an image to excel file.

References Used: Microsoft.Office.Interop.Excel;

Link for downloading the dlls and install : Office XP PIAs

Add Microsoft.Office.Interop.Excel.dll to the bin folder.

Add office.dll to the bin folder.

String sFileImage = System.IO.Path.Combine(System.Configuration.ConfigurationManager.AppSettings["UploadPath"], Session["UserId"].ToString() + ".gif");
String sFilePath = System.IO.Path.Combine(System.Configuration.ConfigurationManager.AppSettings["UploadPath"], Session["UserId"].ToString() + ".xls");
if (File.Exists(sFilePath)) { File.Delete(sFilePath); }

Microsoft.Office.Interop.Excel.ApplicationClass objApp = new Microsoft.Office.Interop.Excel.ApplicationClass();
Microsoft.Office.Interop.Excel.Worksheet objSheet = new Microsoft.Office.Interop.Excel.Worksheet();
Microsoft.Office.Interop.Excel.Workbook objWorkBook = null;
//object missing = System.Reflection.Missing.Value;

try
{
objWorkBook = objApp.Workbooks.Add(Type.Missing);
objSheet = (Microsoft.Office.Interop.Excel.Worksheet)objWorkBook.ActiveSheet;

//Add picture to single sheet1
objSheet = (Worksheet)objWorkBook.Sheets[1];
objSheet.Name = "Graph with Report";

//////////////

Or multiple sheets

for (int iSheet = 0; iSheet < objWorkBook.Sheets.Count - 1; iSheet++)
{
objSheet = objWorkBook.Sheets[iSheet] as Worksheet;
///(objSheet as Microsoft.Office.Interop.Excel._Worksheet).Activate();
}

/////////////////

objSheet.Shapes.AddPicture(sFileImage, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoCTrue, 10, 10, 700, 350);
objWorkBook.SaveAs(sFilePath, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
}
catch (Exception)
{
//Error Alert
}
finally
{
objApp.Quit();
objWorkBook = null;
objApp = null;
}


Avoid Multiple Form Submits

Multiple form submits can result difficulties in web applications because of unexpected behaviors like multiple entries in database. Here is a solution to help you avoid multiple submits.


If you are submitting the page only once then you can use,

<form onsubmit="return Submit();">
And the method is like,
<script type="text/javascript">
var flag = false;

function Submit()
{
if (flag)
{
return false;
}
else
{
flag = true;
return true;
}
}
<
/script>


For a single button you can use,
btnSubmit.Attributes["onclick"] = "this.disabled=true;" +
GetPostBackEventReference(btnSubmit);
For pages with update panels multiple submit is a real problem as page is posting asynchronously or partially.In that scenario you can use Sys.WebForms.PageRequestManager for fixing the issue,
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>

<script type="text/javascript">

Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequest);

function BeginRequest(sender, e)
{
e.get_postBackElement().disabled = true;
}

</script>

Bonnie.NET cryptographic API

Bonnie.NET is a cryptographic API written for the Microsoft® .NET Framework. It allows the generation and management of cryptographic objects based on the today most used cryptographic algorithms. Bonnie.NET reorganized the cryptographic classes of the Microsoft® .NET Framework giving to them a more developer-friendly common interfaces. Those permit the utilization of cryptographic elements even to the novices, allowing however the possibility to the cryptographic experts to implement complex cryptographic systems. From a security point of view, Bonnie.NET is developed and maintained with great attention about security and code security. In fact, all the cryptographic operation are based on the today most secure standards and those are combined with the excellent protection level achieved by the .NET framework 3.5. All cryptographic data are kept secure in memory by the utilization of the SecureString class and ProtectedMemory class of the .NET framework. Moreover, the cryptographic objects inside the API are disposed and immediately garbage collected as soon as they complete their job. All the assembly methods are controlled by implementing the code access security (CAS) features of the .NET framework. This permits to control, inside the assembly, every operation that can be exposed to a security risk. Bonnie.NET implements CAS policy in such a way that, accessing to the system resource, all permissions are denied exception made for those that must be strictly utilized. For those, checks about permissions of the callers are made. Finally, all the methods of the API implement a sophisticated mechanism for the exception management, allowing the developer the control, in an accurate way, of all the exception conditions that can occur during the Bonnie.NET utilization, giving to her/him the possibility to monitor all the security checks performed when those checks detect some failure.

пятница, 28 августа 2009 г.

Как мне для каждого абзаца добавить отступ первой строки?

Когда требуется установить абзацный отступ, лучше всего использовать стилевой атрибут text-indent. Его значение определяет насколько сдвинуть текст первой строки вправо от исходного положения. Исходная ширина текстового блока при этом не меняется и остается исходно заданной. Величину отступа можно указывать в пикселах, процентах или других доступных единицах.

Пример

Валидный HTML
Валидный CSS

p {
text-indent: 20px; /* Отступ первой строки в пикселах */
}


среда, 26 августа 2009 г.

HTTP 500 - Handler svc-Integrated has a bad module “ManagedPipelineHandler” in its module list

Running Vista and IIS7.0 can be painful at times. I deployed a simple WCF service then started receive HTTP 500 errors when browsing on the svc. Basically even though WCF is installed on Vista byu default the WCF HTTP Activations are disabled.

1. Open Control Panel
2. Go to Programs and Features
3. Pick Turn Windows Features on or off / Install
4. Under Microsoft .NET Framework 3.0 enable Windows Communication Foundation HTTP Activation and Windows Communication Foundation Non-HTTP Activation

ASP.NET Custom Error Pages

ASP.NET provides a simple yet powerful way to deal with errors that occur in your web applications. We will look at several ways to trap errors and display friendly meaningful messages to users. We will then take the discussion a step further and learn how to be instantly notified about problems so you can cope with them right away. As a geek touch we will also track the path 404's travel.

In the days of "classic" ASP you used to get cryptic—an often times downright misleading—error messages. "White pages" leave your visitors in the dark who don't know what happened and what to do next besides closing the browser or navigating away.

It's obvious that "white pages" are ugly but there's another angle to this—ASP.NET displays an exception stack trace which may reveal what your code does and how it works. These days, when the word "security" is in vogue, this could be a dangerous side effect.

Custom error pages are not luxury any more, they are a must-have. You have several ways to implement them.

Trapping Errors On Page Level

Every time you create a web form in Visual Studio .NET you see that your page class derives from System.Web.UI.Page. The Page object helps you trap page-level errors. For this to happen you need to override its OnError method as follows:

protected override void OnError(EventArgs e) {   // At this point we have information about the error   HttpContext ctx = HttpContext.Current;    Exception exception = ctx.Server.GetLastError ();    string errorInfo =       "
Offending URL: " + ctx.Request.Url.ToString () + "
Source: " + exception.Source + "
Message: " + exception.Message + "
Stack trace: " + exception.StackTrace; ctx.Response.Write (errorInfo); // -------------------------------------------------- // To let the page finish running we clear the error // -------------------------------------------------- ctx.Server.ClearError (); base.OnError (e); }

This works for one page only, you may say. To have every page benefit from this kind of error handing we need to take advantage of the Page Controller pattern. You define a base class and have every page inherit from it. Downloadsample code for this article and see the CustomError1 project for an example.

Later on in this article you will learn why may need to collect exception information in this manner. Stay tuned.

Trapping Errors On Application Level

The idea of capturing errors on the application level is somewhat similar. At this point we need to rehash our understanding of the Global.asax file.

From the moment you request a page in your browser to the moment you see a response on your screen a complex process takes place on the server. Your request travels through the ASP.NET pipeline.

In the eyes of IIS each virtual directory is an application. When a request within a certain virtual directory is placed, the pipeline creates an instance ofHttpApplication to process the request. The runtime maintains a pool ofHttpApplication objects. The same instance of HttpApplication will service a request it is responsible for. This instance can be pooled and reused only after it is done processing a request.

Global.asax is optional which means if you are not interested in any session or application events you can live without it. Otherwise the ASP.NET runtime parses your global.asax, compiles a class derived from HttpApplication and hands it a request for your web application.

HttpApplication fires a number of events. One of them is Error. To implement your own handler for application-level errors your global.asax file needs to have code similar to this:

protected void Application_Error(object sender, EventArgs e) { } 

When any exception is thrown now—be it a general exception or a 404—it will end up in Application_Error. The following implementation of this handler is similar to the one above:

protected void Application_Error(Object sender, EventArgs e) {   // At this point we have information about the error   HttpContext ctx = HttpContext.Current;    Exception exception = ctx.Server.GetLastError ();    string errorInfo =       "
Offending URL: " + ctx.Request.Url.ToString () + "
Source: " + exception.Source + "
Message: " + exception.Message + "
Stack trace: " + exception.StackTrace; ctx.Response.Write (errorInfo); // -------------------------------------------------- // To let the page finish running we clear the error // -------------------------------------------------- ctx.Server.ClearError (); }

Be careful when modifying global.asax. The ASP.NET framework detects that you changed it, flushes all session state and closed all browser sessions and—in essence—reboots your application. When a new page request arrives, the framework will parse global.asax and compile a new object derived from HttpApplication again.

Setting Custom Error Pages In web.config

If an exception has not been handed by the Page object, or theHttpApplication object and has not been cleared throughServer.ClearError() it will be dealt with according to the settings ofweb.config.

When you first create an ASP.NET web project in Visual Studio .NET you get aweb.config for free with a small section:

 

With this setting your visitors will see a canned error page much like the one from ASP days. To save your face you can have ASP.NET display a nice page with an apology and a suggested plan of action.

The mode attribute can be one of the following:

  • On – error details are not shown to anybody, even local users. If you specified a custom error page it will be always used.
  • Off – everyone will see error details, both local and remote users. If you specified a custom error page it will NOT be used.
  • RemoteOnly – local users will see detailed error pages with a stack trace and compilation details, while remote users with be presented with a concise page notifying them that an error occurred. If a custom error page is available, it will be shown to the remote users only.

Displaying a concise yet not-so-pretty error page to visitors is still not good enough, so you need to put together a custom error page and specify it this way:

 

Should anything happen now, you will see a detailed stack trace and remote users will be automatically redirected to the custom error page,GeneralError.aspx. How you apologize to users for the inconvenience is up to you. Ian Lloyd gives a couple of suggestions as to the look and feel of a custom 404 page.

The tag may also contain several (see MSDN) subtags for more granular error handling. Each tag allows you to set a custom condition based upon an HTTP status code. For example, you may display a custom 404 for missing pages and a general error page for all other exceptions:

        

The URL to a custom error page may be relative (~/error/PageNotFound.aspx) or absolute (http://www.yoursite.com/errors/PageNotFound.aspx). The tilde (~) in front of URLs means that these URLs point to the root of your web application. Please download sample code for this article and see the CustomErrors3 project.

That's really all there's to it. Before we move on to the next (and last approach) a few words about clearing errors.

Clearing Errors

You probably noticed I chose to call Server.ClearError() in both OnErrorand Application_Error above. I call it to let the page run its course. What happens if you comment it out? The exception will leave Application_Errorand continue to crawl up the stack until it's handled and put to rest. If you set custom error pages in web.config the runtime will act accordingly—you get to collect exception information AND see a friendly error page. We'll talk about utilizing this information a little later.

Handling Errors In An HttpModule

Much is written about HTTP modules. They are an integral part of the ASP.NET pipeline model. Suffice it to say that they act as content filters. An HTTP module class implements the IHttpModule interface (see MSDN). With the help ofHttpModules you can pre- and post-process a request and modify its content.IHttpModule is a simple interface:

public interface IHttpModule {    void Dispose();    void Init(HttpApplication context); } 

As you see the context parameter is of type HttpApplication. It will come in very handy when we write out own HttpModule. Implementation of a simpleHttpModule may look as follows:

using System; using System.Web;  namespace AspNetResources.CustomErrors4 {   public class MyErrorModule : IHttpModule   {     public void Init (HttpApplication app)     {       app.Error += new System.EventHandler (OnError);     }      public void OnError (object obj, EventArgs args)     {       // At this point we have information about the error       HttpContext ctx = HttpContext.Current;        Exception exception = ctx.Server.GetLastError ();        string errorInfo =              "
Offending URL: " + ctx.Request.Url.ToString () + "
Source: " + exception.Source + "
Message: " + exception.Message + "
Stack trace: " + exception.StackTrace; ctx.Response.Write (errorInfo); // -------------------------------------------------- // To let the page finish running we clear the error // -------------------------------------------------- ctx.Server.ClearError (); } public void Dispose () {} } }

The Init method is where you wire events exposed by HttpApplication. Wait, is it the same HttpApplication we talked about before? Yes, it is. You've already learned how to add handlers for various evens toGlobal.asax. What you do here is essentially the same. Personally, I think writing an HttpModule to trap errors is a much more elegant solution than customizing your Global.asax file. Again, if you comment out the line withServer.ClearError() the exception will travel back up the stack and—provided your web.config is properly configured—a custom error page will be served.

To plug our HttpModule into the pipeline we need to add a few lines toweb.config:

                 

As MSDN states the type attribute specifies a comma-separated class/assembly combination. In our case MyErrorModule is the name of a class from the AspNetResources.CustomErrors4 assembly. Tim Ewald and Keith Brown wrote an excellent article for MSDN Magazine on this subject.

You will find a full-fledged sample in the CustomErrors4 project in code download.

To gain deeper understanding of HttpModules and their place in the HTTP pipeline I encourage you to search the web since the nuts and bolts are not relevant to the topic of error handling.

What about HTML pages?

What happens if you request a non-existent HTML page? This question comes up in news groups very often.

By default you will get a canned "white page". When you install the .NET Framework is maps certain file extensions to the ASP.NET ISAPI,aspnet_isapi.dll. Neither HTML nor HTM files are mapped to it (because they are not ASP.NET pages). However, you can configure IIS to treat them as ASP.NET pages and serve our custom error pages.

  1. Run the IIS Manager
  2. Select a web application
  3. Right click and go to Properties
  4. On the Virtual Directory tab click Configuration
  5. In the Extension column find .aspx, double click and copy the full path toaspnet_isapi.dll. It should be something likeC:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\«
    aspnet_isapi.dll
  6. Click Add and paste the path in the Executable box
  7. In the Extension box type .html
  8. Make sure Check that file exists is NOT checked
  9. Dismiss all dialogs

If you type a wrong URL to an HTML page now you should get our user-friendly error page.

Analyze That

The reason we went this far with error trapping is the insight we gain about the problem. As strange as it may sound, exceptions are your friends because once you trap one you can alert responsible people right away. There are several ways to go about it:

  • Write it to the system event log. You could have WMI monitor events in the log and act on them, too. See MSDN for more information
  • Write it to a file
  • Email alerts

Please refer to an excellent whitepaper, Exception Management Architecture Guide from Microsoft for a comprehensive discussion of different aspects of error handling.

I've implemented the last option—email alerts—in a production environment and it worked great. Once someone pulls up a (custom) error page we get an email and jump right on it. Given the fact that users grow impatient with faulty sites and web applications, it's critical to be notified of errors right away.

The Path of 404

As I was researching the topic of custom error pages I couldn't help wondering where 404s originate from and how we end up seeing custom 404 pages. To follow this exercise you will need MSDN and Lutz Roeder's Reflector.

HttpApplication Lifetime

When IIS receives a resource request it first figures out if it will process it directly or match against an ISAPI. If it is one of the ASP.NET resources, IIS hands the request to the ASP.NET ISAPI, aspnet_isapi.dll.

For example, when a request for an .aspx page comes the runtime creates a whole pipeline of objects. At about this time an object of type HttpApplication (which we already talked about) is instantiated. This object represents your web application. By tapping into the various events ofHttpApplication you can follow request execution every step of the way (the image on the left shows the sequence of these events).

Next, HttpApplication calls itsMapHttpHandler method which returns an instance of IHttpHandler (an object that implements IHttpHandler, to be more precise). The IHttpHandler interface is a very simple one:

public interface IHttpHandler {    void ProcessRequest(HttpContext context);    bool IsReusable { get; } } 

The IsReusable property specifies if the same instance of the handler can be pooled and reused repeatedly. Each request gets its own instance ofHttpHandler which is dedicated to it throughout the lifetime of the request itself. Once the request is processed its HttpHandler is returned to a pool and later reused for another request.

The ProcessRequest method is where magic happens. Ultimately, this method processes a request and generates a response stream which travels back up the pipeline, leaves the web server and is delivered to the client. How does HttpApplication know which HttpHandler to instantiate? It's all pre-configured in machine.config:

...        ...  

This is only an excerpt. You have more HttHandlers configured for you. See how .aspx files are mapped to the System.Web.UI.PageHandlerFactoryclass?

To instantiate the right handler HttpApplication calls its MapHttpHandlermethod:

internal IHttpHandler MapHttpHandler (           HttpContext context, string requestType,            string path, string pathTranslated,            bool useAppConfig); 

If you follow the assembly code of this method you will also see a call to thePageHandlerFactory.GetHandler method which returns an instance ofHttpHandler:

L_0022: call HttpApplication.GetHandlerMapping L_0027: stloc.2  L_0028: ldloc.2  L_0029: brtrue.s L_004a L_002b: ldc.i4.s 42 L_002d: call PerfCounters.IncrementCounter L_0032: ldc.i4.s 41 L_0034: call PerfCounters.IncrementCounter L_0039: ldstr "Http_handler_not_found_for_request_type" L_003e: ldarg.2  L_003f: call HttpRuntime.FormatResourceString L_0044: newobj HttpException..ctor L_0049: throw  L_004a: ldarg.0  L_004b: ldloc.2  L_004c: call HttpApplication.GetFactory L_0051: stloc.3  L_0052: ldloc.3  L_0053: ldarg.1  L_0054: ldarg.2  L_0055: ldarg.3  L_0056: ldarg.s pathTranslated L_0058: callvirt IHttpHandlerFactory.GetHandler 

Every ASP.NET page you write, whether you insert a base class in-between or not, ultimately derives from the System.Web.UI.Page class. It's interesting to note that the Page class inherits the IHttpHandler interface and is anHttpHandler itself! What that means is the runtime will at some point callPage.ProcessRequest!

Page.ProcessRequest request delegates all work to its internal method,ProcessRequestMain:

if (this.IsTransacted)  { this.ProcessRequestTransacted(); } else  { this.ProcessRequestMain(); } 

Finally, ProcessRequestMain is where all the fun stuff happens. Among all the many things it does, it defines an exception handler as follows:

Try {   // code skipped    catch (Exception exception4)   {     exception1 = exception4;     PerfCounters.IncrementCounter(34);     PerfCounters.IncrementCounter(36);     if (!this.HandleError(exception1)) { throw; }   } } 

If you follow HandleError further you'll notice that it will try to look up the name of your custom error page and redirect you to it:

if ((this._errorPage != null) &&       CustomErrors.GetSettings(base.Context).«                                  CustomErrorsEnabled(this._request)) {   this._response.RedirectToErrorPage(this._errorPage);   return true;  }  internal bool RedirectToErrorPage(string url) {    bool flag1;   try   {    if (url == null)    {     flag1 = false;     goto L_0062;    } 	     if (this._headersWritten)    {     flag1 = false;     goto L_0062;    }     if (this.Request.QueryString["aspxerrorpath"] != null)    {     flag1 = false;     goto L_0062;    }     if (url.IndexOf(63) < url =" string.Concat(url," aspxerrorpath="" flag1 =" false;">

This method does you a favor by appending the offending URL. You can see ?aspxerrorpath= in the URL each time a custom 404 page is displayed.

If everything goes smooth—no exceptions thrown, no redirects to custom error pages—ProcessRequest finishes its job, and the response travels back through the pipeline.

Conclusion

This article gave a detailed overview of ASP.NET custom error pages and several different approaches of setting them up. It's important that users see meaningful, friendly error pages. By the same token it's important that people on the other end are alerted about problems right away. ASP.NET provides a powerful and flexible framework to achieve both goals.