Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Allow @model specified in a page work with @inherits directive in
Browse files Browse the repository at this point in the history
_ViewImports

Fixes #3144
  • Loading branch information
pranavkm committed Sep 23, 2015
1 parent 1574637 commit cac626d
Show file tree
Hide file tree
Showing 30 changed files with 211 additions and 360 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives
/// </summary>
public static class ChunkHelper
{
private const string TModelToken = "<TModel>";
/// <summary>
/// Token that is replaced by the model name in <c>@inherits</c> and <c>@inject</c>
/// chunks as part of <see cref="ChunkInheritanceUtility"/>.
/// </summary>
public static readonly string TModelToken = "<TModel>";

/// <summary>
/// Returns the <see cref="ModelChunk"/> used to determine the model name for the page generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Chunks;

namespace Microsoft.AspNet.Mvc.Razor.Directives
Expand Down
17 changes: 10 additions & 7 deletions src/Microsoft.AspNet.Mvc.Razor.Host/ModelChunk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@

namespace Microsoft.AspNet.Mvc.Razor
{
/// <summary>
/// <see cref="Chunk"/> for an <c>@model</c> directive.
/// </summary>
public class ModelChunk : Chunk
{
/// <summary>
/// Represents the chunk for an @model statement.
/// Initializes a new instance of <see cref="ModelChunk"/>.
/// </summary>
/// <param name="baseType">The base type of the view.</param>
/// <param name="modelType">The type of the view's Model.</param>
public ModelChunk(string baseType, string modelType)
/// <param name="modelType">The type of the view's model.</param>
public ModelChunk(string modelType)
{
BaseType = baseType;
ModelType = modelType;
}

public string BaseType { get; private set; }
public string ModelType { get; private set; }
/// <summary>
/// Gets the type of the view's model.</param>
/// </summary>
public string ModelType { get; }
}
}
17 changes: 6 additions & 11 deletions src/Microsoft.AspNet.Mvc.Razor.Host/ModelChunkGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,31 @@ namespace Microsoft.AspNet.Razor.Generator
{
public class ModelChunkGenerator : SpanChunkGenerator
{
public ModelChunkGenerator(string baseType, string modelType)
public ModelChunkGenerator(string modelType)
{
BaseType = baseType;
ModelType = modelType;
}

public string BaseType { get; private set; }
public string ModelType { get; private set; }
public string ModelType { get; }

public override void GenerateChunk(Span target, ChunkGeneratorContext context)
{
var modelChunk = new ModelChunk(BaseType, ModelType);
var modelChunk = new ModelChunk(ModelType);
context.ChunkTreeBuilder.AddChunk(modelChunk, target, topLevel: true);
}

public override string ToString()
{
return BaseType + "<" + ModelType + ">";
}
public override string ToString() => ModelType;

public override bool Equals(object obj)
{
var other = obj as ModelChunkGenerator;
return other != null &&
string.Equals(ModelType, other.ModelType, StringComparison.Ordinal);
string.Equals(ModelType, other.ModelType, StringComparison.Ordinal);
}

public override int GetHashCode()
{
return ModelType.GetHashCode();
return StringComparer.Ordinal.GetHashCode(ModelType);
}
}
}
37 changes: 0 additions & 37 deletions src/Microsoft.AspNet.Mvc.Razor.Host/ModelChunkVisitor.cs

This file was deleted.

5 changes: 0 additions & 5 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpChunkVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,12 @@ public override void Accept(Chunk chunk)
{
Visit((InjectChunk)chunk);
}
else if (chunk is ModelChunk)
{
Visit((ModelChunk)chunk);
}
else
{
base.Accept(chunk);
}
}

protected abstract void Visit(InjectChunk chunk);
protected abstract void Visit(ModelChunk chunk);
}
}
30 changes: 0 additions & 30 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using Microsoft.AspNet.Mvc.Razor.Directives;
using Microsoft.AspNet.Razor.CodeGenerators;
using Microsoft.AspNet.Razor.CodeGenerators.Visitors;

Expand Down Expand Up @@ -47,8 +45,6 @@ public MvcCSharpCodeGenerator(
_injectAttribute = injectAttribute;
}

private string Model { get; set; }

protected override CSharpCodeVisitor CreateCSharpCodeVisitor(
CSharpCodeWriter writer,
CodeGeneratorContext context)
Expand All @@ -71,32 +67,6 @@ protected override CSharpCodeVisitor CreateCSharpCodeVisitor(
return csharpCodeVisitor;
}

protected override CSharpCodeWritingScope BuildClassDeclaration(CSharpCodeWriter writer)
{
// Grab the last model chunk so it gets intellisense.
var modelChunk = ChunkHelper.GetModelChunk(Context.ChunkTreeBuilder.ChunkTree);

Model = modelChunk != null ? modelChunk.ModelType : _defaultModel;

// If there were any model chunks then we need to modify the class declaration signature.
if (modelChunk != null)
{
writer.Write(string.Format(CultureInfo.InvariantCulture, "public class {0} : ", Context.ClassName));

var modelVisitor = new ModelChunkVisitor(writer, Context);
// This generates the base class signature
modelVisitor.Accept(modelChunk);

writer.WriteLine();

return new CSharpCodeWritingScope(writer);
}
else
{
return base.BuildClassDeclaration(writer);
}
}

protected override void BuildConstructor(CSharpCodeWriter writer)
{
if (writer == null)
Expand Down
4 changes: 0 additions & 4 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcCSharpCodeVistor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,5 @@ public MvcCSharpCodeVisitor(
protected override void Visit(InjectChunk chunk)
{
}

protected override void Visit(ModelChunk chunk)
{
}
}
}
17 changes: 8 additions & 9 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ public class MvcRazorCodeParser : CSharpCodeParser
{
private const string ModelKeyword = "model";
private const string InjectKeyword = "inject";
private readonly string _baseType;
private SourceLocation? _endInheritsLocation;
private bool _modelStatementFound;

public MvcRazorCodeParser(string baseType)
public MvcRazorCodeParser()
{
_baseType = baseType;
MapDirectives(ModelDirective, ModelKeyword);
MapDirectives(InjectDirective, InjectKeyword);
}
Expand Down Expand Up @@ -120,9 +118,10 @@ protected virtual void InjectDirective()
if (!hasTypeError && (EndOfFile || At(CSharpSymbolType.NewLine)))
{
// Add an error for the property name only if we successfully read the type name
Context.OnError(startLocation,
Resources.FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(InjectKeyword),
keywordwithSingleWhitespaceLength + remainingWhitespaceLength + typeName.Length);
Context.OnError(
startLocation,
Resources.FormatMvcRazorCodeParser_InjectDirectivePropertyNameRequired(InjectKeyword),
keywordwithSingleWhitespaceLength + remainingWhitespaceLength + typeName.Length);
}

// Read until end of line. Span now contains 'MyApp.MyService MyServiceName'.
Expand All @@ -135,8 +134,8 @@ protected virtual void InjectDirective()

// Parse out 'MyServicePropertyName' from the Span.
var propertyName = Span.GetContent()
.Value
.Substring(typeName.Length);
.Value
.Substring(typeName.Length);

// ';' is optional
propertyName = RemoveWhitespaceAndTrailingSemicolons(propertyName);
Expand All @@ -149,7 +148,7 @@ protected virtual void InjectDirective()

private SpanChunkGenerator CreateModelChunkGenerator(string model)
{
return new ModelChunkGenerator(_baseType, model);
return new ModelChunkGenerator(model);
}

// Internal for unit testing
Expand Down
12 changes: 7 additions & 5 deletions src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ public class MvcRazorHost : RazorEngineHost, IMvcRazorHost
{
LookupText = "Microsoft.AspNet.Mvc.Razor.TagHelpers.UrlResolutionTagHelper, Microsoft.AspNet.Mvc.Razor"
},
new SetBaseTypeChunk
{
// Microsoft.Aspnet.Mvc.Razor.RazorPage<TModel>
TypeName = BaseType + ChunkHelper.TModelToken
}
};

// CodeGenerationContext.DefaultBaseClass is set to MyBaseType<dynamic>.
// This field holds the type name without the generic decoration (MyBaseType)
private readonly string _baseType;
private readonly IChunkTreeCache _chunkTreeCache;
private readonly RazorPathNormalizer _pathNormalizer;
private ChunkInheritanceUtility _chunkInheritanceUtility;
Expand All @@ -56,10 +59,9 @@ internal MvcRazorHost(IChunkTreeCache chunkTreeCache, RazorPathNormalizer pathNo
: base(new CSharpRazorCodeLanguage())
{
_pathNormalizer = pathNormalizer;
_baseType = BaseType;
_chunkTreeCache = chunkTreeCache;

DefaultBaseClass = BaseType + "<" + DefaultModel + ">";
DefaultBaseClass = BaseType + ChunkHelper.TModelToken;
DefaultNamespace = "Asp";
// Enable instrumentation by default to allow precompiled views to work with BrowserLink.
EnableInstrumentation = true;
Expand Down Expand Up @@ -279,7 +281,7 @@ public override ParserBase DecorateCodeParser(ParserBase incomingCodeParser)
throw new ArgumentNullException(nameof(incomingCodeParser));
}

return new MvcRazorCodeParser(_baseType);
return new MvcRazorCodeParser();
}

/// <inheritdoc />
Expand Down
15 changes: 15 additions & 0 deletions test/Microsoft.AspNet.Mvc.FunctionalTests/DirectivesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,20 @@ public async Task ViewInheritsBasePageFromViewStarts()
// Assert
Assert.Equal(expected, body.Trim());
}

[Fact]
public async Task ViewAndViewComponentsReplaceTModelTokenFromInheritedBasePages()
{
// Arrange
var expected =
@"WriteLiteral says:<h1>Write says:BobWriteLiteral says:</h1>
Write says:WriteLiteral says:<strong>Write says:98052WriteLiteral says:</strong>";

// Act
var body = await Client.GetStringAsync("Directives/ViewReplacesTModelTokenFromInheritedBasePages");

// Assert
Assert.Equal(expected, body.Trim());
}
}
}
Loading

0 comments on commit cac626d

Please sign in to comment.