Helo command rejected: need fully-qualified hostname when sending emails
Wednesday, June 30, 2010 4:20:08 PM (GMT Daylight Time, UTC+01:00)
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:
- Open IIS
- View the properties of you Default SMTP Virtual Server
- Go to the “Delivery” tab
- Click the “Advanced” button (in the bottom right corner)
- Under “Fully-qualified domain name” enter a domain name that points to the server
- Click Ok until you’re back to IIS
Set Umbraco Folder Permissions with Powershell
Thursday, June 17, 2010 2:47:22 PM (GMT Daylight Time, UTC+01:00)
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:
- Save the SetFolderPermission.ps1 script to your server
- Open your PowerShell console (I think it's installed by default if not, you can download PowerShell here)
- Copy the above PowerShell command into notepad
- Update "##PATH TO YOUR INSTALL##" to your Umbraco install
- If your IIS install doesn't use NETWORK SERVICE as the default user, update it to your user
- Make sure it's all on a single line
- 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 ;)
Error 11: An error occurred when merging assemblies: ILMerge.Merge: ERROR!!: Duplicate type 'xyz' found in assembly 'App_Web_rh2sxhkc'. aspnet_merge
Sunday, April 18, 2010 12:47:20 PM (GMT Daylight Time, UTC+01:00)
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.
CodeGarden 09 Open Space Minutes -Space 2: Exception Handling in Umbraco
Thursday, July 09, 2009 1:23:38 AM (GMT Daylight Time, UTC+01:00)
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:
- Space 1: Selling Umbraco
- 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:
- Error Handler v2.0
- ELMAH
- 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.
Store common AppSettings in the web.config and an external file (configSource vs. file)
Saturday, June 27, 2009 8:19:19 AM (GMT Daylight Time, UTC+01:00)
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!
C# FileInfo.MoveTo Cannot create a file when that file already exists exception
Tuesday, May 12, 2009 8:39:35 PM (GMT Daylight Time, UTC+01:00)
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 ;)
Fix missing JavaScript file when you rename the Umbraco admin directory
Tuesday, April 28, 2009 6:49:48 PM (GMT Daylight Time, UTC+01:00)
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:
- Open your UrlRewriting config file (/config/UrlRewriting.config)
- 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.
Quick ASP.Net tip: Half your page size in ASP.Net instantly
Friday, April 17, 2009 3:53:05 PM (GMT Daylight Time, UTC+01:00)
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):
- Download the files: PageStateAdapterv1.0.zip (3KB)
- Put PageStateAdapter.browser into your /App_Browsers/ folder (or create one and add it)
- Put TSDPageStateAdapter.dll into your website's /bin/ folder
- 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!
Advanced Error Reporting in Umbraco, dasBlog and other ASP.Net sites
Friday, February 27, 2009 3:51:13 PM (GMT Standard Time, UTC+00:00)
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.
ASP.Net Profiles - Value cannot be null.Parameter name: type
Sunday, January 18, 2009 6:49:53 PM (GMT Standard Time, UTC+00:00)
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 :)

The remote host closed the connection. The error code is 0x80072746
Wednesday, November 05, 2008 2:03:40 PM (GMT Standard Time, UTC+00:00)
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.
WSDL returning a valid response but object is null
Saturday, November 01, 2008 6:28:49 PM (GMT Standard Time, UTC+00:00)
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: 
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,.
Setting the page title with ASP.Net
Thursday, July 10, 2008 11:57:57 AM (GMT Daylight Time, UTC+01:00)
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";
A seriously elegant SQL Injection -how it was sorted
Thursday, May 29, 2008 3:32:33 PM (GMT Daylight Time, UTC+01:00)
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.
- 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 - Modify the DB user account that is used to have *read only* access to the database
- 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.
- 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.
How to apply Heading Tags in Umbraco
Thursday, May 01, 2008 3:21:56 PM (GMT Daylight Time, UTC+01:00)
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)
The Site Doctor is hiring!
Wednesday, April 30, 2008 4:18:00 PM (GMT Daylight Time, UTC+01:00)
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.
How to: Remove users from the ASP.Net membership database
Sunday, January 13, 2008 8:37:44 PM (GMT Standard Time, UTC+00:00)
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 ;)
Naming conventions for Umbraco
Monday, November 26, 2007 10:30:56 AM (GMT Standard Time, UTC+00:00)
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
Umbraco and Ghost/Cached/Deleted pages appearing in menu
Saturday, November 03, 2007 1:31:42 PM (GMT Standard Time, UTC+00:00)
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:
- Find out the ID of the page in question (easiest way is just to add the ID to the output of the menu XSLT)
- 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"
- 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)
- Reset the applications cache -the easiest way to do this is to simply re-upload the web.config file
That should sort it :)
What have I been up to?
Friday, September 21, 2007 11:20:01 PM (GMT Daylight Time, UTC+01:00)
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
The Controls collection cannot be modified because the control contains code blocks
Friday, August 24, 2007 10:49:56 AM (GMT Daylight Time, UTC+01:00)
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.:
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: <%# ... %>
String.Format- Exception of type System.Web.HttpUnhandledException was thrown.
Wednesday, August 22, 2007 9:43:12 PM (GMT Daylight Time, UTC+01:00)
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
'debug' is undefined with Microsoft AJAX release and TextChangedBehavior.js
Wednesday, August 08, 2007 6:20:47 AM (GMT Daylight Time, UTC+01:00)
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!
Server cannot modify cookies after HTTP headers have been sent (RoleManagerModule.OnLeave)
Monday, August 06, 2007 1:52:08 PM (GMT Daylight Time, UTC+01:00)
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)
ASP.Net Membership SQL Roles access
Tuesday, May 01, 2007 8:41:48 AM (GMT Daylight Time, UTC+01:00)
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...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...end
BEGIN
-- Exited fine, commit the permissions
COMMIT TRAN
END
ELSE
begin...end
GO
How to use Phil's error reporting code
Monday, March 19, 2007 7:34:20 PM (GMT Standard Time, UTC+00:00)
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();
Deal with 404's
#region Deal with
404's
//Redirect the user to a friendly page
if(CheckForErrorType(currentError,
"FileNotFound"))

RedirectToFriendlyUrl(
"");

#endregion
Deal with Spambots
#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;
Handle the Exception
#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
Choose what you're interested in
#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",
true,
true,
false);

}

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
{...}

}

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:
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 ;)
ASP not running on Windows 2003 with ASP.Net installed
Monday, March 12, 2007 10:49:09 AM (GMT Standard Time, UTC+00:00)
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.
Accessing properties by a string name
Friday, March 09, 2007 5:12:02 PM (GMT Standard Time, UTC+00:00)
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");
Add some test data
#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
Create the example email body
Do the work
#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");
Add some test data
#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
Create the example email body
Do the work
#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.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
{...}
public string Name
{...}
public string Email
{...}
public DateTime JoinedDate
{...}

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,
false,
ref obj);

}

public bool GetPropertyValueByName(
string propertyName,
ref object val)
{...}

{
return this.GetPropertyValueByName(propertyName,
false,
ref 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)
{...}

{
Create the example email body

}

}

protected void btnSubmit_Click(
object sender,
EventArgs e)
{...}

{
if (Page.IsValid && !
String.IsNullOrEmpty(txtEmailBody.Text))
{...}

{

litOutput.Text =
"<h2>Output</h2>";
Perform some basic tests
#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",
false,
ref obj))

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

if (testObject.GetPropertyValueByName(
"name",
true,
ref obj))

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

if (testObject.GetPropertyValueByName(
"joineddate",
true,
ref obj))

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

if (testObject.GetPropertyValueByName(
"nothere",
true,
ref obj))

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

#endregion
Create a collection and add a couple of items
#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
Do the work
#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)
{...};

//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!
Reporting errors from AJAX using the WebException Class
Friday, March 09, 2007 7:57:18 AM (GMT Standard Time, UTC+00:00)
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):

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",
true,
true,
false);

}
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)
{...}
{
Deal with 404's
#region Deal with
404's
//Redirect the user to a friendly page
if (CheckForErrorType(currentError,
"FileNotFound") && redirectUser)

RedirectToFriendlyUrl(
TSDGlobals.ErrorPage_PageNotFound);
#endregion
Deal with Spambots
#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;
Handle the Exception
#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
Choose what you're interested in
#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
{...}

}
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 ;)
DasBlog RSS Feed Macro
Saturday, February 24, 2007 2:39:04 PM (GMT Standard Time, UTC+00:00)
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;
Debug output
#region Debug output
if (debugMode)
{...}

{

output.Append(
"<strong><start debug></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
Check whether we need to cache or not
#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");
Debug output
#region Debug output
if (debugMode)
{...}

{

output.AppendFormat(
"<i>cache file</i>: {0}\r\n", cachedFileLoc);

}
#endregion
if (!File.Exists(cachedFileLoc))
{...}

{

cacheExpired =
true;
Debug output
#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;

}
Debug output
#region Debug output
if (debugMode)
{...}

{

output.AppendFormat(
"<i>cache age</i>: : {0} min old <br />\r\n", span1.TotalMinutes);

}
#endregion
}

}
else
{...}

{
Debug output
#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
Debug output
#region Debug output
if (debugMode)
{...}

{

output.Append(
"<hr />");

}
#endregion
//Check whether or not the cache has expired
if (cacheExpired)
{...}

{
Debug output
#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
{...}

{
Debug output
#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();

}
Debug output
#region Debug output
if (debugMode)
{...}

{

output.Append(
"<hr /><strong><end debug></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;
Debug output
#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();
Debug output
#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();
Debug output
#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,
0,
true);
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();

}


}

}
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)
Importing/Referencing DLLs in Visual Studio
Tuesday, February 13, 2007 8:03:03 PM (GMT Standard Time, UTC+00:00)
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)
{...} 
}
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 ;)
WebDD -I was there, were you?
Tuesday, February 06, 2007 12:00:26 PM (GMT Standard Time, UTC+00:00)
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
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 :)
Web.Config, Visual Studio and Intellisense Issues
Friday, January 05, 2007 2:57:57 AM (GMT Standard Time, UTC+00:00)
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*
DasBlog Login issues
Wednesday, January 03, 2007 11:26:03 PM (GMT Standard Time, UTC+00:00)
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.
Dual records in the ASP.Net authentication table
Friday, December 15, 2006 2:38:10 PM (GMT Standard Time, UTC+00:00)
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>
Doug Setzer on Iron Speed / Code Generators
Thursday, December 14, 2006 11:14:09 AM (GMT Standard Time, UTC+00:00)
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
Atlas – AJAX Update issues (Could not load file or assembly Microsoft.Web.Extensions.Design)
Saturday, November 18, 2006 9:52:00 AM (GMT Standard Time, UTC+00:00)
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:
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
Making sense of database responses
Monday, November 13, 2006 8:48:41 PM (GMT Standard Time, UTC+00:00)
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)
{...}
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

}

}
ASP.Net WebException and Error Reporting useful code
Monday, November 06, 2006 6:59:55 PM (GMT Standard Time, UTC+00:00)
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.
Time Breakdown of Modern Web Design
Saturday, November 04, 2006 12:12:12 PM (GMT Standard Time, UTC+00:00)
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!

Originally posted at: http://www.dangerouslyawesome.com/2006/07/02/throwing-a-wobbly/
Parsing Apache Log files
Wednesday, November 01, 2006 5:21:16 PM (GMT Standard Time, UTC+00:00)
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
String.Format and Input string was not in a correct format Error
Monday, October 02, 2006 6:26:29 PM (GMT Daylight Time, UTC+01:00)
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"
IIf Issues
Saturday, September 30, 2006 1:39:47 PM (GMT Daylight Time, UTC+01:00)
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
Page name conflicts
Thursday, September 21, 2006 10:46:33 PM (GMT Daylight Time, UTC+01:00)
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" %>
ASP.Net 2.0 XHTML Valid? It is unless you want to use the W3C validator
Monday, July 31, 2006 12:54:50 PM (GMT Daylight Time, UTC+01:00)
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.
- 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>").
- 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.
Complete Lifecycle of an ASP.Net page and controls
Friday, June 30, 2006 9:01:00 AM (GMT Daylight Time, UTC+01:00)
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:
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)
- 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) |
Error Reporting and the Trace in ASP.Net 2.0 -Code Update
Friday, June 23, 2006 5:34:31 PM (GMT Daylight Time, UTC+01:00)
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!
Phil Winstanley's Error Reporting and the Trace in ASP.Net 2.0
Monday, June 19, 2006 10:43:44 AM (GMT Daylight Time, UTC+01:00)
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;
Custom 404 Error Pages
Friday, June 16, 2006 9:48:54 PM (GMT Daylight Time, UTC+01:00)
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.
New TSD Design
Friday, June 09, 2006 5:08:07 PM (GMT Daylight Time, UTC+01:00)
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