asp.net mvc源碼分析-RenderAction和RenderPartial

來源:互聯網
上載者:User

截止上篇文章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的效能要高出很多。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.