Adding ELMAH to your ASP.NET Web Site

January 29, 2010 09:59

There's already a lot of posts out there on how to set up ELMAH (see References below). Still, as I went through the process of adding ELMAH to my own site, I found myself pulling information from multiple posts and even ran into a couple of surprises during deployment. So, in an effort to consolidate the information I needed and post solutions to the issues I had, I give you this post on the unhandled exception and error handling utility that is ELMAH.

What is ELMAH?

From the Google Code ELMAH home page:

ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.

In short, ELMAH is a utility that reports unhandled errors and exceptions. Looked at another way, ELMAH will display problems with your site that you might not otherwise see.

Adding ELMAH to an ASP.NET Web Site

1.) Get ELMAH

The first step is to actually get ELMAH. You can download from the Google Code page. Once you've got it, copy the file "Elmah.dll" into your site's bin folder.

2.) Modifying your web.config

There are a couple of initial modifications you'll have to make to your web.config:

a.) Add the following to the <httpModules> section:

<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>

b.) Add the following to the <httpHandlers> section:

<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />

3.) Deployment and dealing with initial issues

For the most basic ELMAH setup, that's it. You should be able to access your site's ELMAH logs by accessing http://yoursite/elmah.axd.

However, once you've deployed you might run into this error:

You are attempting to access ELMAH from a remote machine whereas it is currently configured not to allow remote access.

image

Fortunately, getting around this is pretty easy.

a.) Like it says, add a new <sectionGroup>:

<sectionGroup name="elmah">
    <section name="security" type="Elmah.SecuritySectionHandler, Elmah" />
</sectionGroup>

b.) Also, add the <elmah> section. I added it right underneath my <configSection> closing tag.

<elmah>
    <security allowRemoteAccess="yes" />
</elmah>

Again, redeploy the web.config file and we're done.

Except that, now, everyone in the world can view your ELMAH logs. Not good.

Securing ELMAH against unwanted eyes

Allowing everyone to view your ELMAH logs is generally not a good thing because an astute hacker could easily glean information from those logs and potentially exploit your site. So, we'll add some basic security.

1.) Include the admin path

Include the admin path in the httpHandler "add" so it looks like:

<add verb="POST,GET,HEAD" path="/admin/elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />

2.) Add a <location> section

This stands as it's own sub-section beneath the main <configuration> tag:

<location path="admin">
    <system.web>
        <authorization>
            <deny users="?"/>
        </authorization>
    </system.web>
</location>

Now, we're done. When you access http://yoursite/admin/elmah.axd, you should be prompted for your credentials (assuming you're using ASP.NET's built-in security; see Phil Haack's post for more info).

If you're using BlogEngine.NET, add ELMAH to your admin menu

This is just an ease-of-access thing. If you add ELMAH to your web.sitemap, BlogEngine.NET will automatically pick it up and display in your admin menu.

Just add this to your sitemap file:

<siteMapNode url="~/admin/elmah.axd" title="ELMAH" description="" roles="administrators"/>

Conclusion

This concludes our discussion on how to add ELMAH to an ASP.NET web site.

References

 

[ Get 2GB of free online storage from Dropbox ]


Installing a WCF Service on a Shared Hosting Site, Revisited

July 13, 2009 19:30

I wrote a post not too long ago discussing How to Install a WCF Service on a GoDaddy hosted site. In it, I presented a couple of different ways to get around the "This collection already contains an address with scheme http" error, which looks like:

image

While those methods do work, I found myself in perhaps a special situation where only a combination of those solutions resolved my particular problem. Let me explain by first detailing my hosting situation.

My Hosting Schema

I have two sites: scottmarlowe.com and itscodingtime.com. Both are hosted with GoDaddy.com under their Deluxe Hosting Plan, which basically means I have multiple domain names that share server space. One is the "root"; any other domains exist in a folder beneath the root. That last point is important as we get into this.

In my case, my "root" domain is scottmarlowe.com. The sub-domain of interest here is this site, itscodingtime.com.

The Situation

I've deployed a new Silverlight TagCloud control (upgraded to Silverlight 3, of course) to both sites. Each site has its own ClientBin where the control is housed. Now, each Silverlight control accesses their own individual copies of the same WCF service. In other words, I have a .svc and accompanying DLL hosted on each site. Therein begins the problem.

The Problem

Now, I'd already uploaded all the new files to the sub-site the other day and everything was working fine on both root and sub-sites. The problem came about this morning when I did the same update (Silverlight and WCF components) to the root site, then uploaded the web.config (which, in retrospect, I wonder if really needed updating). It was the web.config update that did it. I started seeing the "This collection already contains an address with scheme http" error on the sub-site (itscodingtime.com).

The Solution

I had chosen to modify both web.config's as my previous post suggested. Clearly this was no longer working, so I took a hybrid approach. I left the root's web.config alone, but modified the sub-site's (which, at this time, was the not working).

First, I removed the entire "serviceHostingEnvironment" section from the sub-site's web.config.

Second, for the sub-site only, I went back to the CustomHostFactory approach by creating a new class file called "CustomHostFactory.cs". This file goes in the site's App_Code folder. It's full contents are:

  1:  
  2: using System;
  3: using System.ServiceModel;
  4: using System.ServiceModel.Activation;
  5:  
  6: class CustomHostFactory : ServiceHostFactory
  7: {
  8:   protected override ServiceHost CreateServiceHost (Type serviceType, Uri[] baseAddresses)
  9:   {
 10:     // Specify the exact URL of your web service
 11:     var webServiceAddress = new Uri (
 12:       "http://www.itscodingtime.com/itscodingtime/TagCloudService.svc");
 13:  
 14:     return (new CustomHost (serviceType, webServiceAddress));
 15:   }
 16: }
 17:  
 18: public class CustomHost : ServiceHost
 19: {
 20:   public CustomHost (Type serviceType, params Uri[] baseAddresses) : base (serviceType,
 21:     baseAddresses)
 22:   { }
 23: }

One notable difference between this implementation and the one suggested in the other post is that this one explicitly specifies the url of the WCF service (but we're not quite done; see code below).

Also, note the address (line 11): http://www.itscodingtime.com/itscodingtime/TagCloudService.svc

That extra "itscodingtime" folder is necessary because of my hosting situation as described above. In fact, if I leave it out, and instead specify "http://www.itscodingtime.com/TagCloudService.svc" (which seems more logical) as the WCF service url, I start seeing "There is no compatible TransportManager found for URI" errors:

sshot-72

So, I have to specify the full url with the extra "itscodingtime" folder, which actually corresponds to the folder it is stored at under the root.

Back to the CustomHostFactory. If we leave it as above we'll soon run into problems while running against localhost. So, the final version is this:

  1:  
  2: using System;
  3: using System.ServiceModel;
  4: using System.ServiceModel.Activation;
  5:  
  6: class CustomHostFactory : ServiceHostFactory
  7: {
  8:   protected override ServiceHost CreateServiceHost (Type serviceType, Uri[] baseAddresses)
  9:   {
 10:     // Specify the exact URL of your web service
 11:     var webServiceAddress = baseAddresses[0].ToString ().Contains ("localhost")
 12:       ? baseAddresses[0]
 13:       : new Uri ("http://www.itscodingtime.com/itscodingtime/TagCloudService.svc");
 14:  
 15:     return (new CustomHost (serviceType, webServiceAddress));
 16:   }
 17: }
 18:  
 19: public class CustomHost : ServiceHost
 20: {
 21:   public CustomHost (Type serviceType, params Uri[] baseAddresses) : base (serviceType, baseAddresses)
 22:   { }
 23: }

Lines 11-13 will set our WCF service address based on whether we are running locally or if we're in production.

As a final step, remember to add the "Factory" attribute to the .svc's "ServiceHost" and you're set.

As you can see, I can once more access my WCF service:

sshot-73

Conclusion

If you've got a similar situation where you host multiple domains under the same root location and you find yourself seeing similar errors, then the method described above should get your services up and running again.


Sitemaps

December 21, 2008 17:00

What is a sitemap?

From sitemaps.org,

Sitemaps are an easy way for webmasters to inform search engines about pages on their sites that are available for crawling. In its simplest form, a Sitemap is an XML file that lists URLs for a site along with additional metadata about each URL (when it was last updated, how often it usually changes, and how important it is, relative to other URLs in the site) so that search engines can more intelligently crawl the site.

What is the basic format of the XML file?

Also from sitemaps.org, we have:

   1: <?xml version="1.0" encoding="UTF-8"?>
   2: <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
   3:    <url>
   4:       <loc>http://www.example.com/</loc>
   5:       <lastmod>2005-01-01</lastmod>
   6:       <changefreq>monthly</changefreq>
   7:       <priority>0.8</priority>
   8:    </url>
   9: </urlset> 

Only <urlset>, <url>, and <loc> are required. The other attributes under <url> are optional.

Here's a snippet from itscodingtime's sitemap:

   1: <urlset>
   2:     <url>
   3:         <loc>http://www.itscodingtime.com/itscodingtime/post/URL-Regular-Expression-Validation.aspx</loc>
   4:         <lastmod>2008-12-21</lastmod>
   5:         <changefreq>monthly</changefreq>
   6:     </url>
   7:     <url>
   8:         <loc>http://www.itscodingtime.com/itscodingtime/post/WLW-and-BlogEngineNET-Cant-view-Properties.aspx</loc>
   9:         <lastmod>2008-12-14</lastmod>
  10:         <changefreq>monthly</changefreq>
  11:     </url>
  12:     ...
  13: </urlset>

My content changes all the time. How do I keep my sitemap up to date?

Hopefully, if you're running a blog you're also using a good blogging platform like BlogEngine.NET. Because content on a blog does change quite often, your best option is an httpHandler like the one built into BlogEngine.NET.

Specifically, I'm talking about this line from your site's Web.config:

   1: <add verb="*" path="sitemap.axd" type="BlogEngine.Core.Web.HttpHandlers.SiteMap, BlogEngine.Core" validate="false"/>

The sitemap.axd handler will dynamically generate your sitemap when requested so that when Google's search bot stops in for a visit it gets a comprehensive listing of all of your content.

How do I tell the world about my sitemap?

It's easy. You should have a robots.txt file in your site's root folder. You just need to add the URL of your sitemap httpHandler. Google and other search engines will look for the robots.txt file and, when they find your sitemap entry, they'll follow it and add your content to their indexes.

For reference, here's what my robots.txt looks like:

   1: User-agent: *
   2: Disallow: /login.aspx
   3: Disallow: /search.aspx
   4: Disallow: /error404.aspx
   5: Disallow: /archive.aspx
   6:  
   7: #Remove the '#' character below and replace example.com with your own website address.
   8: sitemap: http://itscodingtime.com/sitemap.axd

Conclusion

That's a quick overview of sitemaps and how they're used. Hope you found some value in it. A future post will go over how to inform Google of your sitemap rather than waiting for them to find you.

Sources:

  1. Google Sitemap and BlogEngine.NET
  2. sitemaps.org
  3. MSDN: <httpHandlers> Element
  4. HTTP Handlers and HTTP Modules in ASP.NET