วิธีตั้งค่าเส้นทางเริ่มต้น (ไปยังพื้นที่) ใน MVC


122

ตกลงเคยถามมาก่อนแล้ว แต่ไม่มีวิธีแก้ปัญหาที่ชัดเจน ดังนั้นเพื่อวัตถุประสงค์ของตัวเองและคนอื่น ๆ ที่อาจพบว่ามีประโยชน์

ใน MVC2 (ASP.NET) ฉันต้องการดังนั้นเมื่อมีคนไปที่เว็บไซต์จะมีการระบุพื้นที่เริ่มต้น ดังนั้นการนำทางไปยังไซต์ของฉันควรส่งคุณไปที่ ControllerX ActionY ใน AreaZ

ใช้เส้นทางต่อไปนี้ใน Global.asax

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

ตอนนี้ใช้งานได้เหมือนพยายามแสดงหน้าที่ถูกต้อง อย่างไรก็ตาม MVC ดำเนินการเพื่อค้นหา View ในรูทของไซต์และไม่อยู่ในโฟลเดอร์ Area

มีวิธีแก้ไขปัญหานี้หรือไม่?

แก้ไข

มี 'โซลูชัน' และอยู่ใน ControllerX, ActionY ส่งคืนเส้นทางแบบเต็มของมุมมอง แฮ็คเล็กน้อย แต่ใช้งานได้ อย่างไรก็ตามฉันหวังว่าจะมีทางออกที่ดีกว่านี้

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

แก้ไข:

นอกจากนี้ยังกลายเป็นปัญหาเมื่อมี HTML ActionLink ของเพจ หากไม่ได้ตั้งค่าพื้นที่ไว้ Action Link จะเป็นเอาต์พุตว่าง

ทั้งหมดนี้เกิดจากการออกแบบหรือเป็นข้อบกพร่อง?

คำตอบ:


98

สิ่งนี้ทำให้ฉันสนใจและในที่สุดฉันก็มีโอกาสได้ดู เห็นได้ชัดว่าคนอื่น ๆ ไม่เข้าใจว่านี่เป็นปัญหาในการค้นหามุมมองไม่ใช่ปัญหาในการกำหนดเส้นทางเองและนั่นอาจเป็นเพราะชื่อคำถามของคุณบ่งชี้ว่าเกี่ยวข้องกับการกำหนดเส้นทาง

ในกรณีใด ๆ เพราะนี่เป็นปัญหาที่ดูที่เกี่ยวข้องกับวิธีเดียวที่จะได้รับสิ่งที่คุณต้องการคือการแทนที่เครื่องยนต์มุมมองเริ่มต้น โดยปกติเมื่อคุณทำสิ่งนี้มันมีจุดประสงค์ง่ายๆในการเปลี่ยนเอนจินมุมมองของคุณ (เช่น Spark, NHaml และอื่น ๆ ) ในกรณีนี้ไม่ใช่ตรรกะการสร้างมุมมองที่เราต้องแทนที่ แต่เป็นFindPartialViewและFindViewวิธีการในVirtualPathProviderViewEngineคลาส

คุณสามารถขอบคุณดาวที่โชคดีของคุณที่วิธีการเหล่านี้เป็นเสมือนจริงเพราะทุกอย่างในVirtualPathProviderViewEngineนั้นไม่สามารถเข้าถึงได้ - มันเป็นส่วนตัวและนั่นทำให้มันน่ารำคาญมากที่จะแทนที่ตรรกะการค้นหาเพราะคุณต้องเขียนโค้ดครึ่งหนึ่งใหม่โดยทั่วไปแล้ว ถูกเขียนขึ้นหากคุณต้องการให้เล่นได้ดีกับแคชตำแหน่งและรูปแบบสถานที่ หลังจากขุดในตัวสะท้อนแสงในที่สุดฉันก็สามารถหาโซลูชันที่ใช้งานได้

สิ่งที่ฉันได้ทำที่นี่คือครั้งแรกสร้างนามธรรม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เพื่อลงทะเบียนเอนจินมุมมองนี้:

Global.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.

และนั่นแหล่ะ ในที่สุดเราก็เสร็จแล้ว

ส่วนใหญ่คุณควรจะสามารถที่จะเพียงแค่ใช้เวลาBaseAreaAwareViewEngineและAreaAwareViewEngineและวางไว้ในโครงการ MVC ใด ๆ ดังนั้นแม้ว่ามันเอามากรหัสเพื่อรับนี้ทำคุณจะต้องมีการเขียนครั้งเดียว หลังจากนั้นก็เป็นเพียงการแก้ไขไม่กี่บรรทัดในglobal.asax.csและสร้างโครงสร้างไซต์ของคุณ


นี่เป็นทางออกที่ดีที่สุดในปัจจุบัน แต่ยังห่างไกลจากอุดมคติ ข้างต้นเมื่อคุณเพิ่ม Actionlink หรือปัญหาเดียวกันนี้เกิดขึ้น
LiamB

1
@Pino: ผมคิดว่าคุณควรจะสามารถที่จะแก้ActionLinkปัญหาโดยการเพิ่มเดียวกันarea = "AreaZ"ที่ "เริ่มต้น" global.asax.csการทำแผนที่เส้นทาง ฉันไม่คิดบวก; ลองดู
Aaronaught

ใน MVC4 "ค่าเริ่มต้น" การประกาศเส้นทางย้ายจาก Global.asax ไปที่ ~ / App_Start / RouteConfig.cs / RegisterRoutes ()
Andriy F.

3
ฉันเกลียดการโหวตลงคะแนน แต่ฉันไม่อยากจะเชื่อเลยว่าคำตอบด้านล่างของ @Chris Alderson ไม่ได้รับการโหวตมากกว่านี้ มันเป็นมากวิธีการแก้ปัญหาที่ง่ายกว่านี้และดูเหมือนว่าจะแก้ปัญหากรณีขอบ (ActionLinks ฯลฯ )
jdmcnair

ดูเหมือนว่ามีข้อบกพร่องที่นี่ ตัวอย่างเช่นมุมมองสำหรับพื้นที่ชื่อ "Re" จะอยู่ใน ~ / Areas / Re / Views / Ctrlr / blah.aspx แต่โค้ดที่นี่ใช้ ~ / {2} / {1} / {0} ซึ่งจะเป็น ~ /Re/Ctrl/blah.aspx ไม่มีไดเร็กทอรีพื้นที่สำคัญในเส้นทาง ควรจะเป็น "~ / Areas / {2} / Views / {1} / {0} .aspx"
Chris Moschini

100

นี่คือวิธีที่ฉันทำ ฉันไม่รู้ว่าทำไม MapRoute () ไม่อนุญาตให้คุณตั้งค่าพื้นที่ แต่มันส่งคืนวัตถุเส้นทางเพื่อให้คุณทำการเปลี่ยนแปลงเพิ่มเติมตามที่คุณต้องการได้ต่อไป ฉันใช้สิ่งนี้เนื่องจากฉันมีไซต์ MVC แบบแยกส่วนที่ขายให้กับลูกค้าองค์กรและพวกเขาจำเป็นต้องสามารถวาง dlls ลงในโฟลเดอร์ bin เพื่อเพิ่มโมดูลใหม่ ฉันอนุญาตให้พวกเขาเปลี่ยน "HomeArea" ในการกำหนดค่า AppSettings

var route = routes.MapRoute(
                "Home_Default", 
                "", 
                new {controller = "Home", action = "index" },
                new[] { "IPC.Web.Core.Controllers" }
               );
route.DataTokens["area"] = area;

แก้ไข: คุณสามารถลองสิ่งนี้ได้เช่นกันใน AreaRegistration.RegisterArea สำหรับพื้นที่ที่คุณต้องการให้ผู้ใช้ไปตามค่าเริ่มต้น ฉันยังไม่ได้ทดสอบ แต่ AreaRegistrationContext.MapRoute ตั้งค่าroute.DataTokens["area"] = this.AreaName;ให้คุณ

context.MapRoute(
                    "Home_Default", 
                    "", 
                    new {controller = "Home", action = "index" },
                    new[] { "IPC.Web.Core.Controllers" }
                   );

มันได้ผล. ระวังไฟล์ web.config ใหม่อาจแทนที่การกำหนดค่าส่วนกลางเดิมของคุณ
Mert Akcakaya

56

แม้ว่าจะได้รับคำตอบแล้วก็ตาม - นี่คือไวยากรณ์สั้น ๆ (ASP.net 3, 4, 5):

routes.MapRoute("redirect all other requests", "{*url}",
    new {
        controller = "UnderConstruction",
        action = "Index"
        }).DataTokens = new RouteValueDictionary(new { area = "Shop" });

6
วิธีนี้ใช้ได้ผลดีสำหรับฉัน ฉันไม่มีคอนโทรลเลอร์ที่รูทและใช้เฉพาะพื้นที่เท่านั้น สำหรับ MVC 4 ฉันได้แทนที่ค่าเริ่มต้นใน RouteConfig.cs ขอบคุณ!
Marc

2
ฉันใช้ MVC4 และนี่เป็นวิธีที่ง่ายที่สุดสำหรับฉัน อนุญาตให้แอปพลิเคชันใช้มุมมองดัชนีภายในพื้นที่ใดพื้นที่หนึ่งเป็น 'โฮมเพจ' ของไซต์
JTech

2
โซลูชันนี้จะใช้ไม่ได้ในอนาคต (จาก Asp.Net MVC6 ขึ้นไป)
Patrick Desjardins

@PatrickDesjardins: เหตุผลใดที่ไม่สนับสนุนวิธีแก้ปัญหาข้างต้น?
Akash KC

@SeriousM คุณตอบคือเขียวตลอดปี มันยังคงเป็นประโยชน์ คุณช่วยฉันคืนหนึ่ง
skpaul

16

ขอบคุณแอรอนที่ชี้ให้เห็นว่ามันเกี่ยวกับการค้นหามุมมองฉันเข้าใจผิด

[UPDATE] ฉันเพิ่งสร้างโปรเจ็กต์ที่ส่งผู้ใช้ไปยังพื้นที่ต่อค่าเริ่มต้นโดยไม่ยุ่งกับโค้ดหรือเส้นทางการค้นหาใด ๆ :

ใน global.asax ลงทะเบียนตามปกติ:

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

        routes.MapRoute(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "Home", action = "Index", id = ""}  // Parameter defaults,
        );
    }

ในการApplication_Start()ตรวจสอบให้แน่ใจที่จะใช้คำสั่งดังต่อไปนี้

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterRoutes(RouteTable.Routes);
    }

ในการลงทะเบียนพื้นที่ของคุณให้ใช้

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "ShopArea_default",
            "{controller}/{action}/{id}",
            new { action = "Index", id = "", controller = "MyRoute" },
            new { controller = "MyRoute" }
        );
    }

สามารถดูตัวอย่างได้ที่ http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/

ฉันหวังเป็นอย่างยิ่งว่านี่คือสิ่งที่คุณขอ ...

////

ฉันไม่คิดว่าการเขียนหลอกViewEngineเป็นทางออกที่ดีที่สุดในกรณีนี้ (ขาดชื่อเสียงไม่สามารถแสดงความคิดเห็นได้). WebFormsViewEngineเป็นพื้นที่ตระหนักและมีAreaViewLocationFormatsซึ่งถูกกำหนดให้เป็นค่าเริ่มต้นต่อ

AreaViewLocationFormats = new[] {
        "~/Areas/{2}/Views/{1}/{0}.aspx",
        "~/Areas/{2}/Views/{1}/{0}.ascx",
        "~/Areas/{2}/Views/Shared/{0}.aspx",
        "~/Areas/{2}/Views/Shared/{0}.ascx",
    };

ฉันเชื่อว่าคุณไม่ปฏิบัติตามอนุสัญญานี้ คุณโพสต์

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

เป็นแฮ็กที่ใช้งานได้ แต่ก็ควรจะเป็น

   return View("~/Areas/AreaZ/views/ControllerX/ActionY.aspx"); 

อย่างไรก็ตามหากคุณไม่ต้องการทำตามแบบแผนคุณอาจต้องการใช้เส้นทางสั้น ๆ โดยหาจากWebFormViewEngine(ที่ทำใน MvcContrib เป็นต้น) ซึ่งคุณสามารถตั้งค่าเส้นทางการค้นหาในตัวสร้างหรือ -a แฮ็คเล็กน้อย - โดยระบุการประชุมของคุณเช่นนี้ในApplication_Start:

((VirtualPathProviderViewEngine)ViewEngines.Engines[0]).AreaViewLocationFormats = ...;

แน่นอนว่าควรดำเนินการด้วยความระมัดระวังมากกว่านี้ แต่ฉันคิดว่ามันแสดงให้เห็นถึงความคิด เขตข้อมูลเหล่านี้มีpublicอยู่ในVirtualPathProviderViewEngineใน MVC 2 RC


เป็นที่น่าสังเกตว่าสิ่งนี้ใช้กับ MVC 2 RC เท่านั้น - MVC 1 VirtualPathProviderViewEngineไม่มีคุณสมบัตินี้และไม่ทราบพื้นที่ และในขณะที่คำถามนี้ถูกระบุว่าเกี่ยวกับ MVC 2 แต่ผู้คนจำนวนมากยังคงไม่ได้ใช้มัน (และจะไม่เป็นเวลานาน) ดังนั้นคำตอบของคุณง่ายกว่าสำหรับคำถามที่เจาะจง แต่ของฉันเป็นเพียงคำเดียวที่ใช้ได้กับผู้ใช้ MVC1 ที่สะดุดกับคำถามนี้ ฉันต้องการให้คำตอบที่ไม่ขึ้นอยู่กับฟังก์ชันก่อนวางจำหน่ายที่อาจมีการเปลี่ยนแปลง
Aaronaught

นอกจากนี้ยังไม่ใช่ "เครื่องมือดูหลอก" - คลาสเอนจินมุมมองถูกสร้างขึ้นโดยเจตนาให้ขยายได้เพื่อให้สามารถใช้มุมมองประเภทต่างๆได้
Aaronaught

นั่นไม่ได้ตั้งใจที่จะดูถูกคุณฉันขอโทษ มันเป็น 'หลอก' โดยที่มันไม่ได้เปลี่ยนวิธีจัดการ Views อย่างมีนัยสำคัญ แต่จะแทนที่ค่าบางอย่างเท่านั้น
mnemosyn

ฉันไม่ได้โกรธเคืองฉันแค่อยากจะเคลียร์ความจริงที่ว่ามันไม่ใช่เหตุผลที่ผิดปกติโดยเฉพาะอย่างยิ่งในการได้มาซึ่งกลไกการดูแบบกำหนดเองดังที่เห็นได้จากข้อเท็จจริงที่ว่าวิธีการที่เกี่ยวข้องนั้นสามารถลบล้างได้
Aaronaught

2
เคล็ดลับที่ดีเกี่ยวกับการไปก่อนRegisterAreas RegisterRoutesสงสัยว่าทำไมโค้ดของฉันหยุดทำงานกะทันหันและสังเกตเห็นว่า refactor;)
webnoob

6

ฉันเดาว่าคุณต้องการให้ผู้ใช้เปลี่ยนเส้นทางไปยัง~/AreaZURL เมื่อเขาเข้าชม~/URL แล้ว HomeControllerฉันต้องการบรรลุโดยความหมายของรหัสต่อไปนี้ภายในรากของคุณ

public class HomeController
{
    public ActionResult Index()
    {
        return RedirectToAction("ActionY", "ControllerX", new { Area = "AreaZ" });
    }
}

และเส้นทางต่อไปนี้ในGlobal.asax.

routes.MapRoute(
    "Redirection to AreaZ",
    String.Empty,
    new { controller = "Home ", action = "Index" }
);

วิธีนี้ใช้ได้ แต่เปลี่ยนเป็น URL บนเบราว์เซอร์ของผู้ใช้ ไม่เหมาะจริงๆ
LiamB

2

ก่อนอื่นคุณใช้ MVC2 เวอร์ชันใด มีการเปลี่ยนแปลงที่สำคัญจากการแสดงตัวอย่าง 2 เป็น RC

สมมติว่าคุณใช้ RC ฉันคิดว่าการทำแผนที่เส้นทางของคุณน่าจะแตกต่างออกไป ในAreaRegistration.csพื้นที่ของคุณคุณสามารถลงทะเบียนเส้นทางเริ่มต้นบางประเภทเช่น

        context.MapRoute(
            "ShopArea_default",
            "{controller}/{action}/{id}",
            new { action = "Index", id = "", controller="MyRoute" }
        );

รหัสด้านบนจะส่งผู้ใช้ไปยังค่าเริ่มต้นMyRouteControllerของเราShopArea

การใช้สตริงว่างเป็นพารามิเตอร์ที่สองควรทำให้เกิดข้อยกเว้นเนื่องจากต้องระบุคอนโทรลเลอร์

แน่นอนว่าคุณจะต้องเปลี่ยนเส้นทางเริ่มต้นGlobal.asaxเพื่อที่จะไม่รบกวนเส้นทางเริ่มต้นนี้เช่นโดยใช้คำนำหน้าสำหรับไซต์หลัก

ดูหัวข้อนี้และคำตอบของ Haack: MVC 2 AreaRegistration Routes Order

หวังว่านี่จะช่วยได้


ขอบคุณ แต่ฉันไม่แน่ใจว่าวิธีนี้จะช่วยแก้ปัญหาที่อธิบายไว้ในคำถามได้ และ MVC RC
LiamB

2

การเพิ่มสิ่งต่อไปนี้ใน Application_Start ของฉันใช้ได้ผลสำหรับฉันแม้ว่าฉันจะไม่แน่ใจว่าคุณมีการตั้งค่านี้ใน RC:

var engine = (WebFormViewEngine)ViewEngines.Engines.First();

// These additions allow me to route default requests for "/" to the home area
engine.ViewLocationFormats = new string[] { 
    "~/Views/{1}/{0}.aspx",
    "~/Views/{1}/{0}.ascx",
    "~/Areas/{1}/Views/{1}/{0}.aspx", // new
    "~/Areas/{1}/Views/{1}/{0}.ascx", // new
    "~/Areas/{1}/Views/{0}.aspx", // new
    "~/Areas/{1}/Views/{0}.ascx", // new
    "~/Views/{1}/{0}.ascx",
    "~/Views/Shared/{0}.aspx",
    "~/Views/Shared/{0}.ascx"
};

1

สิ่งที่ฉันทำเพื่อให้ได้ผลมีดังต่อไปนี้:

  1. ฉันสร้างตัวควบคุมเริ่มต้นในโฟลเดอร์ root / Controllers ฉันตั้งชื่อคอนโทรลเลอร์ว่า DefaultController
  2. ในคอนโทรลเลอร์ฉันได้เพิ่มรหัสต่อไปนี้:

    namespace MyNameSpace.Controllers {
    public class DefaultController : Controller {
        // GET: Default
        public ActionResult Index() {
            return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
        }
    } }
  3. ใน RouterConfig.cs ของฉันฉันได้เพิ่มสิ่งต่อไปนี้:

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new {controller = "Default", action = "Index", id = UrlParameter.Optional});

เคล็ดลับเบื้องหลังทั้งหมดนี้คือฉันสร้างตัวสร้างเริ่มต้นซึ่งจะเป็นตัวควบคุมการเริ่มต้นทุกครั้งที่แอปของฉันเริ่มทำงาน เมื่อกระทบกับตัวควบคุมเริ่มต้นนั้นระบบจะเปลี่ยนเส้นทางไปยังตัวควบคุมใด ๆ ที่ฉันระบุใน Index Action เริ่มต้น ซึ่งในกรณีของฉันคือ

www.myurl.com/FolderName/ControllerName

.


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

คุณลองแล้วหรือยัง?


ใช่ปัญหาอยู่ที่ว่าตอนนี้ไซต์มองหามุมมองในรูท ไม่พบมุมมอง 'ActionY' หรือต้นแบบ ค้นหาตำแหน่งต่อไปนี้: ~ / Views / ActionY / ActionY.aspx ~ / Views / ActionY / ActionY.ascx ~ / Views / Shared / ActionY.aspx ~ / Views / Shared / ActionY.ascx
LiamB

2
ฉันเข้าใจ. ฉันจะพยายามหาทางแก้ไข +1 สำหรับคำถาม
Barbaros Alp

0

การค้นหาหน่วยการสร้างที่แตกต่างกันจะทำในวงจรชีวิตของคำขอ หนึ่งในขั้นตอนแรกในวงจรชีวิตการร้องขอ ASP.NET MVC คือการแมป URL ที่ร้องขอกับวิธีการดำเนินการของคอนโทรลเลอร์ที่ถูกต้อง กระบวนการนี้เรียกว่าการกำหนดเส้นทาง เส้นทางเริ่มต้นถูกเตรียมใช้งานในไฟล์ Global.asax และอธิบายถึงกรอบงาน ASP.NET MVC ถึงวิธีจัดการกับคำขอ ดับเบิลคลิกที่ไฟล์ Global.asax ในโครงการ MvcApplication1 จะแสดงรหัสต่อไปนี้:

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

namespace MvcApplication1 {

   public class GlobalApplication : System.Web.HttpApplication
   {
       public static void RegisterRoutes(RouteCollection routes)
       {
           routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

           routes.MapRoute(
               "Default",                                          // Route name
               "{controller}/{action}/{id}",                       // URL with parameters
               new { controller = "Home", action = "Index",
                     id = "" }  // Parameter defaults
           );

       }

       protected void Application_Start()
       {
           RegisterRoutes(RouteTable.Routes);
       }
   }

}

ในตัวจัดการเหตุการณ์ Application_Start () ซึ่งจะเริ่มทำงานเมื่อใดก็ตามที่แอปพลิเคชันถูกคอมไพล์หรือเว็บเซิร์ฟเวอร์เริ่มทำงานใหม่ตารางเส้นทางจะถูกลงทะเบียน เส้นทางเริ่มต้นมีชื่อว่า Default และตอบสนองต่อ URL ในรูปแบบของhttp://www.example.com/ {controller} / {action} / {id} ตัวแปรระหว่าง {และ} จะถูกเติมด้วยค่าจริงจาก URL คำขอหรือด้วยค่าเริ่มต้นหากไม่มีการแทนที่ใน URL เส้นทางเริ่มต้นนี้จะแมปกับตัวควบคุมหน้าแรกและวิธีการดำเนินการดัชนีตามพารามิเตอร์การกำหนดเส้นทางเริ่มต้น เราจะไม่มีการดำเนินการอื่นใดกับแผนที่เส้นทางนี้

โดยค่าเริ่มต้น URL ที่เป็นไปได้ทั้งหมดสามารถแมปผ่านเส้นทางเริ่มต้นนี้ นอกจากนี้ยังสามารถสร้างเส้นทางของเราเองได้ ตัวอย่างเช่นลองแมป URL http://www.example.com/Employee/Maartenกับตัวควบคุมพนักงานแสดงการดำเนินการและพารามิเตอร์ชื่อ คุณสามารถแทรกข้อมูลโค้ดต่อไปนี้ในไฟล์ Global.asax ที่เราเพิ่งเปิดได้ เนื่องจากเฟรมเวิร์ก ASP.NET MVC ใช้เส้นทางแรกที่ตรงกันจึงควรแทรกข้อมูลโค้ดนี้ไว้เหนือเส้นทางเริ่มต้น มิฉะนั้นจะไม่มีการใช้เส้นทางนี้

routes.MapRoute(

   "EmployeeShow",                    // Route name
   "Employee/{firstname}",            // URL with parameters
    new {                             // Parameter defaults
       controller = "Employee",
       action = "Show", 
       firstname = "" 
   }  

);

ตอนนี้ขอเพิ่มส่วนประกอบที่จำเป็นสำหรับเส้นทางนี้ ก่อนอื่นสร้างคลาสชื่อ EmployeeController ในโฟลเดอร์ Controllers คุณสามารถทำได้โดยการเพิ่มรายการใหม่ลงในโปรเจ็กต์และเลือกเทมเพลต MVC Controller Class ที่อยู่ใต้เว็บ | ประเภท MVC ลบเมธอด Index action และแทนที่ด้วย method หรือ action ที่ชื่อว่า Show วิธีนี้ยอมรับพารามิเตอร์ firstname และส่งข้อมูลไปยังพจนานุกรม ViewData พจนานุกรมนี้จะถูกใช้โดยมุมมองเพื่อแสดงข้อมูล

คลาส EmployeeController จะส่งผ่านอ็อบเจ็กต์ Employee ไปยังมุมมอง ควรเพิ่มคลาสพนักงานนี้ในโฟลเดอร์ Models (คลิกขวาที่โฟลเดอร์นี้จากนั้นเลือก Add | Class จากเมนูบริบท) นี่คือรหัสสำหรับคลาสพนักงาน:

namespace MvcApplication1.Models {

   public class Employee
   {
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public string Email { get; set; }
   }

} 

1
ขอบคุณฉันไม่ค่อยแน่ใจว่าสิ่งนี้เกี่ยวข้องกับการตั้งค่า AREA เริ่มต้นอย่างไร : - /
LiamB

0

ในขณะที่สร้างเอ็นจินมุมมองที่กำหนดเองสามารถทำงานได้ แต่คุณยังมีทางเลือกอื่นได้

  • ตัดสินใจว่าคุณต้องแสดงอะไรเป็นค่าเริ่มต้น
  • สิ่งนั้นมีตัวควบคุมและการกระทำ (และพื้นที่) ใช่ไหม?
  • เปิดการลงทะเบียนพื้นที่และเพิ่มสิ่งนี้:
public override void RegisterArea(AreaRegistrationContext context)
{
    //this makes it work for the empty url (just domain) to act as current Area.
    context.MapRoute(
        "Area_empty",
        "",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional },
        namespaces: new string[] { "Area controller namespace" }
    );
        //other routes of the area
}

ไชโย!


ตกลง แม้ว่าฉันคิดว่าสถานที่ที่เหมาะสมกว่าสำหรับคำจำกัดความเส้นทางนี้อยู่ในไฟล์ Global.asax
nuhusky2003

ในกรณีเช่นนี้คำจำกัดความ global.asax ของคุณจะรู้เกี่ยวกับการมีอยู่ของเนมสเปซตัวควบคุมพื้นที่ซึ่งฉันคิดว่าไม่ถูกต้อง พื้นที่เป็นฟังก์ชันเพิ่มเติมซึ่งหมายความว่าคุณต้องสามารถเพิ่ม / ลบออกได้โดยไม่ต้องแตะคำจำกัดความ global.asax ในแนวทางของฉันสำหรับคำถามนี้ฉันต้องการพื้นที่ในการ "เข้าครอบครอง" คำขอแทนที่จะเป็นเว็บไซต์ [ทั่วโลก] เพื่อ "ส่งมอบ" คำขอ
Tengiz

0

วิธีแก้ปัญหาที่ได้รับการยอมรับคือในขณะที่การสรุปวิธีการสร้างกลไกมุมมองแบบกำหนดเองถูกต้อง แต่ไม่ได้ตอบคำถามอย่างถูกต้อง ออกที่นี่เป็นที่Pino ไม่ถูกต้องระบุเส้นทางเริ่มต้นของเขา โดยเฉพาะคำจำกัดความ "พื้นที่" ของเขาไม่ถูกต้อง "พื้นที่" ถูกตรวจสอบผ่านคอลเลกชัน DataTokens และควรเพิ่มดังนี้:

var defaultRoute = new Route("",new RouteValueDictionary(){{"controller","Default"},{"action","Index"}},null/*constraints*/,new RouteValueDictionary(){{"area","Admin"}},new MvcRouteHandler());
defaultRoute.DataTokens.Add("Namespaces","MyProject.Web.Admin.Controller"); 
routes.Add(defaultRoute);

ระบุ "พื้นที่" ในค่าเริ่มต้นวัตถุจะถูกละเว้น โค้ดด้านบนจะสร้างเส้นทางเริ่มต้นซึ่งรับคำขอไปยังรูทของไซต์ของคุณจากนั้นจึงเรียกตัวควบคุมเริ่มต้น, การดำเนินการดัชนีในพื้นที่ผู้ดูแลระบบ โปรดทราบว่ามีการเพิ่มคีย์ "Namespaces" ลงใน DataTokens ซึ่งจำเป็นต่อเมื่อคุณมีคอนโทรลเลอร์หลายตัวที่มีชื่อเดียวกัน โซลูชันนี้ได้รับการตรวจสอบด้วย Mvc2 และ Mvc3 .NET 3.5 / 4.0


-1

อืมฉันไม่รู้ว่าทำไมการเขียนโปรแกรมทั้งหมดนี้ฉันคิดว่าปัญหาดั้งเดิมสามารถแก้ไขได้อย่างง่ายดายโดยระบุเส้นทางเริ่มต้นนี้ ...

routes.MapRoute("Default", "{*id}", 
                 new { controller = "Home"
                     , action = "Index"
                     , id = UrlParameter.Optional 
                     }
              );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.