Oct 24 2008

The middle ground for code analysis custom dictionaries

Category: .NetRory Primrose @ 09:45

I posted almost a year ago about using custom dictionaries for spell checking in code analysis. At the time, it seemed like a good idea to modify the custom dictionary in C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop so that any code on the machine will be evaluated against that dictionary. I'm now starting to swing away from this idea.

Having a custom dictionary in one spot on the machine is a good because any solution you use on that machine will adhere to that dictionary. The problem in a team environment is that the custom dictionary is not in source control. Any new dev machine will not have those changes and is likely to fail code analysis. Build servers will also suffer a maintenance nightmare with their custom dictionary as it will need to support all possible solutions being built, not just the ones on a particular dev machine.

If you swing to the other extreme, suppressing spelling failures in code is messy, but is in source control for other developers and build servers to benefit from.

I am working with a new model that is the middle ground of these two extremes.

I noticed a while ago that Visual Studio now contains more build actions for files in a project. One of them is CodeAnalysisDictionary. This becomes very helpful.

image

My new method of satisfying spell checking in code analysis is to have my custom dictionary checked into source control. This usually means that the xml file will be located in the same place as the solution file.

The first thing to do is add the custom dictionary as a solution item.

image

This makes it easy to open, check out, edit and check in this file without having to go through Source Code Explorer to do a manual check out, edit, check in cycle. It also means that it is part of the solution in the Pending Changes dialog when check in files are filtered by the current solution.

Next, we need to associate this single custom dictionary with all of the projects in the solution. We do not want to copy this file between the projects as this would still be a maintenance nightmare. Visual Studio is kind enough to have a file link facility. In each project, open up the Add Existing Item dialog, find the custom dictionary and add it as a link.

image

This will then add this file as a link to the project. You will notice that the overlay of the icon in Solution Explorer also indicates that this file is a shortcut to another location.

image

Open up the properties on this file, and select CodeAnalysisDictionary as the Build Action for this xml file.

image

As you compile the application and run code analysis, this custom dictionary will be referenced.

The advantages of this solution are:

  • Under source control so it is accessible to all developers
  • Under source control so it is accessible to team build servers
  • It contains only custom dictionary entries that are related to the projects that reference it
  • Common to all projects in the solution
  • Common to all projects in a TFS project/branch depending on TFS project structure
  • Easily updated and checked in
  • Visible to developers who use the solution

The disadvantage of this solution is:

  • The custom dictionary may need to be duplicated for each solution/TFS project

Tags:

Oct 17 2008

At war with F1

Category: IT RelatedRory Primrose @ 05:30

After implementing the double F1 solution a few moths ago, I now get continuously bitten by the change. I keep pressing F1 just once and then wait for help to come up only to remember that I need to hit F1 a second time. It probably doesn't help that MSDN from Visual Studio takes a long time to come up the first time, but eventually I realise that I need to hit F1 a second time.

It's a war between my automated single F1 press for help vs my misfiring over from the ESC key. I will keep the setting how it is though. Forgetting the second F1 press isn't as bad as accidentally hitting F1 instead of ESC.

Tags:

Oct 17 2008

One day, there will be a Cloud Camp

Category: IT RelatedRory Primrose @ 04:11

So we have Code Camp, SQL Down Under Code Camp and Security Code Camp. I don't think it will be too long before we have Cloud Camp.

Tags:

Oct 15 2008

Supporting Community Server urls in BlogEngine.Net

Category: .Net | Applications | My SoftwareRory Primrose @ 08:23

One of the parts of my migration from CS to BlogEngine.Net was to continue support for CS url formats. Primarily, this is because I have lots of Google traffic and people who are subscribed to syndication feeds on those urls. Writing an http module was the easiest way to provide support for these existing urls.

I have released the first version of this module which is being used on this site currently. You can get the binaries and source from the CodePlex project here.

The following are the release notes which are also found in the comments in the code:

Neovolve.BlogEngine.Net.Web 1.0 contains a redirector module that translates Community Server url formats into BlogEngine.Net urls. This module will redirect Community Server pages, posts, month post lists, day post lists, tags, syndication and tagged syndication urls to the most appropriate location in BlogEngine.Net. BlogEngine.Net relies on its own friendly written urls when the urls are processed. This module cannot rewrite Community Server urls as BlogEngine.Net will not be able to understand what the requested resource is. The module must redirect requests to the equivalent BlogEngine.Net friendly url so that BlogEngine.Net url writing can occur such that resources are resolved correctly.

In Community Server, tags and categories are the same thing and it supports tag filtering by allowing multiple tags to be defined. BlogEngine.Net supports both tags and categories as separate entities and has a concept of a category hierarchy, but doesn't support tag filtering. When a Community Server url is encountered where multiple tags are defined, the first Community Server tag in the url that can be matched to either a BlogEngine.Net category or tag will be the resource used to response to the client request. Preference is given to categories over tags.

To use this module, put the release binary into your bin directory and add the following to the httpModules section in web.config.

<add name="CSUrlRewrite" type="Neovolve.BlogEngine.Web.CSUrlRedirector, Neovolve.BlogEngine.Web"/>

Download from here.

Tags:

Oct 15 2008

Conflict between HttpUtility.UrlEncode and IIS7

Category: .NetRory Primrose @ 07:03

I have previously posted about the issues I had with CS on IIS7 with + characters in urls. Under the default configuration, IIS7 returns a 404 error because it is concerned about double escaping. With my migration from CS to BlogEngine.Net, I wanted to maintain existing incoming CS links. This means that I still needed to support + characters in urls.

I also found that the TagCloud control in BlogEngine.Net renders + characters as well. After looking at the code, it eventually calls down into HttpUtility.UrlEncode. This code uses + instead of %20 for encoding spaces. This is a big problem. Any code that uses the HttpUtility.UrlEncode (which is very common), will prevent pages being served correctly on IIS7 without using the workaround indicated in the previous post.

Tags:

Oct 11 2008

Moving categories to tags

Category: Applications | .NetRory Primrose @ 08:08

As part of my CS to BlogEngine.Net migration, I wanted to clean up my use of categories. I had quite a few of them, and reducing the number of categories by moving a lot of them into tags seemed like a good idea. There were a few scenarios involved. Sometimes I wanted to leave the existing category. I also wanted to rename a category and optionally move the old category into a tag.

Here is the script:

BEGIN TRANSACTION 

SET NOCOUNT ON 

DECLARE @OldCategory NVARCHAR(50) 
DECLARE @NewCategory NVARCHAR(50) 
DECLARE @Tag NVARCHAR(50) 

SET @OldCategory = 'BlogEngine.Net' 
SET @NewCategory = 'Applications' 
SET @Tag = 'BlogEngine.Net' 

/* 

An old category must be provided 
If a tag is provided, posts with the old category will be assigned the provided tag 
If the new category is the same as the old category, the existing category will be left unchanged 
If a new category is provided, the old category will be renamed to the new category 
If a new category is not provided, the old category will be deleted 

*/ 

IF ISNULL(@OldCategory, '') = '' 
BEGIN 

      RAISERROR('No old category has been specified', 16, 1) 

END 

IF ISNULL(@Tag, '') != '' 
BEGIN 

      PRINT 'Converting category ' + @OldCategory + ' to tag ' + @Tag 

      INSERT INTO be_PostTag 
      ( 
            PostID, 
            Tag 
      ) 
      SELECT PC.PostID, @Tag 
      FROM be_PostCategory PC 
            INNER JOIN be_Categories C ON PC.CategoryID = C.CategoryID 
            LEFT OUTER JOIN be_PostTag PT ON PC.PostID = PT.PostID AND PT.Tag = @Tag 
      WHERE C.CategoryName = @OldCategory 
            AND PT.Tag IS NULL 

      PRINT CAST(@@ROWCOUNT AS NVARCHAR(50)) + ' categories converted into tags' 
      PRINT '' 

END 

IF ISNULL(@OldCategory, '') != ISNULL(@NewCategory, '') 
BEGIN 

      IF ISNULL(@NewCategory, '') != '' 
      BEGIN 

            PRINT 'Renaming category ' + @OldCategory + ' to ' + @NewCategory 
            PRINT 'WARNING: The descriptions of renamed categories need to be manually reviewed' 

            IF NOT EXISTS 
            ( 
                  SELECT CategoryID 
                  FROM be_Categories 
                  WHERE CategoryName = @NewCategory 
            ) 
            BEGIN 

                  PRINT 'Category ' + @NewCategory + ' doesn''t exist. The category ' + @OldCategory + ' will be renamed' 

                  -- The new category doesn't yet exist 
                  UPDATE be_Categories 
                  SET CategoryName = @NewCategory 
                  WHERE CategoryName = @OldCategory 

            END 
            ELSE 
            BEGIN 

                  PRINT 'Category ' + @NewCategory + ' already exists. Posts for the category ' + @OldCategory + ' will be migrated before the category is deleted' 

                  -- The new category already exists 
                  -- We need to migrate to the new category Id and delete the old category 
                  DECLARE @OldCategoryID UNIQUEIDENTIFIER 
                  DECLARE @NewCategoryID UNIQUEIDENTIFIER 

                  SELECT @OldCategoryID = CategoryID 
                  FROM be_Categories 
                  WHERE CategoryName = @OldCategory 

                  SELECT @NewCategoryID = CategoryID 
                  FROM be_Categories 
                  WHERE CategoryName = @NewCategory 

                  -- Migrate posts to the new category if they aren't already assigned to it 
                  UPDATE be_PostCategory 
                  SET CategoryID = @NewCategoryID 
                  WHERE CategoryID = @OldCategoryID 
                        AND PostID NOT IN 
                        ( 
                              SELECT PostID 
                              FROM be_PostCategory 
                              WHERE CategoryID = @NewCategoryID 
                        ) 


                  PRINT CAST(@@ROWCOUNT AS NVARCHAR(50)) + ' posts migrated to new category' 
                  PRINT '' 

                  -- Remove relationships which had both categories assigned 
                  DELETE 
                  FROM be_PostCategory 
                  WHERE CategoryID = @OldCategoryID

                  DELETE 
                  FROM be_Categories 
                  WHERE CategoryID = @OldCategoryID 

            END 

      END 
      ELSE IF ISNULL(@NewCategory, '') = '' 
      BEGIN 

            PRINT 'Deleting category ' + @OldCategory 

            DELETE 
            FROM be_PostCategory 
            WHERE CategoryID IN 
            ( 
                  SELECT CategoryID 
                  FROM be_Categories 
                  WHERE CategoryName = @OldCategory 
            ) 

            DELETE 
            FROM be_Categories 
            WHERE CategoryName = @OldCategory 

      END 

END 

COMMIT TRANSACTION

Normal deal applies, use this at your own risk and don't run it on a production database until you have thoroughly tested it first.

Tags:

Oct 11 2008

Migration SQL script from Community Server to BlogEngine.Net

Category: Applications | .NetRory Primrose @ 08:02

I took the inspiration from Dave to write a script to migrate the things I wanted from CS over to BlogEngine.Net. His script was a great help as he identified some of the obscure bits of information like post type id values.

Here is my script. It copies across posts, pages, comments and trackbacks (in BlogEngine.Net format). It pulls out author information and slug names for URLs. The main bit of information I didn't migrate was stats over views, reads and downloads.

-- TODO 
-- Replace [AccountId] with your account id in the site 
-- Replace [AccountName] with your account name in the site 
-- Replace [NewDatabaseName] with your new database name 
-- Replace [OldDatabaseName] with your old database name 
-- Replace [DomainName] with your domain name 
-- Set @BlogSectionId to your section id

USE [NewDatabaseName] 
GO 

ALTER TABLE [NewDatabaseName].dbo.be_Posts 
ADD CSPostID INT NULL 
GO 

ALTER TABLE [NewDatabaseName].dbo.be_categories 
ADD CSCategoryID INT NULL 
GO 

-- Posts and Comments ------------------------------- 

DECLARE @BlogSectionId INT 
SET @BlogSectionId = 3 

-- Insert pages 
INSERT INTO [NewDatabaseName].dbo.be_Pages 
( 
    PageID, 
    Title, 
    [Description], 
    PageContent, 
    Keywords, 
    DateCreated, 
    DateModified, 
    IsPublished, 
    IsFrontPage, 
    Parent, 
    ShowInList 
) 
SELECT NEWID(), 
    [Subject], 
    ([OldDatabaseName].dbo.FetchExtendendAttributeValue('Excerpt', c.PropertyNames, c.PropertyValues)), 
    Body, 
    NULL, 
    PostDate, 
    PostDate, 
    1, 
    0, 
    NULL, 
    1 
FROM [OldDatabaseName].dbo.cs_Posts c 
WHERE SectionID = @BlogSectionId 
    AND PostLevel = 1 
    AND ApplicationPostType = 2 

-- Insert posts 
INSERT INTO [NewDatabaseName].dbo.be_Posts 
( 
    PostID, 
    Title, 
    [Description], 
    PostContent, 
    DateCreated, 
    DateModified, 
    Author, 
    IsPublished, 
    IsCommentEnabled, 
    Slug, 
    CSPostID 
) 
SELECT NEWID(), 
    [Subject], 
    ([OldDatabaseName].dbo.FetchExtendendAttributeValue('Excerpt', c.PropertyNames, c.PropertyValues)), 
    Body, 
    PostDate, 
    PostDate, 
    '[AccountId]', 
    1, 
    1, 
    CASE WHEN ISNULL(PostName, '') = '' THEN 
        CAST(PostID AS NVARCHAR(255)) 
    ELSE 
        PostName 
    END, 
    PostID 
FROM [OldDatabaseName].dbo.cs_Posts c 
WHERE SectionID = @BlogSectionId 
    AND PostLevel = 1 
    AND ApplicationPostType = 1 

-- Insert comments 
INSERT INTO [NewDatabaseName].dbo.be_PostComment 
( 
    PostID, 
    CommentDate, 
    Author, 
    Email, 
    Website, 
    Comment, 
    Ip, 
    IsApproved 
) 
SELECT b.PostID, 
    c.PostDate, 
    ([OldDatabaseName].dbo.FetchExtendendAttributeValue('SubmittedUserName', c.PropertyNames, c.PropertyValues)), 
    'nobody@[DomainName]', 
    ([OldDatabaseName].dbo.FetchExtendendAttributeValue('TitleUrl', c.PropertyNames, c.PropertyValues)) , 
    c.Body, 
    c.IPAddress, 
    1 
FROM [NewDatabaseName].dbo.be_Posts b 
    INNER JOIN [OldDatabaseName].dbo.cs_posts c ON b.CSPostID = c.ParentID 
WHERE c.SectionId = @BlogSectionId 
    AND c.PostLevel = 2 
    AND c.PostType = 1 
    AND c.ApplicationPostType <> 8 

-- Insert trackbacks 
INSERT INTO [NewDatabaseName].dbo.be_PostComment 
( 
    PostID, 
    CommentDate, 
    Author, 
    Email, 
    Website, 
    Comment, 
    Ip, 
    IsApproved 
) 
SELECT b.PostID, 
    c.PostDate, 
    ([OldDatabaseName].dbo.FetchExtendendAttributeValue('trackbackName', c.PropertyNames, c.PropertyValues)), 
    'trackback', 
    ([OldDatabaseName].dbo.FetchExtendendAttributeValue('TitleUrl', c.PropertyNames, c.PropertyValues)) , 
    'Trackback from ' + ([OldDatabaseName].dbo.FetchExtendendAttributeValue('trackbackName', c.PropertyNames, c.PropertyValues)) + '\n\n' + CAST(c.Body AS NVARCHAR(MAX)), 
    c.IPAddress, 
    1 
FROM [NewDatabaseName].dbo.be_Posts b 
    INNER JOIN [OldDatabaseName].dbo.cs_posts c ON b.CSPostID = c.ParentID 
WHERE c.SectionId = @BlogSectionId 
    AND c.PostLevel = 2 
    AND c.PostType = 1 
    AND c.ApplicationPostType = 8 

UPDATE [NewDatabaseName].dbo.be_PostComment 
SET Author = '[AccountName]' 
WHERE Author IS NULL 

-- Categories -------------------------------------------- 

INSERT INTO [NewDatabaseName].dbo.be_categories 
( 
    CategoryName, 
    CSCategoryID 
) 
SELECT [Name], 
    CategoryID 
FROM [OldDatabaseName].dbo.cs_post_categories c 
WHERE c.IsEnabled = 1 
    AND SectionID = @BlogSectionId 

INSERT INTO [NewDatabaseName].dbo.be_postcategory 
( 
    PostID, 
    CategoryID 
) 
SELECT b.postID, 
    bc.CategoryID 
FROM [NewDatabaseName].dbo.be_categories bc 
    INNER JOIN [OldDatabaseName].dbo.cs_post_categories c ON c.CategoryID = bc.CSCategoryID 
    INNER JOIN  [OldDatabaseName].dbo.cs_posts_incategories cic ON c.CategoryID = cic.CategoryID 
    INNER JOIN  [OldDatabaseName].dbo.cs_posts p ON cic.PostId = p.PostId 
    INNER JOIN [NewDatabaseName].dbo.be_posts b ON b.CSPostID = p.postID 

GO 

-- Clean up 

ALTER TABLE [NewDatabaseName].dbo.be_Posts 
DROP COLUMN CSPostID 
GO 

ALTER TABLE [NewDatabaseName].dbo.be_categories 
DROP COLUMN CSCategoryID 
GO 

I haven't run this script for a few weeks now and I did do a few other little manual scripts at the time. As usual, use this at your own risk and definitely don't use this on a production database without testing it thoroughly first.

I also had a script to clean up the membership store, but I can't publish that one without giving away some secrets.

Tags:

Oct 11 2008

Migrating from Community Server to BlogEngine.Net

Category: .Net | ApplicationsRory Primrose @ 07:37

I have finished my migration from CS to BlogEngine.Net. It has taken a lot longer than expected, but I am very happy with the outcome.

I outlined my migration plan on this page and I will publish a set of posts in the coming week about some of the things I put together along the way to help with the migration.

Tags:

Oct 10 2008

Load test results not available

Category: ApplicationsRory Primrose @ 04:15

I have received an email report from TFS for a nightly team build that I have set up. The build ran the unit tests successfully, but failed on some of the load tests. The warning provided is Warning: only a part of test result was loaded because test type implementation is not available. If I try to open the test, I get a message box saying There are no Test Result Details available.

Google fails to provide any insight into this error.

Load Test Results Error

Anyone come across this before?

Tags:

Oct 8 2008

WCF service contract design article

Category: .Net | Software DesignRory Primrose @ 18:26

I had a conversation yesterday regarding WCF service contract design with my tech lead at work. Funnily enough, I then got a comment on an old post that afternoon from Ciaran O'Neill which is really about the same topic. I thought that I should write up my thoughts on the subject. See here for the article.

Tags: