• 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的性能要高出很多

  • 相关阅读:
    微信小程序地图组件中的include-points怎样缩放视野并将所有坐标点在规定的视野内展示?
    两种常见的mysql集群架构
    layui+oss阿里云附件上传回调报错问题
    redis hash过期时间
    Static和Extern关键字理解
    代理模式
    中介者模式
    访问者模式
    模板方法模式
    迭代器模式
  • 原文地址:https://www.cnblogs.com/majiang/p/2767144.html
Copyright © 2020-2023  润新知