// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor { public class RazorPageCreateModelExpressionTest { public static TheoryData IdentityExpressions { get { return new TheoryData, string> { // m => m { page => page.CreateModelExpression1(), string.Empty }, // m => Model { page => page.CreateModelExpression2(), string.Empty }, }; } } public static TheoryData NotQuiteIdentityExpressions { get { return new TheoryData, string, Type> { // m => m.Model { page => page.CreateModelExpression1(), "Model", typeof(RecursiveModel) }, // m => ViewData.Model { page => page.CreateModelExpression2(), "ViewData.Model", typeof(RecursiveModel) }, // m => ViewContext.ViewData.Model // This property has type object because ViewData is not exposed as ViewDataDictionary. { page => page.CreateModelExpression3(), "ViewContext.ViewData.Model", typeof(object) }, }; } } public static TheoryData>, string> IntExpressions { get { var somethingElse = 23; return new TheoryData>, string> { { model => somethingElse, "somethingElse" }, { model => model.Id, "Id" }, { model => model.SubModel.Id, "SubModel.Id" }, { model => model.SubModel.SubSubModel.Id, "SubModel.SubSubModel.Id" }, }; } } public static TheoryData>, string> StringExpressions { get { var somethingElse = "This is something else"; return new TheoryData>, string> { { model => somethingElse, "somethingElse" }, { model => model.Name, "Name" }, { model => model.SubModel.Name, "SubModel.Name" }, { model => model.SubModel.SubSubModel.Name, "SubModel.SubSubModel.Name" }, }; } } [Theory] [MemberData(nameof(IdentityExpressions))] public void CreateModelExpression_ReturnsExpectedMetadata_IdentityExpressions( Func createModelExpression, string expectedName) { // Arrange var viewContext = CreateViewContext(); var modelExplorer = viewContext.ViewData.ModelExplorer.GetExplorerForProperty( nameof(RazorPageCreateModelExpressionModel.Name)); var viewData = new ViewDataDictionary(viewContext.ViewData) { ModelExplorer = modelExplorer, }; viewContext.ViewData = viewData; var page = CreateIdentityPage(viewContext); // Act var modelExpression = createModelExpression(page); // Assert Assert.NotNull(modelExpression); Assert.Equal(expectedName, modelExpression.Name); Assert.Same(modelExplorer, modelExpression.ModelExplorer); } [Theory] [MemberData(nameof(NotQuiteIdentityExpressions))] public void CreateModelExpression_ReturnsExpectedMetadata_NotQuiteIdentityExpressions( Func createModelExpression, string expectedName, Type expectedType) { // Arrange var viewContext = CreateViewContext(); var viewData = new ViewDataDictionary(viewContext.ViewData); viewContext.ViewData = viewData; var modelExplorer = viewData.ModelExplorer; var page = CreateNotQuiteIdentityPage(viewContext); // Act var modelExpression = createModelExpression(page); // Assert Assert.NotNull(modelExpression); Assert.Equal(expectedName, modelExpression.Name); Assert.NotNull(modelExpression.ModelExplorer); Assert.NotSame(modelExplorer, modelExpression.ModelExplorer); Assert.NotNull(modelExpression.Metadata); Assert.Equal(ModelMetadataKind.Property, modelExpression.Metadata.MetadataKind); Assert.Equal(expectedType, modelExpression.Metadata.ModelType); } [Theory] [MemberData(nameof(IntExpressions))] public void CreateModelExpression_ReturnsExpectedMetadata_IntExpressions( Expression> expression, string expectedName) { // Arrange var viewContext = CreateViewContext(); var page = CreatePage(viewContext); // Act var result = page.ModelExpressionProvider.CreateModelExpression(page.ViewData, expression); // Assert Assert.NotNull(result); Assert.NotNull(result.Metadata); Assert.Equal(typeof(int), result.Metadata.ModelType); Assert.Equal(expectedName, result.Name); } [Theory] [MemberData(nameof(StringExpressions))] public void CreateModelExpression_ReturnsExpectedMetadata_StringExpressions( Expression> expression, string expectedName) { // Arrange var viewContext = CreateViewContext(); var page = CreatePage(viewContext); // Act var result = page.ModelExpressionProvider.CreateModelExpression(page.ViewData, expression); // Assert Assert.NotNull(result); Assert.NotNull(result.Metadata); Assert.Equal(typeof(string), result.Metadata.ModelType); Assert.Equal(expectedName, result.Name); } private static IdentityRazorPage CreateIdentityPage(ViewContext viewContext) { return new IdentityRazorPage { ViewContext = viewContext, ViewData = (ViewDataDictionary)viewContext.ViewData, ModelExpressionProvider = CreateModelExpressionProvider(), }; } public static NotQuiteIdentityRazorPage CreateNotQuiteIdentityPage(ViewContext viewContext) { return new NotQuiteIdentityRazorPage { ViewContext = viewContext, ViewData = (ViewDataDictionary)viewContext.ViewData, ModelExpressionProvider = CreateModelExpressionProvider(), }; } private static TestRazorPage CreatePage(ViewContext viewContext) { return new TestRazorPage { ViewContext = viewContext, ViewData = (ViewDataDictionary)viewContext.ViewData, ModelExpressionProvider = CreateModelExpressionProvider(), }; } private static IModelExpressionProvider CreateModelExpressionProvider() { var provider = new TestModelMetadataProvider(); var modelExpressionProvider = new ModelExpressionProvider( provider, new ExpressionTextCache()); return modelExpressionProvider; } private static ViewContext CreateViewContext() { var provider = new TestModelMetadataProvider(); var viewData = new ViewDataDictionary(provider); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(provider); serviceCollection.AddSingleton(); var httpContext = new DefaultHttpContext { RequestServices = serviceCollection.BuildServiceProvider(), }; var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); return new ViewContext( actionContext, NullView.Instance, viewData, Mock.Of(), new StringWriter(), new HtmlHelperOptions()); } public class IdentityRazorPage : TestRazorPage { public ModelExpression CreateModelExpression1() { return ModelExpressionProvider.CreateModelExpression(ViewData, m => m); } public ModelExpression CreateModelExpression2() { return ModelExpressionProvider.CreateModelExpression(ViewData, m => Model); } public override Task ExecuteAsync() { throw new NotImplementedException(); } } public class NotQuiteIdentityRazorPage : TestRazorPage { public ModelExpression CreateModelExpression1() { return ModelExpressionProvider.CreateModelExpression(ViewData, m => m.Model); } public ModelExpression CreateModelExpression2() { return ModelExpressionProvider.CreateModelExpression(ViewData, m => ViewData.Model); } public ModelExpression CreateModelExpression3() { return ModelExpressionProvider.CreateModelExpression(ViewData, m => ViewContext.ViewData.Model); } public override Task ExecuteAsync() { throw new NotImplementedException(); } } private class TestRazorPage : TestRazorPage { public override Task ExecuteAsync() { throw new NotImplementedException(); } } public class TestRazorPage : RazorPage { public IModelExpressionProvider ModelExpressionProvider { get; set; } public override Task ExecuteAsync() { throw new NotImplementedException(); } } public class RecursiveModel { public RecursiveModel Model { get; set; } } public class RazorPageCreateModelExpressionModel { public int Id { get; set; } public string Name { get; set; } public RazorPageCreateModelExpressionSubModel SubModel { get; set; } } public class RazorPageCreateModelExpressionSubModel { public int Id { get; set; } public string Name { get; set; } public RazorPageCreateModelExpressionSubSubModel SubSubModel { get; set; } } public class RazorPageCreateModelExpressionSubSubModel { public int Id { get; set; } public string Name { get; set; } } } }