Skip Navigation Links / Posts / Posts By Category
Search site. 
Powered by Google
Darren Neimke (Me)

My Book

Readify

">ASP.NET MVP


Interesting Portals 

NetVibes
This portal feels similar to PageFlakes in many ways but I love their gallery. They also have a feature whre certain chrome elements only become visible when you hover over the web part.

Xtra
A New Zealand news portal. I especially liked the content rotator web part at the top of the middle row. Seems like a nice way to allow a user to browse through data.

 

Posts Archive 

Posts for Category: ASP.NET 2.0

Feed for this Category
LinqDatasource

If you haven't seen the LinqDatasource control in action then take a look at this post by Scott Guthrie:

LINQ to SQL (Part 9 - Using a Custom LINQ Expression with the <asp:LinqDatasource> control)

After looking at that you might wonder - as I do - about where we stand with Datasource controls.  It appears that Dino Esposito was wondering the same thing too:

LinqDataSource vs ObjectDataSource vs SqlDataSource

Personally, I think that this is where Microsoft gets itself into a pickle.  Which Datasource to use?  When to use which one?

For most enterprise developers this won't be an issue as they will only ever bind to the ObjectDatasource, but for the enthusiast it's a pain.  Do I use the SqlDataSource or the LinqDataSource?  The question is mostly that it doesn't matter, but if you get to advanced scenario's - such as the one in ScottGu's post - then the LinqDatasource will likely give you a more 'strongly typed' outcome.

My preference would be that we cast the SqlDatasource aside and move to only ObjectDatasource and LinqDatasource from 3.5 onwards. 

posted on 10/13/2007 7:04:53 PM ( 1 Comments )


Serialization Exceptions when calling Web Services from Ajax

Today I was calling a web service from an Ajax client in an application that I'm working on and I noticed that I wasn't getting any results.  To get a better idea of what was going on, I wired up the error handler for the web service call and did some tracing to see the error message.  The error handling code that I added looked like this:

var msg = String.format(
    "Stack trace: {0}\nService Error: {0}\nMessage: {1}\nStatus Code: {2}\nException Type: {3}\nTimed out: {4}",
    error.get_stackTrace(),
    error.get_message(),
    error.get_statusCode(),
    error.get_exceptionType(),
    error.get_timedOut()
    );

Sys.Debug.trace(msg);

I re-ran the application and this is the exception that was getting returned:

Stack trace:    at System.Web.Script.Serialization.JavaScriptSerializer.SerializeValueInternal...
Message: Specified cast is not valid.
Status Code: 500
Exception Type: System.InvalidCastException
Timed out: false

The exception was telling me that there was a serialization problem with the Type being returned from the web service call.  This was interesting to me because I had just changed the return Type of the ws method call.  I initially wondered whether there was some generated JS type that was cached and that the new Type that I was returning was different from it.  That seemed unlikely though so I looked at the members on the new Type that I was returning.  There was a Guid, an Int32, a custom Enum, and a string.  Hrmm.  I decided to remove the Enum property from the Type to see whether it was causing the failure, and sure enough, my application ran fine.  Here's the Enum that I was using:

public enum PinType : short {
    Standard = 0,
    Favourite = 1,
}

Next I added the enum based property back but I changed the enum so that it was a standard Int32-based one.  Again, it ran fine... so the problem is obviously that the built in JavaScriptSerializer cannot handle short's.  In my case I just decided to cut my losses and leave my enum inheriting from Int32, but here's an article that describes a bit about how to implement your own custom Json serializer to use with non-supported Types:

http://www.asp.net/ajax/documentation/live/mref/T_System_Web_Script_Serialization_JavaScriptConverter.aspx

When you have written your custom converter you can either add it programatically or via the web configuration file like so:

<jsonSerialization maxJsonLength="500">
    <converters>
        <add name="MyConverter"
            type="MarkItUp.CustomEnumTypeConverter"/>
    </converters>
</jsonSerialization>

posted on 9/4/2007 4:45:43 PM ( 1 Comments )


Silverlight meeting in SL tonight

Event Details:

Guest Speaker: Brad Abrams, Group Program Manager, .NET Framework

When: Thursday, August 30th, 3 – 4 PM PST

Where: Visual Studio Island Auditorium in Second Life

 

Here’s the SLURL:http://slurl.com/secondlife/Microsoft/101/123/30/

 

Here’s a Silverlight poster that you can get for free from in the auditorium:

posted on 8/30/2007 4:32:28 PM ( 0 Comments )


Displaying added effects with a Context Menu in Virtual Earth

I've been playing around with Virtual Earth a lot lately for a new, personal web application that I'm writing.  Writing this application has been a great learning experience as I'm using .NET 3.5 in VS2008 Beta 2, Virtual Earth, Silverlight, and plenty of ASP.NET Ajax goodness.  The application is a real client-centric beauty - actually there's only a Default.aspx page with a map on it at this stage, the rest is all driven in the client with async web service calls... the lot! smile_regular

Working with the map has been an especially good experience, and it takes a bit to get into the new paradigm of navigating between locations on a map as opposed to navigating to different pages in a typical web site.

Anyways, today I added some effects that I thought worthy of blogging, so here goes... First, I added a context menu that appears when you right click on the map to display extra functions that the user can use.  Displaying a context menu is a simple matter of attaching to the onclilck event of the map:

this._map.AttachEvent("onclick", this.MapClickHandler);

In your handler function 'MapClickHandler' you can then check for the right button and display some UI for a context menu:

if( e.rightMouseButton ) {
    this._ContextMenu.Show(e.clientX, e.clientY, e.zoomLevel) ;
}

There's a good article here which shows how to do this, but you'll want to use the JavaScript that I've shown above instead of theirs, as mine works against the V5 version of the VirtualEarth API.  The article is good for showing a nice, simple context menu UI though, so go have a read of it.  The context menu ends up looking like this:

The next thing that I wanted to do was to add a little dot when you right click to display the context menu to better visualize the selection.  This is the same behavior as the little red dot that you see when you right click on the map at http://local.live.com/ - notice the little red dot at the top left corner of the context menu in the following image:

Live Maps

To achieve this effect, I inserted some code when handling the right mouse click to display a DIV with the appearance that I wanted:

if( e.rightMouseButton ) {
    this._DisplayDot(e.clientX, e.clientY) ;
    this._ContextMenu.Show(e.clientX, e.clientY, e.zoomLevel) ;
}else{
    this._HideDot() ;
    this._ContextMenu.Hide() ;

}

The _DisplayDot function looks like so:

_DisplayDot: function(clientX, clientY) {
    var dotselected = $get("dotselected") ;
    dotselected.style.display='block'; 
    dotselected.style.left = clientX - 5;
    dotselected.style.top = clientY - 5;

}

As you can see, it simply uses the $get function to get a hold of the DIV instance and then sets it to visible.  And Hiding it is just as simple...

_HideDot: function() {
    var dotselected = $get("dotselected") ;
    dotselected.style.display='none'; 
}

The next thing that I wanted to do was to ensure that the map zooms to where you right click when you choose Zoom from the context menu.  A lot of mapping apps don't do this, but it's definitely what I would class as expected behavior.  This is done by storing the co-ordinates when the ContextMenu is created like so:

if( e.rightMouseButton ) {
    this._DisplayDot(e.clientX, e.clientY) ;
    this._ContextMenu.Show(e.clientX, e.clientY, e.zoomLevel) ;
    var pixel = new VEPixel(e.mapX - 5, e.mapY - 5);
    this._selectedLL = this._map.PixelToLatLong(pixel);
}else{
    this._HideDot(e.clientX, e.clientY) ;
    this._ContextMenu.Hide() ;
    this._selectedLL = null ;
}

Doing this stores the LatLong so that it is available for any of the callback handlers for your context menu functions.  An example of how this might be used can be seen in this context menu function that I have for zooming to street level:

ZoomToCityLevel: function() {
    this._map.SetCenterAndZoom(this._selectedLL, 12);
    this._contextMenu.Hide() ;
    this._HideDot() ;
    this._selectedLL = null ;
}

When the user clicks on a custom context menu function I call this function which sets the map's zoom level to '12' and centers the map based on the stored co-ordinates that we collected when they right-clicked on the map.  This gives the desired effect of zooming to city level and scrolling the map to where the user selected - as opposed to simply zooming, which would zoom to the currently centered location, which might not have been anywhere near where the user clicked.

Anyways, a long post, but I hope that it helps someone, somewhere smile_regular

posted on 8/27/2007 11:21:26 PM ( 2 Comments )


What Google Gears really is.

A couple of months ago I opened Google Reader and was presented with a message from the web site that there was now an option of working with my reader while offline.  To do this I was required to download a small, client-side program called Google Gears.  The idea sounded cool enough so I installed the program pretty much straight away.  In the following 5 or 6 weeks that I had "Gears" installed, I discovered that browsing the web in offline mode is obviously not a scenario that I'm all that interested in, because I think that I tried it once - and it didn't work because opening the web page in either offline or online mode while I was disconnected gave me errors.  So I uninstalled it without ever having enjoyed that experience that I was expecting.

Having thought about it some more I don't really think that we'll ever really work with web pages while in disconnected mode.  Instead, we'll use smart client applications that allow us to achieve certain goals while disconnected such as some of the applications that we are seeing underneath the Live brand such as Live Writer and Live Mail.

It's worth dissecting Google Gears though because if it's just something that allows you to browse the web in disconnected mode, and I don't believe that that is such a key scenario, then either I'm wrong or Google is - and quite frankly, I don't believe that I'm that smart. Nerd

Google Gears is essentially 3 things:

  1. A client-side persistence store for storing large amounts of data.
  2. A technology that enables an offline browsing experience in the browser.
  3. Enables the scenario of querying data in the client using SQL syntax.

I've already discounted point #2 as not that important, so let's focus on points #1 and #3.

Storing larger amounts of data on the client

This is a key scenario as we look to create richer web experiences for our end users.  Here's a couple of scenario's where having a larger client-side cache of data is very important.

Saving draft copies of documents

Whether they are personal articles such as blog articles and emails, or business documents, we are all using the the web to create and update documents a lot more these days.  And if you've ever had the experience that I have, where you've just finished writing a large document in the browser only to have it crash on you, then you will understand exactly how important it is, to be able to save draft copies of documents as you are working with them.  To do this now would require saving the data on a server somewhere, thus making it impossible to continue working with the document when you go offline.  With a client-side cache, you could start writing a document in the browser while online and then continue later in offline mode by accessing the document via a smart client application.  Either way, having a draft copy would mean that the experience of losing data could be a thing of the past.

Providing a better application experience

While at Tech.Ed last week, I had the pleasure of attending Dan Green's presentation on UX.  It was a very stimulatory (yep, a made up word Smile) and during it he gave the example of how the Visual Studio intellisense engine remembers our previous choices - and therefore when we repeatedly re-access certain members, they are already pre-selected for us.  Consider when you do lots of Console.WriteLine's in a row just how easy it is to simply accept the defaults that are thrown up at you by Intellisense, without having to constantly scan a list from the top each time. 

In the same way it would be nice to provide a richer experience to users of web applications by storing more defaults and settings in the client.  With the size limitations of cookie's, storing a large cache of preference settings in the past has been unreliable or impossible - and so having a client-side database would alleviate these types of issues and would allow us to provide much richer client experiences, similar to that of the Intellisense scenario.

Querying data in the client using SQL syntax 

For certain types of applications - such as mail - having the ability to query data in the client is an important optimization.  I imagine that Google came up with this idea through features that they had to provide with their GMail application.  For an application that works in the browser - such as GMail - it would be quite costly to send such large amounts of data back and forth between the server and the browser whenever you wanted to work with it.  However pushing the logic down into the client would increase the client-side complexity for certain operations like sorting and finding data.  For these types of scenarios, having the ability to work with data via SQL statements in the client is highly desirable.

Silverlight

In Silverlight 1.1 we will have the ability to write managed code which runs on the client, and as a part of this we will initially have access to an IsolatedStorage implementation.  This will solve the first problem of having a larger client-side cache of data.  I'm not sure about Linq and Silverlight but if we were to get Linq capabilities in Silverlight then many of the features that Google Gears delivers will actually be present within Silverlight itself.

posted on 8/12/2007 9:29:54 PM ( 0 Comments )


2 little things I learned today

This week I've managed to get in and do some ASP.NET coding again and I thought that I'd make a note of a couple of little things that I noticed/learned:

Normally when you want to add client-side behaviors to an ASP.NET server control you use it's Attributes collection like so:

TextBox1.Attributes.Add("onblur", "Foobar(this);") ;

If you try that with a Checkbox control the javascript gets bound to a <span> element which encapsulates the <input type="checkbox"... />.  That means that the 'this' instance will not be the checkbox element that you are expecting.  To bind the event handler to the Checkbox element you use the InputAttributes property like so:

Checkbox1.InputAttributes.Add("onblur", "Foobar(this);") ;

The second thing that I noticed was if you attempt to format a date value in a BoundColumn like so, you will not get the results you expect:

<asp:BoundField DataField="SomeDateTime" DataFormatString="{0:MMM-yyyy}" HeaderText="Month" />

The problem is that, for date values, the BoundField HtmlEncode's the values before the format string is evaluated.  The trick is to tell the column NOT to HtmlEncode the output, like so:

<asp:BoundField DataField="SomeDateTime" DataFormatString="{0:MMM-yyyy}" HeaderText="Month" HtmlEncode="false" />

It's always the little things that throw you! smile_regular

posted on 7/26/2007 5:40:37 PM ( 3 Comments )


ELMAH

In this post, Phil reminds us to place our ELMAH pages behind secure paths so that they cannot be viewed by all visitors to our sites.  I implemented ELMAH on a new public-facing website that I've been working on for the past few weeks and, having not implemented it for a few years was surprised to see that it's now on Google Code

posted on 7/25/2007 7:39:15 AM ( 0 Comments )


Create a list of all countries

This might be useful for creating lists of countries to display in things such as DropDown lists on forms and so-forth:

List<string> GetCountries() {
    List<string> countries = new List<string>();

    countries.Add("Afghanistan");
    countries.Add("Aland Islands");
    countries.Add("Albania");
    countries.Add("Algeria");
    countries.Add("American Samoa");
    countries.Add("Andorra");
    countries.Add("Angola");
    countries.Add("Anguilla");
    countries.Add("Antarctica");
    countries.Add("Antigua and Barbuda");
    countries.Add("Argentina");
    countries.Add("Armenia");
    countries.Add("Aruba");
    countries.Add("Australia");
    countries.Add("Austria");
    countries.Add("Azerbaijan");
    countries.Add("Bahamas");
    countries.Add("Bahrain");
    countries.Add("Bangladesh");
    countries.Add("Barbados");
    countries.Add("Belarus");
    countries.Add("Belgium");
    countries.Add("Belize");
    countries.Add("Benin");
    countries.Add("Bermuda");
    countries.Add("Bhutan");
    countries.Add("Bolivia");
    countries.Add("Bosnia and Herzegovina");
    countries.Add("Botswana");
    countries.Add("Bouvet Island");
    countries.Add("Brazil");
    countries.Add("British Indian Ocean Territory");
    countries.Add("Brunei");
    countries.Add("Bulgaria");
    countries.Add("Burkina Faso");
    countries.Add("Burundi");
    countries.Add("Cambodia");
    countries.Add("Cameroon");
    countries.Add("Canada");
    countries.Add("Cape Verde");
    countries.Add("Cayman Islands");
    countries.Add("Central African Republic");
    countries.Add("Chad");
    countries.Add("Chile");
    countries.Add("China");
    countries.Add("Christmas Island");
    countries.Add("Cocos Islands");
    countries.Add("Colombia");
    countries.Add("Comoros");
    countries.Add("Congo");
    countries.Add("Congo, Democratic Republic of the");
    countries.Add("Cook Islands");
    countries.Add("Costa Rica");
    countries.Add("Côte d&#39;Ivoire");
    countries.Add("Croatia");
    countries.Add("Cuba");
    countries.Add("Cyprus");
    countries.Add("Czech Republic");
    countries.Add("Denmark");
    countries.Add("Djibouti");
    countries.Add("Dominica");
    countries.Add("Dominican Republic");
    countries.Add("East Timor");
    countries.Add("Ecuador");
    countries.Add("Egypt");
    countries.Add("El Salvador");
    countries.Add("Equatorial Guinea");
    countries.Add("Eritrea");
    countries.Add("Estonia");
    countries.Add("Ethiopia");
    countries.Add("Falkland Islands");
    countries.Add("Faroe Islands");
    countries.Add("Fiji");
    countries.Add("Finland");
    countries.Add("France");
    countries.Add("French Guiana");
    countries.Add("French Polynesia");
    countries.Add("French Southern Territories");
    countries.Add("Gabon");
    countries.Add("Gambia");
    countries.Add("Georgia");
    countries.Add("Germany");
    countries.Add("Ghana");
    countries.Add("Gibraltar");
    countries.Add("Greece");
    countries.Add("Greenland");
    countries.Add("Grenada");
    countries.Add("Guadeloupe");
    countries.Add("Guam");
    countries.Add("Guatemala");
    countries.Add("Guernsey");
    countries.Add("Guinea");
    countries.Add("Guinea-Bissau");
    countries.Add("Guyana");
    countries.Add("Haiti");
    countries.Add("Heard Island and McDonald Islands");
    countries.Add("Honduras");
    countries.Add("Hong Kong");
    countries.Add("Hungary");
    countries.Add("Iceland");
    countries.Add("India");
    countries.Add("Indonesia");
    countries.Add("Iran");
    countries.Add("Iraq");
    countries.Add("Ireland");
    countries.Add("Isle of Man");
    countries.Add("Israel");
    countries.Add("Italy");
    countries.Add("Jamaica");
    countries.Add("Japan");
    countries.Add("Jersey");
    countries.Add("Jordan");
    countries.Add("Kazakhstan");
    countries.Add("Kenya");
    countries.Add("Kiribati");
    countries.Add("Kuwait");
    countries.Add("Kyrgyzstan");
    countries.Add("Laos");
    countries.Add("Latvia");
    countries.Add("Lebanon");
    countries.Add("Lesotho");
    countries.Add("Liberia");
    countries.Add("Libya");
    countries.Add("Liechtenstein");
    countries.Add("Lithuania");
    countries.Add("Luxembourg");
    countries.Add("Macao");
    countries.Add("Macedonia");
    countries.Add("Madagascar");
    countries.Add("Malawi");
    countries.Add("Malaysia");
    countries.Add("Maldives");
    countries.Add("Mali");
    countries.Add("Malta");
    countries.Add("Marshall Islands");
    countries.Add("Martinique");
    countries.Add("Mauritania");
    countries.Add("Mauritius");
    countries.Add("Mayotte");
    countries.Add("Mexico");
    countries.Add("Micronesia");
    countries.Add("Moldova");
    countries.Add("Monaco");
    countries.Add("Mongolia");
    countries.Add("Montenegro");
    countries.Add("Montserrat");
    countries.Add("Morocco");
    countries.Add("Mozambique");
    countries.Add("Myanmar");
    countries.Add("Namibia");
    countries.Add("Nauru");
    countries.Add("Nepal");
    countries.Add("Netherlands");
    countries.Add("Netherlands Antilles");
    countries.Add("New Caledonia");
    countries.Add("New Zealand");
    countries.Add("Nicaragua");
    countries.Add("Niger");
    countries.Add("Nigeria");
    countries.Add("Niue");
    countries.Add("Norfolk Island");
    countries.Add("Northern Mariana Islands");
    countries.Add("North Korea");
    countries.Add("Norway");
    countries.Add("Oman");
    countries.Add("Pakistan");
    countries.Add("Palau");
    countries.Add("Palestine");
    countries.Add("Panama");
    countries.Add("Papua New Guinea");
    countries.Add("Paraguay");
    countries.Add("Peru");
    countries.Add("Philippines");
    countries.Add("Pitcairn");
    countries.Add("Poland");
    countries.Add("Portugal");
    countries.Add("Puerto Rico");
    countries.Add("Qatar");
    countries.Add("Reunion");
    countries.Add("Romania");
    countries.Add("Russia");
    countries.Add("Rwanda");
    countries.Add("Saint Helena");
    countries.Add("Saint Kitts and Nevis");
    countries.Add("Saint Lucia");
    countries.Add("Saint Pierre and Miquelon");
    countries.Add("Saint Vincent and the Grenadines");
    countries.Add("Samoa");
    countries.Add("San Marino");
    countries.Add("São Tomé and Príncipe");
    countries.Add("Saudi Arabia");
    countries.Add("Senegal");
    countries.Add("Serbia");
    countries.Add("Serbia and Montenegro");
    countries.Add("Seychelles");
    countries.Add("Sierra Leone");
    countries.Add("Singapore");
    countries.Add("Slovakia");
    countries.Add("Slovenia");
    countries.Add("Solomon Islands");
    countries.Add("Somalia");
    countries.Add("South Africa");
    countries.Add("South Georgia and the South Sandwich Islands");
    countries.Add("South Korea");
    countries.Add("Spain");
    countries.Add("Sri Lanka");
    countries.Add("Sudan");
    countries.Add("Suriname");
    countries.Add("Svalbard and Jan Mayen");
    countries.Add("Swaziland");
    countries.Add("Sweden");
    countries.Add("Switzerland");
    countries.Add("Syria");
    countries.Add("Taiwan");
    countries.Add("Tajikistan");
    countries.Add("Tanzania");
    countries.Add("Thailand");
    countries.Add("Togo");
    countries.Add("Tokelau");
    countries.Add("Tonga");
    countries.Add("Trinidad and Tobago");
    countries.Add("Tunisia");
    countries.Add("Turkey");
    countries.Add("Turkmenistan");
    countries.Add("Turks and Caicos Islands");
    countries.Add("Tuvalu");
    countries.Add("Uganda");
    countries.Add("Ukraine");
    countries.Add("United Arab Emirates");
    countries.Add("United Kingdom");
    countries.Add("United States");
    countries.Add("United States minor outlying islands");
    countries.Add("Uruguay");
    countries.Add("Uzbekistan");
    countries.Add("Vanuatu");
    countries.Add("Vatican City");
    countries.Add("Venezuela");
    countries.Add("Vietnam");
    countries.Add("Virgin Islands, British");
    countries.Add("Virgin Islands, U.S.");
    countries.Add("Wallis and Futuna");
    countries.Add("Western Sahara");
    countries.Add("Yemen");
    countries.Add("Zambia");
    countries.Add("Zimbabwe");

    return countries;
}

posted on 7/19/2007 11:11:28 AM ( 3 Comments )


Setting a custom timeout for ASP.NET's "Remember me next time" functionality

The ASP.NET 2.0 Login control comes with an embedded piece of UI known as the "Remember me next time" checkbox.  By checking this checkbox when they login, a user can choose to have their authentication ticket persisted so that they do not have to login every time they visit the site.  By default this ticket will be remembered for a duration of 30 minutes before the user is again asked to login.  

As a user I find it a little annoying when I've asked a site to remember me that I have to login each time.  You can tweak the amount of time that the authentication cookie is persisted for by playing with the timeout and expiration policy settings of the forms element in web.config.  Scott has done a great job of describing the behavior of cookie timeout's in his article here:

A Potential Security Hole with "Remember Me Next Time"

posted on 7/18/2007 10:13:55 AM ( 0 Comments )


My thoughts on EntLib

The other day I wrote this post poking fun at how ADNUG had me down - incorrectly - as speaking about EntLib at Code Camp this weekend. In the comments for that article Jeff inquired as to my opinion on EntLib - a question that I've been asked a few times of late.

The short answer is that I'm no fan of EntLib and actively avoid using it in any projects that I'm involved in. I have 2 major objections with EntLib:

  1. It's a massive dependency to take
  2. It's overly complex

The Dependency Thing
Last time I looked, EntLib was a collection of something like 20 projects. I don't know how many lines of code live in all of those projects but realize that when you take a dependency on EntLib that you are taking a dependency on every one of those lines.

Complexity
The argument for using EntLib (or at least the one that I've heard) seems to be that it's a framework that simplifies common tasks such as data access and adding tracing to applications. Having implemented distributed transaction support in EntLib I definitely do not agree that it simplifies data access! Oh sure it's all config file based and you could do things such as switch databases easily, but a) you can achieve that simply with standard ADO.NET code, and b) nobody ever does that anyway!

As for tracing, the tracing API's in .NET 2.0 along with their standard Listeners make tracing to multiple sources as simple as making a call such as:

Trace.TraceInformation(...) ;

So no. I really do not think that EntLib simplifies tracing either.

And then there's performance, but I won't go there without raw facts, other than to say... think about the cost associated with reading config files, the amount of reflection going on, and the overall number of objects that are being created.

Sorry EntLib - I don't love you.

posted on 7/5/2007 8:05:55 PM ( 6 Comments )


Rule-based reporting

This week we had a requirement to display many differently filtered views of data within a web page.  In this particular instance, the data represented a resultset of items from one of our line of business apps.  Each view of the data was based on a particular set of logic and needed to be displayed in a grid like so:

rule processor

 

The report was for showing various exceptional states of resource scheduling data and so each category represented a different exceptional state, such as:

  1. Over allocated entries
  2. Under allocated entries
  3. Entries where the submitted values were less than the scheduled values
  4. Entries submitted on days when the sun was shining and where there were 3 or more red cars parked in the carpark

You get the idea smile_regular  In creating the report I wanted to ensure that we could easily add new category sections quickly and so I went for a processing system where you could easily create and add new rules to a single processing pipeline.  So we start with a generic definition of a rule and an item that the rule will act upon:

internal class GenericItem {

     private int _id;
     public int ID { get { return _id; } set { _id = value; } }

     public GenericItem(int id) { this._id = id; }

     public override string ToString() { return "Name: " + _id.ToString(); }
}

internal abstract class ItemRule {

     List<GenericItem> _matches = new List<GenericItem>();
     public List<GenericItem> Matches { get { return this._matches; } set { this._matches = value; } }

     abstract public void Evaluate(GenericItem item);
     virtual public void Complete() { this.Clear(); }
     virtual public void Clear() { }
}

You can see from this code that each rule will receive an item to evaluate and will expose a Matches collection of the items which match the rule definition.  The ItemRule class is abstract so the implementation logic for each rule will be contained in the Evaluate method of each of the derived rule classes.  Here's a couple of simple rules which filter items based on whether they are odd or even:

internal class IsEvenRule : ItemRule {
     public override void Evaluate(GenericItem item) {
          if (item.ID % 2 == 0) this.Matches.Add(item);
     }
}

internal class IsOddRule : ItemRule {
     public override void Evaluate(GenericItem item) {
          if (item.ID % 2 == 1) this.Matches.Add(item);
     }
}

These rules are very simplistic.  They perform a simple piece of logic and then if the item matches that logic they add the item to their Matches collection.  Some rules would be much more complex though.  Consider a rule where you need to match where there are a certain number of items in the list - meaning that you couldn't fully report until you had done a complete scan of all of the items.  Here's a rule for such a situation where the Match collection is populated only where there are greater than 10 instances of odd-numbered items in the list:

internal class TenOddsRule : ItemRule {

     List<GenericItem> _evaluatedItems = new List<GenericItem>();

     public override void Evaluate(GenericItem item) {
          if (item.ID % 2 == 1) {

               if (this._evaluatedItems.Count == 9) {
                    foreach (GenericItem evaluatedItem in this._evaluatedItems) {
                         this.Matches.Add(evaluatedItem);
                    }
               }

               this._evaluatedItems.Add(item);

               if (this._evaluatedItems.Count >= 10) {
                    this.Matches.Add(item);
               }
          }
     }

     public override void Complete() {
          this._evaluatedItems.Clear();
          base.Complete();
     }
}

Notice that the rule adds has a private list that it adds items to and only populates its Matches collection when the inner-list grows to beyond 9 items.

Now we can wrap our rules logic within a generic processing pump that will contain the logic for performing iteration and applying each of the rules to the items in the list, like so:

internal class ItemProcessor {

     List<ItemRule> _rules = new List<ItemRule>();
     public void AddRule(ItemRule rule) {
          rule.Clear();
          _rules.Add(rule);
     }

     public void AddRules(IEnumerable<ItemRule> rules) {
          foreach (ItemRule rule in rules) { AddRule(rule); }
     }

     public void ProcessItems(List<GenericItem> items) {

          foreach (GenericItem item in items) {
               foreach (ItemRule rule in _rules) {
                    rule.Evaluate(item);
               }
          }

          foreach (ItemRule rule in _rules) {
               rule.Complete();
          }
     }
}

Then we can create some rules and add them to our processor:

IsEvenRule evensRule = new IsEvenRule();
IsOddRule oddsRule = new IsOddRule();
TenOddsRule tenOddsRule = new TenOddsRule();

ItemProcessor processor = new ItemProcessor();
processor.AddRules(new ItemRule[] { evensRule, oddsRule, tenOddsRule });

processor.ProcessItems(items);

With the items processed, we can now use the Matches collections to bind to our UI elements, like so:

rpt1.DataSource = evensRule.Matches ;

rpt2.DataSource = oddsRule.Matches ;
rpt3.DataSource = tenOddsRule.Matches ;

The real beauty of using this approach is that new rules can be created quickly and easily to meet the needs of the business while keeping the processing logic distinct and easy to maintain.

posted on 5/11/2007 6:12:18 PM ( 0 Comments )


Home

Here's a link to the documentation for the new May 2007 Futures CTP release for asp.net:

ASP.NET AJAX Futures Release

posted on 5/2/2007 11:01:34 PM ( 0 Comments )


Added the Silverlight logo to my blog

I was browsing around over the weekend and came across Dave Campbell's site which has the cool spinny Silverlight logo on it.  It's the same spinny logo that you can see at the very end of the Silverlight ad.  I think that it originally came from Mike Harsh's blog.

Anyway, thanks to Dave for helping me get it up and running on my own blog.  If you'd like to see it for yourself, click here to be taken to my blog.  You should get to see the spinny Silverlight logo in the top right corner. 

Having now downloaded the WPF/E Feb CTP SDK to play around with and re-familiarized myself with a bunch of links to people who are blogging about this stuff fairly regularly, I'm looking forward to getting stuck into the mechanics of it all.

posted on 4/29/2007 3:31:36 PM ( 0 Comments )


Working with Data and ASP.NET 2.0

My 2 biggest role models in the .NET world are Scott Mitchell and Dino Esposito - sure there are others that I have huge respect for such as Mitch Denny, Scott Hanselmann, and Phil Haack, but somehow Scott and Dino both strike a special chord with me.  Both of these guys are a real testament to the fact that through persisting towards a goal you can create great things of real value.  There are so many examples of the rich assets that both of these guys have built up over the years - blogs, books, websites, articles, starter kits, etc.  Here's an example of the latest work that Scott has produced:

Working with Data and ASP.NET 2.0

This is an asset that Scott has produced over the past 15 or so months: more than 50 high quality tutorials.  A great example of what you can do if you stick at something, not lose focus, and give it your all!

posted on 4/19/2007 9:49:50 PM ( 1 Comments )


AJAX Apps Ripe Targets for JavaScript Hijacking? Not ASP.NET Ajax!

Recently on an ASP.NET list there was some discussion about this article:

Link to AJAX Apps Ripe Targets for JavaScript Hijacking

In the article - dated April 2, 2007 - it is mentioned that ASP.NET Ajax, along with several other leading AJAX implementations - such as the Google Web Toolkit - has a JavaScript-related security vulnerability.  This article was based on a blog post written by Brian Chess, co-founder and "Chief Scientist" of a company named Fortify Software.

2 days after that article was released, Scott Guthrie posted his own blog article which states that ASP.NET Ajax is not vulnerable to this threat:

http://weblogs.asp.net/scottgu/archive/2007/04/04/json-hijacking-and-how-asp-net-ajax-1-0-mitigates-these-attacks.aspx

It's all interesting reading and makes you thankful to be building atop tested, patchable frameworks such as ASP.NET smile_regular

posted on 4/8/2007 10:38:34 PM ( 1 Comments )


Custom Control Authoring - the differences between now and then

So many things were improved in ASP.NET 2.0!  Today I was reminded of some of these improvements and so I wanted to make a bit of a blog "Note to Self" about what they were.

Remember what it was like when you created custom server controls in ASP.NET 1.x?  It was cool, and you could do it, but there were many things that caused pain.  I created lots of custom controls in the 1.x days and packaged many of them for use by external users, so I got a pretty good feel for the "pain points".  This weekend was the first time that I really sat down with the aim of creating a custom server control in ASP.NET 2.0 that I was hoping to package for external users and some of the changes surprised me.

Resources

The first notable improvement was in how resources are handled.  In 1.x you would typically either add resources as embedded resources or, as I saw on many occassions - including with ASP.NET itself - package resources as external files to be copied to the filesystem.  If you took the embedded resources path, here's the type of code that you had to write to extract your scripts and embed them in the page with your control:

Type t = typeof(timer) ;
Assembly ass = t.Assembly ;

using (StreamReader reader = new StreamReader(ass.GetManifestResourceStream(t, "TimerScript.js"))) {

    string script = "<script language='javascript' type='text/javascript' >" + reader.ReadToEnd() + "</script>" ;
    this.Page.RegisterClientScriptBlock(scriptKey, script);
}

There's some tricky, low-level code there - and it get's much more difficult if you want to maximise caching and stuff.  In ASP.NET 2.0 there's a much more "typed" version of how to embed resources.  Nikhil has a couple of great articles which describe how this works:

WebResourceAttribute

Web Resouces - Design Time

Essentially, to embed a JavaScript in a custom control in ASP.NET 2.0 you would do the following:

  1. Add your JavaScript file to your Custom Control library and set it's build action as "Embedded Resource"
  2. Add a WebResource assembly attribute to your assembly to mark your resource as web accessible:

        [assembly: WebResource("Button.gif", ContentType.ImageGif)]
  3. If your resource is an image, you can now simply reference that image like so:

        myButton.ImageUrl = Page.GetWebResourceUrl(typeof(MyCustomControl), "Button.gif");

Nikhil's articles explain all of the other details that you need to know about WebResourceAttribute and how it works.

 

Client Scripts

Even before tMicrosoft Ajax, ASP.NET 2.0 added new support for working with client scripts as a control author.  Most importantly, they moved the script logic from the Page, ie:

Page.RegisterStartupScript(...) ;

to a new ClientScriptManager class:

Page.ScriptManager.RegisterStartupScript(...);

where ScriptManager represents an instance of the ClientScriptManager class.  One of the interesting things about the ClientScriptManager class is that many of it's methods require a "Type" argument to be passed in.  Here's an example of using the Type argument in the RegisterStartupScript method:

scripts.RegisterClientScriptResource(typeof(PageLoad), "MarkItUp.WebControls.PageLoadScripts.js");

Note that, for the first argument I pass in the type of my custom control class (PageLoad).  This argument is used as a key for determining the scope of the resource that will be emitted.  Here's a nice article by Rick Strahl which talks about the Type argument and discusses some gotchas with it:

http://west-wind.com/WebLog/posts/9837.aspx

 

Script Control

With the advent of Ajax, ASP.NET has included much more support for working with scripts and client-side object from within server-side code.  I'd actually forgotten about some of this stuff until today when Glav tidied up some of my "old school" custom control code and replaced it with the shiny new stuff.  Here's an overview of the changes that he made. 

First, he changed my control so that, instead of inheriting from WebControl, it instead inherited from ScriptControl.  This control implements the IScriptControl interface which means that it contains two important methods for control authors: GetScriptDescriptors, and GetScriptReferences.

GetScriptReferences

...allows you to return a collection of each script reference that you wish emit with your control.  The syntax is really simple:

protected override IEnumerable<ScriptReference> GetScriptReferences() {
    ScriptReference script = new ScriptReference("YourNamespace.YourJavascriptFile.js", "Your.Assembly");
    yield return script;
}

GetScriptDescriptors

...is a very useful method which removes a lot of the fiddly and potentially buggy (and non-typed) code that control authors used to use to instantiate client side instances of their control and to set its properties.  There are ScriptComponentDescriptors - for working with Ajax component client types - and ScriptBehaviourDescriptors - for working with Ajax behaviour client types.  Under the hood, ScriptDescriptors map to the Ajax client $create method which is used to instantiate client types at runtime, but this provides a very type-safe and clean way of working with these client-side statements.

As a refresher, this is what the client-side $create Ajax method looks like:

$create(type, properties, events, references, element);

The ASP.NET syntax for creating Ajax types from the server is to use ScriptDescriptors.  To create a type instance, simply create an instance of a descriptor and return it from the GetScriptDescriptors method of your ScriptControl:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
    ScriptComponentDescriptor cmp = new ScriptComponentDescriptor("MyNamespace.MyClass");
    yield return cmp;
}

To fill in other arguments in the $create statement, you can use the AddProperty, AddEvent, and AddComponentProperty methods to fill those in.  In the example where you want to instantiate a client type named Samples.SimpleComponent and construct that type by setting it's AProp property to 5000, you can use the AddProperty method to fill in that constructor information like so:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors() {
    ScriptComponentDescriptor cmp = new ScriptComponentDescriptor("Samples.SimpleComponent");
    cmp.AddProperty("AProp", 5000);
    cmp.AddProperty("id", this.ID) ;
    yield return cmp;
}

You can see what the Javascript code for the Samples.SimpleComponent looks like here:

http://ajax.asp.net/docs/tutorials/CreatingClientComponentPrototype.aspx

posted on 4/1/2007 10:13:41 PM ( 1 Comments )


Creating Responsive Portals - Async Tasks

Web portals are the ultimate companion to that cool SOA architecture you've been working on.  With all of those great line-of-business (LOB) services that you've created, web parts seem like the ideal way of allowing your users to interact with that data. 

Getting started with this is real easy.  You write 20-30 lines of code and whoosh!... your LOB data is right up there on your portal.

protected override void OnPreRender(EventArgs e) {
    base.OnPreRender(e);

    WeatherType weather = GetWeather();
    img1.ImageUrl = string.Format("~/images/{0}.gif", weather.ToString());
}

In this piece of code we can see that in the PreRender code of our web part that we make our service call - GetWeather() - and use the data that is returned from that call to display a relevant image to the end user.  Pretty typical code really. As part of the processing of the portal page by ASP.NET, your web part will get called upon to render itself.

Control execution within the page lifecycle

 

Of course, as time goes by, you will undoubtedly add many new web parts to your portal page: Calendars, Weather, CRM data, Reports, etc.  And each of these will trundle off to your SOA services, get their data, and come back with their own view for your users to use.  Of course there's a problem here.  Because of the synchronous nature of the ASP.NET web page where controls are loaded and executed in sequence, each control adds time to the overall processing length of the page:

 

Multiple Control execution within the page lifecycle

 

So now you have a page which - with all of those calls to external resources - is taking ages to load... maybe 10-20 seconds for a page with only a few web parts on it.  What to do?

Given that each of these web parts are independent of each other we'd ideally like to run them in parallel so that we don't wear the large time penalty for the whole page.  Something like this:

 

Asynchronous Control execution with the page lifecycle

 

In ASP.NET 2.0 they actually added support for easily creating pages which allow tasks to be run asynchronously in a manner similar to this.  All that you have to do to gain these benefits are to include an Async="true" attribute to your Page directive so that ASP.NET knows to implement the IHttpAsyncHandler in the page, and then write your code so that it executes within asynchronous task blocks.  There's an excellent article by Fritz Onion which discusses this architecture here:

http://msdn.microsoft.com/msdnmag/issues/06/07/ExtremeASPNET/

In the article, Fritz highlights the fact that we can simply use the existing asynchronous interfaces that come with things like ADO.NET and Web Service generated proxies in .NET and plug them into the architecture.  When you need to execute asynchronous tasks for which no asynchronous interfaces already exist - such as reading from the file system - then you will need to queue your own delegate to to that work:

private delegate WeatherType DoStuffDelegate();

IAsyncResult BeginGetWeather(object src, EventArgs e, AsyncCallback cb, object state) {
    DoStuffDelegate d = new DoStuffDelegate(GetWeather);
    return d.BeginInvoke(cb, state);
}

void EndGetWeather(IAsyncResult ar) {
    DoStuffDelegate d = (DoStuffDelegate)((AsyncResult)ar).AsyncDelegate;
    WeatherType weather = d.EndInvoke(ar);
    img1.ImageUrl = string.Format("~/images/{0}.gif", weather.ToString());
}

Notice that in this code, our expensive GetWeather() operation is queued to run asynchronously.  The final thing is to show the pattern that you should implement to ensure that your web parts are ready to take advantage of a hosting page that is set to Async="true":

if (Page.IsAsync) {
    Page.RegisterAsyncTask(
        new PageAsyncTask(
            new BeginEventHandler(BeginGetWeather),
            new EndEventHandler(EndGetWeather),
            null, null, true)
    );

} else {
    WeatherType weather = GetWeather();
    img1.ImageUrl = string.Format("~/images/{0}.gif", weather.ToString());
}

Now our web part will always work, but will allow our users to gain the benefits of asynchronous processing when it is enabled at Page level.

posted on 3/30/2007 11:32:42 AM ( 0 Comments )


Virtual Earth Seminar : Sydney 2/4/07 and Melbourne 3/4/07

Just saw this link via Frank's blog:

Link to frankarr - an aussie microsoft blogger : Virtual Earth Seminar : Sydney 2/4/07 and Melbourne 3/4/07

This should be interesting.  The days of being able to call yourself a web developer just because you've mastered the DataGrid are long gone.  Today's modern web application is a dynamic, interactive affair.  And because of this, mapping technology and the role it plays in providing visual data to mash-ups is going to increase massively.

posted on 3/21/2007 1:29:34 AM ( 0 Comments )


Rocky Heckman wouldn't like Ajax

Rocky was a pretty paranoid guy and I'm not quite sure what he'd think about cool new technologies like Ajax.  Me: I see them as a great new way to build engaging UI's by providing more responsive web applications.  Rocky: he'd be likely to just see them as a new attack surface or something.  Poor Rocky.

Anyway, some of working with Rock must have rubbed off on me because now I find that I'm acting more responsibly by doing things like locking my keyboard when I leave my desk and checking my inputs when I code.  To that end I watched this great webcast (not sure what happened in the first 5 minutes... just forward past that part) about the next generation of attacks centered on Ajax:

The Next Generation of AJAX Attacks – A New Generation of Attack Theories event

The webcast highlights some key areas to think about that are made more prevalent through the use of Ajax - such as attackers using the Bridges that are used in Mash-ups to deliver attacks from behind the safety of another web application - and what the implications of this are.  There's also a lot of the usual common sense stuff like checking input parameters.  The good thing is that these guys talk about those topics in a very relevant way.  For example, for data being passed around it's not just rendering of that data to the UI that can expose you to nasties - like cross-site scripting attacks - but there are many times in JavaScript where strings and objects are dynamically executed - examples include setTimeout, setInterval, eval, and dynamic html writing which all allow for malicious use of strings.

Well that's it from me... just gunna head off an check my buffers for overflows!

posted on 2/23/2007 1:10:01 PM ( 0 Comments )


Podcast with Craig Shoemaker on Web Parts

This morning I had a podcast interview with Craig Shoemaker on his Polymorphic Podcast where we discussed web parts and the portal framework:

Link to Craig Shoemaker : Podcasting (Web) Parts

 

The podcast will hopefully go up sometime around the new year so I will blog again when it does.  I was very grateful for the opportunity to discuss the portal framework in such detail because I'm sure that it's still a widely misunderstood and underutilized piece of the application development puzzle.

Craig's podcast is a unique format and I've been listening to it for a while - in fact it's the only technical podcast that I listen to regularly.  The style of Craig's podcast is extremely conversational and the speakers have always been excellent.  Craig's recent interviews with Miguel (creating extensible architectures) and Rob Howard were especially enjoyable.

posted on 11/9/2006 12:09:58 PM ( 0 Comments )


Permissions for ASP.NET DB Services

This is a reproduction of a post that I made here earlier today.

You'll notice that when you install the ASPNETDB that there are a bunch of application roles which get created in that database.  There are groups of roles for:

  • membership

  • roles

  • profile

  • personalization

and for each of those, there is a "basic", "full", and "reporting" role.  So, for example, with personalization role there exists:

  • aspnet_Personalization_basic

  • aspnet_Personalization_full

  • aspnet_Personalization_reporting

in order to "enable" the web parts functionality, you should only have to grant your database user access to the aspnet_Personalization_basic role.  Likewise, if you want to use membership, then grant access to the aspnet_Membership_basic role.

Of course that's not the full story, if you want to learn about the exact difference between each of those roles, take a peek at this article:

http://msdn2.microsoft.com/en-us/library/ms164596.aspx

posted on 10/28/2006 5:44:18 AM ( 0 Comments )


ASP.NET 2.0 Web Parts in Action

After 8 months of planning, writing, and fine-tuning my book has finally set sail for production!  You can see the home page for the book here: http://www.manning.com/neimke/

ASP.NET 2.0 Web Parts in Action

If everything goes according to plan the book will be available in bookstores from mid-August although the PDF version should be available prior to that.  The picture on the cover is of a Pirate and I chose it because my eldest son - Harrison (5) - is very much into pirates right now :-)

posted on 5/25/2006 1:54:11 AM ( 6 Comments )


App_Offline.htm

A cool feature that has been added in ASP.NET 2.0 is the App_Offline.htm file.  Scott Guthrie blogged about it a while ago when he had this to say:

Basically, if you place a file with this name in the root of a web application directory, ASP.NET 2.0 will shut-down the application, unload the application domain from the server, and stop processing any new incoming requests for that application. ASP.NET will also then respond to all requests for dynamic pages in the application by sending back the content of the app_offline.htm file (for example: you might want to have a “site under construction” or “down for maintenance” message).

For my blog I have an HTML file named App_Offline.RENAMETO_htm that contains the page that I want people to see whenever I'm updating my website - such as when I'm uploading a new set of assemblies.  Before I start the FTP process of the new files I simply rename that file to App_Offline.htm.  Once the upload process has completed I then rename the file back to App_Offline.RENAMETO_htm.

The content that is displayed on my App_Offline.htm file basically tells the viewer about App_Offline.htm and explains that I'm probably updating my site right now :-)

posted on 3/18/2006 12:56:59 PM ( 4 Comments )


Managing Personalization Data with PersonalizationAdministration

I'm just finishing off Chapter 9 in my book which is a chapter on how to manage a web portal and in that chapter I take some time to look at the PersonalizationAdministration class to show what it has to offer.  The PersonaliztionAdministration class has static properties and methods that allow us to query personalization data.  For example, using this class you can run a query to view all stale personalization data - that is personalization data that has not been accessed for longer than a given time.  You can also run queries that allow you to view all personalization data for a given user or users.  Most of the queries return a PersonalizationStateInfoCollection which represents the personalization data that matches your query.  Here's an example of a query that is used to return all personalization data that is older than 200 days:


DateTime inactiveSince = DateTime.Now.AddDays(-200) ;

PersonalizationStateInfoCollection inactiveUserResult =
   PersonalizationAdministration.GetAllInactiveUserState(inactiveSince) ;


Running this type of query would be useful for a large, highly active site that did not wish to keep personalization data for users who no longer used the site.  In such a case the site administrator could run this query and then make decisions about which users to delete the personalization information for.  To assist with such a task the administrator could write another query which allowed them to view all personalization data for a specific user before they deleted the data for that user.  The following picture shows what these queries might look like when presented as a couple of administration web parts.

 

Two web parts that are used to view the results of personalization data queries.

 

The code for finding the personalization data for a specific user looks like this:


PersonalizationStateInfoCollection userResult =
    PersonalizationAdministration.FindUserState(null, UserNameTextBox.Text);


When the administrator has decided to delete the personalization data for a specific user they can simply call the ResetUserState method of the PersonalizationAdministration class like so ;


PersonalizationAdministration.ResetUserState(null, UserNameTextBox.Text);


Finally, the PersonalizationStateInfoCollection class contains a collection of PersonalizationStateInfo instances.  The PersonalizationStateInfo class is an abstract class and so each of the items will actually be an instance of either the UserPersonalizationStateInfo class or the SharedPersonalizationStateInfo class.  The PersonalizationStateInfo class itself only contains 3 properties that are useful to us which are: LastUpdatedDate, Path, and Size.  When looping through a collection of personalization data from a query such as GetAllInactiveUserState we must therefore cast each item to a UserPersonalizationStateInfo object before we are able to get at the Username of the user that is associated with the personalization data.  The following code snippet shows an example of the properties that can be found on a PersonalizationStateInfo item:

 

inactiveUserResult[0].LastUpdatedDate;  
inactiveUserResult[0].Path ;
inactiveUserResult[0].Size ;

((UserPersonalizationStateInfo)inactiveUserResult[0]).Username;
((UserPersonalizationStateInfo)inactiveUserResult[0]).LastActivityDate;

posted on 3/13/2006 7:31:41 AM ( 6 Comments )


Making better use of the UrlFormatter in SUB

As Brendan pointed out in his recent post, I got pretty slack with implementing the UrlFormatter class in SUB.  Basically, every data type in SUB inherits from a base class known as "ApplicationDataObject" which gives each item enough properties to be displayed within an RSS feed.  One of the classes that I implemented quite early on in the piece was the UrlFormatter whose job it was to take an instance of a generic ApplicationDataObject class and to produce a suitable URL from it.  Internally the code looks something like this:

public class UrlFormatter {
 
    public static string FormatUri(ApplicationDataObject item) {
        if (item is Post) {
            return string.Format("{0}Posts/Post.aspx?postId={1}", Globals.UrlStartPath, item.Id);
        }else if( ... ) {
            ...
        }else{
            return Globals.UrlStartPath;
        }
    }
}

Here we can see that the UrlFormatter is passed an ApplicationDataObject and then uses an if statement to determine the type of the object and then returns the URL for that particular instance.

Over the weekend I went through the SUB source code and identified several of the Repeater that Brendan mentioned in his post that were using independent logic for displaying the URL's for posts.  What I ended up doing was to remove the ItemTemplate code from each of the main display Repeaters that are used to display Posts on the Home Page, the Posts By Category page, and the Posts By Month Page and I put the HTML that was within those ItemTemplates into a User Control and I load that as the ItemTemplate of the Repeaters.  The code for the User Control looks like this:

<div class="post">
 <h5><a href='<%# UrlFormatter.FormatUri((Post) ((RepeaterItem)Container).DataItem) %>'>
            <%# ((Post)((RepeaterItem)Container).DataItem).DisplayName%></a></h5>
 <p><asp:literal runat="server" text='<%# ((Post) ((RepeaterItem)Container).DataItem).Description %>' id="Label4"/></p>
 <p class="postfoot">posted on <%# ((Post)((RepeaterItem)Container).DataItem).DateCreated.ToString()%>
  ( <a href="<%# UrlFormatter.FormatUri((Post) ((RepeaterItem)Container).DataItem) %>#Feedback">
                <%# ((Post)((RepeaterItem)Container).DataItem).CommentCount.ToString()%> Comments</a> )
 </p>
</div>

You can see that the Container must now be cast to a RepeaterItem before it is cast to a Post.  The code in the hosting Repeaters now looks like this:

<asp:repeater id="PostList" runat="server" enableviewstate="False">
    <separatortemplate>
        <hr />
    </separatortemplate>
</asp:repeater>

… and I simply put in some code to load the template at runtime:


protected override void OnInit(EventArgs e) {
    base.OnInit(e);
    this.PostList.ItemTemplate = Page.LoadTemplate("~/UserControls/PostItemTemplate.ascx");


Now I can easily implement Brendan's cool idea for using the FormatUri function of the UrlFormatter as an abstraction for implementing friendly formatted Url's for posts in SUB.

posted on 3/7/2006 5:42:58 PM ( 1 Comments )


Url Mappings and Feedburner

The other day I posted a plea for subscribers of my blog to point the subscription for my feed to my feedburner link but today I'm asking that you change it back to the original Url of:

    http://MarkItUp.com/Rss.ashx

The reason for this is that in a comment to that post, Rick Klau who is the VP of Business Development with Feedburner mentioned that I'd be better off having people continue to point at my Rss.ashx page and performing a silent re-direct to the Feedburner URL under the covers.  This makes a lot of sense because it means that I'm not constantly asking people to change their link - like I am now :-) 

To implement the solution I created a special URL which still exposes my RSS feed and I point the Feedburner bot at that link.  The link for the bot is: http://MarkItUp.com/RssForBots.ashx.  Notice that if you browse to my http://MarkItUp.com/Rss.ashx page that you will end up at the Feedburner page but it you point at the http://MarkItUp.com/RssForBots.ashx URL you will see my raw feed.

Anyways, as with all simple programming tasks things quickly went south and it resulted in a major operation.  My initial thought was that I could simply use the UrlMappings service that comes with ASP.NET and re-target my RSS feed like so:

<urlMappings>
  <add url="~/Rss.ashx" mappedUrl="http://feeds.Feedburner.com/MarkItUp" />
</urlMappings>

Unfortunately it turns out that the UrlMappings service will not serve up URL's that are external to your site.  Damn!  I'm starting to hate that darned UrlMappings thing… me and it just don't hit it off.

So what I decided to do was to create my own UrlMappings service instead.  I iplemented my service as an HttpHandler that would read in a Mappings.xml file and handle re-directions to external URL's. 

First I created a mappings file named UrlMappings.xml which lives in ~/App_Data and which looks like this:

<UrlMappings>
  <UrlMapping url="~/Rss.ashx" mappedUrl="http://feeds.feedburner.com/MarkItUp"></UrlMapping>
</UrlMappings>

Next, because my feed is now served up at two locations I deleted the Rss.ashx physical file and moved it into the ~/App_Code directory and configured a couple of UrlMappings using that silly ASP.NET UrlMapping service like so:

<urlMappings>
  <add url="~/Rss.ashx" mappedUrl="Redirector.ashx" />
</urlMappings>

In this case, Redirector.ashx is the location of the custom HttpHandler that I wrote to manage my own UrlMappings file.  You can see that from the HttpHandler mappings that I created to handle the requests:

<httpHandlers>
  <add verb="GET,POST" path="Rss.ashx" type="MarkItUp.SingleUserBlog.Web.RssFeedHandler" />
  <add verb="GET,POST" path="RssForBots.ashx" type="MarkItUp.SingleUserBlog.Web.RssFeedHandler" />
  <add verb="GET,POST" path="Redirector.ashx" type="MarkItUp.SingleUserBlog.Web.Redirector" />
</httpHandlers>

So now when a user browses to my Rss.ashx page they are redirected to the Redirector which reads my UrlMappings file and sends the request off to Feedburner.  Of course the upside of all this is that SUB now has a dinky little redirector that can map out to external links - whoop-dee-doo!

posted on 3/6/2006 8:47:40 PM ( 3 Comments )


Implementing the Cache Pattern

I mentioned recently that the performance of SUB has been quite poor and I've started diagnosing the cause of this by instrumenting my code to see what's going on.  I also wrote an introductory article on code instrumentation which can be found here:

     http://markitup.com/Posts/Post.aspx?postId=fa94c852-fd16-4ba9-a5f0-1f72437f7cd2

I'll talk more about the specifics of the instrumentation and diagnostics in a follow-up post when I have things totally sorted although the changes that I've made so far have improved things quite dramatically. 

While going through the SUB file handling code I've been taking the time to check some of my coding patterns to ensure that operations such as caching and how I'm handling resource locking is all sweet.  Because caching plays such a large and important role within SUB I decided that I should check my caching pattern just to be sure that things are cool there and that the pattern is solid.  Currently my caching pattern looks something like this:

Data GetData( dataID ) {

     string key = "GetData" + dataID ;

     if (Cache[key] == null) {
         Data item = GotoDatabaseAndFetchData( dataID ) ;
         CacheHelper.Insert(key, item);
     }

     return HttpRuntime.Cache[key] as Data;
}

To check my pattern I headed off to Steve Smith's blog since he is the caching expert that I know best.  While browsing Steve's blog I found the exact post that I was after here:

     http://weblogs.asp.net/ssmith/archive/2003/06/20/9062.aspx

As Steve pointed the correct pattern to use is this:

Data GetData( dataID ) {

    string key = "GetData" + dataID ;
    Data item = Cache[key] as Data ;

    if (item == null) {
         item = GotoDatabaseAndFetchData( dataID ) ;
         CacheHelper.Insert(key, item);
    }

     return item;
}

Notice that in Steve's pattern a local variable is populated and then inserted into the Cache but that it is the variable that is returned from the method.  In my original code I was returning the value held in the Cache directly from the method.  As Steve notes in a recent email, on a busy site where you have sufficient load/memory pressure the code in my original pattern can cause inconsistent behavior because the item can actually be removed from the Cache in-between adding the item to the Cache and returning it at the end of the method.

Thanks Steve!

posted on 3/6/2006 5:15:14 PM ( 2 Comments )


Health Monitoring and Instrumentation - Prescriptive Guidance

As ASP.NET developers we are all familiar with the concept of writing trace statements to the Trace log so that we can use that information later at runtime to assist us with diagnosing problems in our applications.  For example, writing exception messages to the Trace log can help understand why things are not working as we expect - especially when our code is layered and finding the root cause of small inconsistencies may not be easy to detect by simply viewing the output of a web page.

As for prescriptive guidance about where to place tracing statements within an application’s code, the obvious strategic targets that are most likely to cause problems at runtime should be targeted so that we can work out how to diagnose those errors when they occur.  At a minimum I would suggest instrumenting the following code:

  • Exception handling blocks
  • Calls to external systems which may be expensive so that we can monitor how long those operations are taking
  • Calls to complex business logic operations to help detect erroneous logic

Writing trace statements is useful for diagnosing problems that already exist in our applications but wouldn't it be nice to be alerted to potential problems before they actually occur so that we can do something to remedy them before they start causing our application to fail.  In this way we can be proactive to potential problems rather than always being reactive.  The activity of and detecting certain types of issues before they become problems is known as “Health Monitoring” and involves keeping track of performance counters in our code and viewing these vital statistics periodically to keep a look-out for things that might be going wrong. 

ASP.NET 2.0 contains an event based health monitoring system known simply as “Health Monitoring” that we can tap into to keep track of the health of our applications.  We can use the Health Monitoring system to monitor the health of our applications and send notifications as thresholds for certain types of events are reached.  For example, periodically the ASP.NET process will be forced to recycle which will cause an application restart to occur.  Most of the time these restarts are to be expected, such as when an administrator makes a change to the web configuration file an application restart will occur.  However there are other times when application restarts indicate that an application is experiencing extreme difficulty and we would very much be interested in receiving notifications about these events to let us know that everything is not fine.  As an example, large numbers of application restarts are a typical symptom that a denial-of-service attack is taking place.  Using the Health Monitoring service we could configure our application to listen for application restarts and configure a rule that would cause a notification to be sent after a certain threshold is breached within a given time.

The Health Monitoring system comes with a large number of standard events that are raised during the lifecycle of the application and also some standard providers (Sinks) that are used to log the event notifications.  The standard provders include an Email provider for sending email, a SqlWebEventProvider for logging notification information into a SQL Server database, and a WMIEventProvider that maps ASP.NET health-monitoring events to Windows Management Instrumentation (WMI) events.

The Health Monitoring system is highly configurable and so we can easily create our own events and providers to record information about certain types of events and have them forwarded to a store of our choosing.  For example, we might want to send an SMS to an application administrator whenever the time to make web service calls to external sites exceeds a certain time threshold - such as 3 seconds.

Here are some excellent articles that will help you get started with implementing Application Health Monitoring in your ASP.NET applications:


PAG : How To instrument Code
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/paght000016.asp

PAG: How to use Health Monitioring
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag2/html/PAGHT000011.asp

Link to an excellent article which demonstrates how to create custom Events and Providers
http://msmvps.com/blogs/gbvb/archive/2004/09/15/13578.aspx

posted on 3/5/2006 7:57:56 PM ( 9 Comments )


Solution for Viewing Content Pages in Web Application Projects

Grant and I have recently started working on a new ASP.NET 2.0 application for a client of ours and, for the project we decided to use the new ASP.NET Web Application Projects instead of the Website applications that shipped with VS.NET 2005.  You can grab the latest preview of the Web Application Projects from here:

    http://msdn.microsoft.com/asp.net/reference/infrastructure/wap/default.aspx

There's lots of benefits to using these projects over the Website applications in my opinion as I much prefer to have a project file and a single assembly output. 

One of the issues that we came up against was that when we declared the MasterPageFile for our content pages in the web.config file and not in each page, then we couldn't view the pages in the designer and we would just see an error message being displayed like so:

Master Pages Error

The workaround for this tis to set the master-page in the Web.config file, and set the MasterPageFile attribute of the page directive to an empty string like so:

    masterpagefile=””

Doing this still won’t give you all the surrounding content in WYSIWYG – but it will allow you to edit the page and use the design-surface.  A small price to pay for being able to use the web application projects in my opinion.

posted on 2/24/2006 12:46:25 AM ( 2 Comments )


Error Rendering Control - Server Controls do not display

The other day I noticed that all of the controls in SingleUserBlog do not display nicely within the design view of Visual Studio.  Whenever I bring a page up in the designer, each of the controls appreas with its Error chrome visible, as can be seen in the following image:

Error Rendering Control

The trick here is that when you declare your controls in the web.config file, you must specify which namespace and assembly they are in - specifying a namespace will not do.  So what assembly do you specify when your classes are contained within the current web project?  Why the __code assembly of course!  Apparently specifying App_Code for the assembly also works.  Here is a sample config entry taken from SUB which shows the correct entry to get designer support:

 

<pages styleSheetTheme="Default" validateRequest="false" masterPageFile="~/SUB.master">
  <controls>
    <add namespace="MarkItUp.SingleUserBlog.Web" tagPrefix="blog" assembly="__code" />
    <add namespace="MarkItUp.SingleUserBlog.Web.WebParts" tagPrefix="portal" assembly="__code" />
  </controls>
</pages>

posted on 2/1/2006 6:44:38 PM ( 7 Comments )


SUB V2 - First Release

OK, so I've just uploaded the source code for this blog (SUB V2) to ProjectDistributor.  To download it go here:

     http://projectdistributor.net/Projects/Project.aspx?projectId=182

The biggest features in this release are that the blog uses MasterPages, Themes, and has a pretty cool WebPart framework which is coming into shape.

To run the blog just open the solution file and do a build.  You also need to configure a database to use the ASPNET SQL Server tables because this version uses Profile to store some user information.

Aside from using the major new ASP.NET 2.0 features such Themes, MaserPages, WebPart, Profile etc, and the new runtime feature - Generics - it's a parity release with the ASP.NET 1.1 version of SingleUserBlog.  Here is a screenshot of the features that will appear in the next version (Iteration 1) of the software:

SUB V2 features that are coming soon

posted on 1/27/2006 7:13:09 PM ( 0 Comments )


Building ASP.NET 2.0 Web Sites Using Web Standards

I found this link via Phil's blog:

    Building ASP.NET 2.0 Web Sites Using Web Standards

It is an article by Stephen Walther (geez that guy writes a lot of stuff) that covers all of the web standards "hooks" in ASP.NET 2.0.  There's a heap of little treasures in there.  For example I can honestly say that today is the first time that I'd heard of the GenerateEmptyAlternateText property for the Image web control.

   

posted on 1/21/2006 4:59:38 AM ( 0 Comments )


Dynamic URL Rewriter for ASP.NET 2.0

Seen via Scott Guthrie's blog is a cool HttpModule that you can use to perform dynamic URL rewrites using regular expressions.  There's a UrlMapper that comes out-of-the-box with ASP.NET 2.0 but it doesn't allow for dynamic rewrites.  Here is the link to the dynamic mapper:

     http://www.urlrewriting.net/Config.aspx?Language=en&AspxAutoDetectCookieSupport=1

This will come in handy if you are using SUB but you still want your old links to work.  For example, let's say that you were using .Text 0.95 which would format your post links like so:

     http://yourdomain.com/posts/9999.aspx

When you migrate to SUB this link will become:

     http://yourdomain.com/posts/post.aspx?postId=9999

Which means that anyone who has links to your old posts will find them missing.  Not anymore!  Add a reference to the UrlRewritingNet.UrlRewriter and add the following configuration and you are away!


<urlrewritingnet
  rewriteOnlyVirtualUrls="true"
  contextItemsPrefix="QueryString"
  compileRegex="true"
  xmlns="http://www.urlrewriting.net/schemas/config/2006/01" >
 
  <rewrites>
    <add virtualUrl="^~/Posts/(.*).aspx"
         rewriteUrlParameter="ExcludeFromClientQueryString"
         destinationUrl="~/posts/post.aspx?postId=$1"
         ignoreCase="false"/>
  </rewrites>
 
</urlrewritingnet>

posted on 1/20/2006 2:18:59 AM ( 0 Comments )