Files
Umbraco-CMS/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs
2018-06-29 19:52:40 +02:00

250 lines
10 KiB
C#

using System;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages;
using LightInject;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
namespace Umbraco.Web.Mvc
{
/// <summary>
/// Represents the properties and methods that are needed in order to render an Umbraco view.
/// </summary>
public abstract class UmbracoViewPage<TModel> : WebViewPage<TModel>
{
private UmbracoContext _umbracoContext;
private UmbracoHelper _helper;
// note
// properties marked as [Inject] below will be property-injected (vs constructor-injected) since
// we have no control over the view constructor (generated by eg the Razor engine).
// this means that these properties have a setter.
// what can go wrong?
/// <summary>
/// Gets or sets the database context.
/// </summary>
[Inject]
public ServiceContext Services { get; set; }
/// <summary>
/// Gets or sets the application cache.
/// </summary>
[Inject]
public CacheHelper ApplicationCache { get; set; }
// fixme
// 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.
/// <summary>
/// Gets the Umbraco context.
/// </summary>
public UmbracoContext UmbracoContext => _umbracoContext
?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext);
/// <summary>
/// Gets the public content request.
/// </summary>
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;
}
}
/// <summary>
/// Gets the Umbraco helper.
/// </summary>
public virtual 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 = content == null
? new UmbracoHelper(UmbracoContext, Services, ApplicationCache)
: new UmbracoHelper(UmbracoContext, Services, ApplicationCache, content);
return _helper;
}
}
/// <summary>
/// Gets the membership helper.
/// </summary>
public MembershipHelper Members => Umbraco.MembershipHelper;
// view logic below:
/// <summary>
/// Ensure that the current view context is added to the route data tokens so we can extract it if we like
/// </summary>
/// <remarks>
/// Currently this is required by mvc macro engines
/// </remarks>
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 <TModel>) 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<viewDataModelType> 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;
}
/// <summary>
/// This will detect the end /body tag and insert the preview badge if in preview mode
/// </summary>
/// <param name="value"></param>
public override void WriteLiteral(object value)
{
// filter / add preview banner
if (Response.ContentType.InvariantEquals("text/html")) // ASP.NET default value
{
if (UmbracoContext.Current.IsDebug || UmbracoContext.Current.InPreviewMode)
{
var text = value.ToString();
var pos = text.IndexOf("</body>", StringComparison.InvariantCultureIgnoreCase);
if (pos > -1)
{
string markupToInject;
if (UmbracoContext.Current.InPreviewMode)
{
// creating previewBadge markup
markupToInject =
string.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge,
IOHelper.ResolveUrl(SystemDirectories.Umbraco),
Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path));
}
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<dynamic, HelperResult> 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);
}
}
}