using System; using System.Text; using System.Web; using System.Web.Mvc; using System.Web.WebPages; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Mvc { /// /// Represents the properties and methods that are needed in order to render an Umbraco view. /// public abstract class UmbracoViewPage : WebViewPage { private UmbracoContext _umbracoContext; private UmbracoHelper _helper; /// /// Gets or sets the database context. /// public ServiceContext Services { get; set; } /// /// Gets or sets the application cache. /// public AppCaches AppCaches { get; set; } // TODO: previously, Services and ApplicationCache would derive from UmbracoContext.Application, which // was an ApplicationContext - so that everything derived from UmbracoContext. // UmbracoContext is fetched from the data tokens, thus allowing the view to be rendered with a // custom context and NOT the Current.UmbracoContext - eg outside the normal Umbraco routing // process. // leaving it as-it for the time being but really - the UmbracoContext should be injected just // like the Services & ApplicationCache properties, and have a setter for those special weird // cases. /// /// Gets the Umbraco context. /// public UmbracoContext UmbracoContext => _umbracoContext ?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); /// /// Gets the public content request. /// internal PublishedRequest PublishedRequest { get { const string token = Core.Constants.Web.PublishedDocumentRequestDataToken; // we should always try to return the object from the data tokens just in case its a custom object and not // the one from UmbracoContext. Fallback to UmbracoContext if necessary. // try view context if (ViewContext.RouteData.DataTokens.ContainsKey(token)) return (PublishedRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(token); // child action, try parent view context if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(token)) return (PublishedRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(token); // fallback to UmbracoContext return UmbracoContext.PublishedRequest; } } /// /// Gets the Umbraco helper. /// public UmbracoHelper Umbraco { get { if (_helper != null) return _helper; var model = ViewData.Model; var content = model as IPublishedContent; if (content == null && model is IContentModel) content = ((IContentModel) model).Content; _helper = Current.UmbracoHelper; if (content != null) _helper.AssignedContentItem = content; return _helper; } } /// /// Gets the membership helper. /// public MembershipHelper Members => Umbraco.MembershipHelper; protected UmbracoViewPage() : this( Current.Factory.GetInstance(), Current.Factory.GetInstance() ) { } protected UmbracoViewPage(ServiceContext services, AppCaches appCaches) { Services = services; AppCaches = appCaches; } // view logic below: /// /// Ensure that the current view context is added to the route data tokens so we can extract it if we like /// /// /// Currently this is required by mvc macro engines /// protected override void InitializePage() { base.InitializePage(); if (ViewContext.IsChildAction) return; // this is used purely for partial view macros that contain forms and mostly // just when rendered within the RTE - this should already be set with the // EnsurePartialViewMacroViewContextFilterAttribute if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext) == false) ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, ViewContext); } // maps model protected override void SetViewData(ViewDataDictionary viewData) { // capture the model before we tinker with the viewData var viewDataModel = viewData.Model; // map the view data (may change its type, may set model to null) viewData = MapViewDataDictionary(viewData, typeof (TModel)); // bind the model viewData.Model = ContentModelBinder.BindModel(viewDataModel, typeof (TModel)); // set the view data base.SetViewData(viewData); } // viewData is the ViewDataDictionary (maybe ) that we have // modelType is the type of the model that we need to bind to // // figure out whether viewData can accept modelType else replace it // private static ViewDataDictionary MapViewDataDictionary(ViewDataDictionary viewData, Type modelType) { var viewDataType = viewData.GetType(); // if viewData is not generic then it is a simple ViewDataDictionary instance and its // Model property is of type 'object' and will accept anything, so it is safe to use // viewData if (viewDataType.IsGenericType == false) return viewData; // ensure it is the proper generic type var def = viewDataType.GetGenericTypeDefinition(); if (def != typeof(ViewDataDictionary<>)) throw new Exception("Could not map viewData of type \"" + viewDataType.FullName + "\"."); // get the viewData model type and compare with the actual view model type: // viewData is ViewDataDictionary and we will want to assign an // object of type modelType to the Model property of type viewDataModelType, we // need to check whether that is possible var viewDataModelType = viewDataType.GenericTypeArguments[0]; if (viewDataModelType.IsAssignableFrom(modelType)) return viewData; // if not possible then we need to create a new ViewDataDictionary var nViewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); var tViewData = new ViewDataDictionary(viewData) { Model = null }; // temp view data to copy values var nViewData = (ViewDataDictionary)Activator.CreateInstance(nViewDataType, tViewData); return nViewData; } /// /// This will detect the end /body tag and insert the preview badge if in preview mode /// /// public override void WriteLiteral(object value) { // filter / add preview banner if (Response.ContentType.InvariantEquals("text/html")) // ASP.NET default value { if (Current.UmbracoContext.IsDebug || Current.UmbracoContext.InPreviewMode) { var text = value.ToString(); var pos = text.IndexOf("", StringComparison.InvariantCultureIgnoreCase); if (pos > -1) { string markupToInject; if (Current.UmbracoContext.InPreviewMode) { // creating previewBadge markup markupToInject = string.Format(Current.Configs.Settings().Content.PreviewBadge, IOHelper.ResolveUrl(SystemDirectories.Umbraco), Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Url?.PathAndQuery), Current.UmbracoContext.PublishedRequest.PublishedContent.Id); } else { // creating mini-profiler markup markupToInject = Html.RenderProfiler().ToHtmlString(); } var sb = new StringBuilder(text); sb.Insert(pos, markupToInject); base.WriteLiteral(sb.ToString()); return; } } } base.WriteLiteral(value); } public HelperResult RenderSection(string name, Func defaultContents) { return WebViewPageExtensions.RenderSection(this, name, defaultContents); } public HelperResult RenderSection(string name, HelperResult defaultContents) { return WebViewPageExtensions.RenderSection(this, name, defaultContents); } public HelperResult RenderSection(string name, string defaultContents) { return WebViewPageExtensions.RenderSection(this, name, defaultContents); } public HelperResult RenderSection(string name, IHtmlString defaultContents) { return WebViewPageExtensions.RenderSection(this, name, defaultContents); } } }