From 2005c3cd857874c7ce6e58806eb46d0af858cf8e Mon Sep 17 00:00:00 2001 From: NTaylorMullen Date: Tue, 7 Oct 2014 20:50:20 -0700 Subject: [PATCH] Add ModelExpression code generation. - Sealed the ModelExpression. - We use the stringified version of the ModelExpression type name to detect ModelExpression properties on TagHelpers. This is so the MvcRazorHost can work in tooling and in runtime. - Created a GeneratedTagHelperAttributeContext to represent the specific stringified versions of the ModelExpression assets. - Created an MvcTagHelperAttributeValueCodeRenderer to modify rendering of ModelExpression properties. #1241 --- .../Rendering/ModelExpression.cs | 2 +- .../GeneratedTagHelperAttributeContext.cs | 23 ++++++++ .../MvcCSharpCodeBuilder.cs | 16 +++++- .../MvcRazorHost.cs | 26 ++++++++- .../MvcTagHelperAttributeValueCodeRenderer.cs | 57 +++++++++++++++++++ 5 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor.Host/GeneratedTagHelperAttributeContext.cs create mode 100644 src/Microsoft.AspNet.Mvc.Razor.Host/MvcTagHelperAttributeValueCodeRenderer.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs index bf0372884..d5179f5f9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/ModelExpression.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNet.Mvc.Rendering /// /// Describes an passed to a tag helper. /// - public class ModelExpression + public sealed class ModelExpression { /// /// Initializes a new instance of the class. diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/GeneratedTagHelperAttributeContext.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/GeneratedTagHelperAttributeContext.cs new file mode 100644 index 000000000..296959aea --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/GeneratedTagHelperAttributeContext.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Mvc.Razor +{ + /// + /// Contains information for the attribute code generation process. + /// + public class GeneratedTagHelperAttributeContext + { + /// + /// Name of the model expression type. + /// + public string ModelExpressionTypeName { get; set; } + + /// + /// Name the method to create ModelExpressions. + /// + public string CreateModelExpressionMethodName { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs index ada4b74ac..91b262b9f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeBuilder.cs @@ -11,20 +11,34 @@ namespace Microsoft.AspNet.Mvc.Razor { public class MvcCSharpCodeBuilder : CSharpCodeBuilder { + private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext; private readonly string _defaultModel; private readonly string _activateAttribute; public MvcCSharpCodeBuilder([NotNull] CodeBuilderContext context, [NotNull] string defaultModel, - [NotNull] string activateAttribute) + [NotNull] string activateAttribute, + [NotNull] GeneratedTagHelperAttributeContext tagHelperAttributeContext) : base(context) { + _tagHelperAttributeContext = tagHelperAttributeContext; _defaultModel = defaultModel; _activateAttribute = activateAttribute; } private string Model { get; set; } + protected override CSharpCodeVisitor CreateCSharpCodeVisitor([NotNull] CSharpCodeWriter writer, + [NotNull] CodeBuilderContext context) + { + var csharpCodeVisitor = base.CreateCSharpCodeVisitor(writer, context); + + csharpCodeVisitor.TagHelperRenderer.AttributeValueCodeRenderer = + new MvcTagHelperAttributeValueCodeRenderer(_tagHelperAttributeContext); + + return csharpCodeVisitor; + } + protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer) { // Grab the last model chunk so it gets intellisense. diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 7aa114da1..aaf87e13f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -153,6 +153,22 @@ namespace Microsoft.AspNet.Mvc.Razor get { return "Microsoft.AspNet.Mvc.ActivateAttribute"; } } + /// + /// Gets the type name used to represent model expression properties. + /// + public virtual string ModelExpressionType + { + get { return "Microsoft.AspNet.Mvc.Rendering.ModelExpression"; } + } + + /// + /// Gets the method name used to create model expressions. + /// + public virtual string CreateModelExpressionMethod + { + get { return "CreateModelExpression"; } + } + /// public GeneratorResults GenerateCode(string rootRelativePath, Stream inputStream) { @@ -173,7 +189,15 @@ namespace Microsoft.AspNet.Mvc.Razor [NotNull] CodeBuilderContext context) { UpdateCodeBuilder(context); - return new MvcCSharpCodeBuilder(context, DefaultModel, ActivateAttribute); + + return new MvcCSharpCodeBuilder(context, + DefaultModel, + ActivateAttribute, + new GeneratedTagHelperAttributeContext + { + ModelExpressionTypeName = ModelExpressionType, + CreateModelExpressionMethodName = CreateModelExpressionMethod + }); } private void UpdateCodeBuilder(CodeGeneratorContext context) diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcTagHelperAttributeValueCodeRenderer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcTagHelperAttributeValueCodeRenderer.cs new file mode 100644 index 000000000..1be3000b1 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcTagHelperAttributeValueCodeRenderer.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Razor.Generator; +using Microsoft.AspNet.Razor.Generator.Compiler.CSharp; +using Microsoft.AspNet.Razor.TagHelpers; + +namespace Microsoft.AspNet.Mvc.Razor +{ + /// + public class MvcTagHelperAttributeValueCodeRenderer : TagHelperAttributeValueCodeRenderer + { + private const string ModelLambdaVariableName = "__model"; + + private readonly GeneratedTagHelperAttributeContext _context; + + /// + /// Instantiates a new instance of . + /// + /// Contains code generation information for rendering attribute values. + public MvcTagHelperAttributeValueCodeRenderer([NotNull] GeneratedTagHelperAttributeContext context) + { + _context = context; + } + + /// + /// If the attribute being rendered is of the type + /// then a model expression will be + /// created by calling into . + /// + public override void RenderAttributeValue([NotNull] TagHelperAttributeDescriptor attributeDescriptor, + [NotNull] CSharpCodeWriter writer, + [NotNull] CodeBuilderContext codeBuilderContext, + [NotNull] Action renderAttributeValue) + { + var propertyType = attributeDescriptor.PropertyInfo.PropertyType; + + if (propertyType.FullName.Equals(_context.ModelExpressionTypeName, StringComparison.Ordinal)) + { + writer.WriteStartMethodInvocation(_context.CreateModelExpressionMethodName) + .Write(ModelLambdaVariableName) + .Write(" => ") + .Write(ModelLambdaVariableName) + .Write("."); + + renderAttributeValue(writer); + + writer.WriteEndMethodInvocation(endLine: false); + } + else + { + base.RenderAttributeValue(attributeDescriptor, writer, codeBuilderContext, renderAttributeValue); + } + } + } +} \ No newline at end of file