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);
        }

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

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

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

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

posted on   dz45693  阅读(2491)  评论(3编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构

导航

< 2012年11月 >
28 29 30 31 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 1
2 3 4 5 6 7 8
点击右上角即可分享
微信分享提示