ComoponentGeek から新しいサイトに本格移行をするために、IIS7からサポートされた IIS URL 書き換えモジュールを使用して 旧サイトの特定のURLにアクセスされた際に、新サイトの対応するURLを参照する設定をしていました。URL書き換えって便利だなぁと思いつうあらかた設定してから気付きました。

旧サイトで記事はFCKEditor(WYSIWYGエディタ) で作成していたため、記事内で別の記事を相対URLで参照している場合、新サイトのURLのRouteパターンで想定外のURLになっていると。ちなみにURL書き換えモジュールについては下記URLを参照。

URL 書き換えモジュール 2
http://technet.microsoft.com/ja-jp/library/ff433489

URL書き換えを新サイトに対しても設定すれば解決可能と思われますが、折角なので、旧サイト用のURLがリクエストされた場合に、MvcRouteHandler 用に 新サイト用のコントローラ、アクションメソッド、そのほかの情報を設定する カスタム Route クラスを作成してみることにしたので、覚書を記載します。

検証環境

  • ASP.NET MVC3
  • IIS 7.5
  • Visual Studio 2010 Professional, .NET 4.0

1. LegacyRoute クラスの仕様

今回作成するLegacyRoute クラスの仕様を記載します。LegacyRoute は 特定のURLパターンに一致する パスがリクエストされた場合に、所定のルール(LegacyRouteInfoクラスにより定義)に従って 適当な RouteData を作成します。

2. カスタムRouteクラスの実装

実装した LegacyRoute クラスは次の通りです。LegacyRoute クラスがRouteクラスの本体です。LegacyRouteInfoクラスがURLパターンとMvcHandler 用に Route Value を設定する方法を定義するクラスです。レガシーURLアクセス用のRoute設定をするクラスなので、GetRouteData メソッドは実装しています。GetRouteDataメソッドでリクエストされたURLをチェックし、パターンに一致すればRouteDataを作成しています。出力用のURLを作成する GetVirtualPath メソッドは からなずNULLを返して他のRouteクラスにURLの生成を任せるようにしています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
using System.Text.RegularExpressions;
using System.Web.Mvc;

namespace Netplanetes.MVCSample
{
    public class LegacyRoute : RouteBase
    {
        private IList<LegacyRouteInfo> _legacyInfos = new List<LegacyRouteInfo>();
        public LegacyRoute()
        {
            AddLegacyRouteInfos();
        }
        private void AddLegacyRouteInfos()
        {
            _legacyInfos.Add(new LegacyRouteInfo
            {
                Pattern = new Regex("^~/ShowArticle/(?<ID>[0-9]+)\\.aspx$"),
                AddRouteValue = (route, match) =>
                {
                    route.Values.Add("controller", "Article");
                    route.Values.Add("action", "Archives");
                    route.Values.Add("id", match.Groups["ID"].Value);
                }
            });
            _legacyInfos.Add(new LegacyRouteInfo
            {
                Pattern = new Regex("^~/BrowseArticles.aspx/CatID/(?<ID>[0-9]+)\\.aspx$"),
                AddRouteValue = (route, match) =>
                {
                    route.Values.Add("controller", "Article");
                    route.Values.Add("action", "List");
                    route.Values.Add("id", match.Groups["ID"].Value);
                }
            });

        }
        /// <summary>
        /// LegacyInfo.Pattern に一致するURLへのアクセスの場合は、
        /// RouteData 情報を作成する
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            RouteData route = null;

            string path = httpContext.Request.AppRelativeCurrentExecutionFilePath;
            foreach (var legacy in _legacyInfos)
            {
                Match m = legacy.Pattern.Match(path);
                if (m.Success)
                {
                    route = new RouteData(this, new MvcRouteHandler());
                    legacy.AddRouteValue(route, m);
                    break;
                }
            }

            return route;
        }

        /// <summary>
        /// HtmlHelper.ActionLink などでURLを生成するために呼び出される。
        /// LegacyRouteはレガシー用のリクエストを処理するために使用する。
        /// 本実装ではnullを返し、後続のRouteクラスがURLを生成できるようにする。
        /// </summary>
        /// <param name="requestContext"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            return null;
        }

        /// <summary>
        /// Legacy URL パターンとRouteValue設定情報を格納するクラス
        /// </summary>
        class LegacyRouteInfo
        {
            public Regex Pattern { get; set; }

            public Action<RouteData, Match> AddRouteValue { get; set; }
        }
    }
}

3. LegacyRoute を使う

作成したLegacyRoute クラスは、RouteTable クラスに登録する必要があります。 ASP.NET MVC プロジェクトの Global.asax.cs の RegisterRoutes メソッドで 作成した LegacyRoute クラスを登録します。ちなみに、Routeパターンを登録するときによく使用する  routes.MapRoute メソッドは Route クラスを作成します。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.Add(new LegacyRoute());
    // そのほかのRoute設定
     .....
}

これで 仮想アプリケーションパス配下に旧URLのパターンでアクセスした場合に LegacyRouteInfoの定義に従って対応する新サイトの画面を表示できるようになりました。

4. まとめ

説明は以上です。サンプルプログラムを定義修正すれば、いろいろなパターンに対応できると思います。

間違いや指摘点などありましたらご連絡ください。