1. ホーム
  2. asp.net-mvc

[解決済み] MVCでデフォルトルート(エリア)を設定する方法

2022-08-15 21:21:16

質問

これは以前にも質問されたことがあるのですが、確かな解決策がないのです。そこで、私自身のため、そしてこれが役に立つと思う他の人のために、この質問をします。

MVC2 (ASP.NET) では、誰かがウェブサイトにナビゲートすると、指定されたデフォルトの領域があるようにしたいです。したがって、私のサイトにナビゲートすると、AreaZ の ControllerX ActionY に送られるはずです。

Global.asaxで次のルートを使用します。

routes.MapRoute(
                "Area",
                "",
                new { area = "AreaZ", controller = "ControllerX ", action = "ActionY " }
            );

これで、正しいページを提供しようとするように動作します。しかし、MVC は Area フォルダではなく、サイトのルートにある View を探そうとします。

これを解決する方法はありますか。

EDIT

解決策があります。それは、ControllerX, ActionYで、ビューのフルパスを返すことです。少しハックしていますが、動作しています。しかし、私はより良いソリューションがあることを期待しています。

         public ActionResult ActionY()
        {
            return View("~/Areas/AreaZ/views/ActionY.aspx");
        }

編集する

これは、ページのHTMLアクションリンクがある場合にも問題になります。領域が設定されていない場合、アクションリンクは空白で出力されます。

これはすべてデザインなのでしょうか、それとも欠陥なのでしょうか?

どのように解決するのですか?

これは私が興味を持ったもので、ようやく調べる機会がありました。 他の人たちは、どうやら、この問題が ビューを見つける の問題ではなく ルーティング そのものではなく、質問のタイトルがルーティングに関するものであることを示しているからでしょう。

いずれにせよ、これはViewに関連する問題なので、あなたが望むものを得るための唯一の方法は デフォルトのビューエンジンをオーバーライドする . 通常、これを行う場合、ビューエンジンを切り替える(SparkやNHamlなどに)という単純な目的のために行います。 この場合、オーバーライドする必要があるのはビュー作成ロジックではなく FindPartialViewFindView メソッドを VirtualPathProviderViewEngine クラスで使用されます。

これらのメソッドが実際に仮想的であることを幸運に感謝することができます。 VirtualPathProviderViewEngine アクセス可能な - プライベートなものであり、そのため は非常に なぜなら、ロケーションキャッシュとロケーションフォーマットをうまく使うには、基本的にすでに書かれているコードの半分を書き直さなければならないからです。 Reflector をいくつか調査した後、私は最終的に動作するソリューションを考え出すことに成功しました。

ここで私が行ったことは、まず抽象的な AreaAwareViewEngine から直接派生した VirtualPathProviderViewEngine ではなく WebFormViewEngine . これは、もしあなたがSparkのビューを作成したい場合(あるいは何でも)、このクラスを基本型として使用できるようにしたものです。

以下のコードはかなり長ったらしいので、実際に何をするのか簡単にまとめます。 {2} をエリア名に対応するロケーションフォーマットに入れることができます。 {1} がコントローラ名に対応するのと同じです。 これだけです。そのためにこのようなコードを書かなければならなかったのです。

BaseAreaAwareViewEngine.cs

public abstract class BaseAreaAwareViewEngine : VirtualPathProviderViewEngine
{
    private static readonly string[] EmptyLocations = { };

    public override ViewEngineResult FindView(
        ControllerContext controllerContext, string viewName,
        string masterName, bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(viewName))
        {
            throw new ArgumentNullException(viewName,
                "Value cannot be null or empty.");
        }

        string area = getArea(controllerContext);
        return FindAreaView(controllerContext, area, viewName,
            masterName, useCache);
    }

    public override ViewEngineResult FindPartialView(
        ControllerContext controllerContext, string partialViewName,
        bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentNullException(partialViewName,
                "Value cannot be null or empty.");
        }

        string area = getArea(controllerContext);
        return FindAreaPartialView(controllerContext, area,
            partialViewName, useCache);
    }

    protected virtual ViewEngineResult FindAreaView(
        ControllerContext controllerContext, string areaName, string viewName,
        string masterName, bool useCache)
    {
        string controllerName =
            controllerContext.RouteData.GetRequiredString("controller");
        string[] searchedViewPaths;
        string viewPath = GetPath(controllerContext, ViewLocationFormats,
            "ViewLocationFormats", viewName, controllerName, areaName, "View",
            useCache, out searchedViewPaths);
        string[] searchedMasterPaths;
        string masterPath = GetPath(controllerContext, MasterLocationFormats,
            "MasterLocationFormats", masterName, controllerName, areaName,
            "Master", useCache, out searchedMasterPaths);
        if (!string.IsNullOrEmpty(viewPath) &&
            (!string.IsNullOrEmpty(masterPath) || 
              string.IsNullOrEmpty(masterName)))
        {
            return new ViewEngineResult(CreateView(controllerContext, viewPath,
                masterPath), this);
        }
        return new ViewEngineResult(
            searchedViewPaths.Union<string>(searchedMasterPaths));
    }

    protected virtual ViewEngineResult FindAreaPartialView(
        ControllerContext controllerContext, string areaName,
        string viewName, bool useCache)
    {
        string controllerName =
            controllerContext.RouteData.GetRequiredString("controller");
        string[] searchedViewPaths;
        string partialViewPath = GetPath(controllerContext,
            ViewLocationFormats, "PartialViewLocationFormats", viewName,
            controllerName, areaName, "Partial", useCache,
            out searchedViewPaths);
        if (!string.IsNullOrEmpty(partialViewPath))
        {
            return new ViewEngineResult(CreatePartialView(controllerContext,
                partialViewPath), this);
        }
        return new ViewEngineResult(searchedViewPaths);
    }

    protected string CreateCacheKey(string prefix, string name,
        string controller, string area)
    {
        return string.Format(CultureInfo.InvariantCulture,
            ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:",
            base.GetType().AssemblyQualifiedName,
            prefix, name, controller, area);
    }

    protected string GetPath(ControllerContext controllerContext,
        string[] locations, string locationsPropertyName, string name,
        string controllerName, string areaName, string cacheKeyPrefix,
        bool useCache, out string[] searchedLocations)
    {
        searchedLocations = EmptyLocations;
        if (string.IsNullOrEmpty(name))
        {
            return string.Empty;
        }
        if ((locations == null) || (locations.Length == 0))
        {
            throw new InvalidOperationException(string.Format("The property " +
                "'{0}' cannot be null or empty.", locationsPropertyName));
        }
        bool isSpecificPath = IsSpecificPath(name);
        string key = CreateCacheKey(cacheKeyPrefix, name,
            isSpecificPath ? string.Empty : controllerName,
            isSpecificPath ? string.Empty : areaName);
        if (useCache)
        {
            string viewLocation = ViewLocationCache.GetViewLocation(
                controllerContext.HttpContext, key);
            if (viewLocation != null)
            {
                return viewLocation;
            }
        }
        if (!isSpecificPath)
        {
            return GetPathFromGeneralName(controllerContext, locations, name,
                controllerName, areaName, key, ref searchedLocations);
        }
        return GetPathFromSpecificName(controllerContext, name, key,
            ref searchedLocations);
    }

    protected string GetPathFromGeneralName(ControllerContext controllerContext,
        string[] locations, string name, string controllerName,
        string areaName, string cacheKey, ref string[] searchedLocations)
    {
        string virtualPath = string.Empty;
        searchedLocations = new string[locations.Length];
        for (int i = 0; i < locations.Length; i++)
        {
            if (string.IsNullOrEmpty(areaName) && locations[i].Contains("{2}"))
            {
                continue;
            }
            string testPath = string.Format(CultureInfo.InvariantCulture,
                locations[i], name, controllerName, areaName);
            if (FileExists(controllerContext, testPath))
            {
                searchedLocations = EmptyLocations;
                virtualPath = testPath;
                ViewLocationCache.InsertViewLocation(
                    controllerContext.HttpContext, cacheKey, virtualPath);
                return virtualPath;
            }
            searchedLocations[i] = testPath;
        }
        return virtualPath;
    }

    protected string GetPathFromSpecificName(
        ControllerContext controllerContext, string name, string cacheKey,
        ref string[] searchedLocations)
    {
        string virtualPath = name;
        if (!FileExists(controllerContext, name))
        {
            virtualPath = string.Empty;
            searchedLocations = new string[] { name };
        }
        ViewLocationCache.InsertViewLocation(controllerContext.HttpContext,
            cacheKey, virtualPath);
        return virtualPath;
    }


    protected string getArea(ControllerContext controllerContext)
    {
        // First try to get area from a RouteValue override, like one specified in the Defaults arg to a Route.
        object areaO;
        controllerContext.RouteData.Values.TryGetValue("area", out areaO);

        // If not specified, try to get it from the Controller's namespace
        if (areaO != null)
            return (string)areaO;

        string namespa = controllerContext.Controller.GetType().Namespace;
        int areaStart = namespa.IndexOf("Areas.");
        if (areaStart == -1)
            return null;

        areaStart += 6;
        int areaEnd = namespa.IndexOf('.', areaStart + 1);
        string area = namespa.Substring(areaStart, areaEnd - areaStart);
        return area;
    }

    protected static bool IsSpecificPath(string name)
    {
        char ch = name[0];
        if (ch != '~')
        {
            return (ch == '/');
        }
        return true;
    }
}

さて、前述の通り、これは具体的なエンジンではないので、それも作らなければなりません。 この部分は、幸いなことに 大いに デフォルトのフォーマットを設定し、実際にビューを作成する必要があるだけです。

AreaAwareViewEngine.cs

public class AreaAwareViewEngine : BaseAreaAwareViewEngine
{
    public AreaAwareViewEngine()
    {
        MasterLocationFormats = new string[]
        {
            "~/Areas/{2}/Views/{1}/{0}.master",
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.master",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Views/{1}/{0}.master",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.master"
            "~/Views/Shared/{0}.cshtml"
        };
        ViewLocationFormats = new string[]
        {
            "~/Areas/{2}/Views/{1}/{0}.aspx",
            "~/Areas/{2}/Views/{1}/{0}.ascx",
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.aspx",
            "~/Areas/{2}/Views/Shared/{0}.ascx",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Views/{1}/{0}.aspx",
            "~/Views/{1}/{0}.ascx",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.aspx"
            "~/Views/Shared/{0}.ascx"
            "~/Views/Shared/{0}.cshtml"
        };
        PartialViewLocationFormats = ViewLocationFormats;
    }

    protected override IView CreatePartialView(
        ControllerContext controllerContext, string partialPath)
    {
        if (partialPath.EndsWith(".cshtml"))
            return new System.Web.Mvc.RazorView(controllerContext, partialPath, null, false, null);
        else
            return new WebFormView(controllerContext, partialPath);
    }

    protected override IView CreateView(ControllerContext controllerContext,
        string viewPath, string masterPath)
    {
        if (viewPath.EndsWith(".cshtml"))
            return new RazorView(controllerContext, viewPath, masterPath, false, null);
        else
            return new WebFormView(controllerContext, viewPath, masterPath);
    }
}

標準の ViewLocationFormats . これらは、新しい {2} のエントリで、ここで {2} にマップされます。 area の中に置いた RouteData . を残したまま MasterLocationFormats を残していますが、もちろん変更することも可能です。

では、あなたの global.asax を修正して、このビューエンジンを登録します。

グローバル.asax.cs

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new AreaAwareViewEngine());
}

...そして、デフォルトルートを登録します。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
        "Area",
        "",
        new { area = "AreaZ", controller = "Default", action = "ActionY" }
    );
    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = "" }
    );
}

ここで AreaController を作成します。

DefaultController.cs (~/Controllers/ にあります)

public class DefaultController : Controller
{
    public ActionResult ActionY()
    {
        return View("TestView");
    }
}

もちろん、ディレクトリ構造とそれに付随するビューが必要ですが、ここでは超シンプルにします。

TestView.aspx (~/Areas/AreaZ/Views/Default/ または ~/Areas/AreaZ/Views/Shared/ にある)

<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.

で、終わりです。 ついに完成 .

たいていの場合、あなたは単に BaseAreaAwareViewEngineAreaAwareViewEngine を追加して、任意のMVCプロジェクトにドロップするだけなので、これを実現するために多くのコードが必要だったとしても、一度だけ書けばいいのです。 それ以降は global.asax.cs の数行を編集し、サイト構造を作成するだけです。