eriklane posted on November 5, 2008 13:54

For the the last few years I’ve worked with Visual SourceSafe (VSS), Vault, StarTeam, and Subversion.  For me, the easiest and most straight forward is Subversion (SVN).  Don’t get me wrong, if you come from VSS there is a mind shift to how SVN works but it is well worth it.  After I started my last job and I saw that we still used VSS I immediately started pushing for the change to anything but VSS.

This week I got the approval to start migrating all of our source from VSS over to SVN.  There is a great tool to help with that called VSSMigrate.  Reading through the comments I found that Tim Erickson had created a new version written in C# using SharpSVN (10/22/2008 comment).  Sweet Action!

The tool is really good and does an excellent job.  After running the migration I realized that all of the VSS bindings are still in the project/solution files and that the VSS "*scc" files were in SVN.  I used this codeproject article as the basis for the functionality I added that removes all of the VSS binding information and doesn't import any of the *scc" files. 

I emailed the guys at PowerAdmin and the original author but in the event that it doesn’t get posted anywhere I wanted to make it available.

VssMigrate.Tim2.zip (8.95 mb)

[update: after Dan's comment here is an x86 version that should work on 64 bit systems]

VssMigrate.Tim2(x86).zip (8.95 mb)

Giddy Up!



Posted in: Development & Geek Stuff  Tags:

When I was setting up BE.NET I was glad to see it had support for Gravatars.  What I don’t like are the options available for people that don’t have one.  My option was to use MonsterID, Wavatar, or Identicon.  Those are pretty good but I wanted a more generic looking default…and less goofy looking.

So I went in and added an option to use the selected theme’s icon.

be_avatars

I added the option in the admin/pages/Settings.aspx page.  Saving it to the data store works without hitch since it just a name value pair.  Getting the theme’s icon to display requires a little bit of setup.

Theme Default Avatar

You need to add a noavatar.jpg image at the root of your theme’s folder.  Then update the BlogEngine.Core.Web.CommentViewBase.cs file:

switch (BlogSettings.Instance.Avatar)
{
	case "identicon":
		imgLink = gravatar + "identicon";
		break;

	case "wavatar":
		imgLink = gravatar + "wavatar";
		break;

	case "monsterid":
	imgLink = gravatar + "monsterid";
	break;

	default:
		imgLink = gravatar + HttpUtility.UrlEncode(Utils.AbsoluteWebRoot + "themes/" + BlogSettings.Instance.Theme + "/noavatar.jpg");
		break;
}

Giddy Up!


Posted in: Development & Geek Stuff  Tags:

Back story

My wife and I's blogs have been running on an archaic customized version of .Text for much longer than it should have.  It did the job so it kept getting moved down the to-do list; not to mention I was not looking forward to the data conversion.  David upgraded the server to x64 and after a few tests it was clear that the blog wasn't going to run.  Briefly, I thought I could just recompile in .NET 2.0 and fix a couple of the stored procedures and be done with it.  Thankfully, I came to my senses and decided it was time to bite the bullet and make the jump to something else.

GraffitiCMS and BlogEngine.NET (BE.NET) were the two that I considered because they each could accept a BlogML file of all existing posts.  That would be awesome if my posts where in a BlogML formatted file.  I checked out Rss2BlogML but it kept failing...something with the formatting of my RSS feed.  I realized that I was spending time on getting my blog into BlogML and I'm not even sure it will produce the results I want (read where Dave Donaldson mentioned an issue with redirects for old links if they are not "named" posts in GraffitCMS).  None of my posts are "named" and I wanted a way to keep/update my old links.  So I started down the path of doing the conversion in a more manual fashion.

BE.NET has a pretty slick BlogImporter web service that abstracts away the data repository and it would've done the trick with just a little bit of coding on my part (highly recommend looking at that option).  I have and understand the .Text database and so hitting the database directly seemed like a logical way to go for me.  When I jumped onto this path I felt that GraffitiCMS was out of the picture because of the redirect issues and, having the code to BE.NET, I felt I could get BE.NET to produce the results I was wanting.

Everything after this assumes that you already run the BE.NET database setup scripts and have the BE.NET source code.

Files

Here are the files that I added or modified as part of this process including the SQL script.

Updating BE.NET Database

Thanks to Dave Burke's migration script most of the work was already done for me.  I will summarize it here but please go read it since he explains the script in detail.  Generally speaking, we are going to add the .Text postID and .Text CategoryID to BE.NET so we can use them to link posts, comments, and categories back together once the data is in the BE.NET database.  I told you you needed to go and read Dave's post.  :-)
Following along like a kid in school, I took good notes and updated the CommunityServer references to the .Text equivalent.  In my script, these are the items that you need to update for your personal settings:

  •     BlogID
  •     Author
  •     Default email address for the comments.

I ran the db script a few different times for each of our blogs so I think it is pretty solid.

Updating BE.NET Post Class

In BlogEngine.Core project I added a DottextPostID property to the Post class.

private int _dottextPostId;
///<summary>
/// Dottext Post Id to handle migrated links.
///</summary>
public int DottextPostId
{
    get{ return _dottextPostId; }
    set { _dottextPostId = value; }
}

Update DbBlogProvider Class

In BlogEngine.Core project I modified the DbBlogProvider.SelectPost(Guid id) method to populate the Post object with the DottextPostId value from the database.

string sqlQuery =
 "SELECT PostID, Title, Description, PostContent, DateCreated,"
+ "DateModified, Author, IsPublished, IsCommentEnabled,"
+ "Raters, Rating, Slug, DottextPostID "
+ "FROM " + tablePrefix + "Posts "
+ "WHERE PostID = " + parmPrefix + "id";
///
/// unchanged code left out
///
if (rdr.HasRows)
	{
    	rdr.Read();
		post.Id = rdr.GetGuid(0);
        post.Title = rdr.GetString(1);
        post.Content = rdr.GetString(3);
        if (!rdr.IsDBNull(2))
        	post.Description = rdr.GetString(2);
        if (!rdr.IsDBNull(4))
        	post.DateCreated = rdr.GetDateTime(4);
        if (!rdr.IsDBNull(5))
        	post.DateModified = rdr.GetDateTime(5);
        if (!rdr.IsDBNull(6))
        	post.Author = rdr.GetString(6);
        if (!rdr.IsDBNull(7))
        	post.IsPublished = rdr.GetBoolean(7);
        if (!rdr.IsDBNull(8))
        	post.IsCommentsEnabled = rdr.GetBoolean(8);
        if (!rdr.IsDBNull(9))
        	post.Raters = rdr.GetInt32(9);
        if (!rdr.IsDBNull(10))
        	post.Rating = rdr.GetFloat(10);
        if (!rdr.IsDBNull(12))
        	post.DottextPostId = rdr.GetInt32(12);
        post.Slug = !rdr.IsDBNull(11) ? rdr.GetString(11) : "";
	}

Update UrlReWrite HttpModule

In BlogEngine.Core project I updated the UrlReWrite module to catch any old .Text links that are coming to the site.  By snagging the archive regex expression from the .Text web.config I saved myself some time.

private static readonly Regex DOTTEXT = new Regex(@"/archive/\d{4}/\d{2}/\d{2}/(?<postId>\d+)\.aspx$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

Modify context_BeginRequest
Added an else statement to look for any .Text links.

/// unchanged code left out
///
else if (url.Contains("/AUTHOR/"))
{
    string author = ExtractTitle(context, url);
    context.RewritePath(Utils.RelativeWebRoot + "default" + BlogSettings.Instance.FileExtension + "?name=" + author + GetQueryString(context), false);
}
else
{
    Match match = DOTTEXT.Match(url);
    if (match.Groups.Count > 0)
    {
	int postId;
	if (int.TryParse(match.Groups["postId"].ToString(), out postId))
	{
	    RewriteDottextPost(context, postId);
	}
    }
}

Added ReWriteDottextPost Method

This method locates the right BE.NET post from the .Text post Id in the url, creates the new link, and sends back a response code of 301 so sites know that the link has been permanently moved.

private static void RewriteDottextPost(HttpContext context, int postId)
{
    Post post = Post.Posts.Find(delegate(Post p)
				    {
					return p.DottextPostId == postId;
				    });

    if (post != null)
    {
	string redirUrl = string.Format("{0}://{1}{2}", context.Request.Url.Scheme, context.Request.Url.Authority, post.RelativeLink);
	context.Response.AppendHeader("location", redirUrl);
	context.Response.StatusCode = 301;
	context.Response.End();
    }
}

Update Links in Content

This step is optional because the updated UrlReWriter will catch any of the old links, to my own content, in my posts but I did it for a clean break from .Text.  Beware that depending on your URL structure (blog.domain.com, www.domain.com/blog, etc.) you'll need to update the code so the regex used will catch your specific URL's.  Be sure and backup your BE.NET database or be prepared to do the whole conversion again (like I did) if the links don't turn out the way you want.

Created ConvertDottextLinks.aspx admin page.
This page can go anywhere but I put it in the admin section so it's not open for everyone.  I load up all of the posts in the database, find those which have old .Text links to my own content, find the new BE.NET post for that link, and create the new link for that post.

It is one aspx page with a button and literal on it and here is the code-behind:

public partial class admin_Pages_ConvertDottextLinks : Page
{
	private static readonly List<Post> postList = BlogService.FillPosts();
	protected void Page_Load(object sender, EventArgs e)
	{
	}

    protected void btnGo_Click(object sender, EventArgs e)
    {
       // update this regex with your URL structure.
       Regex regex = new Regex("href\\s*=\\s*(?:\\\"http://blog.eriklane.com/archive/\\d{4}/\\d{2}/\\d{2}/(?<postId>\\d+)\\.aspx)",
       RegexOptions.IgnoreCase
       | RegexOptions.Multiline
       | RegexOptions.Compiled
       );
       int count = 0;
       postList.ForEach(delegate(Post post)
    	{
    		if (regex.IsMatch(post.Content))
    		{
    			string updatedContent = regex.Replace(post.Content, new MatchEvaluator(convertLink));
                post.Content = updatedContent;
                post.Save();
                count++;
            }
        });
    	listResult.Text = string.Format("Finished! - {0} links were converted.", count);
   	}

   	private static string convertLink(Match match)
   	{
      	int dottextPostId;
       	if (int.TryParse(match.Groups["postId"].ToString(), out dottextPostId))
       	{
       	    Post linkPost = postList.Find(delegate(Post post) { return post.DottextPostId == dottextPostId; });
       	    return "href=\"/post/" + linkPost.Slug + BlogSettings.Instance.FileExtension;
       	}
       	return match.ToString();
   	}
}

Copy Images

This will be totally dependent on how how you store your images.  I've kept all of mine in one folder so mine "just worked" by copying the image folder over.  However, you could update your image links to use BE.NET's image HttpHandler to serve them up.  If it were me, I would do it similar to how I updated the content links.

Update Feedburner


You will need to update Feedburner with the new BE.NET feed URL (http://yourdomain.com/syndication.axd).

Conclusion


After all of this I have:

  • All of the posts.
  • All of the post comments.
  • All posts are in the correct categories.
  • All old links are caught and redirected.

These steps pretty much sum up what I did to convert a our .Text blogs over to BE.NET.  It's not a point and click operation but it wasn't as painful as I was expecting.

Posted in: Development & Geek Stuff  Tags:
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2009 Erik Lane