zoukankan      html  css  js  c++  java
  • asp.net mvc源码分析RenderAction和RenderPartial

    截止上篇文章asp.net mvc源码分析-ActionResult篇 RazorView.RenderView 相信大家对mvc的大致流程应该有所了解。现在我们来看看我们在mvc开发中用的最多的几个方法,我想排在第一的应该是Html.RenderAction和Html.RenderPartial吧。先说简单的吧:RenderPartial和Partial

     public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, object model, ViewDataDictionary viewData) {
                htmlHelper.RenderPartialInternal(partialViewName, viewData, model, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
            }

      public static MvcHtmlString Partial(this HtmlHelper htmlHelper, string partialViewName, object model, ViewDataDictionary viewData) {
                using (StringWriter writer = new StringWriter(CultureInfo.CurrentCulture)) {
                    htmlHelper.RenderPartialInternal(partialViewName, viewData, model, writer, ViewEngines.Engines);
                    return MvcHtmlString.Create(writer.ToString());
                }
            }

    从这里我们可以知道RenderPartial和Partial它们返回的东西写到的流不一致,一个是当前的writer,一个是新建的writer,当然新建的writer便于返回字符文本

    RenderPartialInternal的定义和核心代码如下:

      internal virtual void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model, TextWriter writer, ViewEngineCollection viewEngineCollection) {

                ViewContext newViewContext = new ViewContext(ViewContext, ViewContext.View, newViewData, ViewContext.TempData, writer);
                IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
                view.Render(newViewContext, writer);
            }

    看看 是不是很简单,而这里的关键FindPartialView,找到view后然后调用其Render方法,FindPartialView核心代码就一句,ViewEngineResult result = viewEngineCollection.FindPartialView(viewContext, partialViewName);我们从前面的文章中知道FindView 和FindPartialView的逻辑是一致的。

    现在 我们来看看RenderAction和 Action方法

      public static MvcHtmlString Action(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues) {
                using (StringWriter writer = new StringWriter(CultureInfo.CurrentCulture)) {
                    ActionHelper(htmlHelper, actionName, controllerName, routeValues, writer);
                    return MvcHtmlString.Create(writer.ToString());
                }
            }

      public static void RenderAction(this HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues) {
                ActionHelper(htmlHelper, actionName, controllerName, routeValues, htmlHelper.ViewContext.Writer);
            }

    internal static void ActionHelper(HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, TextWriter textWriter) {
                if (htmlHelper == null) {
                    throw new ArgumentNullException("htmlHelper");
                }
                if (String.IsNullOrEmpty(actionName)) {
                    throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
                }
    
                RouteValueDictionary additionalRouteValues = routeValues;
                routeValues = MergeDictionaries(routeValues, htmlHelper.ViewContext.RouteData.Values);
                         
                routeValues["action"] = actionName;
                if (!String.IsNullOrEmpty(controllerName)) {
                    routeValues["controller"] = controllerName;
                }
    
                bool usingAreas;
                VirtualPathData vpd = htmlHelper.RouteCollection.GetVirtualPathForArea(htmlHelper.ViewContext.RequestContext, null /* name */, routeValues, out usingAreas);
                if (vpd == null) {
                    throw new InvalidOperationException(MvcResources.Common_NoRouteMatched);
                }
       
                if (usingAreas) {
                    routeValues.Remove("area");
                    if (additionalRouteValues != null) {
                        additionalRouteValues.Remove("area");
                    }
                }
    
                if (additionalRouteValues != null) {
                    routeValues[ChildActionValueProvider.ChildActionValuesKey] = new DictionaryValueProvider<object>(additionalRouteValues, CultureInfo.InvariantCulture);
                }
    
                RouteData routeData = CreateRouteData(vpd.Route, routeValues, vpd.DataTokens, htmlHelper.ViewContext);
                HttpContextBase httpContext = htmlHelper.ViewContext.HttpContext;
                RequestContext requestContext = new RequestContext(httpContext, routeData);
                ChildActionMvcHandler handler = new ChildActionMvcHandler(requestContext);
                httpContext.Server.Execute(HttpHandlerUtil.WrapForServerExecute(handler), textWriter, true /* preserveForm */);
            }
    
            private static RouteData CreateRouteData(RouteBase route, RouteValueDictionary routeValues, RouteValueDictionary dataTokens, ViewContext parentViewContext) {
                RouteData routeData = new RouteData();
    
                foreach (KeyValuePair<string, object> kvp in routeValues) {
                    routeData.Values.Add(kvp.Key, kvp.Value);
                }
    
                foreach (KeyValuePair<string, object> kvp in dataTokens) {
                    routeData.DataTokens.Add(kvp.Key, kvp.Value);
                }
    
                routeData.Route = route;
                routeData.DataTokens[ControllerContext.PARENT_ACTION_VIEWCONTEXT] = parentViewContext;
                return routeData;
            }
    
            private static RouteValueDictionary MergeDictionaries(params RouteValueDictionary[] dictionaries) {
                // Merge existing route values with the user provided values
                var result = new RouteValueDictionary();
    
                foreach (RouteValueDictionary dictionary in dictionaries.Where(d => d != null)) {
                    foreach (KeyValuePair<string, object> kvp in dictionary) {
                        if (!result.ContainsKey(kvp.Key)) {
                            result.Add(kvp.Key, kvp.Value);
                        }
                    }
                }
    
                return result;
            }

    RenderAction和 Action方法的区别和RenderPartial和Partial的区别一样,一个是把内容返回到当前流一个是返回到一个字符串。

    htmlHelper.ViewContext.RouteData.Values这个Values里面包括我们所有的路由信息,典型的就是action、controller这里面有这么一句 if (!String.IsNullOrEmpty(controllerName)) {routeValues["controller"] = controllerName;},这句就解释了如果我们调用RenderPartial不传controller就会默认为当前的controller。现在让我们来看看GetVirtualPathForArea这个方法是如何获取VirtualPathData的。

       internal static VirtualPathData GetVirtualPathForArea(this RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary values, out bool usingAreas) {
                if (routes == null) {
                    throw new ArgumentNullException("routes");
                }
    
                if (!String.IsNullOrEmpty(name)) {
                    // the route name is a stronger qualifier than the area name, so just pipe it through
                    usingAreas = false;
                    return routes.GetVirtualPath(requestContext, name, values);
                }
    
                string targetArea = null;
                if (values != null) {
                    object targetAreaRawValue;
                    if (values.TryGetValue("area", out targetAreaRawValue)) {
                        targetArea = targetAreaRawValue as string;
                    }
                    else {
                        // set target area to current area
                        if (requestContext != null) {
                            targetArea = AreaHelpers.GetAreaName(requestContext.RouteData);
                        }
                    }
                }
    
                // need to apply a correction to the RVD if areas are in use
                RouteValueDictionary correctedValues = values;
                RouteCollection filteredRoutes = FilterRouteCollectionByArea(routes, targetArea, out usingAreas);
                if (usingAreas) {
                    correctedValues = new RouteValueDictionary(values);
                    correctedValues.Remove("area");
                }
    
                VirtualPathData vpd = filteredRoutes.GetVirtualPath(requestContext, correctedValues);
                return vpd;
            }
    

      我们默认传进来的name=null,usingAreas=false;这里首先获取area,获取的优先级是:(1)当前的RouteValueDictionary是否含有area,(2)当前请求requestContext.RouteData.DataTokens是否含有area,(3)requestContext.RouteData.Route.DataTokens是否含有area。这个我AreaHelpers的代码如下:

     internal static class AreaHelpers {
    
            public static string GetAreaName(RouteBase route) {
                IRouteWithArea routeWithArea = route as IRouteWithArea;
                if (routeWithArea != null) {
                    return routeWithArea.Area;
                }
    
                Route castRoute = route as Route;
                if (castRoute != null && castRoute.DataTokens != null) {
                    return castRoute.DataTokens["area"] as string;
                }
    
                return null;
            }
    
            public static string GetAreaName(RouteData routeData) {
                object area;
                if (routeData.DataTokens.TryGetValue("area", out area)) {
                    return area as string;
                }
    
                return GetAreaName(routeData.Route);
            }
    
        }
    

    FilterRouteCollectionByArea这个方法就是去掉与当前路由信息中area不同的所有路由信息,构建新的路由信息
     private static RouteCollection FilterRouteCollectionByArea(RouteCollection routes, string areaName, out bool usingAreas) {
                if (areaName == null) {
                    areaName = String.Empty;
                }
                usingAreas = false;
                RouteCollection filteredRoutes = new RouteCollection();
                using (routes.GetReadLock()) {
                    foreach (RouteBase route in routes) {
                        string thisAreaName = AreaHelpers.GetAreaName(route) ?? String.Empty;
                        usingAreas |= (thisAreaName.Length > 0);
                        if (String.Equals(thisAreaName, areaName, StringComparison.OrdinalIgnoreCase)) {
                            filteredRoutes.Add(route);
                        }
                    }
                }
                // if areas are not in use, the filtered route collection might be incorrect
                return (usingAreas) ? filteredRoutes : routes;
            }
    最后调用RouteCollection的GetVirtualPath方法。RouteCollection的GetVirtualPath方法其实就是循环调用里面每个RouteBase 的base2.GetVirtualPath方法,里面有这么一句

     foreach (RouteBase base2 in this)
            {
                VirtualPathData virtualPath = base2.GetVirtualPath(requestContext, values);
                if (virtualPath != null)

                {
                    virtualPath.VirtualPath = this.NormalizeVirtualPath(requestContext, virtualPath.VirtualPath);
                    return virtualPath;
                }
            }

    我们知道这里的RouteBase实际上是一个Route实例,我们来看看它的GetVirtualPath方法,里面有这么一句

        BoundUrl url = this._parsedRoute.Bind(requestContext.RouteData.Values, values, this.Defaults, this.Constraints)实际上是调用的ParsedRoute的Bind方法,

    这个Bind方法返回的BoundUrl 有一个很特殊的属性 return new BoundUrl { Url = builder.ToString(), Values = acceptedValues };

    结合整过方法我们就知道它把我们调用RenderAction时传入的routeValues拼接成url字符串。例如:

    可惜的是在mvc中没有使用这个VirtualPath属性,因为我们不需要从这里取值。真正取值是靠下面这句

     routeValues[ChildActionValueProvider.ChildActionValuesKey] = new DictionaryValueProvider<object>(additionalRouteValues, CultureInfo.InvariantCulture);

    这句 就是把我们传进来的routeValues给保存起来,便于后面调用。

    那么这里我们顺便看看ChildActionValueProvider的关键实现代码:

            public override ValueProviderResult GetValue(string key) {
                ValueProviderResult explicitValues = base.GetValue(ChildActionValuesKey);
                if (explicitValues != null) {
                    DictionaryValueProvider<object> rawExplicitValues = explicitValues.RawValue as DictionaryValueProvider<object>;
                    if (rawExplicitValues != null) {
                        return rawExplicitValues.GetValue(key);
                    }

                }
                return null;
            }

    先通过ChildActionValuesKey取得routeValues,再在routeValues中根据key来取值。我想大家看到这里就应该明白为什么RenderAction时DefaultModelBinder会走BindSimpleModel方法了吧,我想大家看到这里就应该明白为什么RenderAction时DefaultModelBinder会走BindSimpleModel方法了吧,但是如果routeValues中并没有传递我们需要的参数,而我们的参数又是一个复杂类型那么就会走BindComplexModel方法,是简单类型就直接返回一个null,是否是简单类型是看起能否转换成string

    CreateRouteData这个方法没什么特别的,只是 routeData.DataTokens[ControllerContext.PARENT_ACTION_VIEWCONTEXT] = parentViewContext;把新的Action作为子Action。

    HttpHandlerUtil.WrapForServerExecute这个方法没什么好说的,把当前ChildActionMvcHandler包装成一个ServerExecuteHttpHandlerWrapper,不过ServerExecuteHttpHandlerWrapper继承于Page类

    从 这里我们知道RenderAction是发起一个handler请求处理和RenderPartial只是呈现试图,所以RenderPartial的性能要高出很多

  • 相关阅读:
    68
    56
    Django manager 命令笔记
    Django 执行 manage 命令方式
    Django 连接 Mysql (8.0.16) 失败
    Python django 安装 mysqlclient 失败
    H.264 SODB RBSP EBSP的区别
    FFmpeg—— Bitstream Filters 作用
    MySQL 远程连接问题 (Windows Server)
    MySQL 笔记
  • 原文地址:https://www.cnblogs.com/majiang/p/2767144.html
Copyright © 2011-2022 走看看