Posted by & filed under Uncategorized.

About chen xiang

Xiang Chen has written 52 post in this blog.

在MVC中,执行完Action之后,会返回一个ActionResult对象,之后再执行该对象的ExecuteResult方法,这也就是【View的呈现】的入口!

【View的呈现】包括了:根据模版去寻找请求的视图页、编译视图页、再执行视图页的内容。本篇就来介绍寻找视图页的详细过程,其中涉及到了MVC 4的一个新特性–“手机视图页”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public abstract class ViewResultBase : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (String.IsNullOrEmpty(ViewName))
        {
            ViewName = context.RouteData.GetRequiredString("action");
        }
      ViewEngineResult result = null;
        if (View == null)
        {
            //通过视图引擎去创建视图对象,并将视图对象和该视图相关的信息封装在ViewEngineResult对象中。
            result = FindView(context);
            View = result.View;
        }
        TextWriter writer = context.HttpContext.Response.Output;
        ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
        View.Render(viewContext, writer);
        if (result != null)
        {
            result.ViewEngine.ReleaseView(context, View);
        }
    }
}
public class ViewResult : ViewResultBase
{
    protected override ViewEngineResult FindView(ControllerContext context)
    {
        //寻找当前请求的视图页,如果能找到则创建视图对象。
        //遍历每个视图引擎(默认有两个WebFormEngine、RazorViewEngine),并执行每一个视图引擎的FindView方法。
        ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
        //如果创建了视图对象,即:指定路径中存在相匹配的视图页(.cshtml文件)。
        if (result.View != null)
        {
            return result;
        }
        //没有创建视图对象,即:指定路径中不存在相匹配的视图页(.cshtml文件)。
        StringBuilder locationsText = new StringBuilder();
        foreach (string location in result.SearchedLocations)
        {
            locationsText.AppendLine();
            locationsText.Append(location);
        }
        throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
                                                          MvcResources.Common_ViewNotFound, ViewName, locationsText));
    }
}

ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);则是遍历每个视图引擎(默认有两个WebFormEngine、RazorViewEngine),并执行每一个视图引擎的FindView方法。
注:在执行视图引擎的FindView方法时,先按照从缓存表中找是否存在请求的视图页,如果没有的话,再进行一次寻找!

下面以RazorViewEngine为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class VirtualPathProviderViewEngine : IViewEngine
{
    //useCache先是true,该方法返回的是null。则让useCache=false,再执行一遍。即:先通过缓存去找,如果没有找到的话,就正经的去找。
    public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (String.IsNullOrEmpty(viewName))
        {
            throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
        }
        string[] viewLocationsSearched;
        string[] masterLocationsSearched;
        string controllerName = controllerContext.RouteData.GetRequiredString("controller");
        //获取视图的路径,这里就是咱们本篇博文的内容的入口点!!!!!!!!!!!!!!!!!!!
        //ViewLocationFormats、AreaViewLocationFormats定义在派生类RazorViewEngine类中。
        //ViewLocationFormats:"~/Views/{1}/{0}.cshtml","~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml","~/Views/Shared/{0}.vbhtml"
        //AreaViewLocationFormats:"~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml","~/Areas/{2}/Views/Shared/{0}.vbhtml"
        string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
        
        
        string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);
        if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
        {
            return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
        }
        return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
    }
}

啪啪啪

GetPath方法在寻找【视图页】时,首先将当前请求的Controller和Action的名称添加到地址格式化器中,这样就有了要寻找的地址(们),之后就来检查格式化后的地址是否真的存在指定的【视图页】。如果是通过手机端来请求,则会对格式化之后的地址进行再进行处理(如:Index.cshtml修改为Index.Mobile.cshtml),之后再检查新地址下是否存在【视图页】。

注:默认情况下会先检查是否为手机端访问!

口说无凭,上源码吧:

 VirtualPathProviderViewEngine
 DisplayModeProvider
 DefaultDisplayMode

由以上源码可知,默认情况下,ASP.NET MVC4在DisplayModeProvider中定义了一个含有两个DefaultDisplayMode对象(用于对地址再处理)的集合:

1
2
3
4
5
6
7
8
9
public static readonly string MobileDisplayModeId = "Mobile";
private readonly List<IDisplayMode> _displayModes = new List<IDisplayMode>
{
    new DefaultDisplayMode(MobileDisplayModeId)
    {
        ContextCondition = context => context.GetOverriddenBrowser().IsMobileDevice
    },
    new DefaultDisplayMode()
};

由于处理时,是按照遍历执行_displayModes集合中DefaultDisplayMode对象的GetDisplayInfo方法(索引值从0开始),所以无论是 PC 还是 Phone发送的请求,都会先执集合中的第一个DefaultDisplayMode对象(判断是否为手机的请求)。如果Phone端发送请求,会去寻找xxx.Mobile.cshtml,如果没有的话,就继续执行第二个DefaultDisplayMode对象,去寻找xxx.cshtml。如果是PC端发送请求,也是首先执行第一个DefaultDisplayMode对象,但是由于不满足 context => context.GetOverriddenBrowser().IsMobileDevice 条件,所以还是需要去执行第二个DefaultDisplayMode对象,去寻找xxx.cshtml。

扩展:
1、指定DisplayMode

模拟需求:对Phone端用户的某个Action请求,返回电脑版网页。

1
2
3
4
5
6
7
public ActionResult Index()
{
    //一些判断条件
    this.ControllerContext.DisplayMode = DisplayModeProvider.Instance.Modes[1];
    DisplayModeProvider.Instance.RequireConsistentDisplayMode = true;
    return View();
}

根据上述设置,即使是Phone端的请求并且还存在Index.Mobile.cshtml文件,也会去执行Index.cshtml,即:实现Phone用户访问电脑版网页。

2、自定义DisplayMode

模拟需求:为Android 2.3用户设置特定的页面

先创建一个类似于Index.Android23.cshtml 的页面,然后在Global.asax中做如下设置即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();
        DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Android23")
        {
            ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
            ("Android 2.3", StringComparison.OrdinalIgnoreCase) >= 0)
        });
    }
}

 

以上就是所有内容,如有不适之处,请指正!!!

Leave a Reply

  • (will not be published)