Getting an absolute URL from ASP.NET Webforms

Related to the post Getting an absolute URL from ASP.NET MVC here is the corresponding ASP.NET Webforms version (not as extension methods):

public static Uri GetBaseUrl(HttpRequest request) 
{ 
    Uri contextUri = new Uri(request.Url, request.RawUrl); 
    UriBuilder realmUri = new UriBuilder(contextUri) { Path = request.ApplicationPath, Query = null, Fragment = null }; 
    return realmUri.Uri; 
}

public static string GetAbsoluteUrl(HttpRequest request, string relativeUrl) 
{ 
    return new Uri(GetBaseUrl(request), VirtualPathUtility.ToAbsolute(relativeUrl)).AbsoluteUri; 
}

Cool upcoming .NET stuff from Microsoft

Silverlight 2.0

Version 2.0 of the ubercool Silverlight, Microsoft’ pendant to Adobe Flash, is drawing nigh. Currently available as beta 2, release in june ’08. Silverlight 2.0 allows embedded code client-side, by some magic, that does not even require having the .NET framework installed on the client.

ASP.NET MVC Framework

Quoting the ASP.NET MVC page:

ASP.NET MVC enables you to build Model View Controller (MVC) applications by using the ASP.NET framework. ASP.NET MVC is an alternative, not a replacement, for ASP.NET Web Forms that offers the following benefits:

  • Clear separation of concerns
  • Testability – support for Test-Driven Development
  • Fine-grained control over HTML and JavaScript
  • Intuitive URLs

References: Official page, Scott Guthrie MVC Preview 5 Release post

Zermatt

Quoting Zermatt home page:

Zermatt is a framework for implementing claims-based identity in your applications. By using it, you’ll more easily reap the benefits of the claims-based identity model described in this paper.

References: Official page, Vittorio Bertocci blog, Keith Brown blog

Velocity

Quoting Velocity Blog

(…) a distributed caching product to provide the .NET application platform support for developing highly performant, scalable, and highly available applications.

References: Velocity blog

Oslo

Quoting the official Oslo page:

”Oslo” is the codename for Microsoft’s forthcoming modeling platform. Modeling is used across a wide range of domains and allows more people to participate in application design and allows developers to write applications at a much higher level of abstraction. “Oslo” consists of:

  • A tool that helps people define and interact with models in a rich and visual manner
  • A language that helps people create and use textual domain-specific languages and data models
  • A relational repository that makes models available to both tools and platform components

References: Official page, Douglas Purdy – "What is Oslo?", Don Box blog, Various MS SOA articles (incl. non Oslo stuff)


Abuse of FormsAuthentication.HashPasswordForStoringInConfigFile() method?

I can’t help but get a annoyingly creepy feeling whenever I see people using FormsAuthentication.HashPasswordForStoringInConfigFile() to generate a hash cipher for a given value (for example here). On the other hand I can’t really decide wether I think it is the method which has a bad name/should be "located" somewhere else or it is the "abuse" of it as it is today.

If we look at the actual implementation of HashPasswordForStoringInConfigFile using Reflector:

public static string HashPasswordForStoringInConfigFile(string password, string passwordFormat)
{
    HashAlgorithm algorithm;
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    if (passwordFormat == null)
    {
        throw new ArgumentNullException("passwordFormat");
    }
    if (StringUtil.EqualsIgnoreCase(passwordFormat, "sha1"))
    {
        algorithm = SHA1.Create();
    }
    else
    {
        if (!StringUtil.EqualsIgnoreCase(passwordFormat, "md5"))
        {
            throw new ArgumentException(SR.GetString("InvalidArgumentValue", new object[] { "passwordFormat" }));
        }
        algorithm = MD5.Create();
    }
    return MachineKeySection.ByteArrayToHexString(algorithm.ComputeHash(Encoding.UTF8.GetBytes(password)), 0);
}

The net result of this of course is that you do get the MD5/SHA1 hash of the password or value.

However the method name is HashPasswordForStoringInConfigFile, i.e. the name suggests it is for hashing passwords and further more it is a method of a class called FormsAuthentication… see a pattern!? The name does not suggest it is a generic MD5 method (although the implementation shows it is that).

IMHO using HashPasswordForStoringInConfigFile as a generic MD5 calculation method does nothing but make code less readable and hence harder to maintain. The very, very least a developer can do is to comment the code, but we all know when that’ll happen…

Look at the alternative:

public static string GetMD5(string value)
{
    MD5 algorithm = MD5.Create();
    byte[] data = algorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
    string md5 = "";
    for (int i = 0; i < data.Length; i++)
    {
        md5 += data[i].ToString("x2").ToLowerInvariant();
    }
    return md5;
}

The ONLY problem with not doing this stuff yourself is the conversion from a string and back to a string…

Yes, this might be a little slower due to the string stuff, but you can always "inherit" ;) the MachineKeySection.ByteArrayToHexString method.

The point is that using FormsAuthentication.HashPasswordForStoringInConfigFile to calculate all MD5/SHA-1 hash values just because the implementation happens to support this seems wrong. Writing this I think I actually blame Microsoft the most for not providing this method somewhere else and named more appropriately.

That was just a little rant…


Sign-up and Login in One Step Membership Provider

Why has it got to be so hard? Why do you always have to go through a user creation wizard to create an account? Why just not create it as you login like they do on LibraryThing for example?

Well, there is of course always two sides to a coin. The obvious reason is: spam. However all that is needed is an username to "aggregate" your info, then this does not really matter.

So here is a very simple solution to this:

Create a custom MembershipProvider and in the ValidateUser() method first check if the user exists. If not then create the user and then validate him/her. There is of course the requirement that RequiresQuestionAndAnswer and RequiresUniqueEmail must be false.

One way of doing this is to refer a standard membership provider and add some logic to the ValidateUser():

public override bool ValidateUser(string username, string password)
{
    if (GetUser(username, false) == null)
    {
        MembershipCreateStatus status = MembershipCreateStatus.Success;
        MembershipUser user = CreateUser(username, password, null, null, null, true, null, out status);
        if (status != MembershipCreateStatus.Success)
        {
            throw new ProviderException(string.Format("Could not automatically create user: {0}", status));
        }
    }
    return MembershipProvider.ValidateUser(username, password);
}

Download the EasyRegisterMembershipProvider here.

To use this, download, compile and use it in Web.config:

<membership defaultProvider="SignUpProvider">
<providers>
  <clear/>
  <add name="SignUpMembershipProvider" type="Veggerby.Utility.EasySignUpMembershipProvider, Veggerby.Utility" providerName="SqlMembershipProvider"/>
  <add name="SqlMembershipProvider" …/>
</providers>
</membership>


Export GridView to Excel

In Scott Guthrie’s August 30th Links I saw a link to Matt Berseth’s blog about exporting a GridView to Excel.

I have used a method similar to this earlier and apart from it being very easily developed, I have found a very, very annoying issue with this method.

The first thing is you need to make sure the output is localized (in regards to formatting), especially for floating point values and dates (but as I’ll explain, this really is not enough).

What I actually found was that it was/is a pretty tricky thing to accomplish. Normally you would of course just set the Culture in Web.config, in the section or imperatively on the current thread. For my Windows is using the Danish regional settings, which means that fx. a date is formatted as dd/mm/yyyy, however at the company where I work (we’re global) we have a standard date format (yyyy-mm-dd) which is being set by group policy.

What ASP.NET does when using the Culture, it uses the default settings for the specified language(s) in the request header. This means for me it would specify “da” as language in the browser. This will make ASP.NET set the “da-DK” Culture and format numbers based on the standard danish settings for numbers, dates, etc. Which means that if I make a request to this kind of Excel sheet containing a localized date it will be formatted “dd/mm/yyyy” which will cause Excel to misinterpret it since it expects my date formats to be yyyy-mm-dd.

The above perhaps sound a but hypothetical, but consider this: I have set my language to english in the browser (since I don’t want fx. Windows Update to send me the danish updates to my english OS – which is would otherwise do). So with my language to “en-us” in the browser, I open the Excel sheet using Matt’s method and it will/should format the HTML table output based on the “en-US” culture, however Excel will try to interpret this using my Windows Regional Settings, which is danish. What a mess!!!

In this case when running Matt’s example I get this in my browser:

Browser layout of Matt Berseth Export GridView to Excel

and this in Excel:

Excel version of Matt Berseth Export GridView to Excel

The problem is here that in Danish numerical layout the decimal symbol is , (comma) and the thousand separator is . (dot) – the complete opposite of en-US. This causes Excel to interpret the UnitPrice in Matt’s example as a number with a thousands separator and not a decimal symbol (even though it is displayed with 4 digits after the decimal/thousand separator). So we get UnitPrice’s of 140000, 98000, 348000, etc. (thousand separators left out on purpose :) ) instead of 14, 9.8, 34.8, etc. (here dot is decimal symbol). Even worse is it for Discount, since a Excel will not interpret a number with 0 in front of the thousand separator as a valid number so all the 0 (zeros) in that column are interpreted as numerical zeros, but 0.15 is not interpreted as a number and is left as a “text” and thereby unusable for use in formulas.

If Matt has included dates in this example it would be even worse!

So bottom line is: this is a very simple method and can be very usefull (especially if you have only text values), but if you need to make sure data is consistent, it can be a big pain since it relies on Excel interpreting the HTML output (strings) which is/can be formatted in a number of different ways.

This is not just theoretical, I have experienced these issues in real-life on a real project. If consistent data is needed it would probably require a native format for the export, such as the one used by Veggerby.Excel. That was the lesson I learned from this :)


Writing a pingback server and client

If you can’t be bothered with all the blah, blah stuff at the beginning, scroll down to Pingback Server Implementation for the gooey details.

One of the nice features of the blogosphere is the interconnections between the blogs, the referencing back and forth, which means that once you find a pretty interesting topic, one can read on forever by following all the links.

A (the) problem with linking is that it is one-way, i.e. if I write a post that references a post on Scott Guthrie’s blog, then the readers on Scott’s blog is unaware that somebody else touched the same topic and hopefully added something to the topic. This one-way referencing also means that you will always (unless posts are updated) move back in time when you follow the references – what if someone discovered something very important that made the post, you were just reading, completely obsolete?!?

To help this the trackback (specs) method were invented that will cause the server with the refering post (i.e. the “new” post), send a notification to the server with the referred post. This was done by sending a simple HTTP request to the server being referred. However trackbacks is now a virtually extinct method, due to it being easily abused for spamming (the so called trackback spam).

So “Out with the old, and in with the nucleus“, and some clever souls thought out the pingback (specs), where the notifications are done by a XML-RPC request. The pingback request in itself does not prevent pingback spamming, however the specs recommend that the pingback “recipient” should retrieve the page sending the pingback and verify that it actually contains a link to the pingback target. Again this does not (theoretically) prevent spamming, but in practice it does anyway, since a pingback spammer would then have to link to all the pages being spammed.

Ok, enough with the talk, lets look at some code.

Pingback Server Implementation

The pingback server can in itself not be shown very generic, since it basically comes down to receiving a XML-RPC request with a source and target URI as parameters. What then happens with the source and target URI is then pretty application specific, but will in the common blog system be turned in to some kind of comment or reference in the comment section of the post.

For the code I am using the Cook Computing XML-RPC.NET library, since it is more or less the defacto standard for doing XML-RPC in .NET (when it comes to open source at least).

I we look at the pingback server implementation using XML-RPC.NET the server is a simple class which derives from CookComputing.XmlRpc.XmlRpcService. The XML-RPC service is fairly similary to the way one would create an ASMX or WCF service (though you can leave out the Service Contract and go straight to the implementation)

public class PingbackServer : XmlRpcService
{
//(…)
}

Similary to ASMX and WCF the methods are then declared by decorating them with an attribute (WebMethod or OperationContract), the XMLRPC.NET methods/operations/RPC’s are decorated with the XmlRpcMethod attribute.

The pingback request receives (as mentioned) 2 parameters:

  1. The source URI of the pingback
  2. The target URI of the pingback

So the source URI want to notify the target URI of its existence. The result of the pingback is a text message describing in some way what happened (successfully) or an errorcode (0 for a generic error, see specs for standard error codes).

So the pingback server class should look like the following:

public class PingbackServer : XmlRpcService
{

[XmlRpcMethod("pingback.ping")]
public object Ping(string sourceUri, string targetUri)
{
//(…)
}

}

Notice the “pingback.ping” parameter to the XmlRpcMethod attribute declaration. This parameter specifies the XML-RPC method name to execute, and the specifications requires this to be “pingback.ping” for interoperability.

The only other thing that can be done generically is to retrieve the page at “sourceUri” and search for a link to “targetUri” in the page – however this will be left as an exercise to the reader ;) or perhaps added later.

Besides creating the actual pingback service implementation, it must of course be “activated” as an XML-RPC service. This is done by declaring a HTTP Handler that reference this pingback server class (XmlRpcService implements the IHttpHandler interface). To do this add the following to web.config:

Of course if there are existing HTTP Handlers, this should be added to the existing <httpHandler></httpHandler> section.

This is actually not enough, because what is done now is that we have added the pingback server, but if a blogger creates a post referring a post on “our” server and sends a pingback to our post, he knows only the post URI not the pingback URI. There are two ways of doing this according to the pingback specifications, the point of both methods are that the page being “pinged” must specify where the pingback server is.

Method 1 is by using a HTTP Header (the X-Pingback header). This header must simply specify the absolute URI of the XML-RPC pingback server (in our example http://myserver/RPC2.ashx).

The way you would do this in ASP.NET is by using the Response.AddHeader() method (fx in Page_Load()):

Response.AddHeader(
“X-Pingback”,
Request.Url.GetComponents(
UriComponents.SchemeAndServer, UriFormat.Unescaped
) + Request.ApplicationPath + “/RPC2.ashx”);

Method 2 is by using a <link /> tag in the HTML <head></head> section. The format of the <link /> tag is:

Again it must be the absolute URI to the pingback server and the <link /> tag is must validate as either HTML or XHTML and be in the strict format as displayed above (including whitespace) – this is to reduce the complexity of extracting the pingback URI.Once this is done, then that’s it, you are now ready to receive pingback requests. In the next section we’ll look at sending pingback requests, i.e. the pingback client.Pingback Client ImplementationThe sending of a pingback request requires three steps:

  1. Determine the target URI(s) to ping
  2. Find the URI of the pingback server for each target URI
  3. Send a pingback request to the pingback server for each target URI, including the source URI (i.e. the information we actually want the target URI to know)

Step 1:

This is pretty straight forward (or maybe not). This step can be either extracting links form a text or simply having a list of links already at hand, either way it is application specific what actually happens in this step

Step 2:

This step is also pretty straight forward. It simply requires the client to send a request to the target URI and first send a HTTP request and receive the HTTP Response Headers and look for the X-Pingback header (as mentioned in the pingback server implementation).

WebRequest request = WebRequest.Create(targetUri);
HttpWebResponse response =
request.GetResponse() as HttpWebResponse;
if (response == null)
{
return null;
}
if (!string.IsNullOrEmpty(response.Headers["X-Pingback"]))
{
return response.Headers["X-Pingback"];
}

If the X-Pingback header is not available retrieve the HTTP response body and perform a search for a <link rel=”pingback” href=”(…)”/> tag, this can be done by a simple Regular Expression search.

using (TextReader reader = new StreamReader(response.GetResponseStream()))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Regex pingback = new Regex(@”<link .*?rel=('|"")pingback('|"") .*?href=('|"")(?([^""]+)(‘|”").*?/>”);
Match pingbackMatch = pingback.Match(line);
if (pingbackMatch.Success)
{
return pingbackMatch.Groups["rpcuri"].Value;
}
}
}

Below are two regular expressions, that can be used, where the first one allows a little more slack when it comes to whitespace.

Tolerant regex:
   @"<link .*?rel=('|"")pingback('|"") .*?href=('|"")(?<rpcuri>([^""]+)('|"").*?/>"
Strict regex:
   @"<link rel=""pingback"" href=""(?<rpcuri>([^"]+)"" ?/?>"

With the pingback server URI at hand it just comes down to sending the pingback request using XML-RPC.NET.

string rpcUri = GetRpcUri(targetUri);
if (!string.IsNullOrEmpty(rpcUri))
{
IPingbackProxy proxy =
XmlRpcProxyGen.Create();
proxy.Url = rpcUri;
object response = proxy.Ping(sourceUri, targetUri);
}

Where IPingbackProxy specifies an contract for the XML-RPC service (similar to the WCF Service Contract). So here we actually need to declare a “service contract” (for the client).

public interface IPingbackProxy : IXmlRpcProxy
{
[XmlRpcMethod("pingback.ping")]
object Ping(string sourceUri, string targetUri);
}

Complete “Solution”

To avoid copy-paste I have compiled all this in a file (that should work :) ). Here I have created an “real” service contract/interface which the PingbackServer also implements.

Veggerby.Pingback (2007-08-06)

Remember you need XML-RPC.NET.

Update 2007-08-02

Funny thing :) Mads Kristensen (author of Blogengine.NET) did a post yesterday (2007-08-01) as well about another ping mechanism. He describes how you can implement ping requests (using XML-RPC, however not using XML-RPC.NET, but simply “hardcoded”) to various ping-services like Ping-o-Matic, Feedburner and Technorati.

Update 2007-08-06
Discovered a small error in the discovery of the pingback URL using the <link /> method, where it only read the first line of the body.


Web Deployment Projects and SourceSafe

So today I was just watching an old webcast from Bradley Bartz regarding Web Deployment Projects in Visual Studio 2005. There was not much new in this (well it was a webcast from december 2005, so it figures), but one thing struck me though. Bradley used a self-developed, custom MS Build task for auto-incrementing assembly version numbers – pretty handy thing.

So my thought was/is: why not take this to the next level!?

At my company we use Source(Un)Safe as our VCS. So why not leverage the Microsoft.VisualStudio.SourceSafe.Interop namespace and create a new MSBuild task that automatically gets the SourceSafe configuration (somehow) and automatically labels the current version in SourceSafe with the current build number (it either be automatically generated from Bradley’s MSBuild taks for Web Sites or from the Class Library or whatever other project that has auto-increment functionality natively).

Of course this would require the files to be checked in before build, but this could perhaps also be done automatically using the SourceSafe Interop functionality (if you release it should be under version control anyway) – only you might want to control the checkin, but then it should be done manyally prior to build.

So… I haven’t developed a MSBuild task before, nor have I tried using the Microsoft.VisualStudio.SourceSafe.Interop namespace, but how hard can it be? :) VCS is not very complicated as it is so why should this be a problem???

The latter statement was a joke!!!!

 Update
It appears that some of the tasks in the SDC Tasks project (now) on Codeplex has most of this functionality. The task Microsoft.Sdc.Tasks.SourceSafe.LabelLatest is able to label the lastest VSS version with a label defined in the project file.  So (theoretically) you should be able to write something similar to:

Where ${AssemblyVersion} has been initialized similar to using Bradley’s method


Paging large amounts of data in ASP.net / SQL2005

If you have a huge dataset, doing standard paging in ASP.net is definitely not the solution. In the normal paging methods all rows are retrieved in the application and the selection is done in code. So if you have 1.000.000 records and a page size of 20, every time you swap page you read 1.000.000 records and then select 20 of these (this is of course if you do paging the “Visual” way).

However if you have SQL 2005 there is a much, much better way of doing paging, albeit it requires some more coding, both on the web-frontend and in the database. The solution is based on the Common Table Expressions (CTE) and the Ranking functions (more specifically the ROW_NUMBER() function) introduced in SQL 2005.

[sql]
CREATE PROCEDURE [schema].[table_List]
@PageIndex int, — Page number from 0 to inf.
@PageSize int — The size of each page
AS
BEGIN
WITH Data AS (SELECT ROW_NUMBER() OVER (ORDER BY [sort order]) AS RowNumber, * FROM [schema].[table] WHERE [condition])
SELECT * FROM Data WHERE
(@PageIndex IS NULL) OR (@PageSize IS NULL) OR
(
RowNumber >= @PageIndex * @PageSize AND
RowNumber < (@PageIndex + 1) * @PageSize
)
END
[/sql]

What this does is create a CTE with all the data (same as in the "old" version) and add a ranking to this based on some sorting condition (fx. sort by primary key) and apply the ROW_NUMBER() function to this. So if we have a table like this:

Id Name
85241F67-100D-4DD0-996D-DBFA8DB08B4A John
2569F112-E026-4B71-BD3D-AC16F12DFDC7 Paul
8479CDFB-B4A5-487B-9A98-9CABDC912F6E George
287D7DDA-F1DB-4C60-B2C2-A7E041DF11B6 Ringo

And apply the SP “scheme” above with sorting by Name to this we would get:

RowNumber Id Name
1 8479CDFB-B4A5-487B-9A98-9CABDC912F6E George
2 85241F67-100D-4DD0-996D-DBFA8DB08B4A John
3 2569F112-E026-4B71-BD3D-AC16F12DFDC7 Paul
4 287D7DDA-F1DB-4C60-B2C2-A7E041DF11B6 Ringo

Now we have a way of selecting a subset of the dataset based on absolute position within the dataset (which paging is actually all about).

Another thing we (most likely) need is to a method to return the total number of rows the query “would have returned”. This is to be able to display a “Page n of m”. It is actually possible to use the ObjectDataSource instead of the SqlDataSource (which you most likely would do already, if using the DataSource server controls at all) and have it “select a specific page” by using parameters along with a “select count method” (the SelectCountMethod) thereby making it possible to use standard ASP.NET server controls to paging in the database.

However while using DataSource server controls lets you do some RAD style VB-ish development – no I’m not biased ;) – it is at the cost of “loosing control” of binding (fx. I have never figured out how to force a bind to occur with DataSource controls – I actually was in a situation where this was needed once). Besides this it is also at the cost of performing two calls to the SQL database (most likely): one for retrieving page count another for retrieving the specific page. But if using manual binding this can easier be accomplished in one step, simply by modifying the stored procedure to:

[sql]
CREATE PROCEDURE [schema].[table_List]
@PageIndex int, — Page number from 0 to inf.
@PageSize int — The size of each page
AS
BEGIN

SELECT COUNT([pk]) AS TotalCount FROM [schema].[table] WHERE [condition];

WITH Data AS (SELECT ROW_NUMBER() OVER (ORDER BY [sort order]) AS RowNumber, * FROM [schema].[table] WHERE [condition])
SELECT * FROM Data WHERE
(@PageIndex IS NULL) OR (@PageSize IS NULL) OR
(
RowNumber >= @PageIndex * @PageSize AND
RowNumber < (@PageIndex + 1) * @PageSize
)
END
[/sql]

If you anyway parse your queries (like I hope you do) into some business objects, then if using a IDataReader (and the Data Access Application Block from the Enterprise Library) could give you something like (0 = page index, 20 = page size):

[csharp]
Database db = DatabaseFactory.CreateDatabase(“con.string name”);
int totalCount = 0;
using (IDataReader reader = db.ExecuteReader(“some sp”, 0 /* page index*/, 20 /* pages ize */))
{
if (reader.Read())
{
totalCount = Convert.ToInt32(reader["TotalCount"]);
}
reader.NextResult();
while (reader.Read())
{
/* Instantiate and initialize objects */
}
}
[/csharp]

So now all we have to do is create some custom paging mechanisms in ASP.net that handles the page selection for us and then executes the SP when binding the data (this could be the topic of another post).


Differences between using Cassini and IIS

Before I begin, let me just clarify: the intent of this post is not to say you should run IIS instead of Cassini or vice-versa, but instead just to outline what one must be aware of if Cassini is chosen as development “host”.

What are the differences in using Cassini instead of IIS when developing ASP.NET applications?

Short:

  • Only serves local requests
  • Runs in user context
  • All requests are parsed by ASP.NET

Longer:

Only serves local requests: It is not possible to access the application from remote machines. This makes it rather difficult (or tedius at least) fx. to debug authorizations when using Windows Authentication.

Runs in user context: An IIS normally runs as Network Service, i.e. a very well defined restricted account. When running the Cassini web server it runs in the context of the user starting Visual Studio. Most often this is a local administrator, which then gives the web application administrative priviliges when run using Cassini. A case where this has the direct cause of some hours of debugging.

A web application was developed that used the Report Viewer to view a report on a MS Reporting Services server. The report pulls data from a database located on another server that running the Reporting Service. The user/developer running the web application was dbo on the database using Windows Authentication. The connection string in the report was set to use Windows Authentication as well. Everything works like a charm. Then deploying to test, where database authentication was done using the machine account (i.e. Windows Authentication is performed on the NetworkService account – not a super method, granted, but lets ignore that for now) of the web server. In this case authentication fails on the Reporting Service to SQL server hop.

All requests are parsed by ASP.NET: This simply means, well, exactly that “All requests are parsed by ASP.NET”. This includes static items such as html, css, js, images, files, etc. Why does this matter? If you are thinking about rewriting URLs this will be obvious. For example you might create a rewrite rule based on HttpContext.RewritePath() (either directly or through a library, fx UrlRewriter.NET), mapping /Profile/JohnDoe to /Profile.aspx?user=JohnDoe. When run in Cassini this will work “out-of-the-box”, because the request for /Profile/JohnDoe is parsed by the ASP.NET engine and the mapping is performed. However when run in IIS the request will fail (for 2 reasons actually). First of all /Profile/JohnDoe will be translated to /Profile/JohnDoe/ and the default page will be tried served (fx. /Profile/JohnDoe/Default.aspx). This file does most likely not exist and IIS will probably serve a 404. Lets ignore this for a second and assume that it will try to serve /Profile/JohnDoe as a file directly. Since files without extension are not handled by aspnet_isapi.dll in a standard IIS installation, this IIS will try to serve the file directly, and again, since it does not exist IIS will probably serve you a 404 instead.

This was just a brief highlight of some of the differences (I have felt on my own CPU) between using Cassini and IIS when developing ASP.NET applications. Maybe more exist, I don’t know, but we can leave this as an exercise for the reader ;)


Custom Sorting using Compare method(s)

Are you (just like me) one of these people who just can’t remember wether A > B should yield 1 or -1 in a compare method. I know there must be some (higher) form of logic behind it that makes it easily deductible when you just think about it.

Anyway as small reference sheet:

Compare(A,B) or A.CompareTo(B)

This should yield:

“Compare statement” Yields Resulting order
A > B 1 B, A
A = B 0 non-deterministic
A < B -1 A, B

Follow

Get every new post delivered to your Inbox.