Avatar

Blog (pg. 17)

  • Published on
    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.
  • Published on
    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.
  • Published on
    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'!
  • Published on
    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 &amp; 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 &amp; 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.
  • Published on
    When using a MultiLine textbox inside an ASP.NET AJAX update panel, you may encounter problems with carriage return line feeds in your text on the server using Firefox (and potentially other browsers). Internet Explorer uses the Windows style CrLf (13 10) for newlines in a textarea but Firefox uses Unix style Lf (10) only. On a synchronous postback it seems ASP.NET fixes this and you will get CrLf in your text on the server. However, when you are posting back asynchronously using AJAX, you only get Lf in your text when Firefox is used. In order to clean this up and have consistant data, I wrote a simple regex replace to make sure all Lf are preceded by a Cr.
    public static string CleanUnixCrLf(string textIn)
    {
       //firefox only uses Lf and not CrLf
       return System.Text.RegularExpressions.Regex.Replace(textIn, "([^\r])[\n]", "$1\r\n");
    }