From 35c22f90748067aa0e7886d3be077c85fc9e92f7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 20 Oct 2020 21:30:45 +0200 Subject: [PATCH] Introduced SetViewDataAsync on UmbracoViewPage Signed-off-by: Bjarke Berg --- .../Views/UmbracoViewPageTests.cs | 378 +++++++++--------- .../AspNetCore/UmbracoViewPage.cs | 58 +++ 2 files changed, 243 insertions(+), 193 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs index 2bc8a53cc1..3b52d0701e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views @@ -49,19 +50,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views Assert.IsInstanceOf(view.Model); } - // [Test] - // public void RenderModel_ContentType1_To_ContentType2() - // { - // // everything is strongly typed, so I'm not even allowed to try and set the ViewData with the wrong type - // var content = new ContentType1(null); - // var model = new ContentModel(content); - // var view = new ContentType2TestPage(); - // var viewData = new ViewDataDictionary(model); - // - // view.ViewContext = GetViewContext(); - // - // Assert.Throws(() => view.SetViewDataX(viewData)); - // } + [Test] + public void RenderModel_ContentType1_To_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType2TestPage(); + var viewData = GetViewDataDictionary(model); + + Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + } [Test] public void RenderModel_ContentType1_To_RenderModelOf_ContentType1() @@ -90,19 +88,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views Assert.IsInstanceOf(view.Model.Content); } - // [Test] - // public void RenderModel_ContentType1_To_RenderModelOf_ContentType2() - // { - // // everything is strongly typed, so I'm not even allowed to try and create the ContentModel with the wrong type - // var content = new ContentType1(null); - // var model = new ContentModel(content); - // var view = new RenderModelOfContentType2TestPage(); - // var viewData = new ViewDataDictionary(model); - // - // view.ViewContext = GetViewContext(); - // - // Assert.Throws(() => view.SetViewDataX(viewData)); - // } + [Test] + public void RenderModel_ContentType1_To_RenderModelOf_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType2TestPage(); + var viewData = GetViewDataDictionary(model); + + Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + } #endregion @@ -121,189 +116,173 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views Assert.AreSame(model, view.Model); } - // [Test] - // public void RenderModelOf_ContentType1_To_ContentType1() - // { - // // Can't create viewdata with ContentType1 from ContentModel because it doesn't actually inherit ContentType1 - // // And can't set viewdata from ContentModel because the page expects ContentType1 and not ContentModel - // // And if I change it the test will be the same as RenderModel_ContentType1_To_ContentType1 - // var content = new ContentType1(null); - // var model = new ContentModel(content); - // var view = new ContentType1TestPage(); - // var viewData = GetViewDataDictionary>(model); - // - // view.ViewData = viewData; - // - // Assert.IsInstanceOf(view.Model); - // } + [Test] + public async Task RenderModelOf_ContentType1_To_ContentType1() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType1TestPage(); + var viewData = GetViewDataDictionary>(model); - // [Test] - // public void RenderModelOf_ContentType2_To_ContentType1() - // { - // // Same issue as above test - // var content = new ContentType2(null); - // var model = new ContentModel(content); - // var view = new ContentType1TestPage(); - // var viewData = new ViewDataDictionary(model); - // - // view.ViewContext = GetViewContext(); - // view.SetViewDataX(viewData); - // - // Assert.IsInstanceOf(view.Model); - // } + await view.SetViewDataAsyncX(viewData); - // [Test] - // public void RenderModelOf_ContentType1_To_ContentType2() - // { - // // Same issue as above, and as RenderModel1_ContentType1_To_ContentType2 - // var content = new ContentType1(null); - // var model = new ContentModel(content); - // var view = new ContentType2TestPage(); - // var viewData = new ViewDataDictionary(model); - // - // view.ViewContext = GetViewContext(); - // Assert.Throws(() => view.SetViewDataX(viewData)); - // } + Assert.IsInstanceOf(view.Model); + } - // [Test] - // public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType1() - // { - // // It's the same as RenderModel_ContentType1_To_RenderModelOf_ContentType1 - // var content = new ContentType1(null); - // var model = new ContentModel(content); - // var view = new RenderModelOfContentType1TestPage(); - // var viewData = GetViewDataDictionary>(model); - // - // view.ViewData = viewData; - // - // Assert.IsInstanceOf>(view.Model); - // Assert.IsInstanceOf(view.Model.Content); - // } + [Test] + public async Task RenderModelOf_ContentType2_To_ContentType1() + { + var content = new ContentType2(null); + var model = new ContentModel(content); + var view = new ContentType1TestPage(); + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) + { + Model = model + }; - // [Test] - // public void RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() - // { - // // Same as RenderModel_ContentType2_To_RenderModelOf_ContentType1 after merge - // var content = new ContentType2(null); - // var model = new ContentModel(content); - // var view = new RenderModelOfContentType1TestPage(); - // var viewData = GetViewDataDictionary>(model); - // - // view.ViewData = viewData; - // - // Assert.IsInstanceOf>(view.Model); - // Assert.IsInstanceOf(view.Model.Content); - // } + await view.SetViewDataAsyncX(viewData); - // [Test] - // public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType2() - // { - // // Same issue as RenderModel1_ContentType1_To_ContentType2 - // var content = new ContentType1(null); - // var model = new ContentModel(content); - // var view = new RenderModelOfContentType2TestPage(); - // var viewData = new ViewDataDictionary(model); - // - // view.ViewContext = GetViewContext(); - // Assert.Throws(() => view.SetViewDataX(viewData)); - // } + Assert.IsInstanceOf(view.Model); + } + + [Test] + public async Task RenderModelOf_ContentType1_To_ContentType2() + { + + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new ContentType2TestPage(); + var viewData = GetViewDataDictionary(model); + + Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + } + + [Test] + public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType1() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType1TestPage(); + var viewData = GetViewDataDictionary>(model); + + view.ViewData = viewData; + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public async Task RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() + { + var content = new ContentType2(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType1TestPage(); + var viewData = GetViewDataDictionary>(model); + + await view.SetViewDataAsyncX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void RenderModelOf_ContentType1_To_RenderModelOf_ContentType2() + { + var content = new ContentType1(null); + var model = new ContentModel(content); + var view = new RenderModelOfContentType2TestPage(); + var viewData = GetViewDataDictionary(model); + + Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + } #endregion #region ContentType To ... - // [Test] - // public void ContentType1_To_RenderModel() - // { - // // ContentType1 cannot be sat as ViewData because ContentModel is expected - // var content = new ContentType1(null); - // var view = new RenderModelTestPage(); - // - // var viewData = GetViewDataDictionary(content); - // - // view.ViewData = viewData; - // - // Assert.IsInstanceOf(view.Model); - // } + [Test] + public async Task ContentType1_To_RenderModel() + { + var content = new ContentType1(null); + var view = new RenderModelTestPage(); - // [Test] - // public void ContentType1_To_RenderModelOf_ContentType1() - // { - // // same as above but with ContentModel instead of ContentModel - // var content = new ContentType1(null); - // var view = new RenderModelOfContentType1TestPage(); - // - // var viewData = GetViewDataDictionary(content); - // view.ViewData = viewData; - // - // Assert.IsInstanceOf>(view.Model); - // Assert.IsInstanceOf(view.Model.Content); - // } + var viewData = GetViewDataDictionary(content); - // [Test] - // public void ContentType2_To_RenderModelOf_ContentType1() - // { - // // Same as above but with ContentModel - // var content = new ContentType2(null); - // var view = new RenderModelOfContentType1TestPage(); - // var viewData = GetViewDataDictionary(content); - // - // view.ViewData = viewData; - // - // Assert.IsInstanceOf>(view.Model); - // Assert.IsInstanceOf(view.Model.Content); - // } + await view.SetViewDataAsyncX(viewData); - // [Test] - // public void ContentType1_To_RenderModelOf_ContentType2() - // { - // // Same as above, and as RenderModel1_ContentType1_To_ContentType2 - // var content = new ContentType1(null); - // var view = new RenderModelOfContentType2TestPage(); - // var viewData = new ViewDataDictionary(content); - // - // view.ViewContext = GetViewContext(); - // Assert.Throws(() =>view.SetViewDataX(viewData)); - // } + Assert.IsInstanceOf(view.Model); + } - // [Test] - // public void ContentType1_To_ContentType1() - // { - // // Same as ContentType1_To_ContentType1 - // var content = new ContentType1(null); - // var view = new ContentType1TestPage(); - // var viewdata = GetViewDataDictionary(content); - // - // view.ViewData = viewdata; - // - // Assert.IsInstanceOf(view.Model); - // } + [Test] + public async Task ContentType1_To_RenderModelOf_ContentType1() + { + var content = new ContentType1(null); + var view = new RenderModelOfContentType1TestPage(); - // [Test] - // public void ContentType1_To_ContentType2() - // { - // // Same issue as RenderModel1_ContentType1_To_ContentType2 - // var content = new ContentType1(null); - // var view = new ContentType2TestPage(); - // var viewData = new ViewDataDictionary(content); - // - // view.ViewContext = GetViewContext(); - // Assert.Throws(() => view.SetViewDataX(viewData)); - // } + var viewData = GetViewDataDictionary(content); + await view.SetViewDataAsyncX(viewData); - // [Test] - // public void ContentType2_To_ContentType1() - // { - // // Will be the same as RenderModel_ContentType2_To_ContentType1 after merge - // var content = new ContentType2(null); - // var view = new ContentType1TestPage(); - // var viewData = new ViewDataDictionary(content); - // - // view.ViewContext = GetViewContext(); - // view.SetViewDataX(viewData); - // - // Assert.IsInstanceOf(view.Model); - // } + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public async Task ContentType2_To_RenderModelOf_ContentType1() + { + // Same as above but with ContentModel + var content = new ContentType2(null); + var view = new RenderModelOfContentType1TestPage(); + var viewData = GetViewDataDictionary(content); + + await view.SetViewDataAsyncX(viewData); + + Assert.IsInstanceOf>(view.Model); + Assert.IsInstanceOf(view.Model.Content); + } + + [Test] + public void ContentType1_To_RenderModelOf_ContentType2() + { + var content = new ContentType1(null); + var view = new RenderModelOfContentType2TestPage(); + var viewData = GetViewDataDictionary(content); + + Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + } + + [Test] + public async Task ContentType1_To_ContentType1() + { + var content = new ContentType1(null); + var view = new ContentType1TestPage(); + var viewData = GetViewDataDictionary(content); + + await view.SetViewDataAsyncX(viewData); + + Assert.IsInstanceOf(view.Model); + } + + [Test] + public void ContentType1_To_ContentType2() + { + var content = new ContentType1(null); + var view = new ContentType2TestPage(); + var viewData = GetViewDataDictionary(content); + + Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + } + + [Test] + public async Task ContentType2_To_ContentType1() + { + var content = new ContentType2(null); + var view = new ContentType1TestPage(); + var viewData = GetViewDataDictionary(content); + + await view.SetViewDataAsyncX(viewData); + + Assert.IsInstanceOf(view.Model); + } #endregion @@ -315,6 +294,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views return new ViewDataDictionary(sourceViewDataDictionary, model); } + private ViewDataDictionary GetViewDataDictionary(object model) + { + var sourceViewDataDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + return new ViewDataDictionary(sourceViewDataDictionary) + { + Model = model + }; + } #endregion @@ -336,6 +323,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views { throw new NotImplementedException(); } + + public async Task SetViewDataAsyncX(ViewDataDictionary viewData) + { + await SetViewDataAsync(viewData); + } } public class RenderModelTestPage : TestPage diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs index fde3d095fe..cf41670d8e 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs @@ -1,7 +1,10 @@ using System; using System.Text; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -11,6 +14,7 @@ using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; +using Umbraco.Web.Common.ModelBinders; namespace Umbraco.Web.Common.AspNetCore { @@ -29,6 +33,7 @@ namespace Umbraco.Web.Common.AspNetCore private ContentSettings ContentSettings => Context.RequestServices.GetRequiredService>().Value; private IProfilerHtml ProfilerHtml => Context.RequestServices.GetRequiredService(); private IIOHelper IOHelper => Context.RequestServices.GetRequiredService(); + private ContentModelBinder ContentModelBinder => new ContentModelBinder(); protected IUmbracoContext UmbracoContext => _umbracoContext ??= UmbracoContextAccessor.UmbracoContext; @@ -85,5 +90,58 @@ namespace Umbraco.Web.Common.AspNetCore base.WriteLiteral(value); } + + // maps model + protected async Task SetViewDataAsync(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 + var bindingContext = new DefaultModelBindingContext(); + await ContentModelBinder.BindModelAsync(bindingContext, viewDataModel, typeof (TModel)); + + viewData.Model = bindingContext.Result.Model; + + // set the view data + ViewData = (ViewDataDictionary) 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 (viewDataType.IsGenericType) + { + // 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 or it is not generic 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; + } } }