Adding multiple components to RichText and load them View rendering dynamically
I had a situation where I need to convert the existing Asp.net project to the Sitecore application, I have found that it is used any usercontrols, As each page as their own unique HTML components. So, it is difficult to go with the component-based approach, But I could see few areas where I can convert to components and insert those components to the page.
I preferred to use RichText editor to place all the necessary HTML along with the components by using “Insert Sitecore Link” to insert the relevant component items to the page.
Let's get started.
- · First identify the components and create the templates and rendering for it.
- · To all components have a Standard value item and in its presentation details add the corresponding rendering to it without Layout definition (It is not possible to add just rendering to item with the layout but using Sitecore Rocks we can achieve it).
- Create page item that needs these components to be inserted in its RichText fields(BodyContent).
- · Add these components as child item for the page item.
- · After creating the page item and the context item lets add these components item to the BodyContent field.
Once all these done let's need to programmatically load the page with all the components along with other contents in BodyContent field.
Steps:
- · First we need to get all the components in the BodyContent field and load its Html in the same order they are in the BodyContent field along with other contents in the field.
- · In order to load the View(cshtml) of the rendering dynamically based on the component inserted we have to write few lines of code.
Dynamically load View from Repository Class:
First of all, the main purpose of loading the view is to store in a variable and append these output HTML to the RichText(BodyContent) Field content, so that we will have the exact content displayed in UI along with all components HTML output.
In order to load the MVC View dynamically, I have created two methods.
- · CreateController
- · RenderViewToString
Our main goal is to not just execute the View but to append its output to the BodyContent field in the same position where it is placed in the editor.
In RenderViewToString() method a ControllerContext is required in order to render a view. There’s really no way around it, as the context provides the background info for a number of key components that are exposed on the view, such as the HttpContext intrinsic objects, the routing data, UrlHelper and more. So, it seems that the view is closely coupled to the controller.
The CreateController method demonstrates how to create an arbitrary controller instance with an attached ControllerContext, which can then be used to call RenderViewToString().
private static string RenderViewToString(ControllerContext context,
string viewPath,
object model = null,
bool partial = false)
{
// first find the ViewEngine for this view
ViewEngineResult viewEngineResult = null;
if (partial)
viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
else
viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
if (viewEngineResult == null)
throw new FileNotFoundException("View cannot be found.");
// get the view and attach the model to view data
var view = viewEngineResult.View;
context.Controller.ViewData.Model = model;
string result = null;
using (var sw = new StringWriter())
{
var ctx = new ViewContext(context, view,
context.Controller.ViewData,
context.Controller.TempData,
sw);
view.Render(ctx, sw);
result = sw.ToString();
}
return result;
}
}
private static T CreateController<T>(RouteData routeData = null) where T : Controller, new()
{
// create a disconnected controller instance
T controller = new T();
// get context wrapper from HttpContext if available
HttpContextBase wrapper;
if (System.Web.HttpContext.Current != null)
wrapper = new HttpContextWrapper(System.Web.HttpContext.Current);
else
throw new InvalidOperationException(
"Can't create Controller Context if no " +
"active HttpContext instance is available.");
if (routeData == null)
routeData = new RouteData();
// add the controller routing if not existing
if (!routeData.Values.ContainsKey("controller") &&
!routeData.Values.ContainsKey("Controller"))
routeData.Values.Add("controller",
controller.GetType()
.Name.ToLower().Replace("controller", ""));
controller.ControllerContext = new ControllerContext(wrapper, routeData, controller);
return controller;
}
· We have to create an Empty Controller class, this will be used to create the ControllerContext that is necessary for rendering the view.
public class EmptyController : Controller
{
// GET: Empty
public ActionResult Index()
{
return View();
}
}
Next we need to get all the components out of BodyContent field, I’m using HtmlAgilityPack to get the anchor tag(components)
public string GenerateBody(string bodyContent)
{
HtmlDocument htmlBody = new HtmlDocument();
using (TextReader textReader = new StringReader(unprocessedHTML))
{
htmlBody.Load(textReader);
}
if (htmlBody.DocumentNode == null) return unprocessedHTML;
HtmlNodeCollection anchorTags = htmlBody.DocumentNode.SelectNodes("//a[@href]");
if ((anchorTags == null) || (!anchorTags.Any())) return unprocessedHTML;
foreach (HtmlNode link in anchorTags)
{
HtmlAttribute href = link.Attributes["href"];
if (string.IsNullOrEmpty(href.Value) || href.Value.StartsWith("#")) continue;
Item component = SitecoreHelper.GetSitecoreDB().GetItem(GetItemPath(href.Value));
if (component == null)
return htmlBody.DocumentNode.OuterHtml;
//Getting the Rendering details
Sitecore.Data.Fields.LayoutField layoutField = component.Fields["__renderings"];
Sitecore.Layouts.RenderingReference[] renderings = layoutField.GetReferences(device);
string viewPath = String.Empty;
string renderingName = String.Empty;
string path = GetItemPath(href.Value);
foreach (var rendering in renderings)
{
//Gettting the View path from the Rendering(cshtml)
viewPath = rendering.RenderingItem.InnerItem["path"];
//Getting Rendering name to get class and model
renderingName = rendering.RenderingItem.Name;
}
//Getting model
BaseGlassItem model = GetInstance(renderingName, path);
StringBuilder builder = new StringBuilder();
using (StringWriter stringWriter = new StringWriter(builder))
{
using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter))
{
string componentHtml =
RenderViewToString(CreateController<EmptyController>().ControllerContext, "~" + viewPath, model, true);
if (string.IsNullOrEmpty(componentHtml)) continue;
HtmlDocument htmlChildBody = new HtmlDocument();
using (TextReader textReader = new StringReader(componentHtml))
{
htmlChildBody.Load(textReader);
}
foreach (var item in
htmlChildBody.DocumentNode.ChildNodes)
{
link.ParentNode.AppendChild(item);
}
link.ParentNode.RemoveChild(link);
}
}
}
return htmlBody.DocumentNode.OuterHtml;
}
/// <summary>
/// This Method is necessary to find the Component item in the Content tree
/// </summary>
/// <param name="path">Path of the component item in BodyContent Field</param>
/// <returns></returns>
public string GetItemPath(string path)
{
if (path.ToLower().Contains("data/"))
return "/sitecore/content/Site/" + path.Replace("-", " ");
else
return "/sitecore/content/Site/SiteHome/" + path.Replace("-", " ");
}
/// <summary>
/// This method is used to load the Glassmapper model for the component item
/// </summary>
/// <param name="renderingName">Component Name(View Rendering)</param>
/// <param name="path">Path of the component item in BodyContent Field</param>
/// <returns></returns>
private BaseGlassItem GetInstance(string renderingName, string path)
{
BaseGlassItem model = null;
switch (renderingName)
{
case "ComponentA":
model = SitecoreHelper.GetItem<ComponentA>(path);
return model;
case " ComponentB ":
model = SitecoreHelper.GetItem<ComponentB>(path);
return model;
default:
break;
}
return model;
}
In the Page View, we need to call this
@Html.Raw(GenerateBody(Model.BodyContent, Sitecore.Context.Device));
Output:
Reference for Dynamic MVC View Loading:
http://www.codemag.com/article/1312081/Rendering-ASP.NET-MVC-Razor-Views-to-String
That’s It 😊
Comments
Post a Comment