From 1833e06984a940c23c25371d3aa12f0923e6751c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 23 Sep 2015 16:11:41 -0700 Subject: [PATCH] TModel substitution in Razor pages has broken intellisense Fixes #3185 --- .../Directives/ChunkHelper.cs | 5 ++- .../MvcCSharpCodeGenerator.cs | 17 +++++++ .../MvcRazorHost.cs | 4 +- .../ViewHierarchyUtility.cs | 6 ++- .../MvcRazorHostTest.cs | 27 ++++++++++++ .../TestFiles/Input/_ViewImports.cshtml | 1 + .../Output/DesignTime/_ViewImports.cs | 44 +++++++++++++++++++ .../TestFiles/Output/Runtime/_ViewImports.cs | 37 ++++++++++++++++ 8 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/_ViewImports.cshtml create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/_ViewImports.cs create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/_ViewImports.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs index c769c7f1ad..8f39c3596a 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkHelper.cs @@ -16,7 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives /// Token that is replaced by the model name in @inherits and @inject /// chunks as part of . /// - public static readonly string TModelToken = ""; + public static readonly string TModelToken = "TModel"; + private static readonly string TModelReplaceToken = $"<{TModelToken}>"; /// /// Returns the used to determine the model name for the page generated @@ -86,7 +87,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives throw new ArgumentNullException(nameof(modelName)); } - return value.Replace(TModelToken, modelName); + return value.Replace(TModelReplaceToken, modelName); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs index 146a3c53cb..7c434d54fe 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; +using Microsoft.AspNet.Mvc.Razor.Directives; using Microsoft.AspNet.Razor.CodeGenerators; using Microsoft.AspNet.Razor.CodeGenerators.Visitors; @@ -45,6 +47,21 @@ namespace Microsoft.AspNet.Mvc.Razor _injectAttribute = injectAttribute; } + protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer) + { + if (Context.Host.DesignTimeMode && + string.Equals( + Path.GetFileName(Context.SourceFile), + ViewHierarchyUtility.ViewImportsFileName, + StringComparison.Ordinal)) + { + // Write a using TModel = System.Object; token during design time to make intellisense work + writer.WriteLine($"using {ChunkHelper.TModelToken} = {typeof(object).FullName};"); + } + + return base.BuildClassDeclaration(writer); + } + protected override CSharpCodeVisitor CreateCSharpCodeVisitor( CSharpCodeWriter writer, CodeGeneratorContext context) diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 7a3a6db05c..5b31b4accf 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Mvc.Razor new SetBaseTypeChunk { // Microsoft.Aspnet.Mvc.Razor.RazorPage - TypeName = BaseType + ChunkHelper.TModelToken, + TypeName = $"{BaseType}<{ChunkHelper.TModelToken}>", // Set the Start to Undefined to prevent Razor design time code generation from rendering a line mapping // for this chunk. Start = SourceLocation.Undefined @@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.Razor _pathNormalizer = pathNormalizer; _chunkTreeCache = chunkTreeCache; - DefaultBaseClass = BaseType + ChunkHelper.TModelToken; + DefaultBaseClass = $"{BaseType}<{ChunkHelper.TModelToken}>"; DefaultNamespace = "Asp"; // Enable instrumentation by default to allow precompiled views to work with BrowserLink. EnableInstrumentation = true; diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/ViewHierarchyUtility.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/ViewHierarchyUtility.cs index aea88d7e8a..676fa65e06 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/ViewHierarchyUtility.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/ViewHierarchyUtility.cs @@ -14,7 +14,11 @@ namespace Microsoft.AspNet.Mvc.Razor public static class ViewHierarchyUtility { private const string ViewStartFileName = "_ViewStart.cshtml"; - private const string ViewImportsFileName = "_ViewImports.cshtml"; + + /// + /// File name of _ViewImports.cshtml file + /// + public static readonly string ViewImportsFileName = "_ViewImports.cshtml"; /// /// Gets the view start locations that are applicable to the specified path. diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index fb741a2220..4c987624d2 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -171,6 +171,7 @@ namespace Microsoft.AspNet.Mvc.Razor [Theory] [InlineData("Basic")] + [InlineData("_ViewImports")] [InlineData("Inject")] [InlineData("InjectWithModel")] [InlineData("InjectWithSemicolon")] @@ -220,6 +221,32 @@ namespace Microsoft.AspNet.Mvc.Razor RunDesignTimeTest(host, "Basic", expectedLineMappings); } + [Fact] + public void MvcRazorHost_GeneratesCorrectLineMappingsAndUsingStatementsForViewImports() + { + // Arrange + var fileProvider = new TestFileProvider(); + var host = new MvcRazorHostWithNormalizedNewLine(new DefaultChunkTreeCache(fileProvider)) + { + DesignTimeMode = true + }; + host.NamespaceImports.Clear(); + var expectedLineMappings = new[] + { + BuildLineMapping( + documentAbsoluteIndex: 8, + documentLineIndex: 0, + documentCharacterIndex: 8, + generatedAbsoluteIndex: 661, + generatedLineIndex: 21, + generatedCharacterIndex: 8, + contentLength: 26), + }; + + // Act and Assert + RunDesignTimeTest(host, "_ViewImports", expectedLineMappings); + } + [Fact] public void InjectVisitor_GeneratesCorrectLineMappings() { diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/_ViewImports.cshtml b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/_ViewImports.cshtml new file mode 100644 index 0000000000..fea9533d09 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/_ViewImports.cshtml @@ -0,0 +1 @@ +@inject IHtmlHelper Model \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/_ViewImports.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/_ViewImports.cs new file mode 100644 index 0000000000..7dc8c0a827 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/_ViewImports.cs @@ -0,0 +1,44 @@ +namespace Asp +{ + using System.Threading.Tasks; + + using TModel = System.Object; + public class ASPV_TestFiles_Input__ViewImports_cshtml : Microsoft.AspNet.Mvc.Razor.RazorPage + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 + #pragma warning restore 219 + } + #line hidden + public ASPV_TestFiles_Input__ViewImports_cshtml() + { + } + #line hidden + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public +#line 1 "TestFiles/Input/_ViewImports.cshtml" + IHtmlHelper Model + +#line default +#line hidden + { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.IUrlHelper Url { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.IViewComponentHelper Component { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.Rendering.IJsonHelper Json { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.Rendering.IHtmlHelper Html { get; private set; } + + #line hidden + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/_ViewImports.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/_ViewImports.cs new file mode 100644 index 0000000000..704c235968 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/_ViewImports.cs @@ -0,0 +1,37 @@ +#pragma checksum "TestFiles/Input/_ViewImports.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "778b41f9406fcda776cc3f1bf093f3b21956e582" +namespace Asp +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Microsoft.AspNet.Mvc; + using Microsoft.AspNet.Mvc.Rendering; + using System.Threading.Tasks; + + public class ASPV_TestFiles_Input__ViewImports_cshtml : Microsoft.AspNet.Mvc.Razor.RazorPage + { + #line hidden + public ASPV_TestFiles_Input__ViewImports_cshtml() + { + } + #line hidden + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public IHtmlHelper Model { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.IUrlHelper Url { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.IViewComponentHelper Component { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.Rendering.IJsonHelper Json { get; private set; } + [Microsoft.AspNet.Mvc.Razor.Internal.RazorInjectAttribute] + public Microsoft.AspNet.Mvc.Rendering.IHtmlHelper Html { get; private set; } + + #line hidden + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +}