# Tuesday, October 26, 2010

header[1]Probably one of the most common features of an ecommerce systems is to "retrieve my details" when logging in -after all that's why you create an account with the seller isn't it?

Out of the box, uCommerce has XSLT to retrieve the customer's last x addresses but one thing it didn't do was automatically re-assign the customer's details when logging in using the built in Umbraco membership code so we need to work around it ourselves -don't worry, it's not too hard (all the code is below for you).

Background

All customer addresses are stored in the uCommerce_Address table automatically, there should be one unique address per customer however if you're on an earlier release you may find you have several copies of the same address for each customer -this is a bug that's been sorted in v1.0.5.0 so upgrade if you can.

Now you'd be forgiven for thinking that you can just select the address from the uCommerce_Address table and then assign the id to the BillingAddressId property of your purchase order however if you do that, you'll find you get the error:

The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_uCommerce_PurchaseOrder_uCommerce_OrderAddress". 
The conflict occurred in database "CommsReadyCMS", table "dbo.uCommerce_OrderAddress", column 'OrderAddressId'.
The statement has been terminated.

 

You'll get this because there is also a second table involved -uCommerce_OrderAddress. uCommerce_OrderAddress stores the actual address used throughout the order process incase the customer changes an address in the future, the order will always have the correct address.

The Solution

Working around this isn't actually too difficult as mentioned before. The easiest solution is to create a new User Control in Visual Studio (I'll call mine login.ascx) and hook into the LoggedIn event. Once logged in, get the Umbraco member and from that, get the customer's billing address.

There's one caveat that I found with uCommerce and that's the way it gets the address. At the moment, there is a function on customer "GetAddress", this is great however if you check out the code it calls, it actually gets the customer's first address from the database -rather than the last address used. I don't think this is a bug as in most cases the first address you enter is your main address. I'll blog separately about managing a default address within the members section.

The code below however retrieves the most recently added address from the database

Login.ascx

<asp:literal runat="server" ID="litLoggedIn" />
<asp:literal runat="server" ID="litLoggedOut" />
<asp:Login runat="server" id="lgnForm" CssClass="checkout-details" 
	DisplayRememberMe="false" TitleText="" OnLoggedIn="lgnForm_LoggedIn"
	UserNameLabelText="Email Address" />

 

Login.ascx.cs

protected void lgnForm_LoggedIn(object sender, EventArgs e)
{
    //If the user has a basket, wire up the shipping address with their last order details
    var basket = SiteContext.Current.OrderContext.GetBasket(true);
    if (basket != null)
    {
        //Get the customers current order
        var po = basket.PurchaseOrder;
        //Look for a shipping address
        var add = po.GetBillingAddress();
        //We only need to assign the address if there isn't already one assigned to this order
        if (add == null)
        {
            //Get the customer who's just logged in
            var mem = Membership.GetUser(lgnForm.UserName);
            //To be safe check that we have a member
            if (mem != null)
            {
                //Find the customer
                var customer = Customer.ForUmbracoMember(Convert.ToInt32(mem.ProviderUserKey));
                if (customer != null)
                {
                    //Get the customer's most recent address
                    var previousAddress = customer.Addresses.ToList().LastOrDefault(a => a.AddressName == "Billing");
                    //If you want to get the customer's first address just uncomment this line
                    //var previousAddress = customer.GetAddress("Billing");

                    //Populate the billing address with the address)
                    if (previousAddress != null)
                    {
                        OrderAddress address = new OrderAddress
                                {
                                    FirstName = previousAddress.FirstName,
                                    LastName = previousAddress.LastName,
                                    EmailAddress = previousAddress.EmailAddress,
                                    PhoneNumber = previousAddress.PhoneNumber,
                                    MobilePhoneNumber = previousAddress.MobilePhoneNumber,
                                    CompanyName = previousAddress.CompanyName,
                                    Line1 = previousAddress.Line1,
                                    Line2 = previousAddress.Line2,
                                    PostalCode = previousAddress.PostalCode,
                                    City = previousAddress.City,
                                    State = previousAddress.State,
                                    Attention = previousAddress.Attention,
                                    CountryId = previousAddress.CountryId,
                                    AddressName = "Billing",
                                    OrderId = new int?(po.OrderId)
                                };
                        //Store the address in the database
                        address.Save();
                        //Assign the address to the purchase order
                        po.BillingAddressId = new int?(address.OrderAddressId);
                        //Save the purchase order (shopping cart)
                        po.Save();
                    }
                }
            }
        }
    }
}
Tuesday, October 26, 2010 11:31:55 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, August 17, 2010

ucommerce-logo-symbol[1]I thought seeing as uCommerce is now an actual product I would start to overview an install/configuration of uCommerce assuming no prior knowledge of uCommerce. Firstly, let me start of by saying that once you've got your head around uCommerce and some of it's complexities, you'll find it a fantastic product that makes creating a new ecommerce website as easy as setting up a standard Umbraco website. It is still missing a few features, but you can easily work around these with a bit of custom XSLT/C#.

Ok, back to setting up your first uCommerce website. I've grouped these into what I feel are logical sections but if I've missed something, please let me know.

1. Install the uCommerce Package

If you've not already done so, go to the uCommerce Download page and download the uCommerce package (at time of writing, I'm using 1.0.4.2) and then download the uCommerce Store package (currently 1.0.1.2).

Install the uCommerce package as you do any other package in Umbraco. Once installed you'll be able to install the store package.

Assuming all your permissions on your Umbraco install are correct, refresh your browser and you should have a new section "Commerce". If they're not right, you'll be told to add a few web.config settings.

2. Wire up the catalog

This is the step that I didn’t “do” when we first got started and it turns out it’s one of the most important steps as it joins the uCommerce catalog to the front end.

  1. Go to your Umbraco "Content" section
  2. Right click on the page you would like to be the store's "home" page (in the example store, this would be "Shop")
  3. Click "Manage hostnames" (see figure below)
    Manage Hostnames Context Menu
  4. Enter your hostname (the domain name the site runs on) in the "Domain" box and then choose the default language for the website
    Manage Hostnames screen
  5. Click "Add new Domain" and then "Close this window"
  6. Click the "Commerce" section button (in the bottom left)
  7. Click the little arrow to the left of "Product Catalog"
  8. Left click the relevant catalog (if you've installed the store package this will be "uCommerce")
  9. Select your new domain from the "Host name" drop down list
    Manage Hostnames screen
  10. Click the save disk button in the top left

3. Setup Your Product Definitions

A “Product Definition” is uCommerce’s concept of document types, it allows you to add additional information to the product. If you’re using the uCommerce starter store, you’ll get a couple of product definitions out of the box –software and support. At the moment, you can't add additional properties through the uCommerce back end (i.e. if you wanted to add additional information such as Meta Keywords/Descriptions etc -I'll cover how we got around this in a later post) but there are a number of default the category/product properties (I've put their XML reference in brackets where relevant):

uCommerce Category Properties

  • Image (@image)
  • Display Name (@displayName)
  • Description (@description)

The default XML looks like this:

<category parentCategoryId="" parentCategoryName="" index="0" id="67" name="Software" displayName="Software" displayOnSite="True" description="" image="" />

uCommerce Product Properties

  • SKU (@sku)
  • Internal name
  • Display on web site (@displayOnSite)
  • Allow ordering (@allowOrdering)
  • Thumbnail (@thumbnailImage)
  • Primary image (@primaryImage)
  • Display name (@displayName)
  • Short description (@shortDescription)
  • Long description (@longDescription)

The default XML looks like this (the variants are not standard but are there because they're setup as part of the store package):

<product index="0" sku="100-000-001" displayName="uCommerce 1.0 RTM" shortDescription="uCommerce is a full featured e-commerce platform with content management features powered by Umbraco. Everything you need to build a killer e-commerce solution for your clients!" longDescription="uCommerce is fully integrated with the content management system Umbraco, which provides not only the frontend renderendering enabling you to create beautifully designed stores, but also the back office capabilities where you configure and cuztomize the store to your liking.&#xD;&#xA;&#xD;&#xA;uCommerce_ foundations provide the basis for an e-commerce solution. Each foundation addresses a specific need for providing a full e-commerce solution to your clients. foundations in the box include a Catalog Foundation, a Transactions Foundation, and an Analytics Foundation.&#xD;&#xA;&#xD;&#xA;Each of the foundations within uCommerce_ are fully configurable right in Umbraco. No need to switch between a multitude of tools to manage your stores. It's all available as you would expect in one convenient location." thumbnailImage="1097" primaryImage="1097" allowOrdering="True" isVariant="False" displayOnSite="True" hasVariants="True" price="3495.0000" currency="EUR">
  <variants>
    <product index="0" sku="100-000-001" displayName="Developer Edition" shortDescription="" longDescription="" thumbnailImage="0" primaryImage="0" allowOrdering="False" isVariant="True" displayOnSite="False" hasVariants="False" variantSku="001" price="0.0000" currency="EUR" Downloadable="on" License="Dev" />
    <product index="1" sku="100-000-001" displayName="30 Days Evaluation" shortDescription="" longDescription="" thumbnailImage="0" primaryImage="0" allowOrdering="False" isVariant="True" displayOnSite="False" hasVariants="False" variantSku="002" price="3495.0000" currency="EUR" Downloadable="on" License="Eval" />
    <product index="2" sku="100-000-001" displayName="Go-Live" shortDescription="" longDescription="" thumbnailImage="0" primaryImage="0" allowOrdering="False" isVariant="True" displayOnSite="False" hasVariants="False" variantSku="003" price="3495.0000" currency="EUR" Downloadable="on" License="Live" />
  </variants>
</product>

Adding additional product properties is simple.

  1. Click the "Commerce" section button
  2. Navigate to: Settings --> Catalog --> Product Definitions
  3. Choose the product definition you would like to edit (or create a new one in the same way that you would with Umbraco document types)
  4. Right click the product definition you need to add extra properties to and click "Create"
  5. Type in a name for your new property i.e. Size
  6. Choose the Data Type for the property (if you need something that's not listed see "Creating your own Data Type" below):
    • ShortText -A textbox
    • LongText -A text area
    • Number -Beleive it or not, a numeric value
    • Boolean -A checkbox
    • Image -A media selector
  7. Click the "Create" button
  8. You can now choose a few additional options for the new property including how it should be shown to the user and whether it's Multilingual.
    • Name -the text used as the label in the uCommerce product editor (it's also the name of the attribute on the XML that will contain it's value)
    • Data Type -the type of control to render in the uCommerce product editor
    • Multilingual -whether the control should be shown on the "Common" tab of the uCommerce product editor or the language specific tab
    • Display On Web Site -A flag that's sent out in the XML so you can decide whether or not to show it on the website
    • Variant Property -Whether this should appear as a table column heading under the "Variants" tab (I'll go into variants more in a later post)
      Note: Do not set Multilingual and Variant property to both true as at the moment, it won't be shown in the uCommerce product editor -you've been warned!
    • Render in Editor -Whether the control should be shown in the uCommerce product editor screen or hidden from the administrator (i.e. for data you want to use internally only and should be editable)
  9. Finally you'll need to enter in a Display Name for the various languages. This is what's shown to the user if you dynamically pull through the various properties on the product details page.

4. Creating Your Own Data Type

Now, you may be thinking that using that set of data types is a little limiting for something like "Size" or "Colour" and you might want to display something a little more flexible to the user -such as a drop down list. This is easy enough:

  1. Right click the "Data Types" node
  2. Enter a name i.e. "Size"
  3. Choose the definition for the Data Type (for size we will use "Enum")
  4. Save and Refresh the "Data Types" node
  5. Right click your new Data Type and click Create
  6. Enter your Option's value i.e. "Small"
  7. Repeat 5-6 until all your options are set i.e. add "Medium" and "Large"

Note: At the moment, the enum values cannot be re-ordered through the UI so make sure you add them in the order you want them in the editor!

5. Load Your Catalog

Once you've finished creating your various product types, it's time to create your catalog. Creating categories and products within uCommerce is as simple as creating pages in Umbraco. Using the same right click menu concept you can create nested categories as deep as your catalog requires. You can add products and categories at any level by choosing either the "Category" or "Product" radio button and choosing your product type.

6. You're Done!

Assuming you've followed the steps above, you should now have a (fairly basic) store setup. Go to your site's homepage and click the "uCommerce" menu item and voila, your categories and products should be listed.

Not getting the categories you were expecting? Perform the helpful xsl “copy-of” trick within either the "RootCategories[XSLT].xslt" file or "Category[XSLT].xslt" file:

<pre><xsl:copy-of select="$categories" /></pre>

and then have a look at the output:

<errors><error>No product catalog group found supporting the current URL.</error></errors>

If you're getting the above error, currently (and this may be a misunderstanding/changed later) you have to have the catalog and catalogue group names the same –in the example site, they’re both “uCommerce”.

As I think the concept store offered with Software/Support isn't particularly real-world, I'm going to work on creating a basic store that you can use to better understand uCommerce and it's intricacies.

Check back soon as I'll be posting an overview of the checkout process, the various XSLT files and integrating payment gateways into uCommerce (initially SagePay, PayPoint, WorldPay and PayPal).

Tuesday, August 17, 2010 5:49:45 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [3]  | 
# Thursday, August 12, 2010

Ever needed to take a large list and split it into smaller subsets of data for processing? Well this is the Extension Method for you. Tonight we had to split a small dataset (500 items) into even smaller sets of 10 so the provider’s web service wouldn’t timeout.

Seeing as I was going to miss out on my evening, I thought I’d see if I could do it a little differently using Linq and this is what I came up with:

/// <summary>
/// Simple method to chunk a source IEnumerable into smaller (more manageable) lists
/// </summary>
/// <param name="source">The large IEnumerable to split</param>
/// <param name="chunkSize">The maximum number of items each subset should contain</param>
/// <returns>An IEnumerable of the original source IEnumerable in bite size chunks</returns>
public static IEnumerable<IEnumerable<TSource>> ChunkData<TSource>(this IEnumerable<TSource> source, int chunkSize)
{
    for (int i = 0; i < source.Count(); i += chunkSize)
        yield return source.Skip(i).Take(chunkSize);
} 

It should extend any IEnumerable and allow you to split it into smaller chunks which you can then process to your heart’s content.

Here’s a quick example of it in use:

var list = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10" };
Console.WriteLine("Original list is {0} items", list.Count);
var chunked = list.ChunkData(3);
Console.WriteLine("Returned the data in {0} subsets", chunked.Count());
int i = 1;
foreach (var subset in chunked)
{
    Console.WriteLine("{0} items are in subset #{1}", subset.Count(), i++);
    int si = 1;
    foreach (var s in subset)
        Console.WriteLine("\t\tItem #{0}: {1}", si++, s);
}

And this will output

Original list is 10 items
Returned the data in 4 subsets
3 items are in subset #1
		Item #1: Item 1
		Item #2: Item 2
		Item #3: Item 3
3 items are in subset #2
		Item #1: Item 4
		Item #2: Item 5
		Item #3: Item 6
3 items are in subset #3
		Item #1: Item 7
		Item #2: Item 8
		Item #3: Item 9
1 items are in subset #4
		Item #1: Item 10

2 lines of code to do all that work -Neat

Thursday, August 12, 2010 9:32:44 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
# Wednesday, June 30, 2010

Simple tip this afternoon. You may have got the following error when sending emails through ASP.Net’s built in mail server before:

From: postmaster@YourWebserversName [mailto:postmaster@YourWebserversName] 
Sent: 25 June 2010 13:22
To: sender@sendingdomainname.com
Subject: Delivery Status Notification (Failure)

This is an automatically generated Delivery Status Notification.

Delivery to the following recipients failed.

      recipient@recievingdomainname.com

Reporting-MTA: dns;YourWebserversName
Received-From-MTA: dns;YourWebserversName
Arrival-Date: Fri, 25 Jun 2010 13:21:30 +0100

Final-Recipient: rfc822;recipient@recievingdomainname.com
Action: failed
Status: 5.5.0
Diagnostic-Code: smtp;504 <YourWebserversName>: Helo command rejected: need fully-qualified hostname

 

The fix is easy:

  1. Open IIS
  2. View the properties of you Default SMTP Virtual Server
  3. Go to the “Delivery” tab
  4. Click the “Advanced” button (in the bottom right corner)
  5. Under “Fully-qualified domain name” enter a domain name that points to the server
  6. Click Ok until you’re back to IIS
Wednesday, June 30, 2010 4:20:08 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, June 17, 2010

powershell2xa4[1] If you're not configuring Umbraco through a web installer, you've had your installs in place for years and never checked the permissions or whoever set the permissions up was lazy and gave IIS write access to the entire folder, there will come a time when you want to restrict modify access to just those user(s) who should have access.

You can find a (pretty) complete list of the files/folders that the Umbraco install should have access to here but assigning them across 101 different installs is a PITA . Thanks to a helpful PowerShell script to set folder permissions from PowerShell.nu you can easily automate the process.

For those of you not familiar with PowerShell (like me) complete instructions are below. For the rest, here's the command:

Get-ChildItem -path ##PATH TO YOUR INSTALL## 
| Where { $_.name -eq "Bin" -or $_.name -eq "Config" -or $_.name -eq "Css" -or $_.name -eq "Data" -or $_.name -eq "Masterpages" -or $_.name -eq "Media" -or $_.name -eq "Scripts" -or $_.name -eq "Umbraco" -or $_.name -eq "Umbraco_client" -or $_.name -eq "UserControls" -or $_.name -eq "Xslt" } 
| ForEach {./SetFolderPermission.ps1 -path $_.Fullname -Access "NETWORK SERVICE" -Permission Modify}

 

Instructions:

  1. Save the SetFolderPermission.ps1 script to your server
  2. Open your PowerShell console (I think it's installed by default if not, you can download PowerShell here)
  3. Copy the above PowerShell command into notepad
  4. Update "##PATH TO YOUR INSTALL##" to your Umbraco install
  5. If your IIS install doesn't use NETWORK SERVICE as the default user, update it to your user
  6. Make sure it's all on a single line
  7. Copy/Paste/Run in PowerShell

Bonus

If you're uber lazy and just have a web folder of Umbraco installs you can set the path to the folder of Umbraco installs and use:

Get-ChildItem -path ##PATH TO YOUR FOLDER## -recurse
| Where { $_.name -eq "Bin" -or $_.name -eq "Config" -or $_.name -eq "Css" -or $_.name -eq "Data" -or $_.name -eq "Masterpages" -or $_.name -eq "Media" -or $_.name -eq "Scripts" -or $_.name -eq "Umbraco" -or $_.name -eq "Umbraco_client" -or $_.name -eq "UserControls" -or $_.name -eq "Xslt" } 
| ForEach {./SetFolderPermission.ps1 -path $_.Fullname -Access "NETWORK SERVICE" -Permission Modify}

 

I've not tried this mind you and can't recommend it but hey, it's there if you want it ;)

Thursday, June 17, 2010 2:47:22 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Sunday, April 18, 2010

You may have come across this error once or twice while deploying your project if you develop using Web Deployment Projects. It's usually caused when you copy and paste a page and forget to update both the page declaration and code behind file.

But the website builds!?!

You don't usually get the ILMerge error until you build the web deployment project because when you build a website directly, it doesn't compile all the code into a single assembly so the class names are seen as different. Part of the web deployment process is to compile all the websites code into a single assembly hence the duplicate references.

What's the solution?

It's surprisingly simple, all you need to do is open up the offending aspx and aspx.cs files and update two lines:

1. In the code behind file, rename the partial class. By default Visual Studio will name the class FolderName_Pagename which should result in a unique name

2. The page declaration (first line of the page) in the aspx file. You have to make sure that both the Inherits attribute and CodeBehind reference are correct.

Tip: To avoid confusing yourself, open the files independently using the solution browser because if you open the aspx and press F7 to switch to the code behind file before updating the page declaration, you'll end up editing the page you copied rather than the copy.

Sunday, April 18, 2010 12:47:20 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, July 09, 2009

Those of you lucky enough to go to CodeGarden '09 you'll know the format of the Open Space already but for those of you who didn't, Open Space is the time that the attendees are invited to talk about something they're interested in so I proposed two:

  1. Space 1: Selling Umbraco
  2. Space 2: Exception handing and error reporting in Umbraco (and other .net websites/applications)

I'll write up the Selling Umbraco talk shortly but I wanted to put a few resources together for it first so decided to write this one up first.

First of all we had a brief chat about how everyone handles errors in their applications and the various error handling options available. We discussed three options:

  1. Error Handler v2.0
  2. ELMAH
  3. Exceptioneer

I've only had a brief look at ELMAH and found at the time it was a little too much in the way of RSS feeds etc and I just want an email alert, that said, Lee Kelleher has written a good article about integrating ELMAH with Umbraco here and I've written another article about integrating Error Handler v2.0 into Umbraco here so I'll overview how to integrate Exceptioneer into Umbraco here instead.

Wiring up Exceptioneer with your site couldn't be easier, the best bit is that they do all the hard work for you with their "Integrate" section of the site but to give you a quick snapshot of how easy it is, first of all, download the dll and pop it into your bin folder. Then edit your web.config:

<?xml version="1.0"?>
<configuration> 
    <configSections> 
        <section name="Exceptioneer" type="Exceptioneer.WebClient.ClientModuleConfiguration, Exceptioneer.WebClient" requirePermission="true" /> 
    </configSections>
    
    <!-- This is where you get to specify your API Key and Application Name --> 
    <Exceptioneer ApiKey="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" ApplicationName="YOUR APPLICATION NAME" /> 
    
    <!-- If you're using IIS 6.0 or Visual Studio's built in web server you'll need to add this bit --> 
    <system.web> 
        <httpModules> 
            <add name="Exceptioneer" type="Exceptioneer.WebClient.ClientModule, Exceptioneer.WebClient" /> 
        </httpModules> 
        <!-- If you want to use the JavaScript handling then add the Http Handler as so --> 
        <httpHandlers> 
            <add path="ExceptioneerJavaScript.axd" verb="GET,POST" type="Exceptioneer.WebClient.JavaScriptHandler, Exceptioneer.WebClient" /> 
        </httpHandlers> 
    </system.web> 
    
    <!-- If you're using IIS 7.0 you'll need to add this bit too --> 
    <system.webServer> 
        <validation validateIntegratedModeConfiguration="false"/> 
        <modules> 
            <add name="Exceptioneer" preCondition="managedHandler" type="Exceptioneer.WebClient.ClientModule, Exceptioneer.WebClient" /> 
        </modules> 
        <handlers> 
            <add name="ExceptioneerJavaScript" path="ExceptioneerJavaScript.axd" verb="GET,POST" type="Exceptioneer.WebClient.JavaScriptHandler, Exceptioneer.WebClient" /> 
        </handlers> 
    </system.webServer>
</configuration>

Now, one of the coolest things about Exceptioneer is that you can now also debug JavaScript errors! To debug the javascript errors, just include this script in your templates:

<script src="/ExceptioneerJavaScript.axd?Reporter=true" type="text/javascript"></script>

That's it, you're done. Easy eh? If you want to know more about what it can do, Phil's put together this "lovely" video overview. Exceptioneer have done a great comparison of the main features of comparison Exceptioneer and ELMAH here, the downside though is Exceptioneer is still in beta.

Remember, regardless of how good you think your code is, you should always integrate some form of error handling in your website even if it is just an email to alert you to the fact.

Thursday, July 09, 2009 1:23:38 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
# Saturday, June 27, 2009

I've started using Rick Strahl's wwAppConfiguration to allow easier access to application constants and one thing that's been bugging me is that it doesn't play nice with configSource -which we update with web deployment projects to specify Development/Staging/Live settings.

The issue is that when you set configSource on the appSettigns node, wwAppConfiguration doesn't correctly set the file's path and instead (when using the default settings) writes the new values within the <appSettings> node. The problem is then that ASP.Net complains that you cannot specify configSource and settings inside the <appSettings> node.

After a little digging, it turns out that you can use "file" in place of "configSource" for the appSettings node (and sadly only the appSettings node) and it allows you to define values within the <appsettings> node and then override them with your external file. This is fantastic because you can store your "default" values in the web.config and then override some or all of them for your various environments.

The next issue you may run into is if you use web deployment projects, in which case you may get the following error:

web.config(2): error WDP00001: section appSettings in "web.config" has 7 elements but "config\STAGING-appSettings.config" has 19 elements.

To work around this, you just need to untick the "Enforce matching section replacements" checkbox within the properties section and you're good to go!

I hope that helps someone!

Saturday, June 27, 2009 8:19:19 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [1]  | 
# Tuesday, May 12, 2009

This was one of those irritating errors that you get when you're trying to do something quickly before you go home and you can't for the life of you fathom the issue.

I had the following code (simple enough):

FileInfo f = new FileInfo("## File's Path ");
try
{
    f.MoveTo("## DROP OFF DIRECTORY ##"));
}
catch (Exception e)
{
    //Log the exception here
}

The fix was simple, you just have to remember to specify the new filename too. (DOH!). Here's the "correct" code.

FileInfo f = new FileInfo("## File's Path ");
try
{
    f.MoveTo(Path.Combine("## DROP OFF DIRECTORY ##", f.Name));
}
catch (Exception e)
{
    //Log the exception here
}

Hope that helps you out ;)

Tuesday, May 12, 2009 8:39:35 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
# Tuesday, April 28, 2009

The Error

For those of you who have tried to rename your Umbraco installation directory to something other than the default /umbraco/ you'll have found that TreeInit.aspx throws a JavaScript error along the lines of:

Message: Object expected
Line: 1
Char: 4236
Code: 0
URI: http://www.yourdomain.co.uk/youradmindirector/js/xloadtree.js

As this only really affects the refresh of the tree/close of a couple of dialogues I've not bothered fixing it but basically the issue is outlined well here: http://tinyurl.com/cx9atv

The Fix

If you're using extension less URLs already then it's easy as pie to sort:

  1. Open your UrlRewriting config file (/config/UrlRewriting.config)
  2. Add this above "</rewrites>":
<...>
<add name="missingjs" 
    virtualUrl="^~/## YOUR ADMIN DIRECTORY GOES HERE ##_client/ui/(.*).js" 
    rewriteUrlParameter="ExcludeFromClientQueryString" 
    destinationUrl="~/umbraco_client/ui/$1.js" 
    ignoreCase="true" />

If you've not already using extension less URLs don't panic, that's easy to setup you can read all about it here. Alternatively you could just copy the js files from one folder to another ;)

The Why

I don't know how many people already rename their admin dir from something else but as Umbraco becomes a more popular choice of CMS you really should consider hiding the folder (the more popular it becomes, the more people will become more familiar with the default admin directory of /umbraco/).

Although there hasn't yet been a breach (AFAIAA) if a vulnerability is found, the first step in prevention is obfuscation -hide your admin directory! A quick Google search will show you how easy some developers have made it for you to find their admin sites.

Tuesday, April 28, 2009 6:49:48 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [3]  | 
# Friday, April 17, 2009

Ok it might be a little less than half side but it's near enough. I've been sitting on this for a while and needed to reference it for someone so I thought I'd post quickly about it. One of the most common complaints about .Net is that you have a lot of hidden "content" by the way of hidden inputs and the likes throughout your site. This can easily get corrupt on postback/slowdown the page load times etc.

Really you should be optimising each control on the page (enabling/disabling where relevant) but if you want to cheat (lets face it, we all do):

  1. Download the files: PageStateAdapterv1.0.zip (3KB)
  2. Put PageStateAdapter.browser into your /App_Browsers/ folder (or create one and add it)
  3. Put TSDPageStateAdapter.dll into your website's /bin/ folder
  4. Load up your website and checkout your ViewState :)

Incase you're interested in the source for it:

PageStateAdapter.browser

<browsers>
    <browser refID="Default">
        <controlAdapters>
            <adapter controlType="System.Web.UI.Page" adapterType="TheSiteDoctor.PageStateAdapter.PageStateAdapter" />
        </controlAdapters>
        <capabilities>
            <capability name="requiresControlStateInSession" value="true" />
        </capabilities>
    </browser>
</browsers>

PageStateAdapter.cs

using System.Web.UI;

namespace TheSiteDoctor.PageStateAdapter
{
    public class PageStateAdapter : System.Web.UI.Adapters.PageAdapter
    {
        public override PageStatePersister GetStatePersister()
        {
            return new SessionPageStatePersister(this.Page);
        }
    }
}

The best example of how much this reduces ViewState by is when you add a large DataGrid to your site.

Post files: PageStateAdapterv1.0.zip (3KB)

Update: Apologies to those of you who downloaded and found it wouldn't compile, the .browser file was a little off (missing the second "PageStateAdapter"). I've updated it and changed the zip file download. Enjoy!

Friday, April 17, 2009 3:53:05 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
# Friday, February 27, 2009

If you've been following my blog you'll know that I've been raving about error reporting within ASP.Net (you can see my ASP.Net Error Reporting category for a couple of them if you like) but until now it's been limited to those sites that you have access to the global.asax file.

One of the irritations I've found with Umbraco and dasBlog is that I don't get notified of errors as they're just logged to a text file/database somewhere. This is fine if you run 2 or 3 sites but we manage too many to check them all everyday. Instead we rely on email error notifications which until today have been a PITA to integrate into Umbraco.

Today I'd like to introduce to you Error Handling v2.0 which instead of relying on the global.asax file for the error hooks, uses a HttpModule which means you can install it into any existing/pre-built application such as Umbraco and dasBlog.

Adding it into the site is simple, you'll need to install the module into the web.config file and add the configuration section a sample (cut down) web.config is below:

<?xml version="1.0"?> 
<configuration> 
    <configSections> 
        <section name="tsdErrorsConfigSection" allowExeDefinition="MachineToApplication" restartOnExternalChanges="true" type="System.Configuration.NameValueFileSectionHandler, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> 
    </configSections> 
 
    <tsdErrorsConfigSection file="ErrorHandling.config"/> 
 
    <system.net> 
        <mailSettings> 
            <smtp from="you@yourdomain.com"> 
                <network host="127.0.0.1" port="25" /> 
            </smtp> 
        </mailSettings> 
    </system.net> 
 
    <system.web> 
        <httpModules> 
            <add name="ErrorModule" type="TheSiteDoctor.ErrorHandling.ErrorModule, TheSiteDoctor.ErrorHandling" /> 
        </httpModules> 
    </system.web> 

<!--  
IIS 7 Settings 
    <system.webServer> 
        <validation validateIntegratedModeConfiguration="false" /> 
        <modules> 
            <add name="ErrorModule" type="TheSiteDoctor.ErrorHandling.ErrorModule, TheSiteDoctor.ErrorHandling" /> 
        </modules> 
    </system.webServer> 
-->
</configuration>

Then you'll need to check all the settings -I recommend storing these in another .config file for clarities sake. Make sure you've configured your SMTP settings and you should be good to go.

If you want to test your settings, I've included a test page for you that will check your settings and show you the defaults if you've not set them. I've got this running now on a couple of Umbraco and dasBlog installs without an issue.

There's also a useful logging system in it which I'll look to overview in a later post but if you want to see it, check out the included aspx file.

Download ErrorHandling_v2.0.zip (25Kb)

If you do use this code I'd be interested to hear how you get on, I think it requires a little more refinement un some areas but it's pretty robust.

Enjoy.

Friday, February 27, 2009 3:51:13 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
# Sunday, January 18, 2009

This is the second time I've come across the error "Value cannot be null.Parameter name: type" when using ASP.Net Membership Profiles.

Profiles are great, they allow you to store little pieces of information e.g. their user id (an integer reference to your database) on the user against their User object. You can then use that as a property of the User which can get you out of a bind or two.

Since switching to Web Deployment projects to get around a few issues with multiple environment configuration switching however I started to get "Value cannot be null.Parameter name: type". After a little Googling around I found that it relates to the setting "Treat as library component (remove the App_Code.compiled file)" setting under the property pages.

Un-checking the box sorts all your woes :)

Sunday, January 18, 2009 6:49:53 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
# Wednesday, November 05, 2008

We've just moved a couple of our sites onto a new server and have intermittently started to receive the error "The remote host closed the connection. The error code is 0x80072746.", usually around the same time as "Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.".

As the infrastructure hadn't changed we were able to rule out network issues, hardware issues (it was an upgrade) and nothing had changed on the code level so we put it down to the new backup routine.

On a little Googling, I found a few sites that were pointing to (among other things) the Request length and Executiuon time which got me thinking, in this particular site there is a lot of data being sent/retrieved and it could be maxing out the request. After a little more digging I found this article from Microsoft about the httpRuntime Element and I quote

This time-out applies only if the debug attribute in the compilation element is False. To help to prevent shutting down the application while you are debugging, do not set this time-out to a large value.

Although I didn't recall making any changes to the site, when updating the database configuration settings, I did change the compilation element to false. So far increasing the executionTimeout value appears to have fixed the issue.

Wednesday, November 05, 2008 2:03:40 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
# Saturday, November 01, 2008

We had an interesting problem the other day while integrating with a web service hosted on an apache server. Every time we called the WSDL methods we could see a valid request being sent and a valid response being returned (ok there were a few syntax errors but nothing that would stop it converting) but despite this every time, without fail, it would return null at code level.

The solution was actually amazingly simple. In short, since our last flurry with web services we've upgraded to Visual Studio 2008 and installed ReSharper (see below for how that's relevant). In VS2008 they've sneakily added a new menu "Add Service Reference" and although it's in the same menu location and a similar wording etc does something different. If you add your WSDL reference this way it will more than likely fail.

To add a "Web Reference" in Visual Studio 2008 follow the instructions below and hopefully you won't go down the same routes that I did trying to debug the XML...

Click the "Add Service Reference" menu option (obviously!)

The screen below should appear, instead of entering the address of the web service into the address bar, click the advanced button at the bottom (highlighted)

That'll then bring up the screen below, on which you need to click the "Add Web Reference" button at the bottom.

You should then be presented with the usual "Add Web Reference" screen (see below).

Why did I mention ReSharper? Well I've notice that as well as adding to the IDE they've also updated some of the menu items, most noticeably "Go to Definition" which is now "go to Declaration" (see below) so I figured this new menu option was thanks to them.

ReSharper:

Without ReSharper: VS2008GoToDeclarationWithoutResharper.png

So the next time your WSDL is returning a null object even though a valid response is being retrieved, check how you added it in Visual Studio,.

Saturday, November 01, 2008 6:28:49 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, July 10, 2008

We've been setting up the page's title using ASP.Net for quite some time now, we tend to word it: Product Name | Category | Site Name as this IMO is the most comprehensive naming convention (though the pipe (|) gets converted to a space for the bookmarks).

When editing one of our sites today though I noticed that the title was resorting to Avant Garde hair salons -which was setup as the brand's name. Looking into it I found that if you set the <title> tag within the page or master page, ASP.Net doesn't override it from the codebehind so watch out!

For those of you who don't know how to set the title of your page from codebehind it's simple:

this.Page.Title = "Put your title here";
ASP.Net | C#
Thursday, July 10, 2008 11:57:57 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, May 29, 2008

Doug Setzer posted this comment in response to my recent "A seriously elegant SQL Injection" post and I thought it may be of interest to others so have promoted it to a post...


Well, I'll step up and say that I am the "mate" who had this done.  Tim's right - *always* sanitize your inputs.  In my defence, this was a site that I inherited from a previous contractor.  I'm not entirely absent of blame, I still should have done a security sweep through the code.

I'd like to document the steps that I went through once this was identified to try and avoid this kind of thing in the future.

  1. Edit every web page that executes a query to sanitize any parameters that are passed in.  Since the site was classic ASP, I used my "SQLStringFieldValue" function:
    www.27seconds.com/kb/article_view.aspx?id=50
  2. Modify the DB user account that is used to have *read only* access to the database
  3. Modify the pages that DO write to the database to have *read/write* access to the specific tables that are being changed.  This limits the number of places that SQL Injection can occur to a smaller set than was previously possible.  I still sanitize all of my input, but I'm extra spastic in these database calls.
  4. Add database auditing (triggers writing to mirror tables with audit event indicator & date/time) to see when data changes occur.  This is still problematic with the pages that have "write" permissions to the tables, but again- that footprint is much smaller.
    My future plans are to move to a view/stored procedure based architecture.  I can then limit write permissions to just the stored procedures and read permissions to just the views.  My grand gusto plans are to move to using command objects & parameters, but I'd sooner re-write the entire site.

Although Doug's attack wasn't the same nihaorr1.com attack that's going around atm it was similar so I would imagine other's will find this useful.

It still amazes me how many developers still fail to sanitise strings, only last week I came across another site (in PHP) that was allowing simple SQL injections to be used to log into their administration system. It was down to a problem with the sanitization string, but why not at least check your site before it goes live? It takes 2 minutes and even less to fix...

For those of you who need a few pointers, there's a good discussion or two about sanitising strings on the 4 Guys From Rolla site.

Thursday, May 29, 2008 3:32:33 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  | 
# Thursday, May 01, 2008

I've seen a couple of posts recently about how to apply headings to text within Umbraco and the main suggestion is that you apply them by hacking about with the StyleSheets that just doesn't sit right with me.

We use a different method -that is to enable the format drop down list. To me it just seems cleaner but I get the feeling I'm about to start a big debate. The method we use was originally discussed here but in short download this zip file with the updated DLLs in and instructions.

6291-leftinymce.zip (241KB)

Thursday, May 01, 2008 3:21:56 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  | 
# Wednesday, April 30, 2008

Despite all the doom and gloom surrounding the pending credit crunch, we're hiring as work is piling in and we need help :). So if you're a developer, designer, sales person, marketing guru -or you're just plain bored check out The Site Doctor's vacancies page for the great posts currently up for grabs!

Not sure why you should come and work with us? There are way too many reasons to list in one go but here are my top 5:

  • You'll have a great boss (ok I'm a touch biased)
  • We have 20% time (every Friday we down tools and do something cool -that doesn't relate to the main projects you're working on at the time -more about that another day)
  • We're committed to your development and will fund courses etc
  • There are bonuses to be had for referrals and working hard!
  • You get your Birthday as an additional bank holiday so you never need to worry about booking it off again!

Oh and there's free Tea and Coffee -so I guess that's 6 reasons to get in touch.

For more information about the posts available (more being added later this week) check out The Site Doctor vacancies page.

Wednesday, April 30, 2008 4:18:00 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Sunday, January 13, 2008

I'm sure I've blogged about this in the past -or perhaps it's just in my "to blog about list" but I thought I would share this little ditty on the Sunday night.

If you ever need to delete a user from your ASP.Net Membership database this is a really useful SQL script to do just that (I often find that the ASP.Net web administration tool throws a SQL Exception while trying to delete a user).

To use the code to delete a user from the ASP.Net membership database simple identify the Guid of the user and enter it where I've written 'THE GUID OF THE USER HERE' and hit go :)

USE ASPNet
GO

DECLARE @UserId uniqueidentifier
SET @UserId = 'THE GUID OF THE USER HERE'

DELETE FROM aspnet_Profile WHERE UserID = @UserId
DELETE FROM aspnet_UsersInRoles WHERE UserID = @UserId
DELETE FROM aspnet_PersonalizationPerUser WHERE UserID = @UserId
DELETE FROM dbo.aspnet_Membership WHERE UserID = @UserId
DELETE FROM aspnet_users WHERE UserID = @UserId

The message I was referring to above usually looks something like the following:

Msg 547, Level 16, State 0, Line 9
The DELETE statement conflicted with the REFERENCE constraint "FK__aspnet_Us__UserI__17036CC0". The conflict occurred in database "ASPNetMemberships", table "dbo.aspnet_UsersInRoles", column 'UserId'.
The statement has been terminated.

I've not looked into why it's happening (I expect it's something to do with an incorrect install on my behalf) but I'm sure there's a solution for it. I know there are a couple of built in SQL scripts i.e. aspnet_Users_DeleteUser but they required more params to get working ;)

Sunday, January 13, 2008 8:37:44 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [4]  | 
# Monday, November 26, 2007

Having only recently started to use Umbraco I've taken a couple of days to familiarise myself with the way it works and try and get a few best practices in place, I expect these will be updated over time but you've got to start somewhere ;)

As with any code, I think it's very important to follow a consistent naming convention -whether it's the same one everyone else follows or not, you need to be able to pickup code you wrote months/years/decades ago and still understand it. Your styles will no doubt change over the years but you get the idea.

I've chosen to follow the following "style":

  • Document Types: Lowercase the first letter of the aliases followed by capitals for the new words (similar to Hungarian Notation). Use descriptive names i.e. Home Page for the document type as it'll be client facing. Suffix with "Page" if it is a page document type (as opposed to i.e. a screen shot)
  • Templates: If the template is specifically for a document type, use the same name for the template, if it relates to multiple document types name it logically i.e. "Master Template" or "Left Menu"
  • Macros: Prefix the macro alias with uppercase TSD to avoid conflicts with other macros. Prefix the name with [Source of the macro] i.e. [XSLT] or [User Control]. This is something I picked up from the sample package created by Warren Buckley that I think makes it easier to understand what's going on
  • XSLT Files: Prefix the name with the site's abbreviation i.e. for www.thesitedoctor.co.uk it would be TSD or for www.wineandhampergifts.co.uk WAHG if it's a site specific XSLT file otherwise name conventionally i.e. CamelCase
Monday, November 26, 2007 10:30:56 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, November 03, 2007

I'm currently investigating a new Open Source ASP.Net CMS system called Umbraco. It looks very promising as it can be fully accessible and has full support for XHTML among many other interesting features. I met with a couple of the guys that are developing the system in Manchester a couple of weeks ago and they've got big plans so keep an eye on it.

Anyway yesterday while setting up the new The Site Doctor site on Umbraco I ran into an issue where by I had "broken" the menu. Basically I had deleted the template from the system which should have deleted all copies of the pages that use that template but instead a cache remained somewhere. I ran through a number of steps to re-create the cache but nothing worked.

If you run into the same issue as I did, follow these steps:

  1. Find out the ID of the page in question (easiest way is just to add the ID to the output of the menu XSLT)
  2. Search the database for the ID using the T-SQL I posted in "How to search every table and field in a SQL Server Database"
  3. Download and install a Unicode and UTF-8 search program such as Text Workbench and search for the ID (I only found it in /data/Umbraco.config)
  4. Reset the applications cache -the easiest way to do this is to simply re-upload the web.config file

That should sort it :)

Saturday, November 03, 2007 1:31:42 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
# Friday, September 21, 2007

It's been rather quiet on my blog recently, if you're wondering why (and don't chat to me on/off-line) I thought I would share with you what we've been working on recently.

For the past month or so The Site Doctor has been developing a new web site (www.wineandhampergifts.co.uk) for Porter and Woodman Gifts Ltd - a local company that produces personalised corporate hampers and gifts. It's been quite a challenge as they have a rather unusual ordering system that allows multiple recipients/addresses multiple items. Looking at it now, it's not so complicated but the delivery charge calculations and initial specs took a while to fully grasp. It's been really enjoyable.

I'll probably cover aspects of the site over the forthcoming months but there are a few really nice features to the Wine and Hamper Gifts site (or at least I think so), some of which the end user will never know about such as the use of generics to calculate the address/recipient/gift variations) and those that they may -for instance the use of the JavaScript1 Zoom function on the product details page (courtesy of LuckyZoom), also the design created by our excellent designer Gareth Brown all adds up to what has to be one of the best sites I've developed to date.

1 Yes, I did just say I've integrated some JavaScript into the site ;)

I doubt most of my readers are interested on the in's and out's of the project itself but from an SEO perspective, I for one am expecting pretty decent results. We opted to use the URL Rewriting ISAPI from Helicon this time round over our usual IISMods URL Rewriting ISAPI as for some reason the IISMods site has been offline for a while (and checking now has been converted into a very weird site).

Another aspect that some people may be unaware of is that the majority of the Wine and Hamper Gifts site operates the same without JavaScript as it does with JavaScript, this is important not only for screen readers but also search engines. There is only one area of the Wine and Hamper Gifts site that I'm aware of that doesn't operate without JavaScript and that is the "Personalise this gift" link on the cart page that allows the user to either edit the existing message or add one that doesn't already exist, that's because it uses a LinkButton, but I may find a way around that later.

Other features that I really like are little things like the way the drop down lists on the left hand menu are created -they're not actually drop down lists but unordered lists that are then manipulated using JavaScript, I think the JavaScript could do with a little tweaking but the result is superb. The Wine and Hamper Gifts site also creates a PDF receipt for the user which is emailed to them, this is something I've been meaning to look into for some time but haven't had the chance, luckily while I was developing the site, Sean Ronan posted to the MsWebDev list about an ASP.Net PDF library iTextSharp (a port from a Java library) which, despite a few oddities from the POV of the Java port does exactly what I wanted. The library is pretty easy to use once you get your head around it and certainly produces some nice results.

There's still more work that's needed to finalise the content and various aspects of the Wine and Hamper Gifts website but if you have a chance, check out the new Porter and Woodman Gifts Ltd Wine and Hamper Gifts website and leave a comment here letting me know what you think :D

Oh, and they've given us a pretty high target to get before Christmas so if you're thinking about treating your customers to a personalised corporate hamper or gift give a little thought to using www.wineandhampergifts.co.uk

AJAX | ASP.Net | C# | CSS | Design | SEO | The Site Doctor | Web Development
Friday, September 21, 2007 11:20:01 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, August 24, 2007

Server Error in '/' Application.


The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Web.HttpException: The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).

Source Error:

 

Line 132:                        metaKey.Name = "keywords";
Line 133:                        metaKey.Content = p.MetaKeywords;
Line 134:                        this.Page.Header.Controls.Add(metaKey);
Line 135:                    }
Line 136:                    if (!String.IsNullOrEmpty(p.MetaDescription))


Source File: a:\xyz\ContentHandler.aspx.cs    Line: 134

Stack Trace:

 

[HttpException (0x80004005): The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).]
   System.Web.UI.ControlCollection.Add(Control child) +2105903
   ContentHandler.Page_Load(Object sender, EventArgs e) in a:\xyz\ContentHandler.aspx.cs:134
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +15
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +34
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   System.Web.UI.Control.LoadRecursive() +47
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1061


Version Information: Microsoft .NET Framework Version:2.0.50727.832; ASP.NET Version:2.0.50727.832

Another day, another issue ;)

This had me going around in circles for a while until I realised what it was, if you're getting this error you can bet your bottom dollar that you have <%= %> somewhere in your page's header -furthermore I'd hazard a guess that you've got it in some JavaScript to reference an ASP.Net control on the page- and then you're trying to add a control to the header programmatically (or a custom control from someone like Telerik is trying to). Am I right1?

1 I'm not allowed to ask you to so I won't, but if I was right, then spend that bottom dollar clicking on one of the Google Ads :P

I can't tell you exactly why this occurs but my understanding of it is that ASP.Net can't re-create the header if it has Response.Write somewhere in the header (<%=) -most likely due to when the header is created it's not available (will look into it). No doubt you want to know the fix?

The Fix
The fix is simple, remove the inline code blocks and JavaScript and move it to your code behind i.e.:

string _manageSearch = String.Format( @" 
        function ManageSearch(){{
                var lbl = document.getElementById(""lblFindAGift"");
                var txt = document.getElementById(""{0}"");
                var btn = document.getElementById(""{1}"");

                .Do Something with it..

        }}",

        txtSearch.ClientID);

this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "ManageSearch", _manageSearch, true);

Remember: You need to escape the curly brackets otherwise you will get a "String.Format- Exception of type System.Web.HttpUnhandledException was thrown"

Update: Thanks to Julian Voelcker for sending me this alternative "fix" for the problem, can't say I like it though ;) basically instead of using <%= ... %> you would write the databinding expression of: <%# ... %>

Friday, August 24, 2007 10:49:56 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [10]  | 
# Wednesday, August 22, 2007

Type
System.FormatException
Message
Exception of type 'System.Web.HttpUnhandledException' was thrown.
StackTrace
at System.Text.StringBuilder.FormatError()
at System.Text.StringBuilder.AppendFormat(IFormatProvider provider, String format, Object[] args)
at System.String.Format(IFormatProvider provider, String format, Object[] args)
Error Line
0

Just got that message (or at most "Exception of type 'System.Web.HttpUnhandledException' was thrown")? Puzzled? I was the first time I got it, I've been meaning to post about it for quite some time now so seeing as I got it again today I took the hint.

The error is horrifingly obvious when you know about it, in short, you've no doubt got some code that looks like this:

String.Format("<html><head><style type=\"text/css\">body{color: #fff;}</style><body>...");

Can you spot it now? Notice your style declaration is using the curly brackets? Basically String.Format is interpreting that as a placeholder i.e. {0} and is throwing a wobbly.

The solution is simple too, just replace all opening/closing brackets with two i.e:

String.Format("<html><head><style type=\"text/css\">body{{color: #fff;}}</style><body>...");

I hope that helps someone out there :)

P.S. Watch out for methods that use String.Format as they may catch you out in the same way -i.e. Subject of System.Net.Mail.MailMessage

Wednesday, August 22, 2007 9:43:12 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [6]  | 
# Wednesday, August 08, 2007

As with my previous post, we upgraded the AJAX framework on the weekend which broke a few things, but one control in particular that broke was our TextChangedTextBox which is based on Pete Kellner's timed postback control. Since updating we were receiving a "'debug' is undefined" error on line 1409 (which was in one of the JavaScript include files).

Having had this issue before I updated the TextChangedBehavior.js but that didn't sort it, I have the latest version of the Futures on the server too so I was lost. Turns out I had an old version of the AJAX Futures DLL within the Bin folder of the project.

So as with my post on the ASP.Net forums before -make sure you update your AJAX Futures when updating your Microsoft AJAX framework!

Wednesday, August 08, 2007 6:20:47 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, August 06, 2007

Over the weekend we had to upgrade the server's version of ASP.Net AJAX which went fine until this morning when we started receiving the error "Server cannot modify cookies after HTTP headers have been sent."

Luckily this wasn't bubbled up to the UI but incase other people are getting the error I thought I would share the fix. The error is apparently a known error with ASP.Net AJAX and the RolesManager. To fix the error all you need to do is add the following to your RolesManager web.config node:

cacheRolesInCookie="false"


The full error message was

Message:
Server cannot modify cookies after HTTP headers have been sent.

StackTrace:
at System.Web.HttpCookieCollection.Add(HttpCookie cookie)
at System.Web.Security.RoleManagerModule.OnLeave(Object source, EventArgs eventArgs)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Monday, August 06, 2007 1:52:08 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, May 01, 2007

We have recently moved over to SQL Server 2005 and as part of this transfer I decided to aggregate two separate ASP.Net Membership databases that were created purely out of error.

For those of you who don’t already know, you can happily run more than application’s security from a single membership database as long as you correctly configure the web.config’s security settings –for more information on doing that see my post on having dual records in the ASP.Net authentication table (see: Dual Records In The ASPNet Authentication Table). The important attribute/value set to configure if you are planning on running more than one application from the same roles database is “applicationName”. If you do not set “applicationName” you will find that users can log in across all your applications, roles/access levels will get mixed up and a whole bunch of other hullabaloo!

Luckily for me, the only records stored in one of the membership databases were two users, both of which I knew the password to so I decided I would simply update the web.config with the new database connection string and add them manually.

The next thing I wanted to sort however was the specific SQL Login’s access to the membership database, previously I simply added the user to all the various aspnet_ roles that were in the database which worked fine. As I’m looking to use this database for other applications in the future and I don’t like sharing usernames/passwords across applications, adding the roles each time would become a real PITA so I decided to add a new role with all the access required for the database so I could simply add the user to this new role. I called the role IIS_User.

A number of our applications build on the foundation of the ASP.Net Membership database with application specific values and so I tend to have another table for the application’s users within the applications specific database to store these values. The user has the usual UserId (usually an int) and a uniqueidentifier which allows me to link the two database together. With this in mind, I need additional access to the ASP.Net Membership database –SELECT permission on the tables. I don’t like adding more permissions to a role than needed but I needed a method of doing this quickly –assigning EXECUTE and SELECT permissions to the new role on the various tables/stored procedures. In time I’ll revisit this and work out which are needed by the role and remove the permissions not needed but for now this’ll do :)

The quick and dirty T-SQL

DECLARE @SQL nvarchar(4000),
    @Owner sysname,
    @objName sysname,
    @Return int,
    @objType nvarchar(5),
    @rolename nvarchar(255)

SET @rolename = 'IIS_User'

-- Cursor of all the stored procedures in the current database
DECLARE cursStoredProcedures CURSOR FAST_FORWARD
FOR
SELECT 
    USER_NAME(uid) Owner, 
    [name] StoredProcedure,
    xtype
FROM
    sysobjects
WHERE
(
    xtype = 'U'
  OR
    xtype = 'P'
)
  AND
    LEFT([name], 7) = 'aspnet_'

OPEN cursStoredProcedures

-- Get the first row
FETCH NEXT FROM cursStoredProcedures
INTO @Owner, @objName, @objType

-- Set the return code to 0
SET @Return = 0

-- Encapsulate the permissions assignment within a transaction
BEGIN TRAN

-- Cycle through the rows of the cursor
-- And grant permissions
WHILE ((@@FETCH_STATUS = 0) AND (@Return = 0))
  BEGIN

    --Determine the object's type (table/stored procedure) -could 
    --be done using a case too if more objects are added later
    IF @objType = 'P'
    BEGIN
        SET @SQL = 'GRANT EXECUTE ON [' + @Owner + '].[' + @objName  + '] TO ' + @rolename
    END

    IF @objType = 'U'
    BEGIN
        SET @SQL = 'GRANT SELECT ON [' + @Owner + '].[' + @objName  + '] TO ' + @rolename
    END

    -- Execute the SQL statement
    EXEC @Return = sp_executesql @SQL

    -- Get the next row
    FETCH NEXT FROM cursStoredProcedures
    INTO @Owner, @objName, @objType
  END

-- Clean-up after the cursor
CLOSE cursStoredProcedures
DEALLOCATE cursStoredProcedures

-- Check to see if the WHILE loop exited with an error.
IF (@Return = 0)
  BEGIN
    -- Exited fine, commit the permissions
    COMMIT TRAN
  END
ELSE
  BEGIN
    -- Exited with an error, rollback any changes
    ROLLBACK TRAN
    
    -- Report the error
    SET @SQL = 'Error granting permission to ['
    + @Owner + '].[' + @objName + ']'
    RAISERROR(@SQL, 16, 1)
  END
GO
Tuesday, May 01, 2007 8:41:48 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, March 19, 2007

I’ve done a number of posts now on Phil Whinstanley’s error reporting class and this blog appears to be getting a lot of hits because of that which is pretty neat, as a result I’ve had a couple of people write to me asking similar questions about the code so I thought it would be an idea to write a little summary.

Where can I download the code?

It would appear that most of the old copies of Phil’s code have disappeared from the web, I’m not sure why so I’ve uploaded the versions I’ve got below. For convenience I have compiled the code into DLLs for those that don’t know/want to do this and I’ve also included the Visual Studio solutions. I’m not sure if these are based on the original codebase but I don’t think I’ve made any major alterations to these versions:

1 This is a version I was sent as his original including changes and example email was lost...

DLLs only:

If you have Visual Studio:

If you don’t have Visual Studio you can either download one of the above projects and delete the solution/project files or download the original WebException code. Ok, now you have the files :) -FWIW I can accept no responsibility for any of the files or the code, I just zipped them!

How to do I use the WebException class?

I’m now using a slightly modified version of the code to enable error reporting within AJAX (see: Reporting errors from AJAX using the WebException Class) which I’ll try and upload later but whichever version of the code you choose the use is pretty much the same.

Once you have referenced the DLL in your project (see: Importing/Referencing DLLs in Visual Studio) you will be able to use the WebException. As I’ve covered what you need to do to use the code from within an AJAX application in another post (see: Reporting errors from AJAX using the WebException Class) I’ll just cover how to use it to report global errors. To capture and respond to all application errors you will need to place this code within the global.asax, your project should automatically have one, if it doesn’t then you will need to add one.

Using the global.asax file, the first thing you need to do is add a reference to the DLL at the top of your code (this will allow you to call the methods and access the properties):

<%@ Import Namespace="ErrorReporting" %>

Next locate the Application_Error event handler, this is the method that handles all errors within the application (with exception of those thrown from within an AJAX application, read this post to report errors from within an AJAX application). Now replace your Application_Error and Application_PreRequestHandlerExecute handlers with (for more information on what I'm doing here see: ASP.Net WebException and Error Reporting useful code):

void Application_Error(object sender, EventArgs e)
{
    bool reportErrors = Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["SendErrors"]);

    if (reportErrors)
    {
        Exception currentError = Server.GetLastError();

        #region Deal with 404's

        //Redirect the user to a friendly page
        if(CheckForErrorType(currentError, "FileNotFound"))
            RedirectToFriendlyUrl("");

        #endregion
        #region Deal with Spambots

        if (CheckForErrorType(currentError, "System.FormatException"))
        {
            if (HttpContext.Current.Request.Form.Count > 0)
            {
                foreach (string key in HttpContext.Current.Request.Form)
                {
                    if (key.IndexOf("_VIEWSTATE") > 0 && HttpContext.Current.Request.Form[key].ToString().IndexOf("Content-Type") > 0)
                        return;
                }
            }
        }

        #endregion

        //Enable the trace for the duration of the error handling
        TraceContext t = HttpContext.Current.Trace;
        bool bCurrentState = t.IsEnabled;
        t.IsEnabled = true;

        #region Handle the Exception

        ErrorHandling.WebException WE = new ErrorHandling.WebException();
        WE.CurrentException = Server.GetLastError();
        WE.MailFrom = "you@yourdomain.com";
        WE.MailTo = "you@yourdomain.com";
        WE.MailAdmin = "you@yourdomain.com";
        WE.Site = "Your Site's Name or URL";
        WE.SmtpServer = "localhost";
        WE.FloodCount = 10;
        WE.FloodMins = 5;

        #endregion
        #region Choose what you're interested in

        WE.ReturnCache = true;
        WE.DrillDownInCache = true;
        WE.IncludeApplication = true;
        WE.IncludeBrowser = true;
        WE.IncludeEnvironmentVariables = true;
        WE.IncludeForm = true;
        WE.IncludeProcess = true;
        WE.IncludeQueryString = true;
        WE.IncludeRequestCookies = true;
        WE.IncludeRequestHeader = true;
        WE.IncludeResponseCookies = true;
        WE.IncludeServerVariables = true;
        WE.IncludeSession = true;
        WE.IncludeTrace = true;
        WE.IncludeVersions = true;
        WE.IncludeAuthentication = true;

        #endregion

        WE.Handle();

        //Return the trace to its original state
        t.IsEnabled = bCurrentState;

        //Redirect the user to a friendly page
        RedirectToFriendlyUrl("");
    }
}

protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
    if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
        ErrorReporting.SessionTracker.AddRequest("Pre Request Handler Execute"truetruefalse);
}

private bool CheckForErrorType(Exception ex, string errorText)
{
    if (ex != null)
    {
        //Check the exception
        if (ex.GetType().ToString().IndexOf(errorText) > 0)
            return true;
        else
            return CheckForErrorType(ex.InnerException, errorText);
    }
    else
    {
        return false;
    }
}

private void RedirectToFriendlyUrl(string Url)
{
    if (!String.IsNullOrEmpty(Url) && (Request.Url.Host.IndexOf("localhost") < 0))
        Response.Redirect(Url);
}

This will create a new instance of the WebException object, assign the various properties accordingly (you will need to configure these) and then finally handle the error.

That’s it! That’s all you really need to do to have super error reporting instantly installed in your application! If that wasn't enough it's overloaded with a couple of filters for you :). I recommend you read one of my previous posts I’ve added which overviews a few simple tips and tricks when using the WebException class to that improves on its functionality (see: ASP.Net WebException and Error Reporting useful code).

All that's left to do is to test it works (see below).

What should I get from it?

That’s the million dollar question! Once the WebException class has been added to your application you should receive an email every time the application throws an error (which of course means you’ll never get an email from the system!)

View an example of the email you’ll get with all outputs set to true.

More tips/Warnings!

Ok so it’s installed and you’re getting no errors through (because your codes perfect) but there are a couple of other little tweaks I would make to the WebException class to make it a little more useable.

Create a centralised class for it

A while ago I posted a set of “useful” tips for reducing the number of spambot related emails, redirecting the user etc (see: ASP.Net WebException and Error Reporting useful code). That’s fine until you start including the WebException class into multiple projects, managing tweaks to the codebase gets a little cumbersome (i.e. adding the spambot check to all our projects that use the WebException meant a couple of hours of copying and pasting). The work around for me was to wrap it all up into a central static method (see: Reporting errors from AJAX using the WebException Class). I did this rather than fiddling with Phil’s WebException class itself incase he ever got around to releasing another version which would mean a bunch of changes etc.

Limit the page request log

If you have a site where every user is likely to have a high page visit count with most of the pages involving some form of form submission then it may be worth limiting the number of request’s stored as we have found that without limiting these we start receiving very large emails (some topping 10MB).

The reason this is happening is because the session tracker logs all the form elements for the request so if you had i.e. a CMS that submits a page of content every other page request all that data will be stored in the tracker, sticking with the idea of a CMS, your typical text word is around 10bytes (see: How many bytes for...), so say the user writes 500 words per page (which isn’t really a lot) that’s 4.9Kb per form submission plus on the re-display of the page you've got ViewState... That’s just the data submitted by the user, around that, you’ve got all the form fields, field names, session info, query string etc, see how it starts to add up?

The solution is fairly straight forward, what you need to do is alter SessionTracker.cs1:

1I thought I'd done this in a project already but cannot find the source so this may not work.

public class SessionTracker
{
    public static void AddRequest(string Comments, bool DoForm, bool DoQueryString, bool DoCookies)
    {
        Request R = new Request();
        R.Time = DateTime.Now;
        R.Comments = Comments;
        
        if (System.Web.HttpContext.Current != null)
        {
            R.Path = System.Web.HttpContext.Current.Request.Path.ToString();
            if (System.Web.HttpContext.Current.Request.UrlReferrer != null)
            {
                R.Referrer = System.Web.HttpContext.Current.Request.UrlReferrer.ToString();
            }
            if (DoForm)
            {
                R.Form = System.Web.HttpContext.Current.Request.Form;
            }
            if (DoQueryString)
            {
                R.QueryString = System.Web.HttpContext.Current.Request.QueryString;
            }
            if (DoCookies)
            {
                R.Cookies = System.Web.HttpContext.Current.Request.Cookies;
            }
        }

        if (System.Web.HttpContext.Current.Session["RequestCollection"] != null)
        {
            RequestCollection RC = ((RequestCollection)System.Web.HttpContext.Current.Session["RequestCollection"]);
            RC.Add(R);
            if(RC.Count > 10)
                RC.RemoveAt(0);
            System.Web.HttpContext.Current.Session["RequestCollection"] = RC;
        }
        else
        {
            RequestCollection RC = new RequestCollection();
            RC.Add(R);
            System.Web.HttpContext.Current.Session["RequestCollection"] = RC;
        }
    }

    public static void AddRequest(string Comments)
    {
        Request R = new Request();
        R.Time = DateTime.Now;
        R.Comments = Comments;
        
        if (System.Web.HttpContext.Current != null)
        {
            R.Path = System.Web.HttpContext.Current.Request.Path.ToString();
            if (System.Web.HttpContext.Current.Request.UrlReferrer != null)
            {
                R.Referrer = System.Web.HttpContext.Current.Request.UrlReferrer.ToString();
            }
            R.Form = System.Web.HttpContext.Current.Request.Form;
            R.QueryString = System.Web.HttpContext.Current.Request.QueryString;
            R.Cookies = System.Web.HttpContext.Current.Request.Cookies;
        }

        if (System.Web.HttpContext.Current.Session["RequestCollection"] != null)
        {
            RequestCollection RC = ((RequestCollection)System.Web.HttpContext.Current.Session["RequestCollection"]);
            RC.Add(R);
            if (RC.Count > 10)
                RC.RemoveAt(0);
            System.Web.HttpContext.Current.Session["RequestCollection"] = RC;
        }
        else
        {
            RequestCollection RC = new RequestCollection();
            RC.Add(R);
            System.Web.HttpContext.Current.Session["RequestCollection"] = RC;
        }
    }

    public static void AddRequest()
    {
        Request R = new Request();
        R.Time = DateTime.Now;
        
        if (System.Web.HttpContext.Current.Session["RequestCollection"] == null)
        {
            RequestCollection RC = ((RequestCollection)System.Web.HttpContext.Current.Session["RequestCollection"]);
            RC.Add(R);
            if (RC.Count > 10)
                RC.RemoveAt(0);
            System.Web.HttpContext.Current.Session["RequestCollection"] = RC;
        }
        else
        {
            RequestCollection RC = new RequestCollection();
            RC.Add(R);
            System.Web.HttpContext.Current.Session["RequestCollection"] = RC;
        }
    }

    public SessionTracker()
    {
    }
}

Outputting the Trace with the WebException Class

I know this is something I’ve posted about in the past but since moving to version 4 of the code and .Net 2.0 I was no longer getting the trace in my lovely error reports, after a little digging I’ve found a solution, in addition to the code that I posted earlier about enabling the trace using C#, the web.config needs to be set as follows:

<trace enabled="true" requestLimit="100" pageOutput="false" traceMode="SortByTime" localOnly="true" />

Storing the WebException code in App_Code Dir

If you use the WebException class in an ASP.Net 2.0 site, be careful you don’t do what we did and throw the site online uncompiled with a compilation error as it won’t get reported. Luckily I found this issue on a test site but it’s still worth noting.

Personally I wouldn’t put the error reporting code in the App_Code directory as this means you’ll end up needing to maintain a plethora of files throughout various projects. Instead compile a separate DLL and include that in your projects, then if like me you find a nice addition to the error reporting code you can easily update all sites to the latest version!

Setup a simple generic test page

Nothing fancy, just a button that throws an exception will do:

TestErrorPage.aspx

<%@ Page Language="C#" %>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void btnError_Click(object sender, EventArgs e)
    {
        throw new ArgumentException("Test Error");
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Test Error Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <p><asp:Button runat="server" ID="btnError" Text="Throw Error" OnClick="btnError_Click" /></p>
    </div>
    </form>
</body>
</html>

Happy Error Reporting :) -I'm hoping this is the last time I need to blog about this code but what's the betting another post is around the corner ;)

Monday, March 19, 2007 7:34:20 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
# Monday, March 12, 2007

Another post from Doug Setzer from 27Seconds.com :)


At my "day job", the systems guys are building new Windows 2003 servers to upgrade our aging Windows 2000 servers.  The plan is to:
 - Build the new Windows 2003 server
 - Install IIS
 - Install .NET
 - Run the IIS migration tool from the old Win2k server

That all went as well as could go - little things got mixed up and had to be corrected.  But, the server would let you request plain HTML files and ASPX files, but classic ASP pages were returned blank.  In poking around Google and the server, we came to find that we had to enable ASP content via:
 - IIS Manager
 - Web Services Extensions
 - Specifically allow Active Server Pages

But, we were still having the same issues.  Stopping and restarting IIS didn't help. Nor did a server reboot.

I found a blog post that mentioned checking that the ASP ISAPI has the correct path.  It tried a random thought that Microsoft has changed the default name of the "Windows"/"Winnt" folder -- Windows NT4, 2000, etc. all use "Winnt", where as Windows 2003 uses the "Windows" folder.  Sure enough, double checking the path to the ASP ISAPI had the wrong path and fixing this path fixed our issues with classic ASP files.

Monday, March 12, 2007 10:49:09 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
# Friday, March 09, 2007

This morning Julian Voelcker came to me with an interesting issue that I’ve looked into before but I’ve never really looked into a re-useable solution. Seeing as it’s fun Friday I thought why not ;)

The scenario: I would like to offer my users a custom mail merge facility where by they can insert values stored in the database such as their name. The selection of columns is unlikely to be changed and if it does then I’ll be the one to do it. There are about 20 fields to choose from.

Easy enough, in the past I’ve kept it to a minimum and then just done a simple find and replace on the body i.e.:

//Create a dataset and add some test columns
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Email");

#region Add some test data

DataRow dr = dt.NewRow();
dr["Name"] = "Julian";
dr["Email"] = "julian@email.com";
dt.Rows.Add(dr);

dr = dt.NewRow();
dr["Name"] = "Tim";
dr["Email"] = "tim@email.com";
dt.Rows.Add(dr);

#endregion

#region Create the example email body

string emailBody = "<p>This is a test email to {{Name}} that would be sent to the email address: {{Email}}.</p>";

#endregion

#region Do the work

//Loop through the rows
for (int i = 0; i < dt.Rows.Count; i++)
{
    //Get the data row for this instance
    DataRow row = dt.Rows[i];

    //Create a new body as this'll be updated for each user
    string body = String.Empty;

    //Update the body
    body = emailBody.Replace("##Name##", row["Name"]);
    body = body.Replace("##Email##", row["Email"]);

    litOutput.Text += String.Format("{0}<hr />", body);
}

#endregion

The issue I see with this however is (among others) having 20 fields is a lot to be doing with a find/replace statement as it wouldn’t be very elegant and a nightmare to manage. Sticking with this method of using a dataset I suggested we use a regular expression to match the field delimiters and do a replace that way:

//Create a dataset and add some test columns
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Email");

#region Add some test data

DataRow dr = dt.NewRow();
dr["Name"] = "Julian";
dr["Email"] = "julian@email.com";
dt.Rows.Add(dr);

dr = dt.NewRow();
dr["Name"] = "Tim";
dr["Email"] = "tim@email.com";
dt.Rows.Add(dr);

#endregion

#region Create the example email body

string emailBody = "<p>This is a test email to {{Name}} that would be sent to the email address: {{Email}}.</p>";

#endregion

#region Do the work

//Loop through the rows
for (int i = 0; i < dt.Rows.Count; i++)
{
    //Get the data row for this instance
    DataRow row = dt.Rows[i];

    MatchEvaluator replaceField = delegate(Match m)
    {
        return row[m.Groups[1].ToString()].ToString();
    };

    //Create a new body as this'll be updated for each user
    string body = String.Empty;
    //Find the fields
    Regex r = new Regex(@"{{(\w{0,15}?)}}");
    body = r.Replace(emailBody, replaceField);

    litOutput.Text += String.Format("{0}<hr />", body);
}

#endregion

This is alright and in many ways very scaleable. I’m not a fan of DataSets but in this instance it works nicely and does mean expanding the available fields at a later date would just be a matter of adding columns to the query.

How does this relate to accessing a property of an object using a string value instead? Well there was a catch, Julian wasn’t using a DataSet and didn’t want to, he had a collection of custom objects all ready and waiting. As he uses a code generator to generate his Data Access Layer and Business Logic Layer there was a method already exposed allowing you to search for a property by string but it's not a standard .Net method so I decided to work out how it was done.

The solution it turned out was a really rather elegant solution IMHO. Using reflection you can use the same concept as above but with custom objects and Robert is your father’s wife’s sister:

Reflection.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Reflection.aspx.cs" Inherits="Reflection" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Reflection Demo</h1>
        <p>Choose from the following fields to build up your email message, the valid fields are (you can choose whether to use non-valid fields as a test if you like):</p>
        <ul>
            <li>Id</li>
            <li>Email</li>
            <li>Name</li>
            <li>JoinedDate</li>
        </ul>
        <p><asp:CheckBox ID="chkCaseSensitive" runat="server" Text="Make the property search case insensitive" /></p>
        <p><label for="txtEmailBody">Example email body:</label><br />
        <asp:TextBox runat="server" ID="txtEmailBody" TextMode="MultiLine" style="width: 500px; height: 200px;" /></p>
        <p><small>HTML submissions are not allowed and they're encoded anyways so no point in spamming -not that you were going to of course!</small></p>
        <p><asp:Button runat="server" ID="btnSubmit" Text="Merge It!" OnClick="btnSubmit_Click" /></p>
        <asp:Literal ID="litOutput" runat="server" />
    </div>
    </form>
</body>
</html>

Reflection.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
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.RegularExpressions;
using System.Collections.Generic;
using System.Reflection;

public class TestObject
{
    private int __Id;
    private string __Name;
    private string __Email;
    private DateTime __JoinedDate;

    public int Id
    {
        get
        {
            return __Id;
        }
        set
        {
            __Id = value;
        }
    }
    public string Name
    {
        get
        {
            return __Name;
        }
        set
        {
            __Name = value;
        }
    }
    public string Email
    {
        get
        {
            return __Email;
        }
        set
        {
            __Email = value;
        }
    }
    public DateTime JoinedDate
    {
        get
        {
            return __JoinedDate;
        }
        set
        {
            __JoinedDate = value;
        }
    }

    public TestObject(int id, string name, string email, DateTime joinedDate)
    {
        __Id = id;
        __Name = name;
        __Email = email;
        __JoinedDate = joinedDate;
    }

    public bool GetPropertyValueByName(string propertyName)
    {
        object obj = null;
        return this.GetPropertyValueByName(propertyName, falseref obj);
    }

    public bool GetPropertyValueByName(string propertyName, ref object val)
    {
        return this.GetPropertyValueByName(propertyName, falseref val);
    }

    public bool GetPropertyValueByName(string propertyName, bool caseInsensitive, ref object val)
    {
        PropertyInfo p = null;
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        //If it's a case-insensitive search then add the flag
        if (caseInsensitive)
            flags = flags | BindingFlags.IgnoreCase;

        p = this.GetType().GetProperty(
               propertyName,
               flags,
               null,
               null,
               Type.EmptyTypes,
               null);

        //Check the property exists and that it has read access
        if (p != null && p.CanRead)
        {
            //There is a property that matches the name, we can read it so get it
            val = this.GetType().InvokeMember(
                propertyName,
                BindingFlags.GetProperty | flags,
                null,
                this,
                null);

            //We return true as the user may just want to check that it exists
            return true;
        }

        return false;
    }
}

public partial class Reflection : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            #region Create the example email body

            txtEmailBody.Text = "Dear {{Name}},\r\n\r\nThis is a test email that would be sent to the email address: {{Email}}.\r\n\r\n{{Name}} joined on: {{JoinedDate}}. This field should not be found {{Don't Find Me}}\r\n\r\nRegards,\r\n\r\nThe webmaster.";

            #endregion
        }
    }

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        if (Page.IsValid && !String.IsNullOrEmpty(txtEmailBody.Text))
        {
            litOutput.Text = "<h2>Output</h2>";

            #region Perform some basic tests
            litOutput.Text += "<h3>Perform some basic tests:</h3>";
            TestObject testObject = new TestObject(1"Tim""tim@email.com", DateTime.Today);

            object obj = null;
            if (testObject.GetPropertyValueByName("id"falseref obj))
                litOutput.Text += String.Format("<li>{0}</li>", obj);
            else
                litOutput.Text += "<li>Doesn't Exist</li>";

            if (testObject.GetPropertyValueByName("name"trueref obj))
                litOutput.Text += String.Format("<li>{0}</li>", obj);
            else
                litOutput.Text += "<li>Doesn't Exist</li>";

            if (testObject.GetPropertyValueByName("joineddate"trueref obj))
                litOutput.Text += String.Format("<li>{0}</li>", obj);
            else
                litOutput.Text += "<li>Doesn't Exist</li>";

            if (testObject.GetPropertyValueByName("nothere"trueref obj))
                litOutput.Text += String.Format("<li>{0}</li>", obj);
            else
                litOutput.Text += "<li>Doesn't Exist</li>";

            #endregion

            #region Create a collection and add a couple of items

            List<TestObject> testObjects = new List<TestObject>();
            testObjects.Add(new TestObject(1"Tim""tim@email.com", DateTime.Parse("01/02/2007")));
            testObjects.Add(new TestObject(2"Jim""jim@email.com", DateTime.Parse("20/02/2007")));
            testObjects.Add(new TestObject(3"John""john@email.com", DateTime.Parse("02/03/2007")));
            testObjects.Add(new TestObject(4"Gill""gill@email.com", DateTime.Parse("01/04/2007")));
            testObjects.Add(new TestObject(5"Bill""bill@email.com", DateTime.Parse("11/02/2007")));

            #endregion

            #region Do the work

            //Format it with <pre> for simplicity
            litOutput.Text += "<h3>Now for the reflection example:</h3><hr /><pre>";

            //Loop through the rows
            foreach (TestObject t in testObjects)
            {
                MatchEvaluator replaceField = delegate(Match m)
                {
                    //Get the property name (depending on your regex but
                    //mine groups the squigly brackets in there incase
                    //a match can't be found
                    string pName = m.Groups[2].ToString();

                    //Check it's not null
                    if (!String.IsNullOrEmpty(pName))
                    {
                        //Create an object that'll be returned from the method
                        object o = null;
                        //Check if that property exists, if it does return it
                        if (t.GetPropertyValueByName(pName, chkCaseSensitive.Checked, ref o))
                            return o.ToString();
                    }
                    //We've not found a match for the property in the object
                    //so return the match instead as it's probably a mistake
                    return m.Value;
                };

                //Create a new body as this'll be updated for each user
                string body = String.Empty;

                //Find the fields within the main body -this can be any of the properties of the object
                Regex r = new Regex(@"({{)(\w{0,15}?)(}})");
                body = r.Replace(txtEmailBody.Text, replaceField);
                //Output the example content (HtmlEncoded so not to hurt us!!)
                litOutput.Text += String.Format("{0}<hr />", Server.HtmlEncode(body));
            }

            litOutput.Text += "</pre>";

            #endregion
        }
    }
}

I’ve thrown up a quick demo if you want to test it out. I think in the longer run I’m going to look into having it generate some form of reporting system as that’d be seriously nice, but the suns out and I need to go for a paddle so that’ll have to wait for another day! So that's my first delve into reflection and so far I love it!

Friday, March 09, 2007 5:12:02 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 

I’ve been using Phil Whinstanley’s error reporting class1 within my applications for some time now and it really does help with diagnosing issues with the site’s during development (or client testing) but also alerting me to errors on live sites. I also like it because it can highlight hacking attempts and also spambot form submissions –allowing you to alter the site as needed. A lot of the time it also means we’re alerted to an issue with the site before the client has a chance to call.

1 Note: I've been told the files Phil put online all those years ago are offline but don't panic, I'm posting another post with the relevant files shortly. If you don't want to use the search function (top right) or you're just keen, check out my comment within my post about ASP.Net WebException and Error Reporting useful code.

I’m glad he developed it because before this was around I was using a very simple email alert system that didn’t contain even a third of what this one does. Historically in ASP we always reported 500-100 errors as I don’t like clients spotting issues before I do. It’s very important to include error reporting in your code otherwise you may miss a sequence of events that causes your client to loose out on a sale.

Recently however we got in on the Atlas/AJAX scene pretty early on because we had a new application that would really benefit from a lack of postback and as it was an internal application only where we had complete control over the user’s environment, accessibility wasn’t so much of a concern (though FWIW you can still use the site in the same way without JavaScript activated).

At present, our development server’s SMTP server isn’t working properly so I didn’t think anything of receiving no email when I threw an exception during the early stages of development but as soon as I threw it onto the live server I quickly noticed that I wasn’t receiving errors from the application (we’ve got a test page to ensure the error reporting is working as expected), on investigation I found that the errors were being caught by the Atlas/AJAX handler (in a similar way to a try/catch block) which meant no emails were being sent out –so what do you do?

Note: Since I first started this article, Atlas has been released by Microsoft and is now AJAX and as part of the current release, Atlas/AJAX allows you to capture errors that are otherwise trapped by the framework and handle them as you like but for completeness I’ll overview things I tried.

Firstly I tried simply bubbling the error up to the global.asax’s Application_Error event handler as I normally would but that won’t work as it will still be trapped by the Atlas/AJAX framework, further more, the error returned to the user isn’t very useful (it’s the text within the exception):

Example standard Atlas/AJAX error - a pretty useless error message as far as the user is concerned!

The next thing I tried was taking the exception and passing it to the WebException as you do within the Application_Error event handler, although this worked and for this project would have been an alright solution because the ScriptManager was contained within a single MasterPage, I wanted a solution that I could easily roll out to other projects.

What I decided to do in the end was to wrap the WebException class and adding a single static method that takes an exception, then I replaced the code within the Global.asax and within the ScriptManager’s error event handler and responded to the user with a more informative message. The code below will output a user friendly message -still in a popup though you could redirect if desired. In the live application the user's location and a reference for the incoming error email is also shown to the user.

Note: TSDGlobals is a settings class we use here, it just references the relevant setting and contains a set of useful methods that we use throughout most of our projects.

aspx code

<asp:ScriptManager runat="server" ID="sm" EnablePartialRendering="true" AllowCustomErrorsRedirect="true" OnAsyncPostBackError="atlasScriptManager_PageError"></asp:ScriptManager>

codebehind

protected void atlasScriptManager_PageError(object sender, AsyncPostBackErrorEventArgs e)
{
    //A page reference for you (optional but useful)
    string __PageRef = "132";
    //Update the message the user will see
    sm.AsyncPostBackErrorMessage = String.Format("I'm sorry,  an error has occured, please contact us on 01234 567890. Quoting Page Ref: {0} - {1}", __PageRef, DateTime.Now.ToString());
    //Pass it through to the new Error Handler
    ErrorHandling.ErrorHandler.Handle(e.Exception);
}

global.asax

void Application_Error(object sender, EventArgs e)
{
    ErrorHandling.ErrorHandler.Handle(Server.GetLastError());
}

protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
    if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
        ErrorReporting.SessionTracker.AddRequest("Pre Request Handler Execute"truetruefalse);
}

ErrorHandler.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace ErrorHandling
{
    public class ErrorHandler
    {
        //Declare for the scope of the class
        private static HttpRequest context = HttpContext.Current.Request;

        public static void Handle(Exception currentError)
        {
            Handle(currentError, true);
        }

        public static void Handle(Exception currentError, bool redirectUser)
        {
            if (TSDGlobals.SendSiteErrors)
            {
                #region Deal with 404's

                //Redirect the user to a friendly page
                if (CheckForErrorType(currentError, "FileNotFound") && redirectUser)
                    RedirectToFriendlyUrl(TSDGlobals.ErrorPage_PageNotFound);

                #endregion
                #region Deal with Spambots

                //Check the error type
                if (CheckForErrorType(currentError, "System.FormatException"))
                {
                    if (context.Form.Count > 0)
                    {
                        foreach (string key in context.Form)
                        {
                            if (key.IndexOf("_VIEWSTATE") > 0 && context.Form[key].ToString().IndexOf("Content-Type") > 0)
                                return;
                        }
                    }
                }

                #endregion

                //Enable the trace for the duration of the error handling
                TraceContext t = HttpContext.Current.Trace;
                bool bCurrentState = t.IsEnabled;
                t.IsEnabled = true;

                #region Handle the Exception

                WebException WE = new WebException();
                WE.CurrentException = currentError;
                WE.Site = context.Url.Host.ToString();
                //Pull the information from the web.config here if desired
                WE.FloodCount = 50;
                WE.FloodMins = 5;

                #endregion
                #region Choose what you're interested in

                WE.ReturnCache = true;
                WE.DrillDownInCache = true;
                WE.IncludeApplication = true;
                WE.IncludeBrowser = true;
                WE.IncludeEnvironmentVariables = true;
                WE.IncludeForm = true;
                WE.IncludeProcess = true;
                WE.IncludeQueryString = true;
                WE.IncludeRequestCookies = true;
                WE.IncludeRequestHeader = true;
                WE.IncludeResponseCookies = true;
                WE.IncludeServerVariables = true;
                WE.IncludeSession = true;
                WE.IncludeTrace = true;
                WE.IncludeVersions = true;
                WE.IncludeAuthentication = true;

                #endregion

                WE.Handle();

                //Return the trace to its original state
                t.IsEnabled = bCurrentState;

                //Redirect the user to a friendly page
                if (redirectUser)
                    RedirectToFriendlyUrl(TSDGlobals.ErrorPage_CodeIssue);
            }
        }

        private static bool CheckForErrorType(Exception ex, string errorText)
        {
            if (ex != null)
            {
                //Check the exception
                if (ex.GetType().ToString().IndexOf(errorText) > 0)
                    return true;
                else
                    return CheckForErrorType(ex.InnerException, errorText);
            }
            else
            {
                return false;
            }
        }

        private static void RedirectToFriendlyUrl(string Url)
        {
            //Only redirect the user if the URL is not empty and we're not on a dev machine
            //TODO: Check the referrer to ensure we don't redirect the user to the page causing the error!
            //TODO: Pull the list of development server addresses from an XML file
            if (!String.IsNullOrEmpty(Url) && (context.Url.Host.IndexOf("localhost") < 0))
                HttpContext.Current.Response.Redirect(Url);
        }
    }
}

I’m not sure if this is a recommended way of doing it but it works pretty well and in my case, the majority of settings from the code are the same regardless of the project but you can still alter those if required –as they’re not likely to change project-project I’ve kept the settings within the web.config. I decided to wrap Phil’s code in my own because that way if he ever releases an update (not sure what that’d do tbh) I could just drop the new WebException code into my project and be ready to go straight away.

What do you think Phil? Use or Abuse of your code ;)

Friday, March 09, 2007 7:57:18 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Saturday, February 24, 2007

As part of my blog’s re-design I wanted to integrate my statistics from Last.FM which monitors what music you’re listening to and generates a stack of statistics about your listening habit (see About Last FM for more information).

Anyways, I started writing my own RSS macro when I came across one already developed by John Forsythe (http://www.jforsythe.com/) which did pretty much exactly what I was planning on developing, the only difference though was that his was hard-coded to preset node names whereas I was planning on using an XSL file to format mine to offer maximum flexibility in the long run so I updated his with the use of reflector (thanks to John Forsythe though!!).

There are a couple of difference to note with this code and John Forsythe's:

  • The RSS retrieval is no longer handled by an external library -in this instance I wanted to keep this as simple and stand-alone as possible.
  • There is no max item count at present -this is mainly because I didn't need it for the Last.FM Feed, I may alter that later.

Source code for a dasBlog XSL based RSS reader

using System;
using System.IO;
using System.Security.Cryptography;
using System.Diagnostics;
using System.Text;
using System.Web;
using System.Web.UI;

using newtelligence.DasBlog.Runtime;
using newtelligence.DasBlog.Web.Core;

namespace TSDMacros
{
    public class TheSiteDoctor
    {
        protected SharedBasePage requestPage;
        protected Entry currentEntry;

        public TheSiteDoctor(SharedBasePage page, Entry entry)
        {
            requestPage = page;
            currentEntry = entry;
        }

        /// <summary>
        /// A dasBlog macro to retrieve an RSS feed and apply XSL to 
        /// it before caching it for x minutes
        /// </summary>
        /// <param name="xslVPath">The virtual path of the XSL file</param>
        /// <param name="rssPath">The RSS feed URL</param>
        /// <param name="minutesToCache">Number of minutes to cache the file for</param>
        /// <param name="debugMode">Output the debug information</param>
        /// <returns>A control that can be inserted into a dasBlog template</returns>
        public virtual Control GetRSS(string xslVPath, string rssPath, int minutesToCache, bool debugMode)
        {
            string cacheVDir = "./content/getrsscache/";
            string cachedFileLoc = String.Empty;
            StringBuilder output = new StringBuilder();

            bool writeToCache = false;
            bool cacheExpired = false;
            bool cacheExists = false;

            #region Debug output
            if (debugMode)
            {
                output.Append("<strong>&lt;start debug&gt;</strong><hr />\r\n");
                output.AppendFormat("<i>RssPath</i>: {0}<br />\r\n", rssPath);
                output.AppendFormat("<i>minutesToCache</i>: {0}<br />\r\n", minutesToCache);
                output.AppendFormat("<i>CacheStorageFolder</i>: {0}<br />\r\n", cacheVDir);
                output.Append("<hr />\r\n");
            }
            #endregion

            #region Check whether we need to cache or not
            if (minutesToCache > 0)
            {
                writeToCache = true;
                //Find the cache directory
                string cacheDir = HttpContext.Current.Server.MapPath(cacheVDir);
                //Work out what the file would be called based on the RSS URL
                cachedFileLoc = Path.Combine(cacheDir, HttpUtility.UrlEncode(TheSiteDoctor.GetMd5Sum(rssPath)) + ".cache");
                #region Debug output
                if (debugMode)
                {
                    output.AppendFormat("<i>cache file</i>: {0}\r\n", cachedFileLoc);
                }
                #endregion
                if (!File.Exists(cachedFileLoc))
                {
                    cacheExpired = true;
                    #region Debug output
                    if (debugMode)
                    {
                        output.Append("<i>cache age</i>: no file exists<br />");
                    }
                    #endregion
                }
                else
                {
                    FileInfo info1 = new FileInfo(cachedFileLoc);
                    TimeSpan span1 = (TimeSpan)(DateTime.Now - info1.LastWriteTime);
                    if (span1.TotalMinutes > minutesToCache)
                    {
                        cacheExists = true;
                        cacheExpired = true;
                    }
                    #region Debug output
                    if (debugMode)
                    {
                        output.AppendFormat("<i>cache age</i>: : {0} min old <br />\r\n", span1.TotalMinutes);
                    }
                    #endregion
                }
            }
            else
            {
                #region Debug output
                if (debugMode)
                {
                    output.Append("<strong>caching disabled - CacheStorageAgeLimit=0</strong><br /><span style=\"color:red; font-weight: bold;\">FYI: All requests to this page will cause a new server request to the RssPath</span><br />");
                }
                #endregion
                cacheExpired = true;
            }

            #endregion

            #region Debug output
            if (debugMode)
            {
                output.Append("<hr />");
            }
            #endregion
            //Check whether or not the cache has expired
            if (cacheExpired)
            {
                #region Debug output
                if (cacheExists & debugMode)
                {
                    output.Append("<strong>file cache is expired, getting a new copy right now</strong><br />");
                }
                else if (debugMode)
                {
                    output.Append("<strong>no cache, getting file</strong><br />");
                }
                #endregion
                //The cache has expired so retrieve a new copy
                output.Append(TheSiteDoctor.delegateRss(xslVPath, rssPath, 0, writeToCache, cachedFileLoc, debugMode));
            }
            else
            {
                #region Debug output
                if (debugMode)
                {
                    output.Append("<strong>cool, we got the file from cache</strong><br />");
                }
                #endregion
                //The cache still exists and is valid
                StreamReader reader1 = File.OpenText(cachedFileLoc);
                output.Append(reader1.ReadToEnd());
                reader1.Close();
            }
            #region Debug output
            if (debugMode)
            {
                output.Append("<hr /><strong>&lt;end debug&gt;</strong>");
            }
            #endregion

            output.Append("\r\n<!-- \r\ndasBlog RSS feed produced using the macro from Tim Gaunt\r\nhttp://blogs.thesitedoctor.co.uk/tim/\r\n-->");

            return new LiteralControl(output.ToString());
        }

        /// <summary>
        /// RSS feed retrieval worker method. Retrieves the RSS feed 
        /// and applies the specified XSL document to it before caching 
        /// a copy to the disk -this should be called after it has been 
        /// established the cache is out of date.
        /// </summary>
        /// <param name="xslVPath">The virtual path of the XSL file</param>
        /// <param name="rssPath">The RSS feed URL</param>
        /// <param name="timeoutSeconds">Number of seconds before the request should timeout</param>
        /// <param name="writeCache">Whether to cache a copy on disk</param>
        /// <param name="xmlPath">Physical path of the XML file on the disk</param>
        /// <param name="debugMode">Output the debug information</param>
        /// <returns>An XML document as a string</returns>
        private static string delegateRss(string xslVPath, string rssPath, int timeoutSeconds, bool writeCache, string xmlPath, bool debugMode)
        {
            StringBuilder output = new StringBuilder();
            bool errorThrown = false;
            string cacheVDir = "./content/getrsscache/";
            string xslPath = HttpContext.Current.Server.MapPath(xslVPath);

            try
            {
                //TODO: Replace this with a HttpRequest and timeout to ensure the visitor is not left waiting for the file to load
                //Load the XML
                System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
                xmlDoc.Load(rssPath);

                //Load the XSL
                System.Xml.Xsl.XslTransform xslDoc = new System.Xml.Xsl.XslTransform();
                xslDoc.Load(xslPath);
                
                StringBuilder sb = new StringBuilder();
                StringWriter sw = new StringWriter(sb);

                //Apply the XSL to the XML document
                xslDoc.Transform(xmlDoc, null, sw);

                //Append the resulting code to the output file
                output.Append(sb.ToString());
            }
            catch (Exception ex)
            {
                errorThrown = true;
                #region Debug output
                if (debugMode)
                {
                    //Log the exception to the dasBlog exception handler
                    ErrorTrace.Trace(TraceLevel.Error, ex);
                    output.AppendFormat("<ul style=\"\"><li><strong>RSS request failed :(</strong> <br />{0}</li></ul>", ex.ToString());
                }
                #endregion
            }

            //Save a cache of the returned RSS feed if no errors occured
            if (writeCache & !errorThrown)
            {
                //Find the cache's storage directory
                DirectoryInfo dir = new DirectoryInfo(HttpContext.Current.Server.MapPath(cacheVDir));
                //Check it exists
                if (!dir.Exists)
                {
                    dir.Create();
                    #region Debug output
                    if (debugMode)
                    {
                        output.AppendFormat("<strong>just created the directory:</strong> {0}<br />"HttpContext.Current.Server.MapPath(cacheVDir));
                    }
                    #endregion
                }
                //Create the file
                StreamWriter writer1 = File.CreateText(xmlPath);
                writer1.Write(output);
                writer1.Flush();
                writer1.Close();
                #region Debug output
                if (debugMode)
                {
                    output.Append("<strong>just wrote the new cache file</strong><br />");
                }
                #endregion
            }

            return output.ToString();
        }

        /// <summary>
        /// Worker method to identify the MD5 checksum of a string
        /// in this instance used to ensure the RSS file isn't already
        /// cached (based on the URL supplied)
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static string GetMd5Sum(string str)
        {
            Encoder encoder1 = Encoding.Unicode.GetEncoder();
            byte[] buffer1 = new byte[str.Length * 2];
            encoder1.GetBytes(str.ToCharArray(), 0, str.Length, buffer1, 0true);
            byte[] buffer2 = new MD5CryptoServiceProvider().ComputeHash(buffer1);
            StringBuilder builder1 = new StringBuilder();
            for (int minsToCache = 0; minsToCache < buffer2.Length; minsToCache++)
            {
                builder1.Append(buffer2[minsToCache].ToString("X2"));
            }
            return builder1.ToString();
        }

    }
}

XSL that I use for Last.FM

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" /> 
  <xsl:template match="/">
    <h2>Recent Tracks</h2>
    <ul>
    <xsl:for-each select="recenttracks/track">
        <li>
            <a href="{url}">
                <xsl:value-of select="artist" /> - <em><xsl:value-of disable-output-escaping="yes" select="name" /></em>
            </a>
        </li>
    </xsl:for-each>
    </ul>
    <p><a href="About-Last-FM.aspx" title="last.fm - The Social Music Revolution"><img alt="last.fm - The Social Music Revolution" src="images/lastfm_mini_black.gif" /></a></p>
  </xsl:template>
</xsl:stylesheet>

To use it on the blog template

<% GetRSS("LastFM.xsl", "http://ws.audioscrobbler.com/1.0/user/timgaunt/recenttracks.xml", 25, false)|tsd %>

This is a pretty crude way of doing it IMHO because the XSL transforms the stream directly, eventually I’ll update the code so it includes a timeout (as John’s did) and having seen the performance implications on my blog, make sure the request is made asynchronously.

FWIW I have set my cache value to 25minutes, I did have it as 1min for fun but it killed the blog, why have I set it to 25mins? Well, most of my tracks I would think are 2-3minutes long, as I list 10 tracks at a time that’s 20-30minutes listening time so it’ll still keep a fairly accurate overview of my tracks without having massive performance issues on my blog :)

Incase you don't want to or know how to create this macro as a DLL I have created it for you :)

dasBlog RSS feed macro" onclick="javascript:urchinTracker('/download/zip/TSDMacros_v1_23-02-07');" href="/tim/files/TSDMacros_v1_23-02-07.zip">Download the complete dasBlog RSS feed macro (4KB - MD5 Hash: e3d7d6320109fd07259e8d246b754f13)

Saturday, February 24, 2007 2:39:04 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
# Tuesday, February 13, 2007

A couple of people have got stuck on various lists/forums I’m on moving from ASP to ASP.Net and the differences there are, the one first major sticking point I had was referencing DLLs –so don’t worry you’re not the only one! So this is a really simple look at what you need to look at and how you reference DLLs –if you’ve ever added a DLL before you’ll probably find that this is too simplistic for you but read on anyway!
Firstly, referencing a DLL is basically a way of including someone else’s code within your project (or a common codebase that you re-use), this also includes controls, useful/common functions or just additional functionality such as Crystal reports.
Before you can use someone else’s code (i.e. Phil Whinstanley’s error reporting class) within your code you have to include a reference to the relevant DLL. The first thing I would do is create a folder somewhere that’s easily accessible to all machines that may need to reference the DLL i.e. “c:\Useful DLLs\”. Then, within this folder, I would create the following sub folders:

  • c:\Useful DLLs\.Net 1.1
  • c:\Useful DLLs\.Net 2.0
  • c:\Useful DLLs\.Net 3.0
  • c:\Useful DLLs\ASP.Net 1.1
  • c:\Useful DLLs\ASP.Net 2.0
  • c:\Useful DLLs\ASP.Net 3.0

This is something that I’ve only recently started doing after having multiple releases for the same DLL. For each DLL place a copy within the relevant folder.
Next, load your project within Visual Studio, right click the solution (this is the very top of the tree) and select “Add Reference":

A window will then popup that looks something like this:

Depending on what sort of reference this is, the majority of the time I would expect you’ll be needing to use the “Browse” tab –this allows you to navigate the FSO and find the DLL to reference (which should be somewhere in c:\useful dlls\). Once you’ve found it select the DLL and click Add.

Your DLL is now referenced and you should be able to start using it straight away. Depending on what you need to do with it you’ll also need to add Page and/or Codebehind imports. To check that it has imported correctly, in Visual Studio 2003 you should be able to see it in the references folder or in Visual Studio 2005 you will need to click into the "Class View" tab of the Solution explorer:

How do I identify the namespace?

I had someone ask me a while ago why the his code was throwing a compilation error, it turned out that although he had named the DLL MyDLL, the namespaces within the DLL he wanted to reference was MyNamesapce so how can you identify the namespace?

The easiest way to do this is to use something called the Object Explorer, this should list all the referenced DLLs for a given project and allow you to navigate the namespaces, classes and objects within the class. To open the Object Explorer click on the View menu and then “Object Explorer” within the “Windows” menu. Navigating the DLL is easy, you can either search through it using the search box at the top or alternatively navigate using the object tree.

The best way to work out what declaration you need to add is to locate the object, method or control you plan on using either using the tree navigation or searching, then selecting it. Once selected you will notice the bottom pane of the Object Explorer will change and the namespace will be listed, this is what you need to add as your reference. If you need to enter the assembly name, you can identify this easily as it’s the name given to the top node of the tree –this should have a little grey icon next to it.

If the DLL is adding a control to the page

You’ll need to reference the namespace at the top of the page like this:

<%@ Register TagPrefix="TSD" Namespace="TheSiteDoctor.WebControls" Assembly="TheSiteDoctor" %>

You can use whatever prefix you like for the control, I tend to keep it between 2 and 4 characters in length for ease i.e. “TSD” but that’s up-to-you. Adding the control is done in the same way you add the standard controls:

<TSD:SuggestionTypeRadioButtonList runat="server" ID="radCategories" CssClass="inputRadio" ValidationGroup="suggestion" />

You’re all set :)

If however this is a control set that you plan on re-using throughout the application I would opt to add a reference within the web.config, this means you don’t need to repeatidly add the reference for each page. To do this you’ll need to add the following to your web.config file:

<system.web>
   <pages validateRequest="false">
     <controls>
       <add tagPrefix="TSD" namespace="TheSiteDoctor.WebControls" assembly="TheSiteDoctor" />
     </controls>
   </pages>
</system.web>

If the DLL is adding functionality to the codebehind or you want to use the control within the codebehind

If you want to use the control or add the control to the page dynamically you will need to include a reference to the namespace within the codebehind –in the same way you do the System namespaces. This is really simple, at the top of the page you should see a few “using” statements or in VB “Imports”, you’ll just need to add the referenced DLLs namespace below (or above –or- in the middle!) of these others, as long as it’s with the other statements you’ll be fine. You can then reference the various methods and properties of the control.

using TheSiteDoctor.WebControls;

public partial class SuggestionsPage : System.Web.UI.Page
{
    protected void btnAddEntry_Click(object sender, EventArgs e)
    {
        int cat = radCategories.CategoryId;

        //Do something with it here...
    }
}

I hope that helps you getting started with this new way of importing common code, it’s fairly intuitive once you’ve done it once or twice, but those first few “Could not find xyz –Are you missing an Assembly or Reference” messages do drive you nuts ;)

Tuesday, February 13, 2007 8:03:03 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
# Tuesday, February 06, 2007

What an awesome event, I was originally in two minds about going to the latest conference installment from Phil Winstanley, Dave Sussman (and all the other dedicated people involved with the other DDD events) but boy am I glad I went.

This time I decided to take it to the next level and rather than driving down and back on the day I’d drive down the night before with Stacey and stay over in a local hotel. This worked really well, not only did it mean I was awake for all of the seminars but I could get some work down the next day too ;)

Anyhow, back to the day, for once I had the foresight to choose the seminars I was going to attend before I arrived and decided not to attend all of Scott Guthrie’s talks mainly because of the following I knew he’d have but also because of the great alternatives available so here’s my breakdown of who I went to see and what I thought of their talk:

Microformats - HTML to API (Glenn Jones)

Read Glenn Jones' blog post about the day

GlenN Jones (not Glen Jones as was listed in the schedule ;)) presented a very interesting talk on microformats, it’s not quite what I first thought it was (for some reason I thought it was some form of HTML applets but lets not go there!). Microformats are certainly something I’m going to look into in the future but as Julian Voelcker has pointed out quite how practical they are to use in a CMS situation I’m not sure.

I think from an SEO point of view and also from an information sharing POV they’re very interesting and I’ll certainly be integrating them into various sites for testing purposes sooner rather than later (in fact if you check out my about me page they’ll be there with the new update coming soon … now I just need to re-work my tag output* using IISMods' URLRewrite).

*Glenn pointed out that  when using the rel=”tag” attribute the last “word” in the associated URL should be the tag itself -something I didn’t know but will be sorted as atm it’s along the lines of “CategoryView,category,Business,Business%20Start-up%20Advice.aspx” etc which isn’t very useful.

I think in principle microformats are a good idea for something like a blog or a semi-static site where the developer (or someone with knowledge of microformats) has control over the content but how you could role them out in a client managed site is a little more complicated and something that will need some more thought -do you offer buttons to insert the code markup for them? Can you offer nested content easily etc.

The other thing about them I’m not too sure about is (miss)use of the abbr tag -again that was only something I picked up in the talk so may have missed the point, I’ll need to look into it further.

Either way it was an interesting insight into a new concept that I’m going to support if I can :). Check out the main microformats site at: www.microformats.org

Glenn Jones is also the developer behind the back network site that was used to link all the delegates together, it’s an interesting concept that once again promotes a social network on the internet which is all the rage at the moment but also allows you to interact with other delegates before the event -this is something I’d have done had I had more time before the event!

Download the slides to the Microformats - HTML to API talk by Glenn Jones

Web Accessibility: What, Why, How, and Who Cares? (Bruce Lawson)

Read Bruce Lawson's blog post about the day

Making web sites accessible is something I’ve been interested in pretty much since I got involved with ASP.Net 1.1 and I get endlessly tired of hearing fellow ASP.Net developers complain that you can’t make web sites accessible using the ASP.Net platform -balls can’t you, ok it’s not something that comes out of the box and at times is a little awkward but a lot of it is just common sense and consideration.

Bruce Lawson’s talk was a breath of fresh air, it was great to see someone having the courage that I’m yet to muster (well, more the time but hey) to convince my fellow developers to make their sites accessible.

Why the hell shouldn’t your site be accessible to all? It’s not all about money, in my mind it’s just about being fair to others -following (as ever) Google’s moto of don’t be evil. I liked Bruce’s method of presentation as it was far more personal than the usual “you should care because it’s the law” or “you should care because you’re missing out on a ton of money”, when asking the question “who cares?” -using his words not mine- he said “rather than quoting facts and figures at you trying to convince you, -my mate Theresa does”. I think this in itself was a different method of engaging the audience and I certainly felt it worked.

The talk wasn’t particularly in depth (which baring in mind the audience I expected) but I felt it was enough to plant the seed of interest with those that weren’t otherwise that aware or interested about accessibility. I hope that they’ll now actively encourage fellow developers to take action -not necessarily by redeveloping their past sites as many clients can’t afford this, but by giving some consideration to accessibility in future designs -i.e. DON’T use buttons for menu systems!

I can’t hand on heart say all our sites are overly accessible but I’m learning and I feel each new site we’re involved in is that little bit more accessible. Bruce did share a very useful site called “Blind Webbers” where you can get in contact with screen reader users -I’ll certainly be checking that out with the new design for The Site Doctor, for others interested Bruce sent me the link: http://www.webaim.org/discussion/mail_message.php?id=9019. I’m thinking I’ll see what they think of Miss Mays adult store -could be a good introduction!!

The point that made me laugh the most was his demonstration of using “Click Here” as link text, his demo was simple but effective -you can check it out on his site: http://www.brucelawson.co.uk/index.php/2007/webdd-conference-slides-and-questions

One thing I do need to think about is the order of elements on the page, i.e. at present this blog layout has the menu appearing before the content -mainly because that was the quickest way I could get the layout sorted, but I think I need to re-order it so the menu comes last -that said I do have a “Skip to content” link at the top -how effective it is I’ll let you know. Another thing I also want to pass by Bruce is image replacement techniques as I’ve tried a few now and I’d be interested to see how they perform on screen readers and the like.

Download the slides for the Web Accessibility: What, Why, How, and Who Cares? talk by Bruce Lawson

Quick and dirty Usability tests - one week, no budget, and no usability facility (Zhivko Dimitrov)

Read Zhivko Dimitrov's blog post about the day

Again, interested in making my sites as user friendly as possible I thought that this would be an interesting talk but it wasn’t quite as it was portrayed -instead he went into how they perform remote usability tests with a budget. None the less it was a fairly interesting talk.

Zhivko is from Telerik and clearly has a fair amount of experience in usability testing, I was hoping he’d have some good ideas on how to offer usability testing on no budget but sadly he didn’t. There were a couple of interesting points raised however that I don’t think I would have thought of -firstly the re-use of testers, if you use a tester more than twice within a year they’ll start to know what you want them to say rather than what’s there. The other point raised was if you’re using remote testing, you loose the non-vocal indicators of frustration such as a furrowed brow or someone scratching their head.

Zhivko’s opening demo however was a recording of a guy trying to find a grid component on their competitors site, despite the fact they spent a fair amount of time laughing at the guy in the background I thought this was a great example of a poorly designed site and how important it is to highlight your site’s calls-to-action which is something that I’ll have to remember while optimizing our newest SEO client for online poker The Rivercard -one of the issues we have already highlighted is that many of their download links are below the fold of the screen which reduces the chance the user will click the link.

Download the slides from the Quick and dirty Usability tests - one week, no budget, and no usability facility talk by Zhivko Dimitrov

Connecting Design to Real Business Value (Brandon Schauer)

Visit Brandon Schauer's blog

As with Zhivko’s talk, this was another talk that wasn’t quite as it was portrayed by the title, but I was pleasantly surprised by the content. Brandon Schauer’s talk was more about business modeling and how analyzing the current business method can be improved with a little thinking (and design) -ok that’s obvious ;) but his methods were nice.

I found the talk incredibly interesting -especially following my mini-series on business start-up advice, I thought this was a really well timed and interesting talk. Some of the ideas he offered were simple and to the point so you can apply them to any business, the issue I have with it though is whether I can apply it to any of my clients -I’d love to take the time to go through Miss Mays adult store and help them improve some of their business processes but they don’t have the money to invest and sadly neither do I.

I do however think that I can apply some of the concepts he was talking about to an example business which in turn could then be a starting point to discuss business improvement with clients. This however will take a little time and I think Stacey will need to be involved as this is what she’s primarily trained in. Although I love developing and I don’t think I’ll ever get away from it (certainly not in the foreseeable future anyways) I am getting more and more interested in business analysis, it’s not something that I’ve really got any experience in yet (having only been in business for a few years) but perhaps one day it’s an alternative career path I can choose…

Either way, Brandon’s talk was well worth seeing and if he’s ever at a future conference I attend I’ll certainly make the effort to see him talk.

Download the slids from the Connecting Design to Real Business Value talk by Brandon Schauer

WPF/E (Scott Guthrie)

Visit Scott Guthrie's blog

For the final talk I decided to watch Scott Guthrie’s talk about WPF/E and boy what a talk it was! I almost didn’t get in as we were hearded in like cows (which was most amusing I have to be honest), the woman stopped me right on the entrance -I think much to Julian Voelcker’s delight as he’d managed to get a seat. Luckily though the women on the doors (yes women -not burly bouncers!) took pity on us poor, desperate geeks in admiration of some Yank they didn’t know and let us line the sides of the auditorium -which meant I ended up getting a front row (floor) seat.

The talk was one of those “look at what’s coming” type talks but with a twist, it was something that I can see being of real use -and more than that gave you the urge to try it out. WPF/E looks like a really exciting new technology -even if Julian does think it’s just the same as Flash. As I don’t particularly like flash I think this will be a nice introduction to our development arsenal. That and the possibilities are far greater than those offered by Flash -especially where data interaction is involved.

Scott Guthrie did show an impressive demo of WPF/E which can be seen at www.vista.si -it’s one of those “wow, I can’t believe I’m seeing what I’m seeing” moments, the site is basically a replica (working replica) of Windows Vista -but on the web. It even works with Firefox!

The interesting point that I picked up on is their method of rolling out the WPF/E platform to users, rather than offering the usual Windows Update installer, it sounds as though it’s all going to be done in the same way the flash play is -a small (1.1MB IIRC) file will be downloaded the first time you visit a site that requires WPF/E and that’s it!

I do have concerns over the accessibility of WPF/E but Scott Guthrie did assure us that later versions of WPF/E will be made more accessible. At the end of the day however, I guess it’s just the same situation as entirely flash sites -those that want to offer them, have to offer an accessible alternative (and as Bruce Lawson pointed out -NO, IT DOESN’T HAVE TO BE UGLY!).

It was also nice to see Scott Guthrie talk as there are so many blog posts around the net talking about how they saw him, now I can say that I’ve seen him talk -somewhat sad but hey!

The slides aren’t yet online but I’m sure Scott Guthrie will upload them to the Scott Guthrie's presentations page soon enough!

In summary

I always take a conference as a whole -there’s always going to be at least one talk which isn’t quite what you expected, if you can come away with at least one nugget of information that you didn’t have before -or- with a little of that zest for doing what you do back again it was well worth attending. In this case I got a real buzz out of most of the talks and have plenty of things to try out -now I just need to find the time!

And if all that wasn’t enough to get your juices going and wanting to do some more development, I (I think for the first time ever) won something in the raffle -I was in the queue hoping for the book on accessibility by Bruce Lawson but actually won a years subscription to ComponentArt’s Web.UI component set -I’m well chuffed at that, now I just need to find somewhere to use them!! Oh, I shouldn't forget the free copy of Microsoft Expression Web we were given, and the T-Shirts and, and... :D

I did get to meet up with a few people off the MsWebDev list but sadly not all -Mickey, I’ll have to say hi next time. The one thing that did amaze me was how long the lunch was, I don’t recall any of the DDD events being that long.

If you went and you’ve not already done so, you should go and leave feedback on the event -it’s the only way they can improve it ;) so go leave your feedback on WebDD (http://webdd.co.uk/Feedback.aspx). Apparently you can also review it on the back network site (http://webdd.backnetwork.com/reviews/editreview.aspx

If you missed out on WebDD 1, hopefully there’ll be a WebDD 2, I’ll post any news I have as soon as I have it -for my one blog reader that is :)

Tuesday, February 06, 2007 12:00:26 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, January 05, 2007

I’m not sure how many people this applies to but a while ago I found I was loosing VS2005 IntelliSense when I was working in my web.config. More specifically I lost IntelliSense after editing any of the site’s settings using the Web Site Administration Tool in ASP.Net 2.0.

I quickly realised that the issue was being caused because the Web Site Administration Tool updates the root element (<configuration>) of your Web.Config file to include an attribute of “xmlns”. If you’re loosing IntelliSense, just delete the attribute and you’ll have it back in a tick :)

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

Should be:

<configuration>

p.s. Yes Doug, I can already hear you crying out “Not for me –I don’t use VS*yawn*

Friday, January 05, 2007 2:57:57 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
# Wednesday, January 03, 2007

While updating the layout of the DasBlog I needed to alter some of the source code to output lists rather than tables etc and after uploading the latest version of the blog the login form suddenly failed to work. When logging into DasBlog it would accept the parameters and either kick me out to the error page or redirect me to the logged in page.

DasBlog includes some fairly indepth error logging (not using Phil Winstanley’s Error Reporting code mind you), one of the events it logs is logging in. I noticed that I would get one of two errors when logging in, when I was shown the error page it would say: “Password Challenge was null in ViewState! <br>   at newtelligence.DasBlog.Web.LoginBox.doSignIn_Click(Object sender, EventArgs e)” etc and when it took me to the front page it would log: “Audit: SecuritySuccess” and the standard who logged in from which IP etc.

As I’d edited the DasBlog source my immediate thought was that I’d changed something in the login process so I copied the original DLLs back onto the site and tried again without any success. After a fair amount of searching into the issue on Google and the DasBlog forums I was at a loss as to the issue so I asked on the DasBlog forum (http://www.dasblog.us/viewtopic.php?t=417).

A couple of people responded including Tom but sadly none of the suggestions applied to my situation. In the end I found that the issue was being caused because I was running the site under ASP.Net 2.0. When running DasBlog under ASP.Net 2.0 you need to make additional changes to the web.config file as outlined in the install documentation. When setting up the staging site I copied the directory from my main blog so I had the same content to test with, although I remembered to alter all the relevant areas in the site.config etc files, I had neglected to alter the cookie path setting in the web.config. So if you’re getting the same issues, check the web.config cookie path values.

Wednesday, January 03, 2007 11:26:03 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [6]  | 
# Friday, December 15, 2006

I recently updated one of our sites to use a shared authentication database to save on the maintenance, but in doing so I messed something up and couldn’t work out why I was getting dual records of the user. To summarise what I’d done, I had updated the applicationName value from “/” to “SomeName” and then updated the values in the database to reflect these changes.

In addition to having duplicate records for the users, the LastActivityDate was only being altered on login and subsequent page requests were not being noted which was a problem as this system requires this data. I did look into alternative methods around this until I realised that one of dates for the user was actually being updated –just not the roles/membership one!

There are a number of posts discussing doing this pre-userbase which were about as useful as a chocolate fireman. I finally realised that I’d not added the provider node for the profiles which meant it was not only reverting to the old connection string named “LocalSqlServer” but it was also assigning “/” as the application name. After updating this node all’s fine and dandy again.

For future reference, you need to override 3 web.config settings to ensure your application is referencing the correct database and within each one add the <clear /> node otherwise your application is likely to inherit from the machine.config (I’d got that one!!):

Web.Config Settings

<membership defaultProvider="YourMembershipProvider">
    <providers>
        <clear />
        <add name="YourMembershipProvider" 
         applicationName="YourApplicationName" 
         connectionStringName="YourASPNetSqlServerConn"
         type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> 
    </providers>
</membership>

<roleManager>
    <providers>
        <clear/>
        <add name="YourRoleProvider" 
         connectionStringName="YourASPNetSqlServerConn" 
         applicationName="YourApplicationName" 
         type="System.Web.Security.SqlRoleProvider" />
    </providers>
</roleManager>

<profile enabled="true" defaultProvider="YourProfileProvider">
  <providers>
    <clear />
    <add name="YourProfileProvider"
         connectionStringName="YourASPNetSqlServerConn"
         applicationName="YourApplicationName"
         type="System.Web.Profile.SqlProfileProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  </providers>
</profile>
Friday, December 15, 2006 2:38:10 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, December 14, 2006

Thanks to Doug Setzer from 27 Seconds Inc for these thoughts, he doesn’t blog (yet) so thought I would be interested in posting it –not sure why as it would seem he’s my only reader…!

I tend to agree with Doug on this one, I remember seeing Code Smith in action at a previous Developer Developer Developer day and although it does look, I have similar concerns as Doug, IMSHO I'm concerned it makes the developer lazy and forget his roots. IIRC many generators offer the ability to make your own templates but again, by doing so, how much time do you really save?

Not being a code generator user* I’m also somewhat biased but I’d be interested to hear other peoples thoughts.

Tim

*That said, I do have a number of “tools” that I user to generate the repetitive code but these mainly rotate around Excel and are specific to my coding methods.


The decision to use code generators is a long and tricky decision.  I'll admit from the start, my slant on code-generation tools is against them.

For one of my other clients, I use a simple code generation application that they provided to me (the guy wrote it).  It does a decent job and for their coding style, it works out fairly well.  It takes a very simplistic template and can output (to an extent) customizable code.

The problem that I have with all code generation tools is your ability to make changes.  Very rarely do you know all of the data, or the extents of repeating data, or the nuances of the code that you have to develop.  So - how does the application handle changes?  In my case and this (very) simplistic code generation tool - plain and simple, it doesn't.  I have to hand-code changes (.NET, ASP.NET and database procedures).

And the last thing - the nuances of the code that you have to develop.  How well does a generic tool handle special situations?  Again, I know a free/custom-built tool is a far cry from a $500/$2000 tool - but, anything that is outside of a simple CRUD/1-table procedure and again, I'm hand-coding.  I can honestly say, 50% of what I'm doing is "special" and doesn't fit into cookie-cutter boxes.

The code generator that my customer gave me did exactly that - it generated code for a database.  It was up to me to build the pages and it just gave me an easy database access API.  I like this more than tools like Iron Speed because nothing is being done "for" me, I can control the code it outputs and nothing, nothing is "behind the scenes".  With Iron Speed, they're doing a LOT "for" you.  My fear is what happens if it breaks?  Who do you call?  Where do you look?  And, the purist in me - what does their code really look like?  Are they doing poor-design things that ultimately give you performance woes?

Unfortunately, with tools such as this Iron Speed - the only way to know if it'll work for you is to make the plunge, spend the $500 for the pro. version and see how it goes.  You can help get yourself a glimpse, looking under the covers by downloading the trial and putting together some little apps in a month.  And, I see that they have a public support forum:
http://sjc.ironspeed.com/tool/mb/ironspeed

See what people are saying they love? what they hate? etc.

And, of course - Google is our best friend:
http://www.adtmag.com/article.aspx?id=8421
http://www.developerreviewed.com/ironspeedreview.htm
http://www.angrycoder.com/article.aspx?cid=10&y=2003&m=6&d=8
http://aspadvice.com/blogs/ssmith/archive/2006/04/26/IronSpeed-and-My-Custom-Reports-App.aspx
http://www.411asp.net/func/review?tree=411asp/software/aspcodee&id=5642310&rid=

I hope this helps - it's a lot to digest, but generally what I'm seeing is that it's good for small/simple apps.

-Doug

ASP | ASP.Net | Business
Thursday, December 14, 2006 11:14:09 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
# Saturday, November 18, 2006

Being a fair way into an application that relies heavily on Atlas I wasn’t best pleased to hear that Microsoft had done the usual comedy act of renaming the framework which would mean we’d have to update a plethora of controls.

Today we bit the bullet and along with a few other changes we already had planned made the switch. It wasn’t as bad as I was expecting tbh but it wasn’t without issues. The first issue we ran into was with Visual Studio’s Intellisense which has gone haywire, the recommended solution is to switch quickly between Design Time and HTML View –something that we can’t do as we’re using nested MasterPages which Visual Studio doesn’t support.

The next issue we ran into was the fact that our onKeyPress TextBox has now stopped working as planned but we’re looking into that.

The main issue that we ran into this morning however was on putting the site onto the server as we were faced with the following:

Server Error in '/' Application.


Configuration Error

Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: Could not load file or assembly 'Microsoft.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

Source Error:

Line 97: 			<assemblies>
Line 98: 				<add assembly="Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
Line 99: 				<add assembly="Microsoft.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
Line 100: <add assembly="System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
Line 101: <add assembly="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

Source File: **********************************************\web.config    Line: 99

Assembly Load Trace: The following information can be helpful to determine why the assembly 'Microsoft.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' could not be loaded.

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].


Version Information: Microsoft .NET Framework Version:2.0.50727.42; ASP.NET Version:2.0.50727.42

The official take on this from the Microsoft guys is somewhat comical (http://forums.asp.net/thread/1455060.aspx):

Hi guys - yeah the problem you're having is because you installed on a machine w/o VS.  All of our machines have VS, so we didn't catch this problem.  On a machine w/ VS, Microsoft.Web.Extensions.Design.dll gets dropped.

Probably the best workaround for this is to go get that file from a machine with VS and either install it (gacutil -f Microsoft.Web.Extensions.Design.dll) or try dropping it into the bin directory, which I believe should also work but I haven't tried it.

 I've been talking with the AJAX team about this, so we're on top of it.  Sorry for the confusion.

Not an ideal fix but at least it works! For those of you wondering where you can find the DLL, look in: %Program Files%\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025

AJAX | ASP.Net | Atlas
Saturday, November 18, 2006 9:52:00 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, November 13, 2006

We're nearing the end of a large system for the lovely people over at Consolidated Communications Management Ltd and we've just come across a nice idea to make sense of database responses. Typically in the past we've simply returned -1/0 for failure and a positive integer (usually an id) of it was successful. The new method should add a little more conformity to our code. We already use a common Database Access Layer which handles the opening/closing of our connection, adding parameters etc but I’ve just added an enum with the common database responses, thought it may be of use to others:

public enum DatabaseResponse
{
    Success = 0,
    Init = -1,
    Found = -2,
    NotFound = -3,
    Duplicate = -4
}

This should cover the common responses from our database however to make it a little more futureproof we'll keep -1 through to -10 reserved for this data level and then start more specific responses from -11 (i.e. BadPassword).

Using it is simple (this is not real code before you ask!):

public bool Save()
{
    DatabaseLayer DBLayer = new DatabaseLayer();

    //Check whether we should be saving or inserting this record
    if (this.Id == (int)DatabaseLayer.DatabaseResponse.Init)
    {
        //The Id's set to initialised only so insert
        //The create method simply inserts the values into the database and returns
        //either the Id of the newly inserted record or our database response
        this.Id = DBLayer.Create(this);

        if (this.Id >= (int)DatabaseLayer.DatabaseResponse.Success)
        {
            //The insert was a success
            return true
        }
        else
        {
            //The insert failed for some reason -you could throw an error 
            //to catch or simply bubble the response upto to the code behind layer
            return false
        }
    }
    else
    {
        //When updating the record don't use the Id to store the response, instead
        //throw it into a temp object and use that so the Id's still stored in the object
        int response = DBLayer.Update(this);

        if (response == (int)DatabaseLayer.DatabaseResponse.Success)
            return true//All's ok
        else
            return false//Something went wrong -it could be the item wasn't found etc
    }
}
Monday, November 13, 2006 8:48:41 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
# Monday, November 06, 2006
Since integrating Phil Winstanley’s error reporting code into our first ASP.Net project we've made a number of useful alterations to the way we use it and I thought we'd take another dive how it's used including riding yourself of spambots, 404 errors and a few other useful methods.
Monday, November 06, 2006 6:59:55 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [6]  | 
# Saturday, November 04, 2006

After the fun and games we had with IE and sorting out an old CSS design for www.technikfloor.co.uk on Friday when I saw this pie chart I thought it was an excellent summary of a modern web designer’s life.

Personally I feel my Teal coloured slice is smaller but hey!

Time Breakdown of Modern Wed Design

Originally posted at: http://www.dangerouslyawesome.com/2006/07/02/throwing-a-wobbly/

ASP | ASP.Net | Business | General
Saturday, November 04, 2006 12:12:12 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Wednesday, November 01, 2006

This is a useful Regex that Craig wrote today, it pulls out various info from an apache log file.

private static Regex __Regex = null;  
internal static Regex _Regex  
{  
    get  
    {  
        if (__Regex == null)  
        {  
            __Regex = new Regex(@"(?<remoteHost>[^\ ]+?)\ (?<remoteIdent>[^\ ]+?)\ (?<remoteUs"
                + @"er>[^\ ]+?)\ \[(?<requestTime>[^\]]+?)\]\ \""(?<request>(?<r"  
                + @"equestMethod>[^\ ]+?)?\ ?(?<requestPath>[^\ ]+?)?\ ?(?<reque"
                + @"stProtocol>[^\ ]+?)?)\""\ (?<statusCode>[^\ ]+?)\ (?<sizeByt"
                + @"es>[^\ ]+?)\ \""(?<referer>[^\""]*?)\""\ \""(?<userAgent>[^\""]"
                + @"+?)\""\r?\n?"
                RegexOptions.Compiled & RegexOptions.IgnoreCase & RegexOptions.IgnorePatternWhitespace & RegexOptions.CultureInvariant);
        }  
        return __Regex;  
    }  
}

Update: Craig's finally started writing about it, you can read the article here: Apache Log Fun

ASP.Net | IIS
Wednesday, November 01, 2006 5:21:16 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, October 02, 2006

I ran into (another) interesting error today when using String.Format1. I was getting the error message "Input string was not in a correct format.". In this instance I was calling the content from an XML file, the data was wrapped in CDATA tags so there shouldn't have been an issue with line breaks etc. On investigating the error further I found it was being caused because the content was a HTML page. More specifically, the String.Format error "Input string was not in a correct format." was being caused by the CSS declaration's curly braces.

To work around the method perceiving them as placeholders, simply replace each curly brace "{" with double braces "{{".

To replicate the error:

String.Format("<style type=\"text/css\">p{font-size: 1.2em;}</style><p>Your username is: {0}</p>", "UserName");

The fix in place:

String.Format("<style type=\"text/css\">p{{font-size: 1.2em;}}</style><p>Your username is: {0}</p>", "UserName");

1The String.Format method accepts a format string which can include place holders (designated by the curly braces {} and a number that refers to the location of the item that should appear in the list). A quick example of String.Format:

String.Format("This is some text the date is {0}.", DateTime.Today.ToShortDateString);

Would produce: "This is some text the date is 02/10/2006"

Monday, October 02, 2006 6:26:29 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [8]  | 
# Saturday, September 30, 2006

Sean Ronan (from Activepixels.co.uk) was asking me the other day why he was receiving a “Cannot convert ‘’ to DateTime” when writing:

IIf(String.IsNullOrEmpty(DateOfBirth.Text), Nothing, CDate(DateOfBirth.Text))

Thanks to Doug Setzer from 27Seconds in pointing him in the right direction, unlike C#’s ability to write:

(TrueFalseStatement ? TruePart : FalsePart)

VB’s IIf is a function not a statement so unlike C#’s equivielent which is run un-evaluated, VB’s IIf function evaluates both sides of the statement regardless of whether the statement is true of false.

Yet another reason to use C# IMNSHO :-)

Saturday, September 30, 2006 1:39:47 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Thursday, September 21, 2006

I know a fair few people have come across the error which, having a page called “Login.aspx” causes the error message “CS0108: 'ASP.login_aspx.Profile' hides inherited member 'Login.Profile'. Use the new keyword if hiding was intended.” But I’ve found another when calling a page sitemap.aspx: “CS0108: 'ASP.sitemap_aspx.Profile' hides inherited member 'SiteMap.Profile'. Use the new keyword if hiding was intended.” So just be careful not to name your pages the same as the controls.

If like me however you like to keep some sort of consistency in the page names across your sites, you can easily get around it by renaming the page’s partial class name to something which doesn’t conflict and then updating the page’s codebehind reference i.e. for sitemap.aspx:

public partial class _pgSiteMap : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
}

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="sitemap.aspx.cs" Inherits="_pgSiteMap" Title="Site Map" %>

Thursday, September 21, 2006 10:46:33 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  | 
# Monday, July 31, 2006

I came across an interesting anomaly last week with one of our new sites (http://www.missmays.com/ -not work safe). We first started developing the site in ASP.Net 1.1 and wanted to make it XHTML 1.1 valid so wrote a HttpModule that transforms the offending source (i.e. wrapping inputs in div tags). Over time we realised that we could also encode other offending items such as ampsands which would save us a load of time typing them out in the URL’s and updating the database.

We knew the site’s HTML was XHTML1.1 valid and the tests we’d run with the source were fine but on validating the live site using the W3C URL Validator (http://validator.w3.org/check?verbose=1&uri=http%3A%2F%2Fwww.missmays.com%2F) all was not so rosey. It was falling over on the last two inputs (__PREVIOUSPAGE and __ EVENTVALIDATION). On examing the module code it turned out that I hadn’t included them in the Regex (new to ASP.Net 2.0). Adding them all was fine, but why doesn’t the framework automatically wrap the inputs in div tags as it does for the browsers?

Checking the main ASP.Net site (http://www.asp.net/) the same thing happens, looking at the source through Firefox or IE shows the inputs wrapped nicely but through the validator they’re not, the source isn’t XHTML1.0 Strict anyways btw but it’s still worth noting that validating the URL fails on different points: http://validator.w3.org/check?verbose=1&uri=http%3A%2F%2Fasp.net%2F

I’d be interested to know if anyone else has come across this before or has an insight on why it may be happening.

This is what the validator used to produce when validating against the URL:

This page is not Valid XHTML 1.1!

Below are the results of checking this document for XML well-formedness and validity.

  1. Error Line 327 column 96: document type does not allow element "input" here; missing one of "ins", "del", "h1", "h2", "h3", "h4", "h5", "h6", "p", "div", "address", "fieldset" start-tag.
...GE" value="CLXotzBdGnifesOhxidubQ2" /> 

The mentioned element is not allowed to appear in the context in which you've placed it; the other mentioned elements are the only ones that are both allowed there and can contain the element mentioned. This might mean that you need a containing element, or possibly that you've forgotten to close a previous element.
One possible cause for this message is that you have attempted to put a block-level element (such as "<p>" or "<table>") inside an inline element (such as "<a>", "<span>", or "<font>").

  1. Error Line 328 column 183: document type does not allow element "input" here; missing one of "ins", "del", "h1", "h2", "h3", "h4", "h5", "h6", "p", "div", "address", "fieldset" start-tag.
...K6hIzLASsITXlU6vlPxFkdzgJFxH9ADe8B" /></form> 

The mentioned element is not allowed to appear in the context in which you've placed it; the other mentioned elements are the only ones that are both allowed there and can contain the element mentioned. This might mean that you need a containing element, or possibly that you've forgotten to close a previous element.

One possible cause for this message is that you have attempted to put a block-level element (such as "<p>" or "<table>") inside an inline element (such as "<a>", "<span>", or "<font>"

Update: Although I've not yet had a chance to try it, Phil Winstanley has suggested using .browser files may get around this.

Update 2: Here's a browser file you can use, you'll need to add a "Special ASP.Net 2.0 Directory" in your solution calls App_Browsers and place the w3cvalidator.browser (.5 KB) file into the new directory, recompile and the application should now output valid XHTML.

Monday, July 31, 2006 12:54:50 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  | 
# Friday, June 30, 2006

Julian Voelcker raised a question on the MsWebDev mailing list the other day that I've been meaning to investigate for a while as it's something that I have to double check on every time I write a new custom control -the various lifecycles an ASP.Net page.

Many people have blogged about the lifecycle of an ASP.Net Page or Master Page but every time I need to find the complete lifecycle of a control it’s not so easy. Today I wrote a couple of test pages to generate a reference.

The pages are very simple, I just overrode every method I could find and wrote to the trace. For completeness when the method called its base I wrapped it with “Start MethodName” and “End MethodName” (if you look at the trace outputs below you will see why). I did this for the Master Page (.master), Page (.aspx), User Control (.ascx) and Web Control (.cs), the page structre was simple:

  • Master Page
    • Page
      • User Control
      • Web Control

For clarity I’ve split the produced trace into 5 individual reference sections:

ASP.Net 2.0 Master Page Life Cycle

  • Start Construct()
  • End Construct()
  • Start FrameworkInitialize()
  • End FrameworkInitialize()
  • Start AddParsedSubObject(object obj)
    • CreateControlCollection()
    • Start AddedControl(Control control, int index)
    • End AddedControl(Control control, int index)
  • End AddParsedSubObject(object obj)
  • Start AddParsedSubObject(object obj)
    • Start AddedControl(Control control, int index)
    • End AddedControl(Control control, int index)
  • End AddParsedSubObject(object obj)
  • Start AddParsedSubObject(object obj)
    • Start AddedControl(Control control, int index)
    • End AddedControl(Control control, int index)
  • End AddParsedSubObject(object obj)
  • Start AddParsedSubObject(object obj)
    • Start AddedControl(Control control, int index)
    • End AddedControl(Control control, int index)
  • End AddParsedSubObject(object obj)
  • Start AddParsedSubObject(object obj)
    • Start AddedControl(Control control, int index)
    • End AddedControl(Control control, int index)
  • End AddParsedSubObject(object obj)
  • ResolveAdapter()
  • Start OnInit(EventArgs e)
  • End OnInit(EventArgs e)
  • Start TrackViewState()
  • End TrackViewState()
  • Start OnLoad(EventArgs e)
    • Page_Load(object sender, EventArgs e)
  • End OnLoad(EventArgs e)
  • Start EnsureChildControls()
    • ResolveAdapter()
    • Start CreateChildControls()
    • End CreateChildControls()
  • End EnsureChildControls()
  • Start OnPreRender(EventArgs e)
  • End OnPreRender(EventArgs e)
  • SaveViewState()
  • SaveViewState()
  • HasControls()
  • Start RenderControl(HtmlTextWriter writer)
    • Start Render(HtmlTextWriter writer)
      • Start RenderChildren(HtmlTextWriter writer)
      • End RenderChildren(HtmlTextWriter writer)
    • End Render(HtmlTextWriter writer)
  • End RenderControl(HtmlTextWriter writer)

ASP.Net 2.0 Page Life Cycle

  • ResolveAdapter()
  • DeterminePostBackMode()
  • Begin PreInit
    • Start OnPreInit(EventArgs e)
    • End OnPreInit(EventArgs e)
    • HasControls()
    • Start RemovedControl(Control control)
    • End RemovedControl(Control control)
    • ResolveAdapter()
    • Start AddedControl(Control control, int index)
    • End AddedControl(Control control, int index)
  • End PreInit
  • Begin Init
    • ResolveAdapter()
    • Start OnInit(EventArgs e)
    • End OnInit(EventArgs e)
    • Start TrackViewState()
    • End TrackViewState()
  • End Init
  • Begin InitComplete
    • Start OnInitComplete(EventArgs e)
    • End OnInitComplete(EventArgs e)
  • End InitComplete
  • Begin PreLoad
    • Start OnPreLoad(EventArgs e)
    • End OnPreLoad(EventArgs e)
  • End PreLoad
  • Begin Load
    • Start OnLoad(EventArgs e)
      • Page_Load(object sender, EventArgs e)
    • End OnLoad(EventArgs e)
  • End Load
  • Begin LoadComplete
    • Start OnLoadComplete(EventArgs e)
    • End OnLoadComplete(EventArgs e)
  • End LoadComplete
  • Begin PreRender
    • Start EnsureChildControls()
      • ResolveAdapter()
      • Start CreateChildControls()
      • End CreateChildControls()
    • End EnsureChildControls()
    • Start OnPreRender(EventArgs e)
    • End OnPreRender(EventArgs e)
  • End PreRender
  • Begin PreRenderComplete
    • Start OnPreRenderComplete(EventArgs e)
    • End OnPreRenderComplete(EventArgs e)
  • End PreRenderComplete
  • SaveViewState()
  • Begin SaveState
    • SaveViewState()
    • HasControls()
    • Start SavePageStateToPersistenceMedium(object state)
      • ResolveAdapter()
    • End SavePageStateToPersistenceMedium(object state)
  • End SaveState
  • Begin SaveStateComplete
    • Start OnSaveStateComplete(EventArgs e)
    • End OnSaveStateComplete(EventArgs e)
  • End SaveStateComplete
  • Begin Render
    • CreateHtmlTextWriter(System.IO.TextWriter tw)
    • Start RenderControl(HtmlTextWriter writer)
      • Start Render(HtmlTextWriter writer)
        • Start RenderChildren(HtmlTextWriter writer)
        • End RenderChildren(HtmlTextWriter writer)
      • End Render(HtmlTextWriter writer)
    • End RenderControl(HtmlTextWriter writer)
  • End Render
  • CreateHtmlTextWriter(System.IO.TextWriter tw)

ASP.Net 2.0 User Control Life Cycle

  • Start Construct()
  • End Construct()
  • Start FrameworkInitialize()
  • End FrameworkInitialize()
  • ResolveAdapter()
  • Start OnInit(EventArgs e)
  • End OnInit(EventArgs e)
  • Start TrackViewState()
  • End TrackViewState()
  • Start OnLoad(EventArgs e)
    • Page_Load(object sender, EventArgs e)
  • End OnLoad(EventArgs e)
  • Start EnsureChildControls()
    • ResolveAdapter()
    • Start CreateChildControls()
    • End CreateChildControls()
  • End EnsureChildControls()
  • Start OnPreRender(EventArgs e)
  • End OnPreRender(EventArgs e)
  • SaveViewState()
  • SaveViewState()
  • HasControls()
  • Start RenderControl(HtmlTextWriter writer)
    • Start Render(HtmlTextWriter writer)
      • Start RenderChildren(HtmlTextWriter writer)
      • End RenderChildren(HtmlTextWriter writer)
    • End Render(HtmlTextWriter writer)
  • End RenderControl(HtmlTextWriter writer)

ASP.Net 2.0 Web Control Life Cycle

  • Constructor Test()
  • Start ApplyStyleSheetSkin(Page page)
  • End ApplyStyleSheetSkin(Page page)
  • ResolveAdapter()
  • Start OnInit(EventArgs e)
  • End OnInit(EventArgs e)
  • Start TrackViewState()
  • End TrackViewState()
  • Start OnLoad(EventArgs e)
  • End OnLoad(EventArgs e)
  • Start EnsureChildControls()
    • ResolveAdapter()
    • Start CreateChildControls()
    • End CreateChildControls()
  • End EnsureChildControls()
  • Start OnPreRender(EventArgs e)
  • End OnPreRender(EventArgs e)
  • SaveViewState()
  • SaveViewState()
  • HasControls()
  • Start RenderControl(HtmlTextWriter writer)
    • Start Render(HtmlTextWriter writer)
      • Start RenderBeginTag(HtmlTextWriter writer)
        • Start AddAttributesToRender(HtmlTextWriter writer)
        • End AddAttributesToRender(HtmlTextWriter writer)
      • End RenderBeginTag(HtmlTextWriter writer)
    • Start RenderContents(HtmlTextWriter writer)
      • Start RenderChildren(HtmlTextWriter writer)
      • End RenderChildren(HtmlTextWriter writer)
    • End RenderContents(HtmlTextWriter writer)
    • Start RenderEndTag(HtmlTextWriter writer)
    • End RenderEndTag(HtmlTextWriter writer)
    • End Render(HtmlTextWriter writer)
  • End RenderControl(HtmlTextWriter writer)

Complete Trace

Category Message
Page ResolveAdapter()
Page DeterminePostBackMode()
aspx.page Begin PreInit
Page Start OnPreInit(EventArgs e)
Page End OnPreInit(EventArgs e)
MasterPage Start Construct()
MasterPage End Construct()
Page HasControls()
Page Start RemovedControl(Control control)
Page End RemovedControl(Control control)
MasterPage Start FrameworkInitialize()
MasterPage End FrameworkInitialize()
MasterPage Start AddParsedSubObject(object obj)
MasterPage CreateControlCollection()
MasterPage Start AddedControl(Control control, int index)
MasterPage End AddedControl(Control control, int index)
MasterPage End AddParsedSubObject(object obj)
MasterPage Start AddParsedSubObject(object obj)
MasterPage Start AddedControl(Control control, int index)
Page ResolveAdapter()
MasterPage End AddedControl(Control control, int index)
MasterPage End AddParsedSubObject(object obj)
MasterPage Start AddParsedSubObject(object obj)
MasterPage Start AddedControl(Control control, int index)
MasterPage End AddedControl(Control control, int index)
MasterPage End AddParsedSubObject(object obj)
UserControl Start Construct()
UserControl End Construct()
UserControl Start FrameworkInitialize()
UserControl End FrameworkInitialize()
CustomWebControl Constructor Test()
CustomWebControl Start ApplyStyleSheetSkin(Page page)
CustomWebControl End ApplyStyleSheetSkin(Page page)
MasterPage Start AddParsedSubObject(object obj)
MasterPage Start AddedControl(Control control, int index)
MasterPage End AddedControl(Control control, int index)
MasterPage End AddParsedSubObject(object obj)
MasterPage Start AddParsedSubObject(object obj)
MasterPage Start AddedControl(Control control, int index)
MasterPage End AddedControl(Control control, int index)
MasterPage End AddParsedSubObject(object obj)
Page Start AddedControl(Control control, int index)
Page End AddedControl(Control control, int index)
aspx.page End PreInit
aspx.page Begin Init
Page ResolveAdapter()
MasterPage ResolveAdapter()
UserControl ResolveAdapter()
UserControl Start OnInit(EventArgs e)
UserControl End OnInit(EventArgs e)
UserControl Start TrackViewState()
UserControl End TrackViewState()
CustomWebControl ResolveAdapter()
CustomWebControl Start OnInit(EventArgs e)
CustomWebControl End OnInit(EventArgs e)
CustomWebControl Start TrackViewState()
CustomWebControl End TrackViewState()
MasterPage Start OnInit(EventArgs e)
MasterPage End OnInit(EventArgs e)
MasterPage Start TrackViewState()
MasterPage End TrackViewState()
Page Start OnInit(EventArgs e)
Page End OnInit(EventArgs e)
Page Start TrackViewState()
Page End TrackViewState()
aspx.page End Init
aspx.page Begin InitComplete
Page Start OnInitComplete(EventArgs e)
Page End OnInitComplete(EventArgs e)
aspx.page End InitComplete
aspx.page Begin PreLoad
Page Start OnPreLoad(EventArgs e)
Page End OnPreLoad(EventArgs e)
aspx.page End PreLoad
aspx.page Begin Load
Page Start OnLoad(EventArgs e)
Page Page_Load(object sender, EventArgs e)
Page End OnLoad(EventArgs e)
MasterPage Start OnLoad(EventArgs e)
MasterPage Page_Load(object sender, EventArgs e)
MasterPage End OnLoad(EventArgs e)
UserControl Start OnLoad(EventArgs e)
UserControl Page_Load(object sender, EventArgs e)
UserControl End OnLoad(EventArgs e)
CustomWebControl Start OnLoad(EventArgs e)
CustomWebControl End OnLoad(EventArgs e)
aspx.page End Load
aspx.page Begin LoadComplete
Page Start OnLoadComplete(EventArgs e)
Page End OnLoadComplete(EventArgs e)
aspx.page End LoadComplete
aspx.page Begin PreRender
Page Start EnsureChildControls()
Page ResolveAdapter()
Page Start CreateChildControls()
Page End CreateChildControls()
Page End EnsureChildControls()
Page Start OnPreRender(EventArgs e)
Page End OnPreRender(EventArgs e)
MasterPage Start EnsureChildControls()
MasterPage ResolveAdapter()
MasterPage Start CreateChildControls()
MasterPage End CreateChildControls()
MasterPage End EnsureChildControls()
MasterPage Start OnPreRender(EventArgs e)
MasterPage End OnPreRender(EventArgs e)
UserControl Start EnsureChildControls()
UserControl ResolveAdapter()
UserControl Start CreateChildControls()
UserControl End CreateChildControls()
UserControl End EnsureChildControls()
UserControl Start OnPreRender(EventArgs e)
UserControl End OnPreRender(EventArgs e)
CustomWebControl Start EnsureChildControls()
CustomWebControl ResolveAdapter()
CustomWebControl Start CreateChildControls()
CustomWebControl End CreateChildControls()
CustomWebControl End EnsureChildControls()
CustomWebControl Start OnPreRender(EventArgs e)
CustomWebControl End OnPreRender(EventArgs e)
aspx.page End PreRender
aspx.page Begin PreRenderComplete
Page Start OnPreRenderComplete(EventArgs e)
Page End OnPreRenderComplete(EventArgs e)
aspx.page End PreRenderComplete
Page SaveViewState()
MasterPage SaveViewState()
UserControl SaveViewState()
CustomWebControl SaveViewState()
aspx.page Begin SaveState
Page SaveViewState()
Page HasControls()
MasterPage SaveViewState()
MasterPage HasControls()
UserControl SaveViewState()
UserControl HasControls()
CustomWebControl SaveViewState()
CustomWebControl HasControls()
Page Start SavePageStateToPersistenceMedium(object state)
Page ResolveAdapter()
Page End SavePageStateToPersistenceMedium(object state)
aspx.page End SaveState
aspx.page Begin SaveStateComplete
Page Start OnSaveStateComplete(EventArgs e)
Page End OnSaveStateComplete(EventArgs e)
aspx.page End SaveStateComplete
aspx.page Begin Render
Page CreateHtmlTextWriter(System.IO.TextWriter tw)
Page Start RenderControl(HtmlTextWriter writer)
Page Start Render(HtmlTextWriter writer)
Page Start RenderChildren(HtmlTextWriter writer)
MasterPage Start RenderControl(HtmlTextWriter writer)
MasterPage Start Render(HtmlTextWriter writer)
MasterPage Start RenderChildren(HtmlTextWriter writer)
UserControl Start RenderControl(HtmlTextWriter writer)
UserControl Start Render(HtmlTextWriter writer)
UserControl Start RenderChildren(HtmlTextWriter writer)
UserControl End RenderChildren(HtmlTextWriter writer)
UserControl End Render(HtmlTextWriter writer)
UserControl End RenderControl(HtmlTextWriter writer)
CustomWebControl Start RenderControl(HtmlTextWriter writer)
CustomWebControl Start Render(HtmlTextWriter writer)
CustomWebControl Start RenderBeginTag(HtmlTextWriter writer)
CustomWebControl Start AddAttributesToRender(HtmlTextWriter writer)
CustomWebControl End AddAttributesToRender(HtmlTextWriter writer)
CustomWebControl End RenderBeginTag(HtmlTextWriter writer)
CustomWebControl Start RenderContents(HtmlTextWriter writer)
CustomWebControl Start RenderChildren(HtmlTextWriter writer)
CustomWebControl End RenderChildren(HtmlTextWriter writer)
CustomWebControl End RenderContents(HtmlTextWriter writer)
CustomWebControl Start RenderEndTag(HtmlTextWriter writer)
CustomWebControl End RenderEndTag(HtmlTextWriter writer)
CustomWebControl End Render(HtmlTextWriter writer)
CustomWebControl End RenderControl(HtmlTextWriter writer)
MasterPage End RenderChildren(HtmlTextWriter writer)
MasterPage End Render(HtmlTextWriter writer)
MasterPage End RenderControl(HtmlTextWriter writer)
Page End RenderChildren(HtmlTextWriter writer)
Page End Render(HtmlTextWriter writer)
Page End RenderControl(HtmlTextWriter writer)
aspx.page End Render
Page CreateHtmlTextWriter(System.IO.TextWriter tw)
Friday, June 30, 2006 9:01:00 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [15]  | 
# Friday, June 23, 2006

I've just had it pointed out to me (and quite rightly so) by Craig that my code for enabling the trace with Phil Winstanley's WebException was a touch wrong. I was assuming that the user was (correctly imho) running the application with the trace already disabled. However correct that may be, it may not be the case so as pedantic as this is, the code snippet should really be:

TraceContext t = HttpContext.Current.Trace;
bool bCurrentState = t.IsEnabled;
t.IsEnabled = true;
WebException WE = new WebException();
WE.CurrentException = Server.GetLastError();
WE.Handle();
t.IsEnabled = bCurrentState;

Although I've adapted the code to accomodate this, I really wouldn't run production sites with the trace enabled as this is not only a performace hit but quite a security issue!

Friday, June 23, 2006 5:34:31 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Monday, June 19, 2006

We've been using Phil Winstanley's ASP.Net WebException handler for a while now it's great (see http://weblogs.asp.net/plip/archive/2004/04/17/114984.aspx) the problem with it was that we couldn't get access to the Trace in ASP.Net 1.1. Doug and I spoke with Phil about it but at the time there wasn't a solution however he did say he thought there may be with the emergance of ASP.Net 2.0 (this was a few months before it was due to be released).

Now we're working in 2.0 on a regular basis I thought I'd give it a go and think I have a solution (though quite how reliable it is I'm not 100% sure). The fix is to tap into the current HttpContext and "switch" the trace's output on and then off before and after handling the exception and voila!

Here's my solution:

TraceContext t = HttpContext.Current.Trace;
t.IsEnabled = true;
WebException WE = new WebException();
WE.CurrentException = Server.GetLastError();
WE.Handle();
t.IsEnabled = false;

Monday, June 19, 2006 10:43:44 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, June 16, 2006

I made an interesting discovery this morning. A few weeks ago I was doing a little SEO on The Wargame Company (Devon) and thought I would look into utilising Google SiteMaps. After creating the XML file with the correct format it's just a matter of having Google approve it. They do this by accessing a random page i.e. www.domain.com/GooglesWonderfulPageddmmyyyyhhmmssmmm (which clearly should return a 404) and check the response code -I guess to ensure that you're not trying to spoof the pages in some way.

"What's the problem? I've got custom 404 pages" I hear you cry! Well, if like us you've written some fancy page to handle the error and email you/log it to a database, it turns out that you're not returning a 404 error at all!

What I discovered was that if you configure IIS to handle 404 error pages with a URL you're actually returning a response code of 200. After a little thinking, the only conclusion we could come to was that when setting it as a URL in IIS you're actually redirecting the request which is either a 301 or perhaps a 307 (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html for more information on response codes) and then the final page the user hit's returns a 200 (Response Status "OK") rather than the desired 404 -clearly not what we want!

After a little more investigation we also found that the same thing happened when using ASP.Net's built in handlers and the same thing happens, the only time it doesn't is when you handle the 404 with a File in IIS rather than a URL.

"What can I do about it?" Well that's simple, if you're going to use a URL to handle your 404 errors, make sure you change the Response Status Codes to the correct code, i.e. 404, this is pretty simple to do:

ASP.Net 2.0: Page.Response.StatusCode = 404;

ASP.Net 1.1 (I think): Response.StatusCode = 404;

ASP: Response.Status = "404 You are Unauthorized"

I hope that helps someone out there!

Tim

Update: I've just run fiddler on The Wargame Company (Devon) and and can confirm you get a Response Status Code of 301 before the 200.

ASP | ASP.Net | IIS | SEO
Friday, June 16, 2006 9:48:54 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, June 09, 2006

Ok, The Site Doctor has moved on a fair amount since I started it up, we started off with a somewhat techy design (Version 1) which at the time I loved but as time went on felt it was somewhat cheesy so it was replaced with Version 2 in September 2004 and this has remained the face of The Site Doctor ever since. It’s a nice site but as far as the code quality is concerned it’s terrible, not to mention to SEO issues (to say the least!)


Site Design 1


Site Design 2


Site Design 3?

In the past both designs were tabular based and didn’t care too much for accessibility standards which are now at the fore-front of our minds so we felt it was once again time for a change, but what to do? Although I dabble and with enough time I can come up with some snazzy designs, this time I felt it was necessary to have someone “in the know” to put something together for us.

Mike from Butterfly Media stepped up to the mark with some great concepts, the current design he’s finalising for us is based on the following concepts (click the image to see the next example image): The New TSD design

Watch this space for an update in the next few weeks. I hope to have the final TSD design live shortly after I return from holiday along with a few other, well, niceties :-)

I'd be interested to hear other people's thoughts on the new design

Friday, June 09, 2006 5:08:07 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  |