Sunday, April 22, 2012

Logging Unhandled Exceptions in an ASP.NET Website


When developing an ASP.NET application, one of the routine tasks is to plan for unhandled exceptions. If theexception is left unhandled, by default, the ASP.NET runtime will either display a nasty error message to your user or it will show the exception details page, depending on how you configure your website. Allowing the user to see a nasty error messages is far from recommended (see this article for exception handling best practices). If you simply show a generic exception details page, you will lose valuable information you may need to debug the issue.
For a few years, I have been using this strategy for processing unhandled exceptions in my websites. When an unhandled exception occurs, the strategy is to:
  • Catch the error in the application-level Error event handler (Global.asax).
  • "Process" the error by sending an email containing the exception details.
  • Redirect to a custom webpage displaying a user-friendly message to the end user.
Now, I am not suggesting that you should not use try catch blocks in your code to catch errors where you can recover. This is a strategy for catching those unexpected errors in one central location, logging the exceptioninformation, sending an email to a developer, and displaying a friendly message to your user.
The problem with the approach I have been using is that it leaves the logging and sending of an email up to you to manually implement. In fact, I was only sending out an email with the error message and relying on the server's application logs to record the errors. Tracking down the problem, who the user was, etc., became a real pain using only an email message and application logs. I wished for an easier way to receive a notice of an error and a way to store the information so that I could run a report to view the errors or even see some statistics.
So, I started doing some research on the Microsoft Enterprise Application blocks, and thought that they were exactly what I was looking for. This article in particular was very helpful, and you may want to follow this approach for your N-Tiered applications.
What I love about using the application blocks is that you simply enter a few settings in the web.config file, add references to the application blocks, and with a few lines of code, I was sending emails and logging exceptions to SQL Server. Next, one quick Reporting Services report later, and I could easily view the information in the Logging database!

Using the code

What you will need to use this code:
  • MS Enterprise Library 5.0
  • Must be using at least .NET Framework 3.5
  • SQL Server - with the Enterprise Library Logging database installed
From the MS Enterprise Library documentation:
"In the folder where you installed the Enterprise Library, open the subfolderSource\Blocks\Logging\Src\DatabaseTraceListener\Scripts in Windows Explorer. This folder contains a SQL script named LoggingDatabase.sql and a command file named CreateLoggingDb.cmd that executes this script. The script creates a new database named Logging, and adds to it all of the tables and Stored Procedures required by the database trace listener to write log entries to a database."
Add references to the following application blocks (should be located at C:\Program Files (x86)\Microsoft EnterpriseLibrary 5.0\Bin\):
  • Microsoft.Practices.EnterpriseLibrary.Common.dll
  • Microsoft.Practices.EnterpriseLibrary.Data.dll
  • Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
  • Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll
  • Microsoft.Practices.EnterpriseLibrary.Logging.dll
  • Microsoft.Practices.EnterpriseLibrary.Logging.Database.dll
After you install the MS Enterprise Library, when you right click on the web.config file in Visual Studio, you will see a menu option for Edit Enterprise Library V5 Configuration. You can use this tool to enter the necessary configuration info, or you can do it by manually updating the web.config file. I strongly recommend the former especially if you are new to using the application blocks. For the purposes of this article, under the Blocks menu, choose Add Logging Settings and Add Exception Handling Settings. By default, you should already have Application Settings and Database Settings. After you have those added, you can close and save the tool, which will update yourweb.config file.
For simplicity, update the web.config with this code snippet. You can then go back to the tool to more easily see the settings. Make sure to update the email addresses and STMP server in the Logging Settings and the connection string for the Logging database under Database Settings.
The snippet below has been wrapped to avoid page scrolling.
<configsections />
  <section name="loggingConfiguration" requirepermission="true" 
     type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, 
           Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, 
           Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  <section name="exceptionHandling" requirepermission="true" 
     type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
           Configuration.ExceptionHandlingSettings, 
           Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, 
           Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
...
</configsections />
<loggingconfiguration tracingenabled="true" 
             defaultcategory="General" />
  <listeners />
    <add name="Email Trace Listener" 
        type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.
              EmailTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, 
              Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        listenerdatatype="Microsoft.Practices.EnterpriseLibrary.Logging.
                          Configuration.EmailTraceListenerData, 
                          Microsoft.Practices.EnterpriseLibrary.Logging, 
                          Version=5.0.414.0, Culture=neutral, 
                          PublicKeyToken=31bf3856ad364e35" 
        toaddress="someone@yourcompany.com" 
        fromaddress="noreply@yourcompany.com" 
        subjectlineender="- Log Example Website" 
        smtpserver="YourSMTPServer" 
        formatter="Email Text Formatter" 
        traceoutputoptions="DateTime, Timestamp, ProcessId, Callstack" />
    <add name="Database Trace Listener" 
      type="Microsoft.Practices.EnterpriseLibrary.Logging.Database.
            FormattedDatabaseTraceListener, 
            Microsoft.Practices.EnterpriseLibrary.Logging.Database, 
            Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
      listenerdatatype="Microsoft.Practices.EnterpriseLibrary.Logging.
                        Database.Configuration.FormattedDatabaseTraceListenerData, 
                        Microsoft.Practices.EnterpriseLibrary.Logging.Database, 
                        Version=5.0.414.0, Culture=neutral, 
                        PublicKeyToken=31bf3856ad364e35" 
      formatter="Text Formatter" 
      traceoutputoptions="LogicalOperationStack, DateTime, 
                          Timestamp, ProcessId, ThreadId, Callstack" 
      databaseinstancename="LoggingConnectionString" 
      writelogstoredprocname="WriteLog" 
      addcategorystoredprocname="AddCategory" />
  </listeners />
  <formatters />
    <add name="Text Formatter" 
      type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, 
            Microsoft.Practices.EnterpriseLibrary.Logging, 
            Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
       template="Application Name: Log Example Website
       Message: {message}{newline}
       Category: {category}{newline}
       Extended Properties: {dictionary({key} - {value}{newline})}" />
    <add name="Email Text Formatter" 
      type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, 
            Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, 
            Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
      template="There was an error. Please see the Logging database 
                for more information. Timestamp: {timestamp(local)}
                {newline} Message: {message}{newline}" />
  </formatters />
  <categorysources />
    <add name="ErrorLogging" switchvalue="All" />
      <listeners />
        <add name="Email Trace Listener" />
        <add name="Database Trace Listener" />
      </listeners />
    </add />
  </categorysources />
  <specialsources />
    <allevents name="All Events" switchvalue="All" />
    <notprocessed name="Unprocessed Category" switchvalue="All" />
    <errors name="Logging Errors & Warnings" switchvalue="All" />
    <listeners />
      <add name="Email Trace Listener" />
      <add name="Database Trace Listener" />
    </listeners />
    </errors />
  </specialsources />
 </loggingconfiguration />
 <exceptionhandling />
  <exceptionpolicies />
   <add name="AllExceptionsPolicy" />
    <exceptiontypes />
     <add name="All Exceptions" 
        type="System.Exception, mscorlib, Version=2.0.0.0, 
              Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        posthandlingaction="None" />
      <exceptionhandlers />
       <add title="Enterprise Library Exception Handling" 
         name="AllExceptionsLoggingHandler" 
         type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
               Logging.LoggingExceptionHandler, Microsoft.Practices.
               EnterpriseLibrary.ExceptionHandling.Logging, 
               Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
         logcategory="ErrorLogging" 
         eventid="100" severity="Error" 
         formattertype="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
                        TextExceptionFormatter, 
                        Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" 
         priority="0" />
      </exceptionhandlers />
     </add />
    </exceptiontypes />
   </add />
  </exceptionpolicies />
 </exceptionhandling />
 <connectionstrings />
  <add name="LoggingConnectionString" 
     connectionstring="Data Source=YourSQLServer;
                       Initial Catalog=Logging;
                       Integrated Security=SSPI" 
     providername="System.Data.SqlClient" />
 </connectionstrings />
Place this code in the Global.asax file:
protected void Application_Error(object sender, EventArgs e)
{
    //uncomment in order to bypass logging when running locally.
    //if (!Request.IsLocal)
    //{
        Exception ex = Server.GetLastError();
        if (ex is HttpUnhandledException && ex.InnerException != null)
        {
            ex = ex.InnerException;
        }

        if (ex != null)
        {
            Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
               ExceptionPolicy.HandleException(ex, "AllExceptionsPolicy");
            Server.ClearError();

            Response.Redirect("~/Utility/ErrorPage.htm");
        }
    //}
}
As you can see from the code above, after the error is handled by the application block, the user will be redirected to the ~/Utility/ErrorPage.htm page. So, you will need to create a page for your site, or you can use the one in thesample code for download in this article.
So, that is all there is to using the MS Enterprise Application Blocks to process unhandled exceptions in an ASP.NET website. Download the sample code, which contains a project to demo how to process unhandled exceptions. If you are familiar with SQL Server Reporting Services, you can create your own custom reports to view the data logged to SQL Server.

Saturday, April 21, 2012

Walkthrough: Creating a Windows Service Application in the Component Designer


Walkthrough: Creating a Windows Service Application in the Component Designer 

.NET Framework 2.0
99 out of 135 rated this helpful - Rate this topic

NoteNote
The Windows Service template and associated functionality is not available in the Standard Edition of Visual Studio. For more information, see Visual Studio Editions.
The procedures in this topic walk you through the process of creating a simple Windows Service application that writes messages to an event log. The basic steps that you perform to create and use your service include:
  • Create a project using the Windows Service application template. This template creates a class for you that inherits from ServiceBase and writes much of the basic service code, such as the code to start the service.
  • Write the code for the OnStart and OnStop procedures, and override any other methods that you want to redefine.
  • Add the necessary installers for your service application. By default, a class containing two or more installers is added to your application when you click the Add Installerlink: one to install the process, and one for each of the associated services your project contains.
  • Build your project.
  • Create a setup project to install your service, and then install it.
  • Access the Windows 2000 Services Control Manager and start your service.
To begin, you create the project and set values that are necessary for the service to function correctly.

To create and configure your service

  1. On the File menu, click New Project.
    The New Project dialog box opens.
  2. Select the Windows Service project from the list of Visual Basic, Visual C#, Visual C++, or Visual J# project templates, and name it MyNewService. Click OK.
    NoteNote
    The project template automatically adds a component class called Service1 that inherits from System.ServiceProcess.ServiceBase.
  3. Click the designer to select Service1. Then, in the Properties window, set the ServiceName and the (Name) property for Service1 to MyNewService.
  4. Set the AutoLog property to true.
  5. On the View menu, click Code to open the Code Editor. Edit the Main method to create an instance of MyNewService. When you renamed the service in step 3, the class name was not modified in the Main method. In Visual C# and Visual J# applications, the Main method is located in the Program.cs and Program.js files, respectively.
    static void Main()
    {
        System.ServiceProcess.ServiceBase[] ServicesToRun;
        // Change the following line to match.
        ServicesToRun = new System.ServiceProcess.ServiceBase[] 
          { new MyNewService() };
        System.ServiceProcess.ServiceBase.Run(ServicesToRun);
    }
    
    
    public static void main(String[] args)
    {
        System.ServiceProcess.ServiceBase[] ServicesToRun;
        ServicesToRun = new System.ServiceProcess.ServiceBase[] 
          { new MyNewService() };
        System.ServiceProcess.ServiceBase.Run(ServicesToRun);
    }
    
    
In the next section, you add a custom event log to your Windows service. Event logs are not associated in any way with Windows services. Here the EventLog component is used as an example of the type of component you could add to a Windows service. For more information on custom event logs, see How to: Create and Remove Custom Event Logs.

To add custom event log functionality to your service

  1. In Solution Explorer, right-click Service1.vbService1.cs, or Service1.jsl and select View Designer.
  2. From the Components tab of the Toolbox, drag an EventLog component to the designer.
  3. In Solution Explorer, right-click Service1.vbService1.cs, or Service1.jsl and select View Code.
  4. Edit the constructor to define a custom event log.
    public MyNewService()
    {
        InitializeComponent();
        if (!System.Diagnostics.EventLog.SourceExists("MySource")) 
        {         
                System.Diagnostics.EventLog.CreateEventSource(
                    "MySource","MyNewLog");
        }
        eventLog1.Source = "MySource";
        eventLog1.Log = "MyNewLog";
    }
    
    
    public MyNewService()
    {
        InitializeComponent();
        if (!System.Diagnostics.EventLog.SourceExists("MySource"))
        {
            System.Diagnostics.EventLog.CreateEventSource(
                    "MySource", "MyNewLog");
        }
        eventLog1.set_Source("MySource");
        eventLog1.set_Log("MyNewLog");
    }
    
    

To define what happens when the service starts

  • In the Code Editor, locate the OnStart method that was automatically overridden when you created the project, and write code to determine what occurs when the service begins running:
    protected override void OnStart(string[] args)
    {
        eventLog1.WriteEntry("In OnStart");
    }
    
    
    protected void OnStart(String[] args)
    {
        eventLog1.WriteEntry("In OnStart");
    }
    
    
    NoteNote
    A service application is designed to be long running. As such, it usually polls or monitors something in the system. The monitoring is set up in the OnStart method. However, OnStart does not actually do the monitoring. The OnStart method must return to the operating system once the service's operation has begun. It must not loop forever or block. To set up a simple polling mechanism, you can use the System.Timers.Timer component. In the OnStart method, you would set parameters on the component, and then you would set the Enabled property to true. The timer would then raise events in your code periodically, at which time your service could do its monitoring.

To define what happens when the service is stopped

  • In the Code Editor, select the OnStop procedure from the Method Name drop-down list, which was automatically overridden when you created the project. Write code to determine what occurs when the service is stopped:
    protected override void OnStop()
    {
        eventLog1.WriteEntry("In onStop.");
    }
    
    
    protected void OnStop()
    {
        eventLog1.WriteEntry("In onStop.");
    }
    
    
You can also override the OnPauseOnContinue, and OnShutdown methods to define further processing for your component.

To define other actions for the service

  • For the method you want to handle, override the appropriate method and define what you want to occur.
    The following code shows what it looks like if you override the OnContinue method:
    protected override void OnContinue()
    {
        eventLog1.WriteEntry("In OnContinue.");
    }  
    
    
    protected void OnContinue()
    {
        eventLog1.WriteEntry("In OnContinue.");
    }
    
    
Some custom actions need to occur when installing a Windows service, which can be done by the Installer class. Visual Studio can create these installers specifically for a Windows service and add them to your project.

To create the installers for your service

  1. In Solution Explorer, right-click Service1.vbService1.cs, or Service1.jsl and select View Designer.
  2. Click the background of the designer to select the service itself, rather than any of its contents.
  3. With the designer in focus, right-click, and then click Add Installer.
    By default, a component class containing two installers is added to your project. The component is named ProjectInstaller, and the installers it contains are the installer for your service and the installer for the service's associated process.
  4. In Design view for ProjectInstaller, click ServiceInstaller1 or serviceInstaller1.
  5. In the Properties window, set the ServiceName property to MyNewService.
  6. Set the StartType property to Automatic.
  7. In the designer, click ServiceProcessInstaller1 (for a Visual Basic project), or serviceProcessInstaller1 (for a Visual C# or Visual J# project). Set the Account property toLocalService. This will cause the service to be installed and to run on a local service account.
    Security noteSecurity Note
    The LocalService account acts as a non-privileged user on the local computer, and presents anonymous credentials to any remote server. Use the other accounts with caution, as they run with higher privileges and increase your risk of attacks from malicious code.

To build your service project

  1. In Solution Explorer, right-click to select your project and select Properties from the shortcut menu. The project's Property Designer appears.
  2. on the Application page, from the Startup object list, choose MyNewService.
  3. Press CTRL+SHIFT+B to build the project.
Now that the project is built, it can be deployed. A setup project will install the compiled project files and run the installers needed to run the Windows service. To create a complete setup project you will need to add the project output, MyNewService.exe, to the setup project and then add a custom action to have MyNewService.exe installed. For more information on setup projects, see Setup Projects. For more information on custom actions, see Walkthrough: Creating a Custom Action.

To create a setup project for your service

  1. In Solution Explorer, right-click to select your solution, point to Add, and then click New Project.
  2. In the Project Types pane, select the Setup and Deployment Projects folder.
  3. In the Templates pane, select Setup Project. Name the project MyServiceSetup. Click OK.
    A setup project is added to the solution.
Next you will add the output from the Windows service project, MyNewService.exe, to the setup.

To add MyNewService.exe to the setup project

  1. In Solution Explorer, right-click MyServiceSetup, point to Add, then choose Project Output.
    The Add Project Output Group dialog box appears.
  2. MyNewService is selected in the Project box.
  3. From the list box, select Primary Output, and click OK.
    A project item for the primary output of MyNewService is added to the setup project.
Now add a custom action to install the MyNewService.exe file.

To add a custom action to the setup project

  1. In Solution Explorer, right-click the setup project, point to View, and then click Custom Actions.
    The Custom Actions editor appears.
  2. In the Custom Actions editor, right-click the Custom Actions node and choose Add Custom Action.
    The Select Item in Project dialog box appears.
  3. Double-click the Application Folder in the list box to open it, select Primary Output from MyNewService (Active), and click OK.
    The primary output is added to all four nodes of the custom actions — InstallCommitRollback, and Uninstall.
  4. In Solution Explorer, right-click the MyServiceSetup project and click Build.

To install the Windows Service

  1. To install MyNewService.exe, right-click the setup project in Solution Explorer and select Install.
  2. Follow the steps in the Setup Wizard. Build and save your solution.

To start and stop your service

  1. Open the Services Control Manager by doing one of the following:
    • In Windows XP and 2000 Professional, right-click My Computer on the desktop, then click Manage. In the Computer Management console, expand the Services and Applications node.
      - or -
    • In Windows Server 2003 and Windows 2000 Server, click Start, point to Programs, click Administrative Tools, and then click Services.
      NoteNote
      In Windows NT version 4.0, you can open this dialog box from Control Panel.
    You should now see MyNewService listed in the Services section of the window.
  2. Select your service in the list, right-click it, and then click Start.
  3. Right-click the service, and then click Stop.

To verify the event log output of your service

  1. Open Server Explorer and access the Event Logs node. For more information, see How to: Work with Event Logs in Server Explorer.
    NoteNote
    The Windows Service template and associated functionality is not available in the Standard Edition of Visual Studio. For more information, see Visual Studio Editions.
  2. Locate the listing for MyNewLog and expand it. You should see entries for the actions your service has performed.

To uninstall your service

  1. On the Start menu, open Control Panel and click Add/Remove Programs, and then locate your service and click Uninstall.
  2. You can also uninstall the program by right-clicking the program icon for the .msi file and selecting Uninstall.
    NoteNote
    If you installed the service on Windows 2000, you will need to reboot the system before you can reinstall the service. In Windows 2000, services are not completely deleted until the system is rebooted.
You might explore the use of a ServiceController component to allow you to send commands to the service you have installed. For more information on using theServiceController component, see Monitoring Windows Services.
You can use an installer to create an event log when the application is installed, rather than creating the event log when the application runs. Additionally, the event log will be deleted by the installer when the application is uninstalled. For more information, see Walkthrough: Installing an Event Log Component.