CraigWardman.com

[ Blog ]

/Blog
Categories:

A catalogue of my discoveries in software development and related subjects, that I think might be of use or interest to everyone else, or to me when I forget what I did!

Fixing Binary Formatted Data After a Code Change

Friday, 5 June 2009


I seem to have a habit of changing classnames and/or namespaces as a project develops to make them more meaningful and to help make space for classes which would have benefited from another's name/namespace.

When using the binary formatter to serialize an instance of an object, it is important to note that the type information (assembly, namespace, classname) is stored with the data; as opposed to how the XML formatter works, where it is up to the developer to pass in the type, as a parameter, to deserialize.

This presents a problem when you rename a class/namespace, as the deserialization will fail.

One such occurance was when I was developing my bespoke CMS system, I had a namespace of:

CwCms.DataTypes

This namespace contained the data types that each of my CMS controls would expose (to give a brief background, I have developed templateable CMS controls which deal with the persistence and editing of data, of a given custom type, by serializing the data using the binary formatter and then exposing the typed data though 'container.dataitem' to the developer when implementing the control on a site.)

Anyway, I changed this namespace to 'CwCms.ControlDataTypes' to make it more descriptive, forgetting that I already had some content stored under the old namespace in the database. This of course broke all the content that I had so far and not being one to redo content work, I decided to hack together a snippet of code that would loop through the raw binary data stored in the CMS database and fix the namespace.

Looking at the data in a hex editor, I can't say I had time to fully reverse engineer the format, but I got enough understanding to do the job. In summary here is what I noted:

There is a 0x16byte header, followed by the size of the assembly name, followed by the assembly name. In my case there were then 5bytes to skip, followed by the size of the fully qualified class name, followed by the fully qualified class name (being the namespace and classname).

Using this information, the following code replaces the target namespace with the new namespace, moves the data to create the new space for the longer namespace name and fixes the 'size of fully qualified classname':


Dim SIZEOF_HEADER As Integer = &H16
Dim SKIP_BYTES_AFTER_ASMNAME As Integer = 5

Dim oldNamespace As String = "CwCms.DataTypes"
Dim newNamespace As String = "CwCms.ControlDataTypes"

Dim sizeof_ASMname As Integer = rawCmsRecord.Value(SIZEOF_HEADER)
Dim start_of_classname_struct As Integer = SIZEOF_HEADER + 1 + sizeof_ASMname + SKIP_BYTES_AFTER_ASMNAME


Dim newRecord((rawCmsRecord.Value.Length - 1) + newNamespace.Length - oldNamespace.Length) As Byte
Dim offset As Integer = 0

For i As Integer = 0 To rawCmsRecord.Value.Length - 1
'copy the bytes to the new record
If i <> start_of_classname_struct Then
newRecord(i + offset) = rawCmsRecord.Value(i)
Else
'weve reached the start of the classname struct. inject the custom stuff
Dim sizeof_classname As Integer = rawCmsRecord.Value(start_of_classname_struct)
Dim oldclassname As String = ""

For i2 = i + 1 To i + sizeof_classname
oldclassname &= ChrW(rawCmsRecord.Value(i2))
Next

oldclassname = Right(oldclassname, oldclassname.Length - oldNamespace.Length)

Dim newFQCN As String = newNamespace & oldclassname
offset = newFQCN.Length - sizeof_classname

'overwrite the new length
newRecord(i) = CByte(newFQCN.Length)

'put the new classname on the end
Dim ctr As Integer = 0
For ctr = 1 To newFQCN.Length
newRecord(i + ctr) = CByte(AscW(newFQCN(ctr - 1)))
Next

i += sizeof_classname
End If
Next

rawCmsRecord.Value = newRecord
rawCmsRecord.Save()


As the format is not documented by Microsoft the format may change without notice and as I have mentioned this is only a quick interpretation of what I saw, to get the job done. Before implementing this code for yourself, check it will not break your data, I am not responsible!

Labels: , ,

Setting a validation group on a User Control

Friday, 15 May 2009


Using the built in .NET validation, you can assign a 'validation group' to a set of validation controls, submit buttons and validation summary.

This creates a separation between different sets of controls on the page, so that only the relevant controls are validated for a specific submit button.

When you design your application as separate user control components, you may want to set a different validation group on the validators of the user control, depending on the context.

To allow this, I have exposed a 'ValidationGroup' property on the user control. This subsequently makes a recursive to all its child controls to set the validation group of all the validators.

This way, you can set the validation group of the user control instance as you would any other control and it will assign all of its contained validators to that group.

Code:


Public Property ValidationGroup() As String
Get
Return CStr(ViewState("ValidationGroup"))
End Get
Set(ByVal value As String)
SetValidationGroupOnChildren(Me, value)
ViewState("ValidationGroup") = value
End Set
End Property

Private Sub SetValidationGroupOnChildren(ByVal parent As Control, ByVal validationGroup As String)
For Each ctrl As Control In parent.Controls
If TypeOf ctrl Is BaseValidator Then
CType(ctrl, BaseValidator).ValidationGroup = validationGroup
ElseIf ctrl.HasControls() And ctrl.Visible = True Then
SetValidationGroupOnChildren(ctrl, validationGroup)
End If
Next
End Sub

Labels: , ,

Running Initialization Code in Visual Studio Unit Testing

Tuesday, 12 May 2009


Often in a project, you have code which runs when the application starts (such as in Program.cs or in your Global.asax file) which will set up all of your shared resources for the rest of the project, such as initializing global variables, configuring the BLL, connecting to a datasource etc.

As mentioned above, their are places to put this code provided for you.

When you are working within a unit test project you can also have some initialization code run when you run the tests. To do this, you create a test class with a static method and decorate it with the 'AssemblyInitializeAttribute'.

I usually create a class called 'InitTestEnv.cs' which consists of something like the following:


using Microsoft.VisualStudio.TestTools.UnitTesting;


namespace YourSolutionNamespace.Test
{
[TestClass]
public static class InitTestEnv
{
[AssemblyInitialize]
public static void Initialize(TestContext context)
{
//configure the BLL
BLL.Configuration.EnableCaching = false;

//connect it to a datasource
if (!BLL.DataProviders.HasValidDatasource())
BLL.DataProviders.SetDatasource(BLL.DataProviders.ProviderList.SqlDataProvider("connection string"));
}
}
}


Normally, I would reference the ConfigurationManager classes and then share my 'connectionStrings.config' and 'appSettings.config' files from my UI layer to keep the tests in sync with the settings that will be deployed, but in the above example I have left this out.

Labels: , ,

Keyword Density Cloud

Saturday, 25 April 2009


The 'keyword density cloud' was a nice little widget I worked on at work, to provide a graphical 'tag cloud like' analysis of the keyword density for a given web page.

It can be used by SEO concious writers to quickly examine what a page of content may appear to be about (to search engines) based on the 'top words' and the relative strength of those top words, but is also a nice overview of the page content to end users.

For example:


SEO Keyword Density by WMG





You can use the widget on any page that has a publicly accessible URL by simply pasting in the following javascript:



<div id="wmgcumulus">
<!--This link needs to be included for the keyword density tool to work properly.-->
<a href="http://www.webmarketinggroup.co.uk/" target="_blank">SEO</a> Keyword Density by WMG
<script language="javascript" type="text/javascript" src="http://cumulus.wmg.uk.com/wmgcumulus.js"></script>
<script language="javascript" type="text/javascript">
DrawTagCloud(400,200,'ffffff','0x000000','0xc0c0c0',30);
</script>
</div>



You can modify the parameters for 'DrawTagCloud', which are as follows:
DrawTagCloud(width, height, bgColor, textColor1, textColor2, maxWords)

Yes even I spell 'colour' as 'color' in code :\

As you may have guessed from the names used, the output is given using the WP-Cumulus WordPress plugin Flash component.

Care should be taken when pasting the code into blogging software, such as Blogger, that no '<br>' tags are inserted into the javascript.

Labels: ,

Preventing Uppercase URLs in ASP.NET

Friday, 13 March 2009


According to RFC3986, the path portion of a URI is case-sensitive.

Google and other search engines use this ruling in their crawlers, so that pages which differ by case are treated as different pages.

This can be problematic when working in a non-case sensitive environment, such as IIS on Windows, because this rule is ignored and any variation in case will still return the same page. This leads to duplicate content issues and can also dilute the number of inbound links detected to a particular page.

As a general rule, I always keep my internal site links in lower case, but you cannot control how 3rd parties link to you, so if someone links to you using upper case and this link is spidered by a search engine, the page could be mistaken as a duplicate of its lower case alternative.

E.g.
http://www.craigwardman.com/blog/
http://www.craigwardman.com/Blog/
http://www.craigwardman.com/bLoG/

These all serve up the same page, but as per the RFC should be treated as 3 different pages. We want to prevent these links from being mistaken as different pages, by redirecting requests for pages with uppercase letters to the lowercase equivelant.

In my particular case, I have two other problems to overcome because I am using a shared hosting environment. Firstly, I cannot use HttpHandlers on the server so my fix had to be in the code-behind of any pages I want to apply it to (limiting it to aspx files). Secondly, the shared host uses 'Default.aspx' and not 'default.aspx' as the default page, so this causes an uppercase character in the RawUrl when the root URL is requested.

The following code should be placed in your page_load event:


'prevent wrong case urls
If Text.RegularExpressions.Regex.IsMatch(Request.Url.PathAndQuery.Replace("Default.aspx", "default.aspx"), "[A-Z]") Then
Response.StatusCode = 301
Response.Status = "301 Moved Permanently"

Dim loc As String = Request.Url.PathAndQuery.ToLower()
If loc.EndsWith("default.aspx") Then loc = loc.Remove(loc.LastIndexOf("default.aspx"), "default.aspx".Length)
Response.AddHeader("Location", loc)
End If


The code basically uses a 301 redirect to inform the client (spider/browser) to use the lower case equivelant of the URL. Another SEO measure is to not reveal 'default.aspx' as part of the URL (because that would be a duplicate of '/') - so the code also makes sure not to use this page name in the URL.

Labels: ,

Non-Databound DropDownList SelectedValue Problem

Monday, 9 March 2009


When you have an asp:DropDownList which is *not* databound, (i.e. you add the items manually) there are a few things you have to do to get 'SelectedValue' to work.

ASP.NET will ignore the 'SelectedValue' when you manually add items unless you make a call to 'DataBind' on the drop down list, so you need to add this line of code to force ASP.NET to select the correct item.

However, because the 'DropDownList.Items.Add()' function will accept a string, its all too tempting to pass in the string you want to display when adding the items, but this will cause an error when you try to databind!

e.g.


ddlYear.Items.Clear()
For yr As Integer = Now.Date.Year - 100 To Now.Date.Year - 18
ddlYear.Items.Add(yr.ToString())
Next

ddlYear.DataBind() '<-- This part tries to load SelectedValue - but will error!


Error: 'DropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

What you need to do, is pass in 'new ListItem()' to the Items.Add() function and specify that the string is the 'text' and 'value' for the list item.

e.g.


ddlYear.Items.Clear()
For yr As Integer = Now.Date.Year - 100 To Now.Date.Year - 18
ddlYear.Items.Add(New ListItem(yr.ToString(), yr.ToString()))
Next

ddlYear.DataBind()


You should find that the drop down list will now load with your manually added items and will display the correct 'SelectedValue'!

Labels: ,

Automatic Unique Names for Session/ViewState Data

Tuesday, 24 February 2009


When you store information using a key based storage mechanism, such as is provided by the 'Session' and 'ViewState' objects, you want to avoid referencing these directly by key name, as this means all references to the data need to be casted, leaving you liable to invalid cast exceptions, it means it is up to the developer to remember the key names and also leaves you liable to typos, spelling mistakes and accidental re-use of the same key more than once.

The first step to solving this problem is to create a strongly typed representation of the data, using classes and properties and then have those properties simply persist their data to/from the chosen data store.

As a very basic example, lets say you want to ask a user for their name and age and store it in the Session.


Namespace Example.SessionObjects
Public Class CurrentUserDetails
Public Shared Property Name() As String
Get
Return CStr(HttpContext.Current.Session("CurrentUserName"))
End Get
Set(ByVal value As String)
HttpContext.Current.Session("CurrentUserName") = value
End Set
End Property

Public Shared Property Age() As Integer
Get
Return CInt(HttpContext.Current.Session("CurrentUserAge"))
End Get
Set(ByVal value As Integer)
HttpContext.Current.Session("CurrentUserAge") = value
End Set
End Property
End Class
End Namespace


If you are using ViewState, you will declare these properties as part of your UserControl, for example.

Because you are wrapping access to the data as you would a private field, you gain all the benefits of that, such as validation, initialization, default values etc.

As you can see, the above example still relies on the developer coming up with a unique key for each object, which is the problem addressed by this article.

By nature, all of your strongly typed properties will be unique, since they live in a unique namespace, classname, propertyname - anything else would give you a compiler error. So you can use this unique name as your key name.

The following code shows how you can use the 'StackFrame' object to get a reference to the currectly executing method (which will get get_PropertyName, or set_PropertyName) and use (along with name of the declaring type) to generate a unique key for the data store:


Public Property Thing() As Integer
Get
Dim sfm As Reflection.MethodBase = New StackFrame().GetMethod()
Return CType(HttpContext.Current.Session(sfm.DeclaringType.FullName & sfm.Name.Remove(0, 4)), Integer)
End Get
Set(ByVal value As Integer)
Dim sfm As Reflection.MethodBase = New StackFrame().GetMethod()
HttpContext.Current.Session(sfm.DeclaringType.FullName & sfm.Name.Remove(0, 4)) = value
End Set
End Property


The reason I have used '.Remove(0, 4)' is to get rid of the 'get_' and 'set_' prefixes, so that the getter and setter function refer to the same key.

Labels: ,

CraigWardman.com