Skip Ribbon Commands
Skip to main content

Blog

:

Quick Launch

Home
Blog of Adrian Anttila, containing my thoughts, comments and questions.
May 08
Localization in ASP.NET MVC

Based on a post by Matt Hawley, I created a few extension methods to the HtmlHelper class to provide resource/localization support.  My version is a little simpler, since it assumes the use of the standard MVC views (WebFormView).  Here are the steps you’ll need to perform to create a similar class.

  1. Reference the required assemblies and namespaces
    using System.Web;
    using System.Web.Mvc;
  2. Declare a static class (I prefer it in the same namespace as the class your extending)
    namespace System.Web.Mvc
    {
        public static class HtmlHelperExtensions
        {
        }
    }
  3. Create a global resource method; this method will be used to access .resx files in <ApplicationRoot/App_GlobalResources
    public static string GlobalResource(this HtmlHelper htmlHelper, string classKey, string resourceKey)
    {
        object resource = HttpContext.GetGlobalResourceObject(classKey, resourceKey);
        string resourceString = resource as string;
        return resourceString;
    }
  4. Create a local resource method; this method will be used to access .resx files in the App_LocalResources relative to your views
    For example: \Views\Home\App_LocalResources\Index.aspx.resx
    Note that the name of the resourse file must match the name of your view!
    public static string LocalResource(this HtmlHelper htmlHelper, string resourceKey)
    {
        WebFormView webFormView = htmlHelper.ViewContext.View as WebFormView;
        if (webFormView != null)
        {
            string virtualPath = webFormView.ViewPath;
            object resource = HttpContext.GetLocalResourceObject(virtualPath, resourceKey);
            string resourceString = resource as string;
            return resourceString;
        }
        else
        {
            return null;
        }
    }
  5. Call Html.GlobalResource or Html.LocalResource from your view, depending on which resource you're trying to access
    <link href="<%= Html.LocalResource("IndexCss") %>" rel="stylesheet" type="text/css" />
    or
    <title><%= Html.GlobalResource("Strings", "ApplicationName") %></title>

If you look closely, you'll notice that the GlobalResource call takes two parameters, the first is the name of the resource file (without the extension) and the second is the resource value to retrieve. The reason for this is because you will very likely have more than one global resource file, and you can specify which file to retrieve resources from this way.

January 05
Great quote I saw referenced by 37 Signals

Beauty is more important in computing than anywhere else in technology because software is so complicated. Beauty is the ultimate defense against complexity.

—David Gelernter, Machine Beauty: Elegance and the Heart of Technology

December 29
Getting started with Silverlight unit testing

Microsoft recently released the final versions of their unit testing components for Silverlight 2. While Scott Guthrie and Jeff Wilcox have blogged about it, their blogs targeted pre-release versions (specifically Beta 2), and things have changed quite a bit since then. I've been working on some cross-cutting Silverlight classes recently, and thought I would take the opportunity to build some unit tests for what I was working on. It turns out that configuring a unit testing environment isn't very straightforward, so I thought I would provide an updated (and simplified) set of instructions here.

Step 1: Download the Silverlight Toolkit Source

  1. Head over to http://www.codeplex.com/Silverlight
  2. Under the Get Started section, click the Download the latest release link
  3. Download the Silverlight Toolkit - Binaries, Samples, Documentation, Unit Tests and Sources

Step 2: Update your Silverlight installation with the unit testing binaries

  1. Navigate to the Source\Binaries folder of the extracted Silverlight Toolkit source downloaded in Step 1
  2. Copy the Microsoft.Silverlight.Testing.dll and Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight.dll files to the C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Client

Step 3: Download the Silverlight Unit Test Framework

  1. Go to http://code.msdn.microsoft.com/silverlightut
  2. Click the download new templates link

Step 4: Configuring Visual Studio 2008

  1. Navigate to the extracted Visual Studio Templates downloaded in Step 3
  2. Copy SilverlightTestClass_CSharp.zip to C:\Users\<YourUserNameHere>\Documents\Visual Studio 2008\Templates\ItemTemplates\Visual C#
  3. Copy SilverlightTestProject_CSharp.zip to C:\Users\<YourUserNameHere>\Documents\Visual Studio 2008\Templates\ProjectTemplates\Visual C#

If you prefer Visual Basic, copy the VB versions to the corresponding Visual Basic template folders.

Step 5: Double-Check Visual Studio

  1. Open Visual Studio
  2. Select File -> New -> Project
  3. Select Visual C#, (the root, not one of the sub-types; this part is critical)
  4. Under the My Templates section, select Silverlight Test Project

Hope this helps!

July 08
Media Center Fix for Windows Vista

Using Media Center under the following conditions was resulting in stuttering video and audio, and the occasional green screen:

  • Windows Vista Ultimate SP1, 64-bit
  • 4 GB RAM
  • NVidia GeForce 8800 GTS
  • Hauppauge WinTV 1600 TV Tuner

I've seen a lot of posts on message boards where people are experiencing similar issues, all with strange suggestions that don't seem to work. I accidentally discovered my own solution, which I hope helps anyone else with the same problem.

Here's what I did:

  1. Go to Hauppauge's website and download the newest drivers
  2. Extract the contents of the zip file
  3. Run hcwclear.exe, selecting the Remove All WinTV Drivers and Applications (Total Removal) and Search All .INF Files for Conflicting Hardware options
  4. Reboot
  5. When Windows discovers new hardware after rebooting, allow it to look for new drivers

I have seen other instructions that indicate you should use the downloaded drivers, but in my case, the drivers available through Windows Update worked fine.

June 27
Integrating the Silverlight SDK into Visual Studio’s MSDN Library

If you use local MSDN rather than the internet-based version like I do and you are doing Silverlight work, here's how to integrate Silverlight's SDK into Visual Studio's local help.

  1. Open the Microsoft Visual Studio 2008 Documentation start menu item


  2. Go to the Index tab, and type in "combined help", which should navigate you to the combined Help collection [Visual Studio]entry, and press enter


  3. Open the help topic opens, click on the last link named Visual Studio Combined Help Collection Manager


  4. At this point, you should see a list similar to what you see below. Pick the SDKs you'd like to integrate, then press the Update VSCC button.

Once you press the Update VSCC button, you'll be prompted to close the documentation and Visual Studio. The next time you open the documentation, it will spends several minutes integrating the SDK. Once the integration is complete, when you navigate to a class common to .NET and Silverlight, you'll be able to select which SDK you want to navigate to:

For Silverlight, you'll want to select the dv_silverltmref location.

Good luck!

April 29
Vista Explorer Search Tips

Quite by accident, I discovered some advanced search options using the built-in search box in Windows Explorer. It's the little box in the top right-hand corner of your Explorer windows:

If you search for something but name but can't find it, you get the option of using Advance Search:

Clicking the Advanced Search link shows quite a few search options that are hidden by default.

What I've managed to find today, quite by accident, is that you can specify most of that information from within the search box itself! You just need to qualify the data your searching for with the right keyword. Here are a few examples:

  • type: C#
  • modified: > 4/28/2008
  • created: >= 4/29/2008

Pretty cool, huh?

April 01
TimeZoneInfo : Another reason to love .NET 3.5

One of the new classes in .NET 3.5 is TimeZoneInfo. MSDN's vague and all-encompassing description states "Represents any time zone in the world." Scrolling further down, you'll find a list of actual things that TimeZoneInfo can do:

  • Retrieving a time zone that is already defined by the operating system.
  • Enumerating the time zones that are available on a system.
  • Converting times between different time zones.
  • Creating a new time zone that is not already defined by the operating system.
  • Serializing a time zone for later retrieval.

Personally, I'm using TimeZoneInfo for all of my DateTime conversions to support different time zones. Performing the conversion is as easy as calling ConvertTimeBySystemTimeZoneId. Here are a few examples:

DateTime pstDateTime = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(utcDateTime, TimeZoneInfo.Utc.Id, "Pacific Standard Time");

DateTime utcDateTime = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(pstDateTime, "Pacific Standard Time", TimeZoneInfo.Utc.Id);

TimeZoneInfo supports daylight savings time (DST), and makes supporting time zones much easier than rolling your own solution.

March 28
Excel and CSV Reference

I've been doing a lot of work with CSV and Excel imports recently. There are a lot of poorly documented or disparate pieces of information that are required to interface with a CSV or Excel data source, so I'm recording it here.

Formatting Overview

There are a few formatting rules that you need to consider when working with CSV files.

  • Double-quotes are escaped with two double-quotes: " becomes ""
  • Fields with reserved characters are escaped with double-quotes; this includes carriage-return and new-line characters

Using a StreamReader is probably a no-no

For simple files, using a StreamReader will work fine. However, if you have any fields that contain carriage-returns or line-feeds, ReadLine() won't get you the full set of data for a single record. I suppose that you could try and read subsequent lines until you read the expected number of fields, but that brings up the next problem: commas. Fields can contain commas, so calling Split() with a comma as the parameter can produce unexpected results.

As an aside, a StreamReader won't work for native Excel files, so you would be stuck with a hybrid approach. So what do you do? Use OLE DB.

System.Data.OleDb to the rescue

There are OLE DB providers for CSV, Excel 97-2003, and Excel 2007. The CSV and Excel 97-2003 drivers seem to be available with either Windows, the Microsoft Data Access Components, or the .NET Framework. The Excel 2007 drivers are part of the 2007 Office System Driver: Data Connectivity Components, a separate download.

Each file format requires a slightly different connection string, as shown below.

CSV

Provider=Microsoft.Jet.OLEDB.4.0;
Data Source={0};
Extended Properties="text;HDR=Yes;FMT=Delimited;";

Excel 97-2003

Provider=Microsoft.Jet.OLEDB.4.0;
Data Source={0};
Extended Properties="Excel 8.0;HDR=Yes;IMEX=1;";

Excel 2007

Provider=Microsoft.ACE.OLEDB.12.0;
Data Source={0};
Extended Properties="Excel 12.0;HDR=Yes;IMEX=1;";

Note: The data source for CSV is the directory where the file can be found, not the full path to the file.

HDR=Yes is required if the first line/row in the file has column names, aka, header row. I recommend that you use header rows whenever possible so you're not depending on the column ordering.

IMEX=1 indicates that columns may have mixed data. This is important in situations where a column may contain both text and numerical data. Without IMEX=1, mixed columns default to decimal, and string values are read as null values. By default, the OLE DB provider only reads the first 8 rows to determine a column's data type when IMEX=1 was specified. To increase the number of rows examined, change the TypeGuessRows value in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Excel and HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\Access Connectivity Engine\Engines\Excel sections in your registry. Changing the value to 0 will force all columns to be read before the column type will be determined.

The easiest way to determine which connection string to use is to initialize a new System.IO.FileInfo instance with the name of the file you're working with, and use its Extension property.

Querying for data

With CSV, you query against files, not tables. With Excel (any version), you query against sheets, not tables. However, either format works with SQL queries.

The easiest way to translate the file or sheet name into a table name is to use call OleDbConnection.GetOleDbSchemaTable. GetOleDbSchemaTable returns a DataTable with schema information for the OLEDB data source.

DataTable schemaDataTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
foreach (DataRow dataRow in schemaDataTable.Rows)
{
    string tableName = (string) dataRow["TABLE_NAME"];
    // Do something important here
}

The other important thing to consider is that the names of columns, files, and sheets can contain spaces. When you build your queries, it's a good idea to escape everything with brackets.

    string commandText = String.Format("SELECT [First Name], [Last Name], [E-Mail Address] FROM [{0}]", tableName);

Example

If a header row is present, you can call GetOrdinal to determine the index of a column by its name. Here is a helper method, GetDataReaderString, to read the column value as a string.

private string GetDataReaderString(IDataReader dataReader, int ordinal)
{
    if (dataReader.IsDBNull(ordinal))
    {
        return null;
    }
    else
    {
        return dataReader.GetValue(ordinal).ToString();
    }
}

Here's an example that reads a file with a header row.

using (OleDbConnection connection = new OleDbConnection(connectionString))
{
    connection.Open();
    
    DataTable schemaDataTable = connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
    foreach (DataRow dataRow in schemaDataTable.Rows)
    {
        string tableName = (string) dataRow["TABLE_NAME"];

        if ((extension == ".csv" && tableName.Contains("YourFileNameWithoutExtension")) || extension == ".xls" || extension == ".xlsx")
        {
            // Build the command text
            string commandText = String.Format("SELECT [First Name], [Last Name], [E-Mail Address] FROM [{0}]", tableName);

            // Issue the command and process the results
            using (OleDbCommand command = new OleDbCommand(commandText, connection))
            {
                using (OleDbDataReader dataReader = command.ExecuteReader())
                {
                    int firstNameOrdinal = dataReader.GetOrdinal("First Name");
                    int lastNameOrdinal = dataReader.GetOrdinal("Last Name");
                    int emailOrdinal = dataReader.GetOrdinal("E-Mail Address");

                    while (dataReader.Read())
                    {
                        string firstName = GetDataReaderString(dataReader, firstNameOrdinal);
                        string lastName = GetDataReaderString(dataReader, lastNameOrdinal);
                        string email = GetDataReaderString(dataReader, emailOrdinal);

                        // Do something important here
                    }
                }
            }

            // Only process the first sheet of an Excel 97-2003 or Excel 2007 file; or, only process the CSV that was uploaded by the member
            break;
        }
    }
}

Hope this helps!

March 27
.NET Runtime Optimization error on SQL Server 2005

If you're getting an error similar to the following in your Event Log, I've found a solution that may work for you.

NET Runtime Optimization Service (clr_optimization_v2.0.50727_32) - Failed to compile: Microsoft.ReportingServices.QueryDesigners, Version=9.0.242.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91.

In my case, re-installing SQL Server 2005 SP2 didn't work. Neither did rebooting. The only thing that seemed to correct the problem was to install the Microsoft Report Viewer Redistributable 2005. I'm not really sure why that would be required on a server, but things don't always make sense.

February 29
Customizing a Windows Service during installation

Recently, I needed to add the ability install multiple instances of a Windows Service we've been developing on a single server. After some initial research (read:Google), I was able to get it working. The basic steps are as follows:

  1. Override Installer.Install
  2. Read the InstallContext for specified parameters
  3. Update your service installer with any parameters you found
  4. Call base.Install

Note: If you modify the ServiceInstaller.ServiceName property during Install, you will need to also modify it during Uninstall.

Here's some simple code to illustrate how to support customizations during installation.

using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

namespace MyApplication.WindowsServices

    [RunInstaller(true)] 
    public partial class MyServiceInstaller : Installer 
    { 
        private ServiceProcessInstaller processInstaller; 
        private ServiceInstaller serviceInstaller;         

        public MyServiceInstaller() 
        { 
            InitializeComponent();             

            processInstaller = new ServiceProcessInstaller(); 
            processInstaller.Account = ServiceAccount.User; 
            Installers.Add(processInstaller);             

            serviceInstaller = new ServiceInstaller(); 
            serviceInstaller.Description = "Provides functionality for doing something special."
            serviceInstaller.ServiceName = "My Services (Default)"
            serviceInstaller.StartType = ServiceStartMode.Automatic; 
            Installers.Add(serviceInstaller); 
        }

        public override void Install(System.Collections.IDictionary stateSaver) 
        { 
            if (Context.Parameters.ContainsKey("InstanceName")) 
            { 
                string instanceName = Context.Parameters["InstanceName"]; 
                serviceInstaller.ServiceName = String.Format("My Services ({0})", instanceName); 
            }             

            base.Install(stateSaver); 
        }

        public override void Uninstall(System.Collections.IDictionary savedState) 
        { 
            if (Context.Parameters.ContainsKey("InstanceName")) 
            { 
                string instanceName = Context.Parameters["InstanceName"]; 
                serviceInstaller.ServiceName = String.Format("My Services ({0})", instanceName); 
            }

            base.Uninstall(savedState); 
        } 
    }
}

Notice that if nothing is specified when invoking InstallUtil.exe, the installer won't fail. To actually install, type the following command in the directory where your service executable is located:

    InstallUtil /InstanceName=Production MyService.exe

To uninstall, make sure you specify the InstanceName parameter, or it won't find anything to uninstall.

    InstallUtil /u /InstanceName=Production MyService.exe

Pretty easy, huh?

1 - 10Next