onsdag 5 november 2008

SEO-friendly 404 Exceptions

Custom 404 pages is a simple means of approaching a user-friendly experience even in the (albeit rather specific) case of an unhandled exception. In order to take the step from user-friendly to SEO-friendly, however, merely adjusting the customErrors tag in web.config is sub-par.

The problem
Modifying customErrors to redirect a user to a custom 404 page in the case of a fileNotFoundException will do just that - redirect the user - which means taking the detour to the client, informing of the redirect, which in turn means a 302 response from the server, before the user lands at the 404 page. The only implication of this is that search engine will fail to treat the response as a proper 404.

The solution
To resolve the issue, we have to drop the customErrors clause, and invoke Global.asax. Catching all exceptions in Global.asax, we are able to filter out the 404's, and render the appropriate site without tampering with redirects


void Application_Error(object sender, EventArgs e)
{
   HttpException ex = Server.GetLastError() as HttpException;

   if(ex != null && ex.GetHttpCode() == 404)
   {
      Response.Clear();
      Response.StatusCode = 404;
      Response.WriteFile("~/Pages/404.html");
      Response.End();
   }
}

A brief note on deployment
Finally, when deploying a project with a Global.asax file, from a web deployment project, make sure that the following files are updated:
- PrecompiledApp.config
- bin\App_global.asax.cs
- bin\App_global.asax.cs.compiled

Note that Global.asax is compiled in its entirety, and need not be uploaded to the production environment.

torsdag 31 juli 2008

Exlude files and directories when building deploy project

Its quite ezy ®.


  • Rightclick the deployment project and click "Open project file".
  • Insert the following code (example):
      <ItemGroup>
    <ExcludeFromBuild Include="$(SourceWebPhysicalPath)\Test\**\*.*"/>
    <ExcludeFromBuild
    Include="$(SourceWebPhysicalPath)\Images\**\*.*"/>
    </ItemGroup>


  • Done! Now it wont copy all those directories to your release folder, in the long run it will save you a lifetime of waiting.


torsdag 22 maj 2008

LINQ to SQL: On null values in coalesce expressions

The T-SQL function COALESCE is a convenient way of using optional parameters in an SQL query. It accepts a number of parameters, and returns the first non-null value in the set. When all the values in the set are null, the function returns a null value. For those more acquainted with SQL CASE, the following two expressions are exactly identical:

SELECT COALESCE(@param1, @param2, 'Default value')

SELECT
   CASE WHEN @param1 IS NOT NULL THEN @param1 ELSE
      CASE WHEN @param2 IS NOT NULL THEN @param2 ELSE
         'Default value'
      END
   END

C# provides a convenient shorthand - expr1 ?? expr2 - for coalesce expressions, which allows us to create a function as such:
public int Coalesce(int? var, int defaultValue)
{
   return var ?? defaultValue;
}

In LINQ to SQL, the ?? shorthand is always translated into COALESCE when the SQL query is generated, which equips us with the ability to use optional parameters as seen in figure 1:

Figure 1

public IEnumerable GetItems(int? parentId, int? typeId)
{
   myDataContext db = new myDataContext();
   return from item in db.Items
          where
             item.ParentId == (parentId ?? item.ParentId)
             && item.TypeId == (typeId ?? item.TypeId)
          select item;
}

As a side-remark, note the necessity of brackets in the above code. This code will render the following SQL query:
SELECT
   *
FROM
   Items
WHERE
   ParentId = COALESCE(@parentId, ParentId)
   AND TypeId = COALESCE(@typeId, TypeId)

So far so good, but this, in fact, poses a problem when using nullable database fields. Consider the following table structure:
Items
-----------------------------------
Field name     Type      Allow null
-----------------------------------
Id             int       false
ParentId       int       true
TypeId         int       false

Now, assume we want to find all items of TypeId 5, regardless of their parent items; indeed, regardless of whether they have a parent at all. The following request seems intuitive enough: GetItems(null, 5);. This will result in the C# expression item.ParentId == (null ?? item.ParentId), which is completely valid, but it will translate to the following SQL:
SELECT
   *
FROM
   Items
WHERE
   ParentId = COALESCE(null, ParentId)
   AND TypeId = COALESCE(5, TypeId)

For a record of ParentId 1 and TypeId 5, this will be just the way to do things, but if we consider the fact that there could exist an item with ParentId null and TypeId 5, the first COALESCE would return null, as both of its parameters would be of a null value. In SQL, this results in a null = null comparison, which will return false (hence the IS NULL syntax). Consequently, items with a ParentId of null will not be returned, even though the intention was to specify that the query should disregard of ParentId completely.

A workaround for the problem would be to rewrite a method accordingly:

Figure 2
public IEnumerable GetItems(int? parentId, int? typeId)
{
   myDataContext db = new myDataContext();
   return from item in db.Items
          where
             ((item.ParentId == null && parentId == null) || item.ParentId == (parentId ?? item.ParentId))
             && item.TypeId == (typeId ?? item.TypeId)
          select item;
}

Now, you don't have to look at figure 2 to realize that there's probably a better way to solve this problem. Expression trees would do the trick, for sure, but for our simple implementation, it turns out there's a less complex solution:

Figure 3
public IEnumerable GetItems(int? parentId, int? typeId)
{
   myDataContext db = new myDataContext();
   var items = from item in db.Items select item;

   if(parentId != null)
      items = from item in items where item.ParentItemID == parentId select item;

   if(typeId != null)
      items = from item in items where item.ItemTypeID == typeId select item;

return items;
}

Beginning by selecting the entire set of items available, we then proceed to filter the results to match any of the criterias that may or may not be provided. At first glance, this would appear to begin by executing a plain SELECT * FROM Items query, returning all items in the table, even when we know that's not always requested, but as the result set - items - is never enumerated before the return statement, LINQ to SQL optimizes the above code into one query at runtime, depending on what values have been specified.

An SQL Profiler trace gives us the answers in plain text:
Figure 1
exec sp_executesql N'SELECT Id, TypeId, ParentId FROM Items WHERE (ParentId = (COALESCE(@p0,ParentId))) AND (TypeId = (COALESCE(@p1,TypeId)))',N'@p0 int,@p1 int',@p0=NULL,@p1=5

The query generated in Figure 1 returns 0 rows, which is not what we expect, given the data in the table.

Figure 2
exec sp_executesql N'SELECT Id, TypeId, ParentId FROM Items WHERE ((ParentId IS NULL) OR (ParentId = (COALESCE(@p0,ParentId)))) AND (TypeId = (COALESCE(@p1,TypeId)))',N'@p0 int,@p1 int',@p0=NULL,@p1=5

The query from Figure 2 gives us the result we want, but in a verbose and inconvenient query

Figure 3
exec sp_executesql N'SELECT Id, TypeId, ParentId FROM Items WHERE TypeId = @p0',N'@p0 int',@p0=5

From Figure 3, only one query is executed (although we appear to have begun by selecting everything in the table), and only the non-null parameter is passed.

Visual Studio 2008 fails to open certain projects

Visual Studio 2008 might fail when opening a project using an add-on package. The package might be a silverligt plug-in as well as the standard LINQ-to-SQL dialog.

When this happens VS gives you the following error message:
"Project <Name> could not be opened because the Microsoft Visual C# .NET compiler could not be created. QueryService for '{74946829-37A0-11D2-A273-00C04F8EF4FF}' failed."

...and the project will fail to load.


This is really weird, but the solution is to open "regedit" and navigate to:
"HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Packages\"

Under this element you will find several "GUID"s (none with the GUID in the error message though). Open each of the GUID-elements and change the "SkipLoading" value to "0" (zero).

Restart VS 2008 and voilá - it is working again!

måndag 19 maj 2008

DropDownList databinding versus manually adding ListItems: divergence of behavior

Symptom:
The following two ways of rendering data from a given datasource may yield two completely different results.

Datasource:
List<foo> items = new List<foo>();
{
   new Foo { Text = "A", Value = null },
   new Foo { Text = "B", Value = null },
   new Foo { Text = "C", Value = null }
}

Figure 1, databinding:
ddl.DataSource = items;
ddl.DataTextField = "Text";
ddl.DataValueField = "Value";
ddl.DataBind();

Figure 2, manually adding items:
foreach(Foo item in items)
   ddl.Items.Add(new ListItem(item.Text, item.Value));

Figure 1 will yield the following result:
<select ...>
   <option value="">A</option>
   <option value="">B</option>
   <option value="">C</option>
</select>

Figure 2, however, slightly deviates from this behavior, in that all values are assigned, even though they were null in the datasource:
<select ...>
   <option value="A">A</option>
   <option value="B">B</option>
   <option value="C">C</option>
</select>

Cause:
The reason why Figure 2 renders different client code, is the following way in which the Value for a ListItem is retreived (whereas the native databinding method doesn't bother with the detour of using ListItems, but simply renders the source as HTML):
public string Value
{
   get
   {
      if (this.value != null)
         return this.value;
      if (this.text != null)
         return this.value;
      return String.Empty;
   }
   ...
}

Thus, the value retreived in Figure 2 is that of the Text property, since the Value property is null.

Outcome:
While there is nothing particularly discomforting about any of the two approaches to rendering the same data, it is worth remarking that there are differences between them, and that code written in one way cannot always be rewritten in another, without unexpected side-effects.

How to start an SQlserver Agent from a Script.

Run this script in a query window agains Master DB, and if you have the rights to execute this SP (xp_servicecontrol) then it will start the sqlserverAgent even if you dont have access to SQlServer Configuration Manager. Or the server via Remote.

------------------------------------------------------------------------------------
DECLARE @Err int, @Msg varchar(100), @ServiceName sysname
SET @ServiceName = 'SQLServerAgent'

EXEC master.dbo.xp_servicecontrol 'START', @ServiceName

SET @Err = @@ERROR
IF @Err = 0

BEGIN
RAISERROR ('Successfully started SQL Server Agent', 1, 1) WITH LOG
END
ELSE
BEGIN
SET @Msg = 'Error occured while starting SQL Server Agent. Error code: ' + STR(@Err) RAISERROR (@Msg, 18, 1) WITH LOG
END
END

GO
------------------------------------------------------------------------------------

For startin the SqlServerAgent automatically when it goes down:

EXEC sp_procoption 'AutoStart_SQLAgent', 'startup', 'true'GO

------------------------------------------------------------------------------------

ASP.NET Ajax and the need of UTF-8 encoding

When using ASP.NET Ajax and non standard characters in the page title (such as the swedish "ö"), the Ajax framework might stop working complaining about illegal use of "Response.Write, Response.Filter etc".

The problem has actually nothing to do with this but is related to the character encoding set in web.config. To make Ajax work with special characters the encoding of the "globalization"-element must be set to "UTF-8" in web.config.

The reasons is that the server will return a non legal coding for the special character which will mess up the javascript code in Ajax on the client side.

This is actually a quite old and commonly known issue, but personally I had totally forgot about it since it was a long time I read about it...