using System; using System.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Mvc { /// /// Used for custom routed pages that are being integrated with the Umbraco data but are not /// part of the umbraco request pipeline. This allows umbraco macros to be able to execute in this scenario. /// /// /// This is inspired from this discussion: /// https://our.umbraco.com/forum/developers/extending-umbraco/41367-Umbraco-6-MVC-Custom-MVC-Route?p=3 /// /// which is based on custom routing found here: /// http://shazwazza.com/post/Custom-MVC-routing-in-Umbraco /// public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute { private readonly string _dataTokenName; private IUmbracoContextAccessor _umbracoContextAccessor; private readonly int? _contentId; private UmbracoHelper _helper; /// /// Constructor - can be used for testing /// /// /// public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _contentId = contentId; } /// /// A constructor used to set an explicit content Id to the PublishedRequest that will be created /// /// public EnsurePublishedContentRequestAttribute(int contentId) { _contentId = contentId; } /// /// A constructor used to set the data token key name that contains a reference to a PublishedContent instance /// /// public EnsurePublishedContentRequestAttribute(string dataTokenName) { _dataTokenName = dataTokenName; } /// /// Constructor - can be used for testing /// /// /// public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _dataTokenName = dataTokenName; } /// /// Exposes the UmbracoContext /// protected UmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext; // TODO: try lazy property injection? private IPublishedRouter PublishedRouter => Core.Composing.Current.Factory.GetInstance(); /// /// Exposes an UmbracoHelper /// protected UmbracoHelper Umbraco => _helper ?? (_helper = Current.Factory.GetInstance()); public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything if (Current.UmbracoContext.PublishedRequest != null) { return; } Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext); ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext); } /// /// This assigns the published content to the request, developers can override this to specify /// any other custom attributes required. /// /// /// protected virtual void ConfigurePublishedContentRequest(PublishedRequest request, ActionExecutedContext filterContext) { if (_contentId.HasValue) { var content = Umbraco.Content(_contentId); if (content == null) { throw new InvalidOperationException("Could not resolve content with id " + _contentId); } request.PublishedContent = content; } else if (_dataTokenName.IsNullOrWhiteSpace() == false) { var result = filterContext.RouteData.DataTokens[_dataTokenName]; if (result == null) { throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName); } if (result is IPublishedContent == false) { throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent)); } request.PublishedContent = (IPublishedContent)result; } PublishedRouter.PrepareRequest(request); } } }