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