/// <summary> /// Enriches a given Entity Model using an appropriate (custom) Controller. /// </summary> /// <param name="entity">The Entity Model to enrich.</param> /// <returns>The enriched Entity Model.</returns> /// <remarks> /// This method is different from <see cref="EnrichModel"/> in that it doesn't expect the current Controller to be able to enrich the Entity Model; /// it creates a Controller associated with the Entity Model for that purpose. /// It is used by <see cref="PageController.EnrichEmbeddedModels"/>. /// </remarks> protected EntityModel EnrichEntityModel(EntityModel entity) { if (entity == null || entity.MvcData == null || !IsCustomAction(entity.MvcData)) { return(entity); } MvcData mvcData = entity.MvcData; using (new Tracer(entity, mvcData)) { string controllerName = mvcData.ControllerName ?? SiteConfiguration.GetEntityController(); string controllerAreaName = mvcData.ControllerAreaName ?? SiteConfiguration.GetDefaultModuleName(); RequestContext tempRequestContext = new RequestContext(HttpContext, new RouteData()); tempRequestContext.RouteData.DataTokens["Area"] = controllerAreaName; tempRequestContext.RouteData.Values["controller"] = controllerName; tempRequestContext.RouteData.Values["area"] = controllerAreaName; // Note: Entity Controllers don't have to inherit from EntityController per se, but they must inherit from BaseController. BaseController entityController = (BaseController)ControllerBuilder.Current.GetControllerFactory().CreateController(tempRequestContext, controllerName); entityController.ControllerContext = new ControllerContext(HttpContext, tempRequestContext.RouteData, entityController); return((EntityModel)entityController.EnrichModel(entity)); } }
/// <summary> /// Renders a given Region Model /// </summary> /// <param name="htmlHelper">The HtmlHelper instance on which the extension method operates.</param> /// <param name="region">The Region Model to render. This object determines the View that will be used.</param> /// <param name="containerSize">The size (in grid column units) of the containing element.</param> /// <returns>The rendered HTML or an empty string if <paramref name="region"/> is <c>null</c>.</returns> public static MvcHtmlString DxaRegion(this HtmlHelper htmlHelper, RegionModel region, int containerSize = 0) { if (region == null) { return(MvcHtmlString.Empty); } if (containerSize == 0) { containerSize = SiteConfiguration.MediaHelper.GridSize; } using (new Tracer(htmlHelper, region, containerSize)) { MvcData mvcData = region.MvcData; string actionName = mvcData.ActionName ?? SiteConfiguration.GetRegionAction(); string controllerName = mvcData.ControllerName ?? SiteConfiguration.GetRegionController(); string controllerAreaName = mvcData.ControllerAreaName ?? SiteConfiguration.GetDefaultModuleName(); MvcHtmlString result = htmlHelper.Action(actionName, controllerName, new { Region = region, containerSize = containerSize, area = controllerAreaName }); if (WebRequestContext.IsPreview) { result = new MvcHtmlString(Markup.TransformXpmMarkupAttributes(result.ToString())); } return(Markup.DecorateMarkup(result, region)); } }
private void AddComponentPresentationRegions(IDictionary <string, RegionModelData> regionModels, Page page) { foreach (ComponentPresentation cp in page.ComponentPresentations) { var entityModel = GetEntityModelData(cp); string regionName; MvcData regionMvcData = GetRegionMvcData(cp.ComponentTemplate, out regionName); RegionModelData regionModel; if (regionModels.TryGetValue(regionName, out regionModel)) { if (!regionMvcData.Equals(regionModel.MvcData)) { throw new DxaException($"Conflicting Region MVC data detected: [{regionMvcData}] versus [{regionModel.MvcData}]"); } } else { regionModel = new RegionModelData { Name = regionName, MvcData = regionMvcData, Entities = new List <EntityModelData>() }; regionModels.Add(regionName, regionModel); } regionModel.Entities.Add(entityModel); } }
/// <summary> /// Renders the current (Include) Page as a Region. /// </summary> /// <param name="htmlHelper">The HtmlHelper instance on which the extension method operates.</param> /// <returns>The rendered HTML.</returns> public static MvcHtmlString DxaRegion(this HtmlHelper htmlHelper) { using (new Tracer(htmlHelper)) { PageModel pageModel = (PageModel)htmlHelper.ViewData.Model; // Create a new Region Model which reflects the Page Model string regionName = pageModel.Title; MvcData mvcData = new MvcData { ViewName = regionName, AreaName = SiteConfiguration.GetDefaultModuleName(), ControllerName = SiteConfiguration.GetRegionController(), ControllerAreaName = SiteConfiguration.GetDefaultModuleName(), ActionName = SiteConfiguration.GetRegionAction() }; RegionModel regionModel = new RegionModel(regionName) { MvcData = mvcData }; regionModel.Regions.UnionWith(pageModel.Regions); return(htmlHelper.DxaRegion(regionModel)); } }
public void CreatePageModel_HybridRegion_Success() { Page testPage = (Page)TestSession.GetObject(TestFixture.R2PageIncludesPageWebDavUrl); RenderedItem testRenderedItem; PageModelData pageModel = CreatePageModel(testPage, out testRenderedItem); AssertExpectedIncludePageRegions(pageModel.Regions, new [] { "Header" }, allowEntities: true); // Header (Include Page) Region should also contain an Entity Model for the Test CP. RegionModelData header = pageModel.Regions.First(r => r.Name == "Header"); Assert.IsNotNull(header.Entities, "header.Entities"); Assert.AreEqual(1, header.Entities.Count, "header.Entities.Count"); EntityModelData testEntity = header.Entities[0]; MvcData mvcData = testEntity.MvcData; Assert.IsNotNull(mvcData, "mvcData"); Assert.IsNotNull(mvcData.Parameters, "mvcData.Parameters"); Assert.AreEqual(2, mvcData.Parameters.Count, "mvcData.Parameters.Count"); string name; Assert.IsTrue(mvcData.Parameters.TryGetValue("name", out name), "mvcData.Parameters['name']"); Assert.AreEqual("value", name, "name"); }
/// <summary> /// Registers a View Model and associated View. /// </summary> /// <param name="viewName">The name of the View to register.</param> /// <param name="modelType">The View Model Type to associate with the View. Must be a subclass of Type <see cref="ViewModel"/>.</param> /// <param name="controllerName">The Controller name. If not specified (or <c>null</c>), the Controller name is inferred from the <see cref="modelType"/>: either "Entity", "Region" or "Page".</param> private static void RegisterViewModel(string viewName, Type modelType, string controllerName = null) { if (String.IsNullOrEmpty(controllerName)) { if (typeof(EntityModel).IsAssignableFrom(modelType)) { controllerName = "Entity"; } else if (typeof(RegionModel).IsAssignableFrom(modelType)) { controllerName = "Region"; } else { controllerName = "Page"; } } MvcData mvcData = new MvcData(viewName) { ControllerName = controllerName }; ModelTypeRegistry.RegisterViewModel(mvcData, modelType); }
/// <summary> /// Renders a given Entity Model. /// </summary> /// <param name="htmlHelper">The HtmlHelper instance on which the extension method operates.</param> /// <param name="entity">The Entity to render.</param> /// <param name="viewName">The (qualified) name of the View used to render the entity. This overrides the View set in <see cref="EntityModel.MvcData"/>.</param> /// <param name="containerSize">The size (in grid column units) of the containing element.</param> /// <returns>The rendered HTML or an empty string if <paramref name="entity"/> is <c>null</c>.</returns> public static MvcHtmlString DxaEntity(this HtmlHelper htmlHelper, EntityModel entity, string viewName, int containerSize = 0) { MvcData mvcDataOverride = new MvcData(viewName); entity.MvcData.AreaName = mvcDataOverride.AreaName; entity.MvcData.ViewName = mvcDataOverride.ViewName; return(htmlHelper.DxaEntity(entity, containerSize)); }
protected virtual void SetupViewData(int containerSize = 0, MvcData viewData = null) { ViewData[DxaViewDataItems.ContainerSize] = containerSize; if (viewData != null) { ViewData[DxaViewDataItems.RegionName] = viewData.RegionName; //This enables us to jump areas when rendering sub-views - for example from rendering a region in Core to an entity in ModuleX ControllerContext.RouteData.DataTokens["area"] = viewData.AreaName; } }
[FormatData] // must come first in execution order before output cache public virtual ActionResult Page(string pageUrl) { // The pageUrl parameter provided by ASP.NET MVC is relative to the Web App, but we need a server-relative (i.e. absolute) URL path. string absoluteUrlPath = Request.Url.AbsolutePath; using (new Tracer(pageUrl, absoluteUrlPath)) { try { bool addIncludes = true; object addIncludesViewData; if (ViewData.TryGetValue(DxaViewDataItems.AddIncludes, out addIncludesViewData)) { addIncludes = (bool)addIncludesViewData; } PageModel pageModel; try { pageModel = ContentProvider.GetPageModel(absoluteUrlPath, WebRequestContext.Localization, addIncludes); } catch (DxaItemNotFoundException ex) { Log.Info(ex.Message); return(NotFound()); } PageModelWithHttpResponseData pageModelWithHttpResponseData = pageModel as PageModelWithHttpResponseData; if (pageModelWithHttpResponseData != null) { pageModelWithHttpResponseData.SetHttpResponseData(System.Web.HttpContext.Current.Response); } SetupViewData(pageModel); PageModel model = (EnrichModel(pageModel) as PageModel) ?? pageModel; WebRequestContext.PageModel = model; MvcData mvcData = model.MvcData; if (mvcData == null) { throw new DxaException($"Page Model [{model}] has no MVC data."); } Log.Debug("Page Request for URL path '{0}' maps to Model [{1}] with View '{2}'", absoluteUrlPath, model, mvcData.ViewName); return(View(mvcData.ViewName, model)); } catch (Exception ex) { Log.Error(ex); return(ServerError()); } } }
/// <summary> /// Renders a given Entity Model. /// </summary> /// <param name="htmlHelper">The HtmlHelper instance on which the extension method operates.</param> /// <param name="entity">The Entity to render.</param> /// <param name="containerSize">The size (in grid column units) of the containing element.</param> /// <returns>The rendered HTML or an empty string if <paramref name="entity"/> is <c>null</c>.</returns> public static MvcHtmlString DxaEntity(this HtmlHelper htmlHelper, EntityModel entity, int containerSize = 0) { if (entity == null) { return(MvcHtmlString.Empty); } if (containerSize == 0) { containerSize = SiteConfiguration.MediaHelper.GridSize; } MvcData mvcData = entity.MvcData; using (new Tracer(htmlHelper, entity, containerSize, mvcData)) { if (mvcData == null) { throw new DxaException($"Unable to render Entity Model [{entity}], because it has no MVC data."); } string actionName = mvcData.ActionName ?? SiteConfiguration.GetEntityAction(); string controllerName = mvcData.ControllerName ?? SiteConfiguration.GetEntityController(); string controllerAreaName = mvcData.ControllerAreaName ?? SiteConfiguration.GetDefaultModuleName(); RouteValueDictionary parameters = new RouteValueDictionary(); int parentContainerSize = (int)htmlHelper.ViewData[DxaViewDataItems.ContainerSize]; if (parentContainerSize == 0) { parentContainerSize = SiteConfiguration.MediaHelper.GridSize; } parameters["containerSize"] = (containerSize * parentContainerSize) / SiteConfiguration.MediaHelper.GridSize; parameters["entity"] = entity; parameters["area"] = controllerAreaName; if (mvcData.RouteValues != null) { foreach (string key in mvcData.RouteValues.Keys) { parameters[key] = mvcData.RouteValues[key]; } } MvcHtmlString result = htmlHelper.Action(actionName, controllerName, parameters); // If the Entity is being rendered inside a Region (typical), we don't have to transform the XPM markup attributes here; it will be done in DxaRegion. if (!(htmlHelper.ViewData.Model is RegionModel) && WebRequestContext.IsPreview) { result = new MvcHtmlString(Markup.TransformXpmMarkupAttributes(result.ToString())); } return(Markup.DecorateMarkup(result, entity)); } }
protected virtual void SetupViewData(int containerSize = 0, MvcData viewData = null) { #pragma warning disable 618 // To support (deprecated) use of ViewBag.Renderer in Views. ViewData[DxaViewDataItems.Renderer] = Renderer; #pragma warning restore 618 ViewData[DxaViewDataItems.ContainerSize] = containerSize; if (viewData != null) { ViewData[DxaViewDataItems.RegionName] = viewData.RegionName; //This enables us to jump areas when rendering sub-views - for example from rendering a region in Core to an entity in ModuleX ControllerContext.RouteData.DataTokens["area"] = viewData.AreaName; } }
private void AddComponentPresentationRegions(IDictionary <string, RegionModelData> regionModels, Page page) { foreach (ComponentPresentation cp in page.ComponentPresentations) { ComponentTemplate ct = cp.ComponentTemplate; // Create a Child Rendered Item for the CP in order to make Component linking work. RenderedItem childRenderedItem = new RenderedItem(new ResolvedItem(cp.Component, ct), Pipeline.RenderedItem.RenderInstruction); Pipeline.RenderedItem.AddRenderedItem(childRenderedItem); EntityModelData entityModel; if (ct.IsRepositoryPublishable) { Logger.Debug($"Not expanding DCP ({cp.Component}, {ct})"); entityModel = new EntityModelData { Id = $"{GetDxaIdentifier(cp.Component)}-{GetDxaIdentifier(ct)}" }; } else { entityModel = Pipeline.CreateEntityModel(cp); } string regionName; MvcData regionMvcData = GetRegionMvcData(cp.ComponentTemplate, out regionName); RegionModelData regionModel; if (regionModels.TryGetValue(regionName, out regionModel)) { if (!regionMvcData.Equals(regionModel.MvcData)) { throw new DxaException($"Conflicting Region MVC data detected: [{regionMvcData}] versus [{regionModel.MvcData}]"); } } else { regionModel = new RegionModelData { Name = regionName, MvcData = regionMvcData, Entities = new List <EntityModelData>() }; regionModels.Add(regionName, regionModel); } regionModel.Entities.Add(entityModel); } }
/// <summary> /// Renders a given Entity Model. /// </summary> /// <param name="htmlHelper">The HtmlHelper instance on which the extension method operates.</param> /// <param name="entity">The Entity to render.</param> /// <param name="viewName">The (qualified) name of the View used to render the entity. This overrides the View set in <see cref="EntityModel.MvcData"/>.</param> /// <param name="containerSize">The size (in grid column units) of the containing element.</param> /// <returns>The rendered HTML or an empty string if <paramref name="entity"/> is <c>null</c>.</returns> public static MvcHtmlString DxaEntity(this HtmlHelper htmlHelper, EntityModel entity, string viewName, int containerSize = 0) { MvcData mvcDataOverride = new MvcData(viewName); MvcData orginalMvcData = entity.MvcData; MvcData tempMvcData = new MvcData(orginalMvcData) { AreaName = mvcDataOverride.AreaName, ViewName = mvcDataOverride.ViewName }; try { entity.MvcData = tempMvcData; return(htmlHelper.DxaEntity(entity, containerSize)); } finally { entity.MvcData = orginalMvcData; } }
/// <summary> /// Automatically register all view models for an area. This is done by searching the file system /// for all .cshtml files, determining the controller and view names from the path, and using the /// BuildManager to determine the model type by compiling the view. Note that if your area contains /// a lot of views, this can be a lengthy process and you might be better off explicitly registering /// your views with the RegisterViewModel method /// </summary> protected virtual void RegisterAllViewModels() { DateTime timer = DateTime.Now; int viewCount = 0; string baseDir = AppDomain.CurrentDomain.BaseDirectory; string path = Path.Combine(baseDir, "Areas", this.AreaName, "Views"); Log.Debug("Staring to register views for area: {0}", this.AreaName); foreach (string file in Directory.GetFiles(path, "*.cshtml", SearchOption.AllDirectories)) { string relativePath = file.Substring(path.Length + 1); string virtualPath = @"~/" + file.Replace(baseDir, string.Empty).Replace("\\", "/"); int pos = relativePath.IndexOf("\\"); if (pos > 0) { string controller = relativePath.Substring(0, pos); string view = relativePath.Substring(pos + 1); int extnPos = view.LastIndexOf(".cshtml"); view = view.Substring(0, extnPos); MvcData mvcData = new MvcData { AreaName = this.AreaName, ControllerName = controller, ViewName = view }; try { ModelTypeRegistry.RegisterViewModel(mvcData, virtualPath); viewCount++; } catch { //Do nothing - we ignore views that are not strongly typed } } else { Log.Warn("Cannot add view {0} to view model registry as it is not in a {ControllerName} subfolder", file); } } Log.Info("Registered {0} views for area {1} in {2} milliseconds. This startup overhead can be reduced by explicitly registering view using the Sdl.Web.Mvc.Configuration.BaseAreaRegistration.RegisterView() method.", viewCount, this.AreaName, (DateTime.Now - timer).TotalMilliseconds); }
/// <summary> /// Post-processes the Page Model constructed by the <see cref="DefaultModelBuilder"/>. /// </summary> /// <remarks> /// This implementation relies on the <see cref="DefaultModelBuilder"/> already having constructed Region Models of type <see cref="SmartTargetRegion"/>. /// We "upgrade" the Page Model to type <see cref="SmartTargetPageModel"/> and populate the ST Regions <see cref="SmartTargetPromotion"/> Entities. /// </remarks> public void BuildPageModel(ref PageModel pageModel, IPage page, IEnumerable <IPage> includes, Localization localization) { using (new Tracer(pageModel, page, includes, localization)) { if (pageModel == null || !pageModel.Regions.OfType <SmartTargetRegion>().Any()) { Log.Debug("No SmartTarget Regions on Page."); return; } if (page == null || page.PageTemplate == null || page.PageTemplate.MetadataFields == null || !page.PageTemplate.MetadataFields.ContainsKey("regions")) { Log.Debug("No Regions metadata found."); return; } // "Upgrade" the PageModel to a SmartTargetPageModel, so we can store AllowDuplicates in the Page Model. SmartTargetPageModel smartTargetPageModel = new SmartTargetPageModel(pageModel) { AllowDuplicates = GetAllowDuplicatesOnSamePage(page.PageTemplate, localization) }; pageModel = smartTargetPageModel; // Set SmartTargetRegionModel.MaxItem based on the Region Metadata in the Page Template. foreach (IFieldSet smartTargetRegionField in page.PageTemplate.MetadataFields["regions"].EmbeddedValues) { string regionName; IField regionNameField; if (smartTargetRegionField.TryGetValue("name", out regionNameField) && !String.IsNullOrEmpty(regionNameField.Value)) { regionName = regionNameField.Value; } else { regionName = new MvcData(smartTargetRegionField["view"].Value).ViewName; } SmartTargetRegion smartTargetRegion = smartTargetPageModel.Regions[regionName] as SmartTargetRegion; if (smartTargetRegion != null) { int maxItems = smartTargetRegionField.ContainsKey("maxItems") ? Convert.ToInt32(smartTargetRegionField["maxItems"].Value) : 100; smartTargetRegion.MaxItems = maxItems; } } // Execute a ST Query for all SmartTargetRegions on the Page. ResultSet resultSet = ExecuteSmartTargetQuery(smartTargetPageModel, localization); Log.Debug("SmartTarget query returned {0} Promotions.", resultSet.Promotions.Count); string promotionViewName = localization.GetConfigValue(PromotionViewNameConfig); if (String.IsNullOrEmpty(promotionViewName)) { Log.Warn("No View name for SmartTarget Promotions is configured on CM-side ({0})", PromotionViewNameConfig); promotionViewName = "SmartTarget:Entity:Promotion"; } Log.Debug("Using Promotion View '{0}'", promotionViewName); List <string> itemsAlreadyOnPage = new List <string>(); ExperimentCookies existingExperimentCookies = CookieProcessor.GetExperimentCookies(HttpContext.Current.Request); // TODO: we shouldn't access HttpContext in a Model Builder. // Filter the Promotions for each SmartTargetRegion foreach (SmartTargetRegion smartTargetRegion in smartTargetPageModel.Regions.OfType <SmartTargetRegion>()) { string regionName = smartTargetRegion.Name; List <string> itemsOutputInRegion = new List <string>(); ExperimentCookies newExperimentCookies = new ExperimentCookies(); ExperimentDimensions experimentDimensions; List <Promotion> promotions = new List <Promotion>(resultSet.Promotions); ResultSet.FilterPromotions(promotions, regionName, smartTargetRegion.MaxItems, smartTargetPageModel.AllowDuplicates, itemsOutputInRegion, itemsAlreadyOnPage, ref existingExperimentCookies, ref newExperimentCookies, out experimentDimensions); if (localization.IsStaging) { // The SmartTarget API provides the entire XPM markup tag; put it in XpmMetadata["Query"]. See SmartTargetRegion.GetStartQueryXpmMarkup. smartTargetRegion.XpmMetadata = new Dictionary <string, object> { { "Query", ResultSet.GetExperienceManagerMarkup(smartTargetRegion.Name, smartTargetRegion.MaxItems, promotions) } }; } // Create SmartTargetPromotion Entity Models for visible Promotions in the current SmartTargetRegion. // It seems that ResultSet.FilterPromotions doesn't really filter on Region name, so we do post-filtering here. foreach (Promotion promotion in promotions.Where(promotion => promotion.Visible && promotion.Region.Contains(regionName))) { SmartTargetPromotion smartTargetPromotion = CreatePromotionEntity(promotion, promotionViewName, smartTargetRegion.Name, localization); if (!smartTargetRegion.HasSmartTargetContent) { // Discard any fallback content coming from Content Manager smartTargetRegion.Entities.Clear(); smartTargetRegion.HasSmartTargetContent = true; } smartTargetRegion.Entities.Add(smartTargetPromotion); } } } }
public static void AddViewModelToRegistry(MvcData mvcData, Type modelType) { ModelTypeRegistry.RegisterViewModel(mvcData, modelType); }
public static string GetViewModelRegistryKey(MvcData mvcData) { return(String.Format("{0}:{1}:{2}", mvcData.AreaName, mvcData.ControllerName, mvcData.ViewName)); }
public static void AddViewModelToRegistry(MvcData viewData, string viewPath) { ModelTypeRegistry.RegisterViewModel(viewData, viewPath); }
protected virtual Type GetViewType(MvcData viewData) { return(ModelTypeRegistry.GetViewModelType(viewData)); }
private static bool IsCustomAction(MvcData mvcData) { return(mvcData.ActionName != SiteConfiguration.GetEntityAction() || mvcData.ControllerName != SiteConfiguration.GetEntityController() || mvcData.ControllerAreaName != SiteConfiguration.GetDefaultModuleName()); }