using System; using System.Collections; using System.Globalization; using System.IO; using System.Web; using System.Web.UI; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Templates; using umbraco; using System.Collections.Generic; using umbraco.presentation.templateControls; using Umbraco.Core.Exceptions; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Composing; using Umbraco.Web.Macros; namespace Umbraco.Web { /// /// Methods used to render umbraco components as HTML in templates /// /// /// Used by UmbracoHelper /// internal class UmbracoComponentRenderer : IUmbracoComponentRenderer { private readonly UmbracoContext _umbracoContext; public UmbracoComponentRenderer(UmbracoContext umbracoContext) { _umbracoContext = umbracoContext; } /// /// Renders the template for the specified pageId and an optional altTemplateId /// /// /// If not specified, will use the template assigned to the node /// public IHtmlString RenderTemplate(int pageId, int? altTemplateId = null) { var templateRenderer = new TemplateRenderer(_umbracoContext, pageId, altTemplateId); using (var sw = new StringWriter()) { try { templateRenderer.Render(sw); } catch (Exception ex) { sw.Write("", pageId, ex); } return new HtmlString(sw.ToString()); } } /// /// Renders the macro with the specified alias. /// /// The alias. /// public IHtmlString RenderMacro(string alias) { return RenderMacro(alias, new { }); } /// /// Renders the macro with the specified alias, passing in the specified parameters. /// /// The alias. /// The parameters. /// public IHtmlString RenderMacro(string alias, object parameters) { return RenderMacro(alias, parameters.ToDictionary()); } /// /// Renders the macro with the specified alias, passing in the specified parameters. /// /// The alias. /// The parameters. /// public IHtmlString RenderMacro(string alias, IDictionary parameters) { if (_umbracoContext.PublishedRequest == null) { throw new InvalidOperationException("Cannot render a macro when there is no current PublishedContentRequest."); } return RenderMacro(alias, parameters, _umbracoContext.PublishedRequest.UmbracoPage); } /// /// Renders the macro with the specified alias, passing in the specified parameters. /// /// The alias. /// The parameters. /// The legacy umbraco page object that is required for some macros /// internal IHtmlString RenderMacro(string alias, IDictionary parameters, page umbracoPage) { if (alias == null) throw new ArgumentNullException("alias"); if (umbracoPage == null) throw new ArgumentNullException("umbracoPage"); var m = Current.Services.MacroService.GetByAlias(alias); if (m == null) throw new KeyNotFoundException("Could not find macro with alias " + alias); return RenderMacro(new MacroModel(m), parameters, umbracoPage); } /// /// Renders the macro with the specified alias, passing in the specified parameters. /// /// The macro. /// The parameters. /// The legacy umbraco page object that is required for some macros /// internal IHtmlString RenderMacro(MacroModel m, IDictionary parameters, page umbracoPage) { if (umbracoPage == null) throw new ArgumentNullException(nameof(umbracoPage)); if (m == null) throw new ArgumentNullException(nameof(m)); if (_umbracoContext.PageId == null) { throw new InvalidOperationException("Cannot render a macro when UmbracoContext.PageId is null."); } var macroProps = new Hashtable(); foreach (var i in parameters) { //TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method of macro.cs // looks for a lower case match. WTF. the whole macro concept needs to be rewritten. //NOTE: the value could have html encoded values, so we need to deal with that macroProps.Add(i.Key.ToLowerInvariant(), (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); } var renderer = new MacroRenderer(Current.ProfilingLogger); var macroControl = renderer.Render(m, umbracoPage.Elements, _umbracoContext.PageId.Value, macroProps).GetAsControl(); string html; if (macroControl is LiteralControl) { // no need to execute, we already have text html = (macroControl as LiteralControl).Text; } else { var containerPage = new FormlessPage(); 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 = _umbracoContext.HttpContext.Response.ContentType; var traceIsEnabled = containerPage.Trace.IsEnabled; containerPage.Trace.IsEnabled = false; _umbracoContext.HttpContext.Server.Execute(containerPage, output, true); containerPage.Trace.IsEnabled = traceIsEnabled; //reset the content type _umbracoContext.HttpContext.Response.ContentType = contentType; //Now, we need to ensure that local links are parsed html = TemplateUtilities.ParseInternalLinks(output.ToString(), _umbracoContext.UrlProvider); } } return new HtmlString(html); } } }