2017-07-20 11:21:28 +02:00
using System ;
2019-01-31 15:09:31 +11:00
using System.Linq ;
2015-02-18 17:14:55 +01:00
using System.Collections ;
using System.IO ;
using System.Web ;
using System.Web.UI ;
using Umbraco.Core ;
using Umbraco.Web.Templates ;
using System.Collections.Generic ;
2019-01-31 15:09:31 +11:00
using Umbraco.Core.Logging ;
using Umbraco.Core.Models.PublishedContent ;
using Umbraco.Core.Services ;
2017-05-30 18:13:11 +02:00
using Umbraco.Web.Composing ;
2016-05-26 17:12:04 +02:00
using Umbraco.Web.Macros ;
2015-02-18 17:14:55 +01:00
namespace Umbraco.Web
{
/// <summary>
/// Methods used to render umbraco components as HTML in templates
/// </summary>
/// <remarks>
/// Used by UmbracoHelper
/// </remarks>
2018-03-07 09:36:04 +01:00
internal class UmbracoComponentRenderer : IUmbracoComponentRenderer
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
private readonly IUmbracoContextAccessor _umbracoContextAccessor ;
private readonly IMacroRenderer _macroRenderer ;
private readonly ITemplateRenderer _templateRenderer ;
2019-10-23 14:55:18 +11:00
private readonly HtmlLocalLinkParser _linkParser ;
2015-02-18 17:14:55 +01:00
2019-10-23 14:55:18 +11:00
public UmbracoComponentRenderer ( IUmbracoContextAccessor umbracoContextAccessor , IMacroRenderer macroRenderer , ITemplateRenderer templateRenderer , HtmlLocalLinkParser linkParser )
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
_umbracoContextAccessor = umbracoContextAccessor ;
_macroRenderer = macroRenderer ;
_templateRenderer = templateRenderer ? ? throw new ArgumentNullException ( nameof ( templateRenderer ) ) ;
2019-10-23 14:55:18 +11:00
_linkParser = linkParser ;
2015-02-18 17:14:55 +01:00
}
/// <summary>
/// Renders the template for the specified pageId and an optional altTemplateId
/// </summary>
2019-01-31 15:09:31 +11:00
/// <param name="contentId"></param>
2015-02-18 17:14:55 +01:00
/// <param name="altTemplateId">If not specified, will use the template assigned to the node</param>
/// <returns></returns>
2019-01-31 15:09:31 +11:00
public IHtmlString RenderTemplate ( int contentId , int? altTemplateId = null )
2015-02-18 17:14:55 +01:00
{
using ( var sw = new StringWriter ( ) )
{
try
{
2019-01-31 15:09:31 +11:00
_templateRenderer . Render ( contentId , altTemplateId , sw ) ;
2015-02-18 17:14:55 +01:00
}
catch ( Exception ex )
{
2019-01-31 15:09:31 +11:00
sw . Write ( "<!-- Error rendering template with id {0}: '{1}' -->" , contentId , ex ) ;
2015-02-18 17:14:55 +01:00
}
return new HtmlString ( sw . ToString ( ) ) ;
}
}
/// <summary>
/// Renders the macro with the specified alias.
/// </summary>
2019-01-31 15:09:31 +11:00
/// <param name="contentId"></param>
2015-02-18 17:14:55 +01:00
/// <param name="alias">The alias.</param>
/// <returns></returns>
2019-01-31 15:09:31 +11:00
public IHtmlString RenderMacro ( int contentId , string alias )
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
return RenderMacro ( contentId , alias , new { } ) ;
2015-02-18 17:14:55 +01:00
}
/// <summary>
/// Renders the macro with the specified alias, passing in the specified parameters.
/// </summary>
2019-01-31 15:09:31 +11:00
/// <param name="contentId"></param>
2015-02-18 17:14:55 +01:00
/// <param name="alias">The alias.</param>
/// <param name="parameters">The parameters.</param>
/// <returns></returns>
2019-01-31 15:09:31 +11:00
public IHtmlString RenderMacro ( int contentId , string alias , object parameters )
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
return RenderMacro ( contentId , alias , parameters . ToDictionary < object > ( ) ) ;
2015-02-18 17:14:55 +01:00
}
/// <summary>
/// Renders the macro with the specified alias, passing in the specified parameters.
/// </summary>
2019-01-31 15:09:31 +11:00
/// <param name="contentId"></param>
2015-02-18 17:14:55 +01:00
/// <param name="alias">The alias.</param>
/// <param name="parameters">The parameters.</param>
/// <returns></returns>
2019-01-31 15:09:31 +11:00
public IHtmlString RenderMacro ( int contentId , string alias , IDictionary < string , object > parameters )
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
if ( contentId = = default )
throw new ArgumentException ( "Invalid content id " + contentId ) ;
2015-02-18 17:14:55 +01:00
2019-04-22 18:14:03 +02:00
var content = _umbracoContextAccessor . UmbracoContext . Content ? . GetById ( true , contentId ) ;
2015-02-18 17:14:55 +01:00
2019-01-31 15:09:31 +11:00
if ( content = = null )
throw new InvalidOperationException ( "Cannot render a macro, no content found by id " + contentId ) ;
2015-02-18 17:14:55 +01:00
2019-01-31 15:09:31 +11:00
return RenderMacro ( alias , parameters , content ) ;
2015-02-18 17:14:55 +01:00
}
/// <summary>
/// Renders the macro with the specified alias, passing in the specified parameters.
/// </summary>
2019-01-31 15:09:31 +11:00
/// <param name="alias">The macro alias.</param>
2015-02-18 17:14:55 +01:00
/// <param name="parameters">The parameters.</param>
2019-01-31 15:09:31 +11:00
/// <param name="content">The content used for macro rendering</param>
2015-02-18 17:14:55 +01:00
/// <returns></returns>
2019-01-31 15:09:31 +11:00
private IHtmlString RenderMacro ( string alias , IDictionary < string , object > parameters , IPublishedContent content )
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
if ( content = = null ) throw new ArgumentNullException ( nameof ( content ) ) ;
2015-02-18 17:14:55 +01:00
2019-01-31 15:09:31 +11:00
// TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method looks for a lower case match. the whole macro concept needs to be rewritten.
//NOTE: the value could have HTML encoded values, so we need to deal with that
var macroProps = parameters . ToDictionary (
x = > x . Key . ToLowerInvariant ( ) ,
i = > ( i . Value is string ) ? HttpUtility . HtmlDecode ( i . Value . ToString ( ) ) : i . Value ) ;
var macroControl = _macroRenderer . Render ( alias , content , macroProps ) . GetAsControl ( ) ;
2015-02-18 17:14:55 +01:00
string html ;
2019-01-31 15:09:31 +11:00
if ( macroControl is LiteralControl control )
2015-02-18 17:14:55 +01:00
{
// no need to execute, we already have text
2019-01-31 15:09:31 +11:00
html = control . Text ;
2015-02-18 17:14:55 +01:00
}
else
{
2019-01-31 15:09:31 +11:00
using ( var containerPage = new FormlessPage ( ) )
2015-02-18 17:14:55 +01:00
{
2019-01-31 15:09:31 +11:00
containerPage . Controls . Add ( macroControl ) ;
using ( var output = new StringWriter ( ) )
{
// .Execute() does a PushTraceContext/PopTraceContext and writes trace output straight into 'output'
// and I do not see how we could wire the trace context to the current context... so it creates dirty
// trace output right in the middle of the page.
//
// The only thing we can do is fully disable trace output while .Execute() runs and restore afterwards
// which means trace output is lost if the macro is a control (.ascx or user control) that is invoked
// from within Razor -- which makes sense anyway because the control can _not_ run correctly from
// within Razor since it will never be inserted into the page pipeline (which may even not exist at all
// if we're running MVC).
//
// I'm sure there's more things that will get lost with this context changing but I guess we'll figure
// those out as we go along. One thing we lose is the content type response output.
// http://issues.umbraco.org/issue/U4-1599 if it is setup during the macro execution. So
// here we'll save the content type response and reset it after execute is called.
var contentType = _umbracoContextAccessor . UmbracoContext . HttpContext . Response . ContentType ;
var traceIsEnabled = containerPage . Trace . IsEnabled ;
containerPage . Trace . IsEnabled = false ;
_umbracoContextAccessor . UmbracoContext . HttpContext . Server . Execute ( containerPage , output , true ) ;
containerPage . Trace . IsEnabled = traceIsEnabled ;
//reset the content type
_umbracoContextAccessor . UmbracoContext . HttpContext . Response . ContentType = contentType ;
//Now, we need to ensure that local links are parsed
2019-10-23 14:55:18 +11:00
html = _linkParser . EnsureInternalLinks ( output . ToString ( ) ) ;
2019-01-31 15:09:31 +11:00
}
2015-02-18 17:14:55 +01:00
}
2019-01-31 15:09:31 +11:00
2015-02-18 17:14:55 +01:00
}
return new HtmlString ( html ) ;
}
}
2017-07-20 11:21:28 +02:00
}