234 lines
11 KiB
C#
234 lines
11 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Web.Compilation;
|
|
using System.Web.Mvc;
|
|
using System.Web.Routing;
|
|
using Umbraco.Core;
|
|
using Umbraco.Web.Models;
|
|
using Umbraco.Web.Mvc;
|
|
using Umbraco.Web.Routing;
|
|
using umbraco;
|
|
using Umbraco.Core.Configuration;
|
|
using Umbraco.Core.Services;
|
|
using LightInject;
|
|
using Umbraco.Web.Composing;
|
|
|
|
namespace Umbraco.Web.Templates
|
|
{
|
|
/// <summary>
|
|
/// This is used purely for the RenderTemplate functionality in Umbraco
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This allows you to render either an MVC or Webforms template based purely off of a node id and an optional alttemplate id as string output.
|
|
/// </remarks>
|
|
internal class TemplateRenderer
|
|
{
|
|
private readonly IFileService _fileService = Current.Services.FileService; // fixme inject
|
|
private readonly UmbracoContext _umbracoContext;
|
|
private object _oldPageId;
|
|
private object _oldPageElements;
|
|
private PublishedRequest _oldPublishedRequest;
|
|
private object _oldAltTemplate;
|
|
|
|
public TemplateRenderer(UmbracoContext umbracoContext, int pageId, int? altTemplateId)
|
|
{
|
|
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
|
|
PageId = pageId;
|
|
AltTemplate = altTemplateId;
|
|
_umbracoContext = umbracoContext;
|
|
}
|
|
|
|
// todo - inject!
|
|
private PublishedRouter PublishedRouter => Core.Composing.Current.Container.GetInstance<PublishedRouter>();
|
|
|
|
|
|
/// <summary>
|
|
/// Gets/sets the page id for the template to render
|
|
/// </summary>
|
|
public int PageId { get; }
|
|
|
|
/// <summary>
|
|
/// Gets/sets the alt template to render if there is one
|
|
/// </summary>
|
|
public int? AltTemplate { get; }
|
|
|
|
public void Render(StringWriter writer)
|
|
{
|
|
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
|
|
|
// instanciate a request a process
|
|
// important to use CleanedUmbracoUrl - lowercase path-only version of the current url, though this isn't going to matter
|
|
// terribly much for this implementation since we are just creating a doc content request to modify it's properties manually.
|
|
// fixme - previously that would create an aengine...
|
|
var contentRequest = PublishedRouter.CreateRequest(_umbracoContext);
|
|
|
|
var doc = contentRequest.UmbracoContext.ContentCache.GetById(PageId);
|
|
|
|
if (doc == null)
|
|
{
|
|
writer.Write("<!-- Could not render template for Id {0}, the document was not found -->", PageId);
|
|
return;
|
|
}
|
|
|
|
//in some cases the UmbracoContext will not have a PublishedContentRequest assigned to it if we are not in the
|
|
//execution of a front-end rendered page. In this case set the culture to the default.
|
|
//set the culture to the same as is currently rendering
|
|
if (_umbracoContext.PublishedRequest == null)
|
|
{
|
|
var defaultLanguage = Current.Services.LocalizationService.GetAllLanguages().FirstOrDefault();
|
|
contentRequest.Culture = defaultLanguage == null
|
|
? CultureInfo.CurrentUICulture
|
|
: defaultLanguage.CultureInfo;
|
|
}
|
|
else
|
|
{
|
|
contentRequest.Culture = _umbracoContext.PublishedRequest.Culture;
|
|
}
|
|
|
|
//set the doc that was found by id
|
|
contentRequest.PublishedContent = doc;
|
|
//set the template, either based on the AltTemplate found or the standard template of the doc
|
|
contentRequest.TemplateModel = UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates || AltTemplate.HasValue == false
|
|
? _fileService.GetTemplate(doc.TemplateId)
|
|
: _fileService.GetTemplate(AltTemplate.Value);
|
|
|
|
//if there is not template then exit
|
|
if (contentRequest.HasTemplate == false)
|
|
{
|
|
if (AltTemplate.HasValue == false)
|
|
{
|
|
writer.Write("<!-- Could not render template for Id {0}, the document's template was not found with id {0}-->", doc.TemplateId);
|
|
}
|
|
else
|
|
{
|
|
writer.Write("<!-- Could not render template for Id {0}, the altTemplate was not found with id {0}-->", AltTemplate);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these
|
|
//after this page has rendered.
|
|
SaveExistingItems();
|
|
|
|
try
|
|
{
|
|
//set the new items on context objects for this templates execution
|
|
SetNewItemsOnContextObjects(contentRequest);
|
|
|
|
//Render the template
|
|
ExecuteTemplateRendering(writer, contentRequest);
|
|
}
|
|
finally
|
|
{
|
|
//restore items on context objects to continuing rendering the parent template
|
|
RestoreItems();
|
|
}
|
|
|
|
}
|
|
|
|
private void ExecuteTemplateRendering(TextWriter sw, PublishedRequest request)
|
|
{
|
|
//NOTE: Before we used to build up the query strings here but this is not necessary because when we do a
|
|
// Server.Execute in the TemplateRenderer, we pass in a 'true' to 'preserveForm' which automatically preserves all current
|
|
// query strings so there's no need for this. HOWEVER, once we get MVC involved, we might have to do some fun things,
|
|
// though this will happen in the TemplateRenderer.
|
|
|
|
//var queryString = _umbracoContext.HttpContext.Request.QueryString.AllKeys
|
|
// .ToDictionary(key => key, key => context.Request.QueryString[key]);
|
|
|
|
switch (request.RenderingEngine)
|
|
{
|
|
case RenderingEngine.Mvc:
|
|
var requestContext = new RequestContext(_umbracoContext.HttpContext, new RouteData()
|
|
{
|
|
Route = RouteTable.Routes["Umbraco_default"]
|
|
});
|
|
var routeHandler = new RenderRouteHandler(_umbracoContext, ControllerBuilder.Current.GetControllerFactory());
|
|
var routeDef = routeHandler.GetUmbracoRouteDefinition(requestContext, request);
|
|
var renderModel = new ContentModel(request.PublishedContent);
|
|
//manually add the action/controller, this is required by mvc
|
|
requestContext.RouteData.Values.Add("action", routeDef.ActionName);
|
|
requestContext.RouteData.Values.Add("controller", routeDef.ControllerName);
|
|
//add the rest of the required route data
|
|
routeHandler.SetupRouteDataForRequest(renderModel, requestContext, request);
|
|
|
|
var stringOutput = RenderUmbracoRequestToString(requestContext);
|
|
|
|
sw.Write(stringOutput);
|
|
break;
|
|
case RenderingEngine.WebForms:
|
|
default:
|
|
var webFormshandler = (global::umbraco.UmbracoDefault)BuildManager
|
|
.CreateInstanceFromVirtualPath("~/default.aspx", typeof(global::umbraco.UmbracoDefault));
|
|
//the 'true' parameter will ensure that the current query strings are carried through, we don't have
|
|
// to build up the url again, it will just work.
|
|
_umbracoContext.HttpContext.Server.Execute(webFormshandler, sw, true);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will execute the UmbracoMvcHandler for the request specified and get the string output.
|
|
/// </summary>
|
|
/// <param name="requestContext">
|
|
/// Assumes the RequestContext is setup specifically to render an Umbraco view.
|
|
/// </param>
|
|
/// <returns></returns>
|
|
/// <remarks>
|
|
/// To acheive this we temporarily change the output text writer of the current HttpResponse, then
|
|
/// execute the controller via the handler which innevitably writes the result to the text writer
|
|
/// that has been assigned to the response. Then we change the response textwriter back to the original
|
|
/// before continuing .
|
|
/// </remarks>
|
|
private string RenderUmbracoRequestToString(RequestContext requestContext)
|
|
{
|
|
var currentWriter = requestContext.HttpContext.Response.Output;
|
|
var newWriter = new StringWriter();
|
|
requestContext.HttpContext.Response.Output = newWriter;
|
|
|
|
var handler = new UmbracoMvcHandler(requestContext);
|
|
handler.ExecuteUmbracoRequest();
|
|
|
|
//reset it
|
|
requestContext.HttpContext.Response.Output = currentWriter;
|
|
return newWriter.ToString();
|
|
}
|
|
|
|
private void SetNewItemsOnContextObjects(PublishedRequest request)
|
|
{
|
|
// handlers like default.aspx will want it and most macros currently need it
|
|
request.UmbracoPage = new page(request);
|
|
//now, set the new ones for this page execution
|
|
_umbracoContext.HttpContext.Items["pageElements"] = request.UmbracoPage.Elements;
|
|
_umbracoContext.HttpContext.Items[Core.Constants.Conventions.Url.AltTemplate] = null;
|
|
_umbracoContext.PublishedRequest = request;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save all items that we know are used for rendering execution to variables so we can restore after rendering
|
|
/// </summary>
|
|
private void SaveExistingItems()
|
|
{
|
|
//Many objects require that these legacy items are in the http context items... before we render this template we need to first
|
|
//save the values in them so that we can re-set them after we render so the rest of the execution works as per normal
|
|
_oldPageId = _umbracoContext.PageId;
|
|
_oldPageElements = _umbracoContext.HttpContext.Items["pageElements"];
|
|
_oldPublishedRequest = _umbracoContext.PublishedRequest;
|
|
_oldAltTemplate = _umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restores all items back to their context's to continue normal page rendering execution
|
|
/// </summary>
|
|
private void RestoreItems()
|
|
{
|
|
_umbracoContext.PublishedRequest = _oldPublishedRequest;
|
|
_umbracoContext.HttpContext.Items["pageElements"] = _oldPageElements;
|
|
_umbracoContext.HttpContext.Items[Umbraco.Core.Constants.Conventions.Url.AltTemplate] = _oldAltTemplate;
|
|
}
|
|
}
|
|
}
|