ASP.NET MVC3とRazorを用いたマルチテナントECサイトの動的テーマ切り替えアーキテクチャ

システムアーキテクチャ概要

ASP.NET MVC3とRazorビューエンジンを活用した、複数店舗(テナント)がそれぞれ異なるデザインテンプレートを適用できるECサイトプラットフォームの構築手法について解説します。本システムでは、店舗ごとのブランディングの差異化を図るため、コントローラロジックとビューのレンダリングパスを動的に制御する設計を採用しました。

ソリューションは以下のレイヤードアーキテクチャに基づいて構成されています。

  • Core層: ドメインエンティティ、データアクセスのためのリポジトリインターフェース、および依存性注入(DI)の設定を管理します。
  • Data層: 具体的なデータアクセスロジック(ADO.NETなどを用いた実装)を担当し、Core層で定義されたインターフェースを実装します。ORMに依存しないSQLの直接実行により、クエリのパフォーマンスを厳密に管理します。
  • Services層: ビジネスロジックをカプセル化し、コントローラへのインターフェースを提供します。トランザクション処理や複雑なデータ変換はここで行われます。
  • Web層: MVCアプリケーションの本体。管理画面は Areas機能を用いて「Admin」として分離されていますが、ビルド出力はメインのWebサイト配下に配置されるため、ドメインルート/admin でアクセス可能です。
  • Framework層: 画像処理、メール送信、文字列操作などの共通ユーティリティクラス群を提供します。

テーマ管理とディレクトリ構造

各店舗(テナント)は、あらかじめ用意された複数のテーマ(スキン)からいずれかを選択できます。テーマは単なるCSSの違いだけでなく、HTML構造やレイアウト自体が異なる場合があります。

以下の例では、「Standard」、「Modern」、「Classic」という3つの異なるテーマ構造を想定しています。

/Themes
    /Standard
        /Views
            /Shared
                _Layout.cshtml
                _Header.cshtml
                _Footer.cshtml
            /Home
                Index.cshtml
        /Content
            site.css
    /Modern
        ... (同様の構造)
    /Classic
        ... (同様の構造)

マスターページ(レイアウト)の実装

店舗共通のレイアウトファイル(_Layout.cshtml)では、@Html.Actionヘルパーを使用して、ヘッダーやフッターなどのパーツを「子アクション(Child Action)」として呼び出します。これにより、テーマに応じた部分ビューを動的にレンダリングします。

以下は、マスターページの実装例です。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title - マルチストア</title>
    @* テーマ固有のCSSを読み込み *@
    <link href="@Url.Content("~/Themes/" + ViewBag.CurrentTheme + "/Content/site.css")" rel="stylesheet" type="text/css" />
</head>
<body>
    <div id="page-wrapper">
        <!-- ヘッダーセクション -->
        @Html.Action("RenderHeader", "ShopCommon", new { themeName = ViewBag.CurrentTheme })

        <!-- メインコンテンツ -->
        <div class="main-container">
            @Html.Action("RenderSideNavigation", "ShopCommon", new { themeName = ViewBag.CurrentTheme })
            
            <section class="content-area">
                @RenderBody()
            </section>
        </div>

        <!-- フッターセクション -->
        @Html.Action("RenderFooter", "ShopCommon", new { themeName = ViewBag.CurrentTheme })
    </div>
</body>
</html>

コントローラにおけるテーマ選択ロジック

店舗コントローラでは、リクエストされたテナントIDに基づいて店舗情報を取得し、選択されているテーマパスを解決します。その後、ViewBagを通じてレイアウトへテーマ情報を渡し、適切なビューファイルのパスを指定してレンダリングを行います。

public class ShopController : Controller
{
    private readonly ITenantService _tenantService;

    public ShopController(ITenantService tenantService)
    {
        _tenantService = tenantService;
    }

    public ActionResult Index(int tenantId)
    {
        // テナント情報の取得
        var tenant = _tenantService.GetTenantById(tenantId);
        if (tenant == null) return HttpNotFound();

        // テーマ設定の取得(デフォルトフォールバック付き)
        string activeTheme = string.IsNullOrEmpty(tenant.ThemeName) 
            ? "Standard" 
            : tenant.ThemeName;

        // ViewContextへテーマ情報を渡す
        ViewBag.CurrentTheme = activeTheme;
        ViewBag.Title = tenant.ShopName;

        // ビューモデルの構築
        var viewModel = new ShopIndexViewModel
        {
            Tenant = tenant,
            Products = _tenantService.GetFeaturedProducts(tenantId)
        };

        // テーマ固有のビューパスを構築して返す
        string viewPath = string.Format("~/Themes/{0}/Views/Home/Index.cshtml", activeTheme);
        return View(viewPath, viewModel);
    }
}

子アクションによるパーシャルビューの動的ロード

ヘッダーやフッターなどの共通部品は、[ChildActionOnly]属性が付与されたアクションメソッドで処理します。このメソッドは現在のテーマ名を受け取り、対応するテーマフォルダ内のパーシャルビューをレンダリングします。

以下はフッター描画の実装例です。

[ChildActionOnly]
public ActionResult RenderFooter(string themeName)
{
    // テーマ名が未指定の場合はデフォルトを使用
    if (string.IsNullOrWhiteSpace(themeName))
    {
        themeName = "Standard";
    }

    // テーマに対応するフッタービューのパスを生成
    string footerPath = string.Format("~/Themes/{0}/Views/Shared/_Footer.cshtml", themeName);

    // 必要なモデルを渡してパーシャルビューを返す
    var model = new FooterViewModel { CopyrightYear = DateTime.Now.Year };
    return PartialView(footerPath, model);
}

このアプローチにより、ビジネスロジックを変更することなく、各店舗の設定ファイルやデータベース内の設定値を書き換えるだけで、サイト全体の見た目を切り替えることが可能になります。管理画面から店舗ごとに適用するテーマを選択する機能を実装することで、柔軟なマルチストアシステムが完成します。

タグ: ASP.NET MVC Razor Multi-tenancy Theming C#

6月21日 21:26 投稿