* Add Umbraco specific global usings * Enable implicit usings * v10: Wait for updated ConnectionStrings during install (#12536) * Do not change/reload configuration * Wait for updated connection string options * recase assigndomain (#12448) * Add depth property to ICoreScope (#12540) * Remove ambient scope stack from httpcontext.items. (#12539) This change makes it easier to use service calls in parallel whilst a httpcontext is available. * v10: Prefer SQLite primitive types to flexible types (#12541) * Prefer SQLite primitive types to flexible types. * SQLite - column mappings use TEXT for decimals Thanks @mattbrailsford for sense check. * Fix issue where languages files are not found in subdir of package dir (#12543) * Make FindContent return type nullable (#12545) * Updated nuget dependencies (07-06-2022) (#12525) * Updated nuget dependencies * Move Nerdbank.GitVersioning update to Directory.Build.props * Updated more dependencies * Improve FlagOutOfDateModels property behaviour. (cherry picked from commit 54077725c373495fce0d3fbc5cdb6469aad3b676) * Fix logic error WRT models builder flag out of date models. (#12548) (cherry picked from commit 6b0149803a879d1c6902a5f61d1f2e9dc8545aac) * Fixed issue with expected null value. (#12550) Fixes https://github.com/umbraco/Umbraco-CMS/issues/12526 * Updated Examine to 3.0.0 * Fixes relation issue, when moving a root item to recycle bin, the "Relate Parent Media Folder On Delete"/"Relate Parent Document On Delete" cannot get the parent node type, because it is a fake root. * Fix possible null error * Bump version to 10.0.0 final * Fix attempting to write lock files to LocalTempPath before it exists (#12563) * Re fix usage statements Co-authored-by: Ronald Barendse <ronald@barend.se> Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Co-authored-by: Paul Johnson <pmj@umbraco.com> Co-authored-by: Bjarke Berg <mail@bergmania.dk>
262 lines
9.3 KiB
C#
262 lines
9.3 KiB
C#
using Microsoft.AspNetCore.Html;
|
|
using Microsoft.AspNetCore.Http.Extensions;
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
using Microsoft.AspNetCore.Mvc.Razor;
|
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
|
using Umbraco.Cms.Core.Logging;
|
|
using Umbraco.Cms.Core.Models;
|
|
using Umbraco.Cms.Core.Models.PublishedContent;
|
|
using Umbraco.Cms.Core.Strings;
|
|
using Umbraco.Cms.Core.Web;
|
|
using Umbraco.Cms.Web.Common.ModelBinders;
|
|
using Umbraco.Extensions;
|
|
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
|
|
|
namespace Umbraco.Cms.Web.Common.Views;
|
|
|
|
public abstract class UmbracoViewPage : UmbracoViewPage<IPublishedContent>
|
|
{
|
|
}
|
|
|
|
public abstract class UmbracoViewPage<TModel> : RazorPage<TModel>
|
|
{
|
|
private UmbracoHelper? _helper;
|
|
|
|
/// <summary>
|
|
/// Gets the Umbraco helper.
|
|
/// </summary>
|
|
public UmbracoHelper Umbraco
|
|
{
|
|
get
|
|
{
|
|
if (_helper != null)
|
|
{
|
|
return _helper;
|
|
}
|
|
|
|
TModel model = ViewData.Model;
|
|
var content = model as IPublishedContent;
|
|
if (content is null && model is IContentModel contentModel)
|
|
{
|
|
content = contentModel.Content;
|
|
}
|
|
|
|
if (content is null)
|
|
{
|
|
content = UmbracoContext?.PublishedRequest?.PublishedContent;
|
|
}
|
|
|
|
_helper = Context.RequestServices.GetRequiredService<UmbracoHelper>();
|
|
|
|
if (!(content is null))
|
|
{
|
|
_helper.AssignedContentItem = content;
|
|
}
|
|
|
|
return _helper;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override ViewContext ViewContext
|
|
{
|
|
get => base.ViewContext;
|
|
set
|
|
{
|
|
// Here we do the magic model swap
|
|
ViewContext ctx = value;
|
|
ctx.ViewData = BindViewData(
|
|
ctx.HttpContext.RequestServices.GetRequiredService<ContentModelBinder>(),
|
|
ctx.ViewData);
|
|
base.ViewContext = ctx;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="IUmbracoContext" />
|
|
/// </summary>
|
|
protected IUmbracoContext? UmbracoContext
|
|
{
|
|
get
|
|
{
|
|
if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return umbracoContext;
|
|
}
|
|
}
|
|
|
|
private IUmbracoContextAccessor UmbracoContextAccessor =>
|
|
Context.RequestServices.GetRequiredService<IUmbracoContextAccessor>();
|
|
|
|
private GlobalSettings GlobalSettings =>
|
|
Context.RequestServices.GetRequiredService<IOptions<GlobalSettings>>().Value;
|
|
|
|
private ContentSettings ContentSettings =>
|
|
Context.RequestServices.GetRequiredService<IOptions<ContentSettings>>().Value;
|
|
|
|
private IProfilerHtml ProfilerHtml => Context.RequestServices.GetRequiredService<IProfilerHtml>();
|
|
|
|
private IHostingEnvironment HostingEnvironment => Context.RequestServices.GetRequiredService<IHostingEnvironment>();
|
|
|
|
/// <inheritdoc />
|
|
public override void Write(object? value)
|
|
{
|
|
if (value is IHtmlEncodedString htmlEncodedString)
|
|
{
|
|
WriteLiteral(htmlEncodedString.ToHtmlString());
|
|
}
|
|
else if (value is TagHelperOutput tagHelperOutput)
|
|
{
|
|
WriteUmbracoContent(tagHelperOutput);
|
|
base.Write(value);
|
|
}
|
|
else
|
|
{
|
|
base.Write(value);
|
|
}
|
|
}
|
|
|
|
public void WriteUmbracoContent(TagHelperOutput tagHelperOutput)
|
|
{
|
|
// filter / add preview banner
|
|
// ASP.NET default value is text/html
|
|
if (Context.Response?.ContentType?.InvariantContains("text/html") ?? false)
|
|
{
|
|
if (((UmbracoContext?.IsDebug ?? false) || (UmbracoContext?.InPreviewMode ?? false))
|
|
&& tagHelperOutput.TagName != null
|
|
&& tagHelperOutput.TagName.Equals("body", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
string markupToInject;
|
|
|
|
if (UmbracoContext.InPreviewMode)
|
|
{
|
|
// creating previewBadge markup
|
|
markupToInject =
|
|
string.Format(
|
|
ContentSettings.PreviewBadge,
|
|
HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoPath),
|
|
Context.Request.GetEncodedUrl(),
|
|
UmbracoContext.PublishedRequest?.PublishedContent?.Id);
|
|
}
|
|
else
|
|
{
|
|
// creating mini-profiler markup
|
|
markupToInject = ProfilerHtml.Render();
|
|
}
|
|
|
|
tagHelperOutput.Content.AppendHtml(markupToInject);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders a section with default content if the section isn't defined
|
|
/// </summary>
|
|
public HtmlString? RenderSection(string name, string defaultContents) =>
|
|
RazorPageExtensions.RenderSection(this, name, defaultContents);
|
|
|
|
/// <summary>
|
|
/// Renders a section with default content if the section isn't defined
|
|
/// </summary>
|
|
public HtmlString? RenderSection(string name, HtmlString defaultContents) =>
|
|
RazorPageExtensions.RenderSection(this, name, defaultContents);
|
|
|
|
/// <summary>
|
|
/// Dynamically binds the incoming <see cref="ViewDataDictionary" /> to the required
|
|
/// <see cref="ViewDataDictionary{TModel}" />
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is used in order to provide the ability for an Umbraco view to either have a model of type
|
|
/// <see cref="IContentModel" /> or <see cref="IPublishedContent" />. This will use the
|
|
/// <see cref="ContentModelBinder" /> to bind the models
|
|
/// to the correct output type.
|
|
/// </remarks>
|
|
protected ViewDataDictionary BindViewData(ContentModelBinder contentModelBinder, ViewDataDictionary? viewData)
|
|
{
|
|
if (contentModelBinder is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(contentModelBinder));
|
|
}
|
|
|
|
if (viewData is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(viewData));
|
|
}
|
|
|
|
// check if it's already the correct type and continue if it is
|
|
if (viewData is ViewDataDictionary<TModel> vdd)
|
|
{
|
|
return vdd;
|
|
}
|
|
|
|
// Here we hand the default case where we know the incoming model is ContentModel and the
|
|
// outgoing model is IPublishedContent. This is a fast conversion that doesn't require doing the full
|
|
// model binding, allocating classes, etc...
|
|
if (viewData.ModelMetadata.ModelType == typeof(ContentModel)
|
|
&& typeof(TModel) == typeof(IPublishedContent))
|
|
{
|
|
var contentModel = (ContentModel?)viewData.Model;
|
|
viewData.Model = contentModel?.Content;
|
|
return 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
|
|
var bindingContext = new DefaultModelBindingContext();
|
|
contentModelBinder.BindModel(bindingContext, viewDataModel, typeof(TModel));
|
|
|
|
viewData!.Model = bindingContext.Result.Model;
|
|
|
|
// return the new view data
|
|
return (ViewDataDictionary<TModel>)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)
|
|
{
|
|
Type viewDataType = viewData.GetType();
|
|
|
|
if (viewDataType.IsGenericType)
|
|
{
|
|
// ensure it is the proper generic type
|
|
Type 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
|
|
Type viewDataModelType = viewDataType.GenericTypeArguments[0];
|
|
|
|
if (viewDataModelType != typeof(object) && viewDataModelType.IsAssignableFrom(modelType))
|
|
{
|
|
return viewData;
|
|
}
|
|
}
|
|
|
|
// if not possible or it is not generic then we need to create a new ViewDataDictionary
|
|
Type nViewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
|
|
var tViewData = new ViewDataDictionary(viewData) { Model = default(TModel) }; // temp view data to copy values
|
|
var nViewData = (ViewDataDictionary?)Activator.CreateInstance(nViewDataType, tViewData);
|
|
return nViewData;
|
|
}
|
|
}
|