Make RazorPages work E2E
This commit is contained in:
parent
c349929cc1
commit
9f3dfd9819
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
||||||
"Microsoft.AspNetCore.Mvc.Localization",
|
"Microsoft.AspNetCore.Mvc.Localization",
|
||||||
"Microsoft.AspNetCore.Mvc.Razor",
|
"Microsoft.AspNetCore.Mvc.Razor",
|
||||||
"Microsoft.AspNetCore.Mvc.Razor.Host",
|
"Microsoft.AspNetCore.Mvc.Razor.Host",
|
||||||
|
"Microsoft.AspNetCore.Mvc.RazorPages",
|
||||||
"Microsoft.AspNetCore.Mvc.TagHelpers",
|
"Microsoft.AspNetCore.Mvc.TagHelpers",
|
||||||
"Microsoft.AspNetCore.Mvc.ViewFeatures"
|
"Microsoft.AspNetCore.Mvc.ViewFeatures"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
|
{
|
||||||
|
public abstract class BaseDocumentClassifierPass : IRazorIRPass
|
||||||
|
{
|
||||||
|
public RazorEngine Engine { get; set; }
|
||||||
|
|
||||||
|
// We want to run before the default, but after others since this is the MVC default.
|
||||||
|
public virtual int Order => RazorIRPass.DefaultDocumentClassifierOrder - 1;
|
||||||
|
|
||||||
|
protected abstract string BaseType { get; }
|
||||||
|
|
||||||
|
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||||
|
{
|
||||||
|
if (irDocument.DocumentKind != null)
|
||||||
|
{
|
||||||
|
return irDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
var documentKind = ClassifyDocument(codeDocument, irDocument);
|
||||||
|
if (documentKind == null)
|
||||||
|
{
|
||||||
|
return irDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
irDocument.DocumentKind = documentKind;
|
||||||
|
|
||||||
|
return ExecuteCore(codeDocument, irDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual DocumentIRNode ExecuteCore(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||||
|
{
|
||||||
|
// Rewrite a use default namespace and class declaration.
|
||||||
|
var children = new List<RazorIRNode>(irDocument.Children);
|
||||||
|
irDocument.Children.Clear();
|
||||||
|
|
||||||
|
var @namespace = new NamespaceDeclarationIRNode
|
||||||
|
{
|
||||||
|
Content = "AspNetCore",
|
||||||
|
};
|
||||||
|
|
||||||
|
var @class = new ClassDeclarationIRNode
|
||||||
|
{
|
||||||
|
AccessModifier = "public",
|
||||||
|
Name = GetClassName(codeDocument.Source.Filename) ?? "GeneratedClass",
|
||||||
|
BaseType = BaseType,
|
||||||
|
};
|
||||||
|
|
||||||
|
var method = new RazorMethodDeclarationIRNode()
|
||||||
|
{
|
||||||
|
AccessModifier = "public",
|
||||||
|
Modifiers = new List<string>() { "async", "override" },
|
||||||
|
Name = "ExecuteAsync",
|
||||||
|
ReturnType = "Task",
|
||||||
|
};
|
||||||
|
|
||||||
|
var documentBuilder = RazorIRBuilder.Create(irDocument);
|
||||||
|
|
||||||
|
var namespaceBuilder = RazorIRBuilder.Create(documentBuilder.Current);
|
||||||
|
namespaceBuilder.Push(@namespace);
|
||||||
|
|
||||||
|
var classBuilder = RazorIRBuilder.Create(namespaceBuilder.Current);
|
||||||
|
classBuilder.Push(@class);
|
||||||
|
|
||||||
|
var methodBuilder = RazorIRBuilder.Create(classBuilder.Current);
|
||||||
|
methodBuilder.Push(method);
|
||||||
|
|
||||||
|
var visitor = new Visitor(documentBuilder, namespaceBuilder, classBuilder, methodBuilder);
|
||||||
|
|
||||||
|
for (var i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
visitor.Visit(children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return irDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract string ClassifyDocument(RazorCodeDocument codeDocument, DocumentIRNode irDocument);
|
||||||
|
|
||||||
|
private static string GetClassName(string filename)
|
||||||
|
{
|
||||||
|
if (filename == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SanitizeClassName("Generated_" + Path.GetFileNameWithoutExtension(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSharp Spec §2.4.2
|
||||||
|
private static bool IsIdentifierStart(char character)
|
||||||
|
{
|
||||||
|
return char.IsLetter(character) ||
|
||||||
|
character == '_' ||
|
||||||
|
CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.LetterNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsIdentifierPart(char character)
|
||||||
|
{
|
||||||
|
return char.IsDigit(character) ||
|
||||||
|
IsIdentifierStart(character) ||
|
||||||
|
IsIdentifierPartByUnicodeCategory(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsIdentifierPartByUnicodeCategory(char character)
|
||||||
|
{
|
||||||
|
var category = CharUnicodeInfo.GetUnicodeCategory(character);
|
||||||
|
|
||||||
|
return category == UnicodeCategory.NonSpacingMark || // Mn
|
||||||
|
category == UnicodeCategory.SpacingCombiningMark || // Mc
|
||||||
|
category == UnicodeCategory.ConnectorPunctuation || // Pc
|
||||||
|
category == UnicodeCategory.Format; // Cf
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SanitizeClassName(string inputName)
|
||||||
|
{
|
||||||
|
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
|
||||||
|
{
|
||||||
|
inputName = "_" + inputName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new InplaceStringBuilder(inputName.Length);
|
||||||
|
for (var i = 0; i < inputName.Length; i++)
|
||||||
|
{
|
||||||
|
var ch = inputName[i];
|
||||||
|
builder.Append(IsIdentifierPart(ch) ? ch : '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Visitor : RazorIRNodeVisitor
|
||||||
|
{
|
||||||
|
private readonly RazorIRBuilder _document;
|
||||||
|
private readonly RazorIRBuilder _namespace;
|
||||||
|
private readonly RazorIRBuilder _class;
|
||||||
|
private readonly RazorIRBuilder _method;
|
||||||
|
|
||||||
|
public Visitor(RazorIRBuilder document, RazorIRBuilder @namespace, RazorIRBuilder @class, RazorIRBuilder method)
|
||||||
|
{
|
||||||
|
_document = document;
|
||||||
|
_namespace = @namespace;
|
||||||
|
_class = @class;
|
||||||
|
_method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitChecksum(ChecksumIRNode node)
|
||||||
|
{
|
||||||
|
_document.Insert(0, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitUsingStatement(UsingStatementIRNode node)
|
||||||
|
{
|
||||||
|
_namespace.AddAfter<UsingStatementIRNode>(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
|
||||||
|
{
|
||||||
|
_class.Insert(0, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void VisitDefault(RazorIRNode node)
|
||||||
|
{
|
||||||
|
_method.Add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
{
|
{
|
||||||
var visitor = new Visitor();
|
var visitor = new Visitor();
|
||||||
visitor.Visit(irDocument);
|
visitor.Visit(irDocument);
|
||||||
|
var modelType = ModelDirective.GetModelType(irDocument);
|
||||||
|
|
||||||
var properties = new HashSet<string>(StringComparer.Ordinal);
|
var properties = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
|
@ -50,8 +51,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelType = ModelDirective.GetModelType(irDocument);
|
|
||||||
|
|
||||||
typeName = typeName.Replace("<TModel>", "<" + modelType + ">");
|
typeName = typeName.Replace("<TModel>", "<" + modelType + ">");
|
||||||
|
|
||||||
var member = new CSharpStatementIRNode()
|
var member = new CSharpStatementIRNode()
|
||||||
|
|
@ -74,8 +73,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
|
|
||||||
public IList<DirectiveIRNode> Directives { get; } = new List<DirectiveIRNode>();
|
public IList<DirectiveIRNode> Directives { get; } = new List<DirectiveIRNode>();
|
||||||
|
|
||||||
public IList<DirectiveIRNode> ModelType { get; } = new List<DirectiveIRNode>();
|
|
||||||
|
|
||||||
public override void VisitClass(ClassDeclarationIRNode node)
|
public override void VisitClass(ClassDeclarationIRNode node)
|
||||||
{
|
{
|
||||||
if (Class == null)
|
if (Class == null)
|
||||||
|
|
@ -92,11 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
{
|
{
|
||||||
Directives.Add(node);
|
Directives.Add(node);
|
||||||
}
|
}
|
||||||
else if (node.Descriptor == ModelDirective.Directive)
|
|
||||||
{
|
|
||||||
ModelType.Add(node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
var visitor = new Visitor();
|
var visitor = new Visitor();
|
||||||
visitor.Visit(document);
|
return GetModelType(document, visitor);
|
||||||
|
|
||||||
return GetModelType(visitor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetModelType(Visitor visitor)
|
private static string GetModelType(DocumentIRNode document, Visitor visitor)
|
||||||
{
|
{
|
||||||
|
visitor.Visit(document);
|
||||||
|
|
||||||
for (var i = visitor.ModelDirectives.Count - 1; i >= 0; i--)
|
for (var i = visitor.ModelDirectives.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var directive = visitor.ModelDirectives[i];
|
var directive = visitor.ModelDirectives[i];
|
||||||
|
|
@ -46,7 +46,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "dynamic";
|
if (document.DocumentKind == RazorPageDocumentClassifier.DocumentKind)
|
||||||
|
{
|
||||||
|
return visitor.Class.Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "dynamic";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Pass : IRazorIRPass
|
internal class Pass : IRazorIRPass
|
||||||
|
|
@ -59,9 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||||
{
|
{
|
||||||
var visitor = new Visitor();
|
var visitor = new Visitor();
|
||||||
visitor.Visit(irDocument);
|
var modelType = GetModelType(irDocument, visitor);
|
||||||
|
|
||||||
var modelType = GetModelType(visitor);
|
|
||||||
|
|
||||||
var baseType = visitor.Class.BaseType;
|
var baseType = visitor.Class.BaseType;
|
||||||
for (var i = visitor.InheritsDirectives.Count - 1; i >= 0; i--)
|
for (var i = visitor.InheritsDirectives.Count - 1; i >= 0; i--)
|
||||||
|
|
|
||||||
|
|
@ -1,262 +1,18 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
{
|
{
|
||||||
public class MvcViewDocumentClassifierPass : IRazorIRPass
|
public class MvcViewDocumentClassifierPass : BaseDocumentClassifierPass
|
||||||
{
|
{
|
||||||
public static readonly string Kind = "mvc.1.0.view";
|
public static readonly string DocumentKind = "mvc.1.0.view";
|
||||||
|
|
||||||
public RazorEngine Engine { get; set; }
|
protected override string BaseType => "Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>";
|
||||||
|
|
||||||
// We want to run before the default, but after others since this is the MVC default.
|
protected override string ClassifyDocument(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||||
public virtual int Order => RazorIRPass.DefaultDocumentClassifierOrder - 1;
|
=> DocumentKind;
|
||||||
|
|
||||||
public static string DocumentKind = "default";
|
|
||||||
|
|
||||||
public DocumentIRNode Execute(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
|
||||||
{
|
|
||||||
if (irDocument.DocumentKind != null)
|
|
||||||
{
|
|
||||||
return irDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
irDocument.DocumentKind = DocumentKind;
|
|
||||||
|
|
||||||
// Rewrite a use default namespace and class declaration.
|
|
||||||
var children = new List<RazorIRNode>(irDocument.Children);
|
|
||||||
irDocument.Children.Clear();
|
|
||||||
|
|
||||||
var @namespace = new NamespaceDeclarationIRNode()
|
|
||||||
{
|
|
||||||
Content = "AspNetCore",
|
|
||||||
};
|
|
||||||
|
|
||||||
var @class = new ClassDeclarationIRNode()
|
|
||||||
{
|
|
||||||
AccessModifier = "public",
|
|
||||||
Name = GetClassName(codeDocument.Source.Filename) ?? "GeneratedClass",
|
|
||||||
BaseType = "Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel>"
|
|
||||||
};
|
|
||||||
|
|
||||||
var method = new RazorMethodDeclarationIRNode()
|
|
||||||
{
|
|
||||||
AccessModifier = "public",
|
|
||||||
Modifiers = new List<string>() { "async", "override" },
|
|
||||||
Name = "ExecuteAsync",
|
|
||||||
ReturnType = "Task",
|
|
||||||
};
|
|
||||||
|
|
||||||
var documentBuilder = RazorIRBuilder.Create(irDocument);
|
|
||||||
|
|
||||||
var namespaceBuilder = RazorIRBuilder.Create(documentBuilder.Current);
|
|
||||||
namespaceBuilder.Push(@namespace);
|
|
||||||
|
|
||||||
var classBuilder = RazorIRBuilder.Create(namespaceBuilder.Current);
|
|
||||||
classBuilder.Push(@class);
|
|
||||||
|
|
||||||
var methodBuilder = RazorIRBuilder.Create(classBuilder.Current);
|
|
||||||
methodBuilder.Push(method);
|
|
||||||
|
|
||||||
var visitor = new Visitor(documentBuilder, namespaceBuilder, classBuilder, methodBuilder);
|
|
||||||
|
|
||||||
for (var i = 0; i < children.Count; i++)
|
|
||||||
{
|
|
||||||
visitor.Visit(children[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return irDocument;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetClassName(string filename)
|
|
||||||
{
|
|
||||||
if (filename == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParserHelpers.SanitizeClassName("Generated_" + Path.GetFileNameWithoutExtension(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ParserHelpers
|
|
||||||
{
|
|
||||||
public static bool IsNewLine(char value)
|
|
||||||
{
|
|
||||||
return value == '\r' // Carriage return
|
|
||||||
|| value == '\n' // Linefeed
|
|
||||||
|| value == '\u0085' // Next Line
|
|
||||||
|| value == '\u2028' // Line separator
|
|
||||||
|| value == '\u2029'; // Paragraph separator
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsNewLine(string value)
|
|
||||||
{
|
|
||||||
return (value.Length == 1 && (IsNewLine(value[0]))) ||
|
|
||||||
(string.Equals(value, Environment.NewLine, StringComparison.Ordinal));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the character is Whitespace and NOT a newline
|
|
||||||
public static bool IsWhitespace(char value)
|
|
||||||
{
|
|
||||||
return value == ' ' ||
|
|
||||||
value == '\f' ||
|
|
||||||
value == '\t' ||
|
|
||||||
value == '\u000B' || // Vertical Tab
|
|
||||||
CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.SpaceSeparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsWhitespaceOrNewLine(char value)
|
|
||||||
{
|
|
||||||
return IsWhitespace(value) || IsNewLine(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsIdentifier(string value)
|
|
||||||
{
|
|
||||||
return IsIdentifier(value, requireIdentifierStart: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsIdentifier(string value, bool requireIdentifierStart)
|
|
||||||
{
|
|
||||||
IEnumerable<char> identifierPart = value;
|
|
||||||
if (requireIdentifierStart)
|
|
||||||
{
|
|
||||||
identifierPart = identifierPart.Skip(1);
|
|
||||||
}
|
|
||||||
return (!requireIdentifierStart || IsIdentifierStart(value[0])) && identifierPart.All(IsIdentifierPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsHexDigit(char value)
|
|
||||||
{
|
|
||||||
return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsIdentifierStart(char value)
|
|
||||||
{
|
|
||||||
return value == '_' || IsLetter(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsIdentifierPart(char value)
|
|
||||||
{
|
|
||||||
return IsLetter(value)
|
|
||||||
|| IsDecimalDigit(value)
|
|
||||||
|| IsConnecting(value)
|
|
||||||
|| IsCombining(value)
|
|
||||||
|| IsFormatting(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsTerminatingCharToken(char value)
|
|
||||||
{
|
|
||||||
return IsNewLine(value) || value == '\'';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsTerminatingQuotedStringToken(char value)
|
|
||||||
{
|
|
||||||
return IsNewLine(value) || value == '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDecimalDigit(char value)
|
|
||||||
{
|
|
||||||
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.DecimalDigitNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsLetterOrDecimalDigit(char value)
|
|
||||||
{
|
|
||||||
return IsLetter(value) || IsDecimalDigit(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsLetter(char value)
|
|
||||||
{
|
|
||||||
var cat = CharUnicodeInfo.GetUnicodeCategory(value);
|
|
||||||
|
|
||||||
return cat == UnicodeCategory.UppercaseLetter
|
|
||||||
|| cat == UnicodeCategory.LowercaseLetter
|
|
||||||
|| cat == UnicodeCategory.TitlecaseLetter
|
|
||||||
|| cat == UnicodeCategory.ModifierLetter
|
|
||||||
|| cat == UnicodeCategory.OtherLetter
|
|
||||||
|| cat == UnicodeCategory.LetterNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsFormatting(char value)
|
|
||||||
{
|
|
||||||
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.Format;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsCombining(char value)
|
|
||||||
{
|
|
||||||
UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(value);
|
|
||||||
|
|
||||||
return cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.NonSpacingMark;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsConnecting(char value)
|
|
||||||
{
|
|
||||||
return CharUnicodeInfo.GetUnicodeCategory(value) == UnicodeCategory.ConnectorPunctuation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string SanitizeClassName(string inputName)
|
|
||||||
{
|
|
||||||
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
|
|
||||||
{
|
|
||||||
inputName = "_" + inputName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String((from value in inputName
|
|
||||||
select IsIdentifierPart(value) ? value : '_')
|
|
||||||
.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsEmailPart(char character)
|
|
||||||
{
|
|
||||||
// Source: http://tools.ietf.org/html/rfc5322#section-3.4.1
|
|
||||||
// We restrict the allowed characters to alpha-numerics and '_' in order to ensure we cover most of the cases where an
|
|
||||||
// email address is intended without restricting the usage of code within JavaScript, CSS, and other contexts.
|
|
||||||
return Char.IsLetter(character) || Char.IsDigit(character) || character == '_';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Visitor : RazorIRNodeVisitor
|
|
||||||
{
|
|
||||||
private readonly RazorIRBuilder _document;
|
|
||||||
private readonly RazorIRBuilder _namespace;
|
|
||||||
private readonly RazorIRBuilder _class;
|
|
||||||
private readonly RazorIRBuilder _method;
|
|
||||||
|
|
||||||
public Visitor(RazorIRBuilder document, RazorIRBuilder @namespace, RazorIRBuilder @class, RazorIRBuilder method)
|
|
||||||
{
|
|
||||||
_document = document;
|
|
||||||
_namespace = @namespace;
|
|
||||||
_class = @class;
|
|
||||||
_method = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitChecksum(ChecksumIRNode node)
|
|
||||||
{
|
|
||||||
_document.Insert(0, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitUsingStatement(UsingStatementIRNode node)
|
|
||||||
{
|
|
||||||
_namespace.AddAfter<UsingStatementIRNode>(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
|
|
||||||
{
|
|
||||||
_class.Insert(0, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void VisitDefault(RazorIRNode node)
|
|
||||||
{
|
|
||||||
_method.Add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
// 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.Linq;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
|
{
|
||||||
|
public static class PageDirective
|
||||||
|
{
|
||||||
|
public static readonly DirectiveDescriptor DirectiveDescriptor = DirectiveDescriptorBuilder.Create("page").AddString().Build();
|
||||||
|
|
||||||
|
public static IRazorEngineBuilder Register(IRazorEngineBuilder builder)
|
||||||
|
{
|
||||||
|
builder.AddDirective(DirectiveDescriptor);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetRouteTemplate(DocumentIRNode irDocument, out string routeTemplate)
|
||||||
|
{
|
||||||
|
var visitor = new Visitor();
|
||||||
|
for (var i = 0; i < irDocument.Children.Count; i++)
|
||||||
|
{
|
||||||
|
visitor.Visit(irDocument.Children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
routeTemplate = visitor.RouteTemplate;
|
||||||
|
return routeTemplate != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Visitor : RazorIRNodeWalker
|
||||||
|
{
|
||||||
|
public DirectiveIRNode DirectiveNode { get; private set; }
|
||||||
|
|
||||||
|
public string RouteTemplate { get; private set; }
|
||||||
|
|
||||||
|
public override void VisitDirective(DirectiveIRNode node)
|
||||||
|
{
|
||||||
|
if (node.Descriptor == DirectiveDescriptor)
|
||||||
|
{
|
||||||
|
DirectiveNode = node;
|
||||||
|
RouteTemplate = node.Tokens.First().Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor.Host
|
||||||
|
{
|
||||||
|
public class RazorPageDocumentClassifier : BaseDocumentClassifierPass
|
||||||
|
{
|
||||||
|
public static readonly string DocumentKind = "mvc.1.0.razor-page";
|
||||||
|
|
||||||
|
protected override string BaseType => "Microsoft.AspNetCore.Mvc.RazorPages.Page";
|
||||||
|
|
||||||
|
protected override string ClassifyDocument(RazorCodeDocument codeDocument, DocumentIRNode irDocument)
|
||||||
|
{
|
||||||
|
string routePrefix;
|
||||||
|
if (PageDirective.TryGetRouteTemplate(irDocument, out routePrefix))
|
||||||
|
{
|
||||||
|
return DocumentKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -183,9 +183,11 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
InjectDirective.Register(b);
|
InjectDirective.Register(b);
|
||||||
ModelDirective.Register(b);
|
ModelDirective.Register(b);
|
||||||
|
PageDirective.Register(b);
|
||||||
|
|
||||||
b.Features.Add(new ModelExpressionPass());
|
b.Features.Add(new ModelExpressionPass());
|
||||||
b.Features.Add(new ViewComponentTagHelperPass());
|
b.Features.Add(new ViewComponentTagHelperPass());
|
||||||
|
b.Features.Add(new RazorPageDocumentClassifier());
|
||||||
b.Features.Add(new MvcViewDocumentClassifierPass());
|
b.Features.Add(new MvcViewDocumentClassifierPass());
|
||||||
b.Features.Add(new DefaultInstrumentationPass());
|
b.Features.Add(new DefaultInstrumentationPass());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
if (file.IsDirectory)
|
if (file.IsDirectory)
|
||||||
{
|
{
|
||||||
var children = EnumerateFiles(_provider.GetDirectoryContents(file.PhysicalPath), basePath, prefix + "/" + file.Name);
|
var relativePath = prefix + "/" + file.Name;
|
||||||
|
var subDirectory = _provider.GetDirectoryContents(relativePath);
|
||||||
|
var children = EnumerateFiles(subDirectory, basePath, relativePath);
|
||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
yield return child;
|
yield return child;
|
||||||
|
|
|
||||||
|
|
@ -9,26 +9,26 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
|
||||||
{
|
{
|
||||||
public class DefaultRazorProjectItem : RazorProjectItem
|
public class DefaultRazorProjectItem : RazorProjectItem
|
||||||
{
|
{
|
||||||
private readonly IFileInfo _fileInfo;
|
|
||||||
|
|
||||||
public DefaultRazorProjectItem(IFileInfo fileInfo, string basePath, string path)
|
public DefaultRazorProjectItem(IFileInfo fileInfo, string basePath, string path)
|
||||||
{
|
{
|
||||||
_fileInfo = fileInfo;
|
FileInfo = fileInfo;
|
||||||
BasePath = basePath;
|
BasePath = basePath;
|
||||||
Path = path;
|
Path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IFileInfo FileInfo { get; }
|
||||||
|
|
||||||
public override string BasePath { get; }
|
public override string BasePath { get; }
|
||||||
|
|
||||||
public override string Path { get; }
|
public override string Path { get; }
|
||||||
|
|
||||||
public override bool Exists => _fileInfo.Exists;
|
public override bool Exists => FileInfo.Exists;
|
||||||
|
|
||||||
public override string PhysicalPath => _fileInfo.PhysicalPath;
|
public override string PhysicalPath => FileInfo.PhysicalPath;
|
||||||
|
|
||||||
public override Stream Read()
|
public override Stream Read()
|
||||||
{
|
{
|
||||||
return _fileInfo.CreateReadStream();
|
return FileInfo.CreateReadStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
|
||||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -26,15 +25,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents properties and methods that are needed in order to render a view that uses Razor syntax.
|
/// Represents properties and methods that are needed in order to render a view that uses Razor syntax.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RazorPage : IRazorPage
|
public abstract class RazorPage : RazorPageBase, IRazorPage
|
||||||
{
|
{
|
||||||
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Stack<TagHelperScopeInfo> _tagHelperScopes = new Stack<TagHelperScopeInfo>();
|
private readonly Stack<TagHelperScopeInfo> _tagHelperScopes = new Stack<TagHelperScopeInfo>();
|
||||||
private IUrlHelper _urlHelper;
|
private IUrlHelper _urlHelper;
|
||||||
private ITagHelperFactory _tagHelperFactory;
|
private ITagHelperFactory _tagHelperFactory;
|
||||||
private bool _renderedBody;
|
private bool _renderedBody;
|
||||||
private AttributeInfo _attributeInfo;
|
|
||||||
private TagHelperAttributeInfo _tagHelperAttributeInfo;
|
|
||||||
private StringWriter _valueBuffer;
|
private StringWriter _valueBuffer;
|
||||||
private IViewBufferScope _bufferScope;
|
private IViewBufferScope _bufferScope;
|
||||||
private bool _ignoreBody;
|
private bool _ignoreBody;
|
||||||
|
|
@ -118,8 +115,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IDictionary<string, RenderAsyncDelegate> SectionWriters { get; }
|
public IDictionary<string, RenderAsyncDelegate> SectionWriters { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
protected override TextWriter Writer => Output;
|
||||||
public abstract Task ExecuteAsync();
|
|
||||||
|
protected override HtmlEncoder Encoder => HtmlEncoder;
|
||||||
|
|
||||||
private ITagHelperFactory TagHelperFactory
|
private ITagHelperFactory TagHelperFactory
|
||||||
{
|
{
|
||||||
|
|
@ -188,7 +186,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// non-<see cref="IHtmlContent"/> C# expressions. If <c>null</c>, does not change <see cref="HtmlEncoder"/>.
|
/// non-<see cref="IHtmlContent"/> C# expressions. If <c>null</c>, does not change <see cref="HtmlEncoder"/>.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// All writes to the <see cref="Output"/> or <see cref="ViewContext.Writer"/> after calling this method will
|
/// All writes to the <see cref="Writer"/> or <see cref="ViewContext.Writer"/> after calling this method will
|
||||||
/// be buffered until <see cref="EndTagHelperWritingScope"/> is called.
|
/// be buffered until <see cref="EndTagHelperWritingScope"/> is called.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void StartTagHelperWritingScope(HtmlEncoder encoder)
|
public void StartTagHelperWritingScope(HtmlEncoder encoder)
|
||||||
|
|
@ -235,7 +233,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// Starts a new scope for writing <see cref="ITagHelper"/> attribute values.
|
/// Starts a new scope for writing <see cref="ITagHelper"/> attribute values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// All writes to the <see cref="Output"/> or <see cref="ViewContext.Writer"/> after calling this method will
|
/// All writes to the <see cref="Writer"/> or <see cref="ViewContext.Writer"/> after calling this method will
|
||||||
/// be buffered until <see cref="EndWriteTagHelperAttribute"/> is called.
|
/// be buffered until <see cref="EndWriteTagHelperAttribute"/> is called.
|
||||||
/// The content will be buffered using a shared <see cref="StringWriter"/> within this <see cref="RazorPage"/>
|
/// The content will be buffered using a shared <see cref="StringWriter"/> within this <see cref="RazorPage"/>
|
||||||
/// Nesting of <see cref="BeginWriteTagHelperAttribute"/> and <see cref="EndWriteTagHelperAttribute"/> method calls
|
/// Nesting of <see cref="BeginWriteTagHelperAttribute"/> and <see cref="EndWriteTagHelperAttribute"/> method calls
|
||||||
|
|
@ -286,368 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public override string Href(string contentPath)
|
||||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
|
||||||
public virtual void Write(object value)
|
|
||||||
{
|
|
||||||
WriteTo(Output, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
|
||||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
|
|
||||||
/// <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>.
|
|
||||||
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
|
|
||||||
/// <paramref name="writer"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public virtual void WriteTo(TextWriter writer, object value)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteTo(writer, HtmlEncoder, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the specified <paramref name="value"/> with HTML encoding to given <paramref name="writer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
|
||||||
/// <param name="encoder">
|
|
||||||
/// The <see cref="System.Text.Encodings.Web.HtmlEncoder"/> to use when encoding <paramref name="value"/>.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
|
|
||||||
/// <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>.
|
|
||||||
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
|
|
||||||
/// <paramref name="writer"/>.
|
|
||||||
/// </remarks>
|
|
||||||
public static void WriteTo(TextWriter writer, HtmlEncoder encoder, object value)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encoder == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(encoder));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null || value == HtmlString.Empty)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var htmlContent = value as IHtmlContent;
|
|
||||||
if (htmlContent != null)
|
|
||||||
{
|
|
||||||
var bufferedWriter = writer as ViewBufferTextWriter;
|
|
||||||
if (bufferedWriter == null || !bufferedWriter.IsBuffering)
|
|
||||||
{
|
|
||||||
htmlContent.WriteTo(writer, encoder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var htmlContentContainer = value as IHtmlContentContainer;
|
|
||||||
if (htmlContentContainer != null)
|
|
||||||
{
|
|
||||||
// This is likely another ViewBuffer.
|
|
||||||
htmlContentContainer.MoveTo(bufferedWriter.Buffer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Perf: This is the common case for IHtmlContent, ViewBufferTextWriter is inefficient
|
|
||||||
// for writing character by character.
|
|
||||||
bufferedWriter.Buffer.AppendHtml(htmlContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteTo(writer, encoder, value.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
|
||||||
/// <param name="value">The <see cref="string"/> to write.</param>
|
|
||||||
public virtual void WriteTo(TextWriter writer, string value)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteTo(writer, HtmlEncoder, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void WriteTo(TextWriter writer, HtmlEncoder encoder, string value)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(value))
|
|
||||||
{
|
|
||||||
// Perf: Encode right away instead of writing it character-by-character.
|
|
||||||
// character-by-character isn't efficient when using a writer backed by a ViewBuffer.
|
|
||||||
var encoded = encoder.Encode(value);
|
|
||||||
writer.Write(encoded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Output"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
|
||||||
public virtual void WriteLiteral(object value)
|
|
||||||
{
|
|
||||||
WriteLiteralTo(Output, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the specified <paramref name="value"/> without HTML encoding to the <paramref name="writer"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
|
||||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
|
||||||
public virtual void WriteLiteralTo(TextWriter writer, object value)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
WriteLiteralTo(writer, value.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Output"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
|
||||||
/// <param name="value">The <see cref="string"/> to write.</param>
|
|
||||||
public virtual void WriteLiteralTo(TextWriter writer, string value)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(value))
|
|
||||||
{
|
|
||||||
writer.Write(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void BeginWriteAttribute(
|
|
||||||
string name,
|
|
||||||
string prefix,
|
|
||||||
int prefixOffset,
|
|
||||||
string suffix,
|
|
||||||
int suffixOffset,
|
|
||||||
int attributeValuesCount)
|
|
||||||
{
|
|
||||||
BeginWriteAttributeTo(Output, name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void BeginWriteAttributeTo(
|
|
||||||
TextWriter writer,
|
|
||||||
string name,
|
|
||||||
string prefix,
|
|
||||||
int prefixOffset,
|
|
||||||
string suffix,
|
|
||||||
int suffixOffset,
|
|
||||||
int attributeValuesCount)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prefix == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suffix == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(suffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
_attributeInfo = new AttributeInfo(name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount);
|
|
||||||
|
|
||||||
// Single valued attributes might be omitted in entirety if it the attribute value strictly evaluates to
|
|
||||||
// null or false. Consequently defer the prefix generation until we encounter the attribute value.
|
|
||||||
if (attributeValuesCount != 1)
|
|
||||||
{
|
|
||||||
WritePositionTaggedLiteral(writer, prefix, prefixOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteAttributeValue(
|
|
||||||
string prefix,
|
|
||||||
int prefixOffset,
|
|
||||||
object value,
|
|
||||||
int valueOffset,
|
|
||||||
int valueLength,
|
|
||||||
bool isLiteral)
|
|
||||||
{
|
|
||||||
WriteAttributeValueTo(Output, prefix, prefixOffset, value, valueOffset, valueLength, isLiteral);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteAttributeValueTo(
|
|
||||||
TextWriter writer,
|
|
||||||
string prefix,
|
|
||||||
int prefixOffset,
|
|
||||||
object value,
|
|
||||||
int valueOffset,
|
|
||||||
int valueLength,
|
|
||||||
bool isLiteral)
|
|
||||||
{
|
|
||||||
if (_attributeInfo.AttributeValuesCount == 1)
|
|
||||||
{
|
|
||||||
if (IsBoolFalseOrNullValue(prefix, value))
|
|
||||||
{
|
|
||||||
// Value is either null or the bool 'false' with no prefix; don't render the attribute.
|
|
||||||
_attributeInfo.Suppressed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are not omitting the attribute. Write the prefix.
|
|
||||||
WritePositionTaggedLiteral(writer, _attributeInfo.Prefix, _attributeInfo.PrefixOffset);
|
|
||||||
|
|
||||||
if (IsBoolTrueWithEmptyPrefixValue(prefix, value))
|
|
||||||
{
|
|
||||||
// The value is just the bool 'true', write the attribute name instead of the string 'True'.
|
|
||||||
value = _attributeInfo.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This block handles two cases.
|
|
||||||
// 1. Single value with prefix.
|
|
||||||
// 2. Multiple values with or without prefix.
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(prefix))
|
|
||||||
{
|
|
||||||
WritePositionTaggedLiteral(writer, prefix, prefixOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
BeginContext(valueOffset, valueLength, isLiteral);
|
|
||||||
|
|
||||||
WriteUnprefixedAttributeValueTo(writer, value, isLiteral);
|
|
||||||
|
|
||||||
EndContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void EndWriteAttribute()
|
|
||||||
{
|
|
||||||
EndWriteAttributeTo(Output);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void EndWriteAttributeTo(TextWriter writer)
|
|
||||||
{
|
|
||||||
if (writer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(writer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_attributeInfo.Suppressed)
|
|
||||||
{
|
|
||||||
WritePositionTaggedLiteral(writer, _attributeInfo.Suffix, _attributeInfo.SuffixOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BeginAddHtmlAttributeValues(
|
|
||||||
TagHelperExecutionContext executionContext,
|
|
||||||
string attributeName,
|
|
||||||
int attributeValuesCount,
|
|
||||||
HtmlAttributeValueStyle attributeValueStyle)
|
|
||||||
{
|
|
||||||
_tagHelperAttributeInfo = new TagHelperAttributeInfo(
|
|
||||||
executionContext,
|
|
||||||
attributeName,
|
|
||||||
attributeValuesCount,
|
|
||||||
attributeValueStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddHtmlAttributeValue(
|
|
||||||
string prefix,
|
|
||||||
int prefixOffset,
|
|
||||||
object value,
|
|
||||||
int valueOffset,
|
|
||||||
int valueLength,
|
|
||||||
bool isLiteral)
|
|
||||||
{
|
|
||||||
Debug.Assert(_tagHelperAttributeInfo.ExecutionContext != null);
|
|
||||||
if (_tagHelperAttributeInfo.AttributeValuesCount == 1)
|
|
||||||
{
|
|
||||||
if (IsBoolFalseOrNullValue(prefix, value))
|
|
||||||
{
|
|
||||||
// The first value was 'null' or 'false' indicating that we shouldn't render the attribute. The
|
|
||||||
// attribute is treated as a TagHelper attribute so it's only available in
|
|
||||||
// TagHelperContext.AllAttributes for TagHelper authors to see (if they want to see why the
|
|
||||||
// attribute was removed from TagHelperOutput.Attributes).
|
|
||||||
_tagHelperAttributeInfo.ExecutionContext.AddTagHelperAttribute(
|
|
||||||
_tagHelperAttributeInfo.Name,
|
|
||||||
value?.ToString() ?? string.Empty,
|
|
||||||
_tagHelperAttributeInfo.AttributeValueStyle);
|
|
||||||
_tagHelperAttributeInfo.Suppressed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (IsBoolTrueWithEmptyPrefixValue(prefix, value))
|
|
||||||
{
|
|
||||||
_tagHelperAttributeInfo.ExecutionContext.AddHtmlAttribute(
|
|
||||||
_tagHelperAttributeInfo.Name,
|
|
||||||
_tagHelperAttributeInfo.Name,
|
|
||||||
_tagHelperAttributeInfo.AttributeValueStyle);
|
|
||||||
_tagHelperAttributeInfo.Suppressed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
// Perf: We'll use this buffer for all of the attribute values and then clear it to
|
|
||||||
// reduce allocations.
|
|
||||||
if (_valueBuffer == null)
|
|
||||||
{
|
|
||||||
_valueBuffer = new StringWriter();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(prefix))
|
|
||||||
{
|
|
||||||
WriteLiteralTo(_valueBuffer, prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteUnprefixedAttributeValueTo(_valueBuffer, value, isLiteral);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndAddHtmlAttributeValues(TagHelperExecutionContext executionContext)
|
|
||||||
{
|
|
||||||
if (!_tagHelperAttributeInfo.Suppressed)
|
|
||||||
{
|
|
||||||
// Perf: _valueBuffer might be null if nothing was written. If it is set, clear it so
|
|
||||||
// it is reset for the next value.
|
|
||||||
var content = _valueBuffer == null ? HtmlString.Empty : new HtmlString(_valueBuffer.ToString());
|
|
||||||
_valueBuffer?.GetStringBuilder().Clear();
|
|
||||||
|
|
||||||
executionContext.AddHtmlAttribute(_tagHelperAttributeInfo.Name, content, _tagHelperAttributeInfo.AttributeValueStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual string Href(string contentPath)
|
|
||||||
{
|
{
|
||||||
if (contentPath == null)
|
if (contentPath == null)
|
||||||
{
|
{
|
||||||
|
|
@ -664,36 +301,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return _urlHelper.Content(contentPath);
|
return _urlHelper.Content(contentPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteUnprefixedAttributeValueTo(TextWriter writer, object value, bool isLiteral)
|
|
||||||
{
|
|
||||||
var stringValue = value as string;
|
|
||||||
|
|
||||||
// The extra branching here is to ensure that we call the Write*To(string) overload where possible.
|
|
||||||
if (isLiteral && stringValue != null)
|
|
||||||
{
|
|
||||||
WriteLiteralTo(writer, stringValue);
|
|
||||||
}
|
|
||||||
else if (isLiteral)
|
|
||||||
{
|
|
||||||
WriteLiteralTo(writer, value);
|
|
||||||
}
|
|
||||||
else if (stringValue != null)
|
|
||||||
{
|
|
||||||
WriteTo(writer, stringValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WriteTo(writer, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WritePositionTaggedLiteral(TextWriter writer, string value, int position)
|
|
||||||
{
|
|
||||||
BeginContext(position, value.Length, isLiteral: true);
|
|
||||||
WriteLiteralTo(writer, value);
|
|
||||||
EndContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In a Razor layout page, renders the portion of a content page that is not within a named section.
|
/// In a Razor layout page, renders the portion of a content page that is not within a named section.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -724,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the section to create.</param>
|
/// <param name="name">The name of the section to create.</param>
|
||||||
/// <param name="section">The <see cref="RenderAsyncDelegate"/> to execute when rendering the section.</param>
|
/// <param name="section">The <see cref="RenderAsyncDelegate"/> to execute when rendering the section.</param>
|
||||||
public void DefineSection(string name, RenderAsyncDelegate section)
|
public override void DefineSection(string name, RenderAsyncDelegate section)
|
||||||
{
|
{
|
||||||
if (name == null)
|
if (name == null)
|
||||||
{
|
{
|
||||||
|
|
@ -764,7 +371,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the section to render.</param>
|
/// <param name="name">The name of the section to render.</param>
|
||||||
/// <returns>An empty <see cref="IHtmlContent"/>.</returns>
|
/// <returns>An empty <see cref="IHtmlContent"/>.</returns>
|
||||||
/// <remarks>The method writes to the <see cref="Output"/> and the value returned is a token
|
/// <remarks>The method writes to the <see cref="Writer"/> and the value returned is a token
|
||||||
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
||||||
/// value does not represent the rendered content.</remarks>
|
/// value does not represent the rendered content.</remarks>
|
||||||
public HtmlString RenderSection(string name)
|
public HtmlString RenderSection(string name)
|
||||||
|
|
@ -783,7 +390,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// <param name="name">The section to render.</param>
|
/// <param name="name">The section to render.</param>
|
||||||
/// <param name="required">Indicates if this section must be rendered.</param>
|
/// <param name="required">Indicates if this section must be rendered.</param>
|
||||||
/// <returns>An empty <see cref="IHtmlContent"/>.</returns>
|
/// <returns>An empty <see cref="IHtmlContent"/>.</returns>
|
||||||
/// <remarks>The method writes to the <see cref="Output"/> and the value returned is a token
|
/// <remarks>The method writes to the <see cref="Writer"/> and the value returned is a token
|
||||||
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
||||||
/// value does not represent the rendered content.</remarks>
|
/// value does not represent the rendered content.</remarks>
|
||||||
public HtmlString RenderSection(string name, bool required)
|
public HtmlString RenderSection(string name, bool required)
|
||||||
|
|
@ -806,7 +413,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task{HtmlString}"/> that on completion returns an empty <see cref="IHtmlContent"/>.
|
/// A <see cref="Task{HtmlString}"/> that on completion returns an empty <see cref="IHtmlContent"/>.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>The method writes to the <see cref="Output"/> and the value returned is a token
|
/// <remarks>The method writes to the <see cref="Writer"/> and the value returned is a token
|
||||||
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
||||||
/// value does not represent the rendered content.</remarks>
|
/// value does not represent the rendered content.</remarks>
|
||||||
public Task<HtmlString> RenderSectionAsync(string name)
|
public Task<HtmlString> RenderSectionAsync(string name)
|
||||||
|
|
@ -828,7 +435,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task{HtmlString}"/> that on completion returns an empty <see cref="IHtmlContent"/>.
|
/// A <see cref="Task{HtmlString}"/> that on completion returns an empty <see cref="IHtmlContent"/>.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <remarks>The method writes to the <see cref="Output"/> and the value returned is a token
|
/// <remarks>The method writes to the <see cref="Writer"/> and the value returned is a token
|
||||||
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
/// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the
|
||||||
/// value does not represent the rendered content.</remarks>
|
/// value does not represent the rendered content.</remarks>
|
||||||
/// <exception cref="InvalidOperationException">if <paramref name="required"/> is <c>true</c> and the section
|
/// <exception cref="InvalidOperationException">if <paramref name="required"/> is <c>true</c> and the section
|
||||||
|
|
@ -908,7 +515,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes <see cref="TextWriter.FlushAsync"/> on <see cref="Output"/> and <see cref="M:Stream.FlushAsync"/>
|
/// Invokes <see cref="TextWriter.FlushAsync"/> on <see cref="Writer"/> and <see cref="M:Stream.FlushAsync"/>
|
||||||
/// on the response stream, writing out any buffered content to the <see cref="HttpResponse.Body"/>.
|
/// on the response stream, writing out any buffered content to the <see cref="HttpResponse.Body"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="Task{HtmlString}"/> that represents the asynchronous flush operation and on
|
/// <returns>A <see cref="Task{HtmlString}"/> that represents the asynchronous flush operation and on
|
||||||
|
|
@ -936,7 +543,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
throw new InvalidOperationException(message);
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Output.FlushAsync();
|
await Writer.FlushAsync();
|
||||||
await Context.Response.Body.FlushAsync();
|
await Context.Response.Body.FlushAsync();
|
||||||
return HtmlString.Empty;
|
return HtmlString.Empty;
|
||||||
}
|
}
|
||||||
|
|
@ -977,7 +584,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginContext(int position, int length, bool isLiteral)
|
public override void BeginContext(int position, int length, bool isLiteral)
|
||||||
{
|
{
|
||||||
const string BeginContextEvent = "Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext";
|
const string BeginContextEvent = "Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext";
|
||||||
|
|
||||||
|
|
@ -996,7 +603,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndContext()
|
public override void EndContext()
|
||||||
{
|
{
|
||||||
const string EndContextEvent = "Microsoft.AspNetCore.Mvc.Razor.EndInstrumentationContext";
|
const string EndContextEvent = "Microsoft.AspNetCore.Mvc.Razor.EndInstrumentationContext";
|
||||||
|
|
||||||
|
|
@ -1026,20 +633,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
return HtmlString.Empty;
|
return HtmlString.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsBoolFalseOrNullValue(string prefix, object value)
|
|
||||||
{
|
|
||||||
return string.IsNullOrEmpty(prefix) &&
|
|
||||||
(value == null ||
|
|
||||||
(value is bool && !(bool)value));
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsBoolTrueWithEmptyPrefixValue(string prefix, object value)
|
|
||||||
{
|
|
||||||
// If the value is just the bool 'true', use the attribute name as the value.
|
|
||||||
return string.IsNullOrEmpty(prefix) &&
|
|
||||||
(value is bool && (bool)value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureMethodCanBeInvoked(string methodName)
|
private void EnsureMethodCanBeInvoked(string methodName)
|
||||||
{
|
{
|
||||||
if (PreviousSectionWriters == null)
|
if (PreviousSectionWriters == null)
|
||||||
|
|
@ -1048,68 +641,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct AttributeInfo
|
|
||||||
{
|
|
||||||
public AttributeInfo(
|
|
||||||
string name,
|
|
||||||
string prefix,
|
|
||||||
int prefixOffset,
|
|
||||||
string suffix,
|
|
||||||
int suffixOffset,
|
|
||||||
int attributeValuesCount)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Prefix = prefix;
|
|
||||||
PrefixOffset = prefixOffset;
|
|
||||||
Suffix = suffix;
|
|
||||||
SuffixOffset = suffixOffset;
|
|
||||||
AttributeValuesCount = attributeValuesCount;
|
|
||||||
|
|
||||||
Suppressed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int AttributeValuesCount { get; }
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
public string Prefix { get; }
|
|
||||||
|
|
||||||
public int PrefixOffset { get; }
|
|
||||||
|
|
||||||
public string Suffix { get; }
|
|
||||||
|
|
||||||
public int SuffixOffset { get; }
|
|
||||||
|
|
||||||
public bool Suppressed { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct TagHelperAttributeInfo
|
|
||||||
{
|
|
||||||
public TagHelperAttributeInfo(
|
|
||||||
TagHelperExecutionContext tagHelperExecutionContext,
|
|
||||||
string name,
|
|
||||||
int attributeValuesCount,
|
|
||||||
HtmlAttributeValueStyle attributeValueStyle)
|
|
||||||
{
|
|
||||||
ExecutionContext = tagHelperExecutionContext;
|
|
||||||
Name = name;
|
|
||||||
AttributeValuesCount = attributeValuesCount;
|
|
||||||
AttributeValueStyle = attributeValueStyle;
|
|
||||||
|
|
||||||
Suppressed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
public TagHelperExecutionContext ExecutionContext { get; }
|
|
||||||
|
|
||||||
public int AttributeValuesCount { get; }
|
|
||||||
|
|
||||||
public HtmlAttributeValueStyle AttributeValueStyle { get; }
|
|
||||||
|
|
||||||
public bool Suppressed { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct TagHelperScopeInfo
|
private struct TagHelperScopeInfo
|
||||||
{
|
{
|
||||||
public TagHelperScopeInfo(ViewBuffer buffer, HtmlEncoder encoder, TextWriter writer)
|
public TagHelperScopeInfo(ViewBuffer buffer, HtmlEncoder encoder, TextWriter writer)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,514 @@
|
||||||
|
// 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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||||
|
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
|
||||||
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents properties and methods that are needed in order to render a view that uses Razor syntax.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class RazorPageBase
|
||||||
|
{
|
||||||
|
private AttributeInfo _attributeInfo;
|
||||||
|
private TagHelperAttributeInfo _tagHelperAttributeInfo;
|
||||||
|
private StringWriter _valueBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="TextWriter"/> that the page is writing output to.
|
||||||
|
/// </summary>
|
||||||
|
protected abstract TextWriter Writer { get; }
|
||||||
|
|
||||||
|
protected abstract HtmlEncoder Encoder { get; }
|
||||||
|
|
||||||
|
public abstract Task ExecuteAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||||
|
public virtual void Write(object value)
|
||||||
|
{
|
||||||
|
WriteTo(Writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||||
|
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
|
||||||
|
/// <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>.
|
||||||
|
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
|
||||||
|
/// <paramref name="writer"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public virtual void WriteTo(TextWriter writer, object value)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteTo(writer, Encoder, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> with HTML encoding to given <paramref name="writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||||
|
/// <param name="encoder">
|
||||||
|
/// The <see cref="System.Text.Encodings.Web.HtmlEncoder"/> to use when encoding <paramref name="value"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// <paramref name="value"/>s of type <see cref="IHtmlContent"/> are written using
|
||||||
|
/// <see cref="IHtmlContent.WriteTo(TextWriter, HtmlEncoder)"/>.
|
||||||
|
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to the
|
||||||
|
/// <paramref name="writer"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public static void WriteTo(TextWriter writer, HtmlEncoder encoder, object value)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null || value == HtmlString.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var htmlContent = value as IHtmlContent;
|
||||||
|
if (htmlContent != null)
|
||||||
|
{
|
||||||
|
var bufferedWriter = writer as ViewBufferTextWriter;
|
||||||
|
if (bufferedWriter == null || !bufferedWriter.IsBuffering)
|
||||||
|
{
|
||||||
|
htmlContent.WriteTo(writer, encoder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var htmlContentContainer = value as IHtmlContentContainer;
|
||||||
|
if (htmlContentContainer != null)
|
||||||
|
{
|
||||||
|
// This is likely another ViewBuffer.
|
||||||
|
htmlContentContainer.MoveTo(bufferedWriter.Buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Perf: This is the common case for IHtmlContent, ViewBufferTextWriter is inefficient
|
||||||
|
// for writing character by character.
|
||||||
|
bufferedWriter.Buffer.AppendHtml(htmlContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteTo(writer, encoder, value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> with HTML encoding to <paramref name="writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||||
|
/// <param name="value">The <see cref="string"/> to write.</param>
|
||||||
|
public virtual void WriteTo(TextWriter writer, string value)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteTo(writer, Encoder, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteTo(TextWriter writer, HtmlEncoder encoder, string value)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
// Perf: Encode right away instead of writing it character-by-character.
|
||||||
|
// character-by-character isn't efficient when using a writer backed by a ViewBuffer.
|
||||||
|
var encoded = encoder.Encode(value);
|
||||||
|
writer.Write(encoded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||||
|
public virtual void WriteLiteral(object value)
|
||||||
|
{
|
||||||
|
WriteLiteralTo(Writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> without HTML encoding to the <paramref name="writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||||
|
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||||
|
public virtual void WriteLiteralTo(TextWriter writer, object value)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
WriteLiteralTo(writer, value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified <paramref name="value"/> without HTML encoding to <see cref="Writer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||||
|
/// <param name="value">The <see cref="string"/> to write.</param>
|
||||||
|
public virtual void WriteLiteralTo(TextWriter writer, string value)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
writer.Write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void BeginWriteAttribute(
|
||||||
|
string name,
|
||||||
|
string prefix,
|
||||||
|
int prefixOffset,
|
||||||
|
string suffix,
|
||||||
|
int suffixOffset,
|
||||||
|
int attributeValuesCount)
|
||||||
|
{
|
||||||
|
BeginWriteAttributeTo(Writer, name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void BeginWriteAttributeTo(
|
||||||
|
TextWriter writer,
|
||||||
|
string name,
|
||||||
|
string prefix,
|
||||||
|
int prefixOffset,
|
||||||
|
string suffix,
|
||||||
|
int suffixOffset,
|
||||||
|
int attributeValuesCount)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suffix == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
_attributeInfo = new AttributeInfo(name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount);
|
||||||
|
|
||||||
|
// Single valued attributes might be omitted in entirety if it the attribute value strictly evaluates to
|
||||||
|
// null or false. Consequently defer the prefix generation until we encounter the attribute value.
|
||||||
|
if (attributeValuesCount != 1)
|
||||||
|
{
|
||||||
|
WritePositionTaggedLiteral(writer, prefix, prefixOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteAttributeValue(
|
||||||
|
string prefix,
|
||||||
|
int prefixOffset,
|
||||||
|
object value,
|
||||||
|
int valueOffset,
|
||||||
|
int valueLength,
|
||||||
|
bool isLiteral)
|
||||||
|
{
|
||||||
|
WriteAttributeValueTo(Writer, prefix, prefixOffset, value, valueOffset, valueLength, isLiteral);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteAttributeValueTo(
|
||||||
|
TextWriter writer,
|
||||||
|
string prefix,
|
||||||
|
int prefixOffset,
|
||||||
|
object value,
|
||||||
|
int valueOffset,
|
||||||
|
int valueLength,
|
||||||
|
bool isLiteral)
|
||||||
|
{
|
||||||
|
if (_attributeInfo.AttributeValuesCount == 1)
|
||||||
|
{
|
||||||
|
if (IsBoolFalseOrNullValue(prefix, value))
|
||||||
|
{
|
||||||
|
// Value is either null or the bool 'false' with no prefix; don't render the attribute.
|
||||||
|
_attributeInfo.Suppressed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are not omitting the attribute. Write the prefix.
|
||||||
|
WritePositionTaggedLiteral(writer, _attributeInfo.Prefix, _attributeInfo.PrefixOffset);
|
||||||
|
|
||||||
|
if (IsBoolTrueWithEmptyPrefixValue(prefix, value))
|
||||||
|
{
|
||||||
|
// The value is just the bool 'true', write the attribute name instead of the string 'True'.
|
||||||
|
value = _attributeInfo.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This block handles two cases.
|
||||||
|
// 1. Single value with prefix.
|
||||||
|
// 2. Multiple values with or without prefix.
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(prefix))
|
||||||
|
{
|
||||||
|
WritePositionTaggedLiteral(writer, prefix, prefixOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginContext(valueOffset, valueLength, isLiteral);
|
||||||
|
|
||||||
|
WriteUnprefixedAttributeValueTo(writer, value, isLiteral);
|
||||||
|
|
||||||
|
EndContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void EndWriteAttribute()
|
||||||
|
{
|
||||||
|
EndWriteAttributeTo(Writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void EndWriteAttributeTo(TextWriter writer)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_attributeInfo.Suppressed)
|
||||||
|
{
|
||||||
|
WritePositionTaggedLiteral(writer, _attributeInfo.Suffix, _attributeInfo.SuffixOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BeginAddHtmlAttributeValues(
|
||||||
|
TagHelperExecutionContext executionContext,
|
||||||
|
string attributeName,
|
||||||
|
int attributeValuesCount,
|
||||||
|
HtmlAttributeValueStyle attributeValueStyle)
|
||||||
|
{
|
||||||
|
_tagHelperAttributeInfo = new TagHelperAttributeInfo(
|
||||||
|
executionContext,
|
||||||
|
attributeName,
|
||||||
|
attributeValuesCount,
|
||||||
|
attributeValueStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHtmlAttributeValue(
|
||||||
|
string prefix,
|
||||||
|
int prefixOffset,
|
||||||
|
object value,
|
||||||
|
int valueOffset,
|
||||||
|
int valueLength,
|
||||||
|
bool isLiteral)
|
||||||
|
{
|
||||||
|
Debug.Assert(_tagHelperAttributeInfo.ExecutionContext != null);
|
||||||
|
if (_tagHelperAttributeInfo.AttributeValuesCount == 1)
|
||||||
|
{
|
||||||
|
if (IsBoolFalseOrNullValue(prefix, value))
|
||||||
|
{
|
||||||
|
// The first value was 'null' or 'false' indicating that we shouldn't render the attribute. The
|
||||||
|
// attribute is treated as a TagHelper attribute so it's only available in
|
||||||
|
// TagHelperContext.AllAttributes for TagHelper authors to see (if they want to see why the
|
||||||
|
// attribute was removed from TagHelperOutput.Attributes).
|
||||||
|
_tagHelperAttributeInfo.ExecutionContext.AddTagHelperAttribute(
|
||||||
|
_tagHelperAttributeInfo.Name,
|
||||||
|
value?.ToString() ?? string.Empty,
|
||||||
|
_tagHelperAttributeInfo.AttributeValueStyle);
|
||||||
|
_tagHelperAttributeInfo.Suppressed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (IsBoolTrueWithEmptyPrefixValue(prefix, value))
|
||||||
|
{
|
||||||
|
_tagHelperAttributeInfo.ExecutionContext.AddHtmlAttribute(
|
||||||
|
_tagHelperAttributeInfo.Name,
|
||||||
|
_tagHelperAttributeInfo.Name,
|
||||||
|
_tagHelperAttributeInfo.AttributeValueStyle);
|
||||||
|
_tagHelperAttributeInfo.Suppressed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// Perf: We'll use this buffer for all of the attribute values and then clear it to
|
||||||
|
// reduce allocations.
|
||||||
|
if (_valueBuffer == null)
|
||||||
|
{
|
||||||
|
_valueBuffer = new StringWriter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(prefix))
|
||||||
|
{
|
||||||
|
WriteLiteralTo(_valueBuffer, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteUnprefixedAttributeValueTo(_valueBuffer, value, isLiteral);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndAddHtmlAttributeValues(TagHelperExecutionContext executionContext)
|
||||||
|
{
|
||||||
|
if (!_tagHelperAttributeInfo.Suppressed)
|
||||||
|
{
|
||||||
|
// Perf: _valueBuffer might be null if nothing was written. If it is set, clear it so
|
||||||
|
// it is reset for the next value.
|
||||||
|
var content = _valueBuffer == null ? HtmlString.Empty : new HtmlString(_valueBuffer.ToString());
|
||||||
|
_valueBuffer?.GetStringBuilder().Clear();
|
||||||
|
|
||||||
|
executionContext.AddHtmlAttribute(_tagHelperAttributeInfo.Name, content, _tagHelperAttributeInfo.AttributeValueStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string Href(string contentPath);
|
||||||
|
|
||||||
|
private void WriteUnprefixedAttributeValueTo(TextWriter writer, object value, bool isLiteral)
|
||||||
|
{
|
||||||
|
var stringValue = value as string;
|
||||||
|
|
||||||
|
// The extra branching here is to ensure that we call the Write*To(string) overload where possible.
|
||||||
|
if (isLiteral && stringValue != null)
|
||||||
|
{
|
||||||
|
WriteLiteralTo(writer, stringValue);
|
||||||
|
}
|
||||||
|
else if (isLiteral)
|
||||||
|
{
|
||||||
|
WriteLiteralTo(writer, value);
|
||||||
|
}
|
||||||
|
else if (stringValue != null)
|
||||||
|
{
|
||||||
|
WriteTo(writer, stringValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteTo(writer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WritePositionTaggedLiteral(TextWriter writer, string value, int position)
|
||||||
|
{
|
||||||
|
BeginContext(position, value.Length, isLiteral: true);
|
||||||
|
WriteLiteralTo(writer, value);
|
||||||
|
EndContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a named content section in the page.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the section to create.</param>
|
||||||
|
/// <param name="section">The <see cref="RenderAsyncDelegate"/> to execute when rendering the section.</param>
|
||||||
|
public abstract void DefineSection(string name, RenderAsyncDelegate section);
|
||||||
|
|
||||||
|
public abstract void BeginContext(int position, int length, bool isLiteral);
|
||||||
|
|
||||||
|
public abstract void EndContext();
|
||||||
|
|
||||||
|
private bool IsBoolFalseOrNullValue(string prefix, object value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(prefix) &&
|
||||||
|
(value == null ||
|
||||||
|
(value is bool && !(bool)value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsBoolTrueWithEmptyPrefixValue(string prefix, object value)
|
||||||
|
{
|
||||||
|
// If the value is just the bool 'true', use the attribute name as the value.
|
||||||
|
return string.IsNullOrEmpty(prefix) &&
|
||||||
|
(value is bool && (bool)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct AttributeInfo
|
||||||
|
{
|
||||||
|
public AttributeInfo(
|
||||||
|
string name,
|
||||||
|
string prefix,
|
||||||
|
int prefixOffset,
|
||||||
|
string suffix,
|
||||||
|
int suffixOffset,
|
||||||
|
int attributeValuesCount)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Prefix = prefix;
|
||||||
|
PrefixOffset = prefixOffset;
|
||||||
|
Suffix = suffix;
|
||||||
|
SuffixOffset = suffixOffset;
|
||||||
|
AttributeValuesCount = attributeValuesCount;
|
||||||
|
|
||||||
|
Suppressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int AttributeValuesCount { get; }
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string Prefix { get; }
|
||||||
|
|
||||||
|
public int PrefixOffset { get; }
|
||||||
|
|
||||||
|
public string Suffix { get; }
|
||||||
|
|
||||||
|
public int SuffixOffset { get; }
|
||||||
|
|
||||||
|
public bool Suppressed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TagHelperAttributeInfo
|
||||||
|
{
|
||||||
|
public TagHelperAttributeInfo(
|
||||||
|
TagHelperExecutionContext tagHelperExecutionContext,
|
||||||
|
string name,
|
||||||
|
int attributeValuesCount,
|
||||||
|
HtmlAttributeValueStyle attributeValueStyle)
|
||||||
|
{
|
||||||
|
ExecutionContext = tagHelperExecutionContext;
|
||||||
|
Name = name;
|
||||||
|
AttributeValuesCount = attributeValuesCount;
|
||||||
|
AttributeValueStyle = attributeValueStyle;
|
||||||
|
|
||||||
|
Suppressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public TagHelperExecutionContext ExecutionContext { get; }
|
||||||
|
|
||||||
|
public int AttributeValuesCount { get; }
|
||||||
|
|
||||||
|
public HtmlAttributeValueStyle AttributeValueStyle { get; }
|
||||||
|
|
||||||
|
public bool Suppressed { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
|
{
|
||||||
|
public static class MvcRazorPagesMvcCoreBuilderExtensions
|
||||||
|
{
|
||||||
|
public static IMvcCoreBuilder AddRazorPages(this IMvcCoreBuilder builder)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddRazorViewEngine();
|
||||||
|
AddServices(builder.Services);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IMvcCoreBuilder AddRazorPages(
|
||||||
|
this IMvcCoreBuilder builder,
|
||||||
|
Action<RazorViewEngineOptions> setupAction)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(builder));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setupAction == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(setupAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddRazorViewEngine();
|
||||||
|
AddServices(builder.Services);
|
||||||
|
|
||||||
|
builder.Services.Configure(setupAction);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for testing.
|
||||||
|
internal static void AddServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Singleton<IActionDescriptorProvider, PageActionDescriptorProvider>());
|
||||||
|
|
||||||
|
services.TryAddEnumerable(
|
||||||
|
ServiceDescriptor.Singleton<IActionInvokerProvider, PageActionInvokerProvider>());
|
||||||
|
|
||||||
|
services.TryAddSingleton<IPageModelActivatorProvider, DefaultPageModelActivatorProvider>();
|
||||||
|
services.TryAddSingleton<IPageModelFactoryProvider, DefaultPageModelFactoryProvider>();
|
||||||
|
|
||||||
|
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivator>();
|
||||||
|
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactory>();
|
||||||
|
|
||||||
|
services.TryAddSingleton<IPageLoader, DefaultPageLoader>();
|
||||||
|
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();
|
||||||
|
services.TryAddSingleton<PageResultExecutor>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -71,7 +71,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
|
|
||||||
if (string.Equals(IndexFileName, item.Filename, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(IndexFileName, item.Filename, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
model.Selectors.Add(CreateSelectorModel(item.BasePath, template));
|
var parentDirectoryPath = item.Path;
|
||||||
|
var index = parentDirectoryPath.LastIndexOf('/');
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
parentDirectoryPath = string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parentDirectoryPath = parentDirectoryPath.Substring(0, index);
|
||||||
|
}
|
||||||
|
model.Selectors.Add(CreateSelectorModel(parentDirectoryPath, template));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
|
for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
using Microsoft.AspNetCore.Mvc.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
using Microsoft.AspNetCore.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
: base(writerFactory, compositeViewEngine, diagnosticSource)
|
: base(writerFactory, compositeViewEngine, diagnosticSource)
|
||||||
{
|
{
|
||||||
_razorViewEngine = razorViewEngine;
|
_razorViewEngine = razorViewEngine;
|
||||||
_razorPageActivator = razorPageActivator;
|
_razorPageActivator = new PassThruRazorPageActivator(razorPageActivator);
|
||||||
_htmlEncoder = htmlEncoder;
|
_htmlEncoder = htmlEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
var view = new RazorView(_razorViewEngine, _razorPageActivator, pageContext.PageStarts, result.Page, _htmlEncoder);
|
var view = new RazorView(_razorViewEngine, _razorPageActivator, pageContext.PageStarts, result.Page, _htmlEncoder);
|
||||||
|
pageContext.View = view;
|
||||||
return ExecuteAsync(pageContext, result.ContentType, result.StatusCode);
|
return ExecuteAsync(pageContext, result.ContentType, result.StatusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class DefaultPageHandlerMethodSelector : IPageHandlerMethodSelector
|
||||||
|
{
|
||||||
|
public HandlerMethodDescriptor Select(PageContext context)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class DefaultPageLoader : IPageLoader
|
||||||
|
{
|
||||||
|
private readonly IRazorCompilationService _razorCompilationService;
|
||||||
|
private readonly RazorProject _project;
|
||||||
|
|
||||||
|
public DefaultPageLoader(
|
||||||
|
IRazorCompilationService razorCompilationService,
|
||||||
|
RazorProject razorProject)
|
||||||
|
{
|
||||||
|
_razorCompilationService = razorCompilationService;
|
||||||
|
_project = razorProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type Load(PageActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
var item = _project.GetItem(actionDescriptor.RelativePath);
|
||||||
|
if (!item.Exists)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"File {actionDescriptor.RelativePath} was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectItem = (DefaultRazorProjectItem)item;
|
||||||
|
var compilationResult = _razorCompilationService.Compile(new RelativeFileInfo(projectItem.FileInfo, item.Path));
|
||||||
|
compilationResult.EnsureSuccessful();
|
||||||
|
|
||||||
|
return compilationResult.CompiledType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -145,6 +145,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
tempData,
|
tempData,
|
||||||
_htmlHelperOptions);
|
_htmlHelperOptions);
|
||||||
|
|
||||||
|
pageContext.ActionDescriptor = cacheEntry.ActionDescriptor;
|
||||||
|
|
||||||
return new PageActionInvoker(
|
return new PageActionInvoker(
|
||||||
_selector,
|
_selector,
|
||||||
_diagnosticSource,
|
_diagnosticSource,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// 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 Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
|
||||||
|
{
|
||||||
|
public class PassThruRazorPageActivator : IRazorPageActivator
|
||||||
|
{
|
||||||
|
private readonly IRazorPageActivator _pageActivator;
|
||||||
|
|
||||||
|
public PassThruRazorPageActivator(IRazorPageActivator pageActivator)
|
||||||
|
{
|
||||||
|
_pageActivator = pageActivator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Activate(IRazorPage page, ViewContext context)
|
||||||
|
{
|
||||||
|
var razorView = (RazorView)context.View;
|
||||||
|
if (ReferenceEquals(page, razorView.RazorPage))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pageActivator.Activate(page, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,18 +3,25 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
using Microsoft.AspNetCore.Html;
|
using Microsoft.AspNetCore.Html;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A base class for a Razor page.
|
/// A base class for a Razor page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Page : IRazorPage
|
public abstract class Page : RazorPageBase, IRazorPage
|
||||||
{
|
{
|
||||||
|
private IUrlHelper _urlHelper;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IHtmlContent BodyContent { get; set; }
|
public IHtmlContent BodyContent { get; set; }
|
||||||
|
|
||||||
|
|
@ -41,13 +48,84 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ViewContext ViewContext { get; set; }
|
public ViewContext ViewContext { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a <see cref="System.Diagnostics.DiagnosticSource"/> instance used to instrument the page execution.
|
||||||
|
/// </summary>
|
||||||
|
[RazorInject]
|
||||||
|
public DiagnosticSource DiagnosticSource { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="System.Text.Encodings.Web.HtmlEncoder"/> to use when this <see cref="RazorPage"/>
|
||||||
|
/// handles non-<see cref="IHtmlContent"/> C# expressions.
|
||||||
|
/// </summary>
|
||||||
|
[RazorInject]
|
||||||
|
public HtmlEncoder HtmlEncoder { get; set; }
|
||||||
|
|
||||||
|
protected override HtmlEncoder Encoder => HtmlEncoder;
|
||||||
|
|
||||||
|
protected override TextWriter Writer => ViewContext.Writer;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void EnsureRenderedBodyOrSections()
|
public void EnsureRenderedBodyOrSections()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public override void BeginContext(int position, int length, bool isLiteral)
|
||||||
public abstract Task ExecuteAsync();
|
{
|
||||||
|
const string BeginContextEvent = "Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext";
|
||||||
|
|
||||||
|
if (DiagnosticSource?.IsEnabled(BeginContextEvent) == true)
|
||||||
|
{
|
||||||
|
DiagnosticSource.Write(
|
||||||
|
BeginContextEvent,
|
||||||
|
new
|
||||||
|
{
|
||||||
|
httpContext = ViewContext,
|
||||||
|
path = Path,
|
||||||
|
position = position,
|
||||||
|
length = length,
|
||||||
|
isLiteral = isLiteral,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void EndContext()
|
||||||
|
{
|
||||||
|
const string EndContextEvent = "Microsoft.AspNetCore.Mvc.Razor.EndInstrumentationContext";
|
||||||
|
|
||||||
|
if (DiagnosticSource?.IsEnabled(EndContextEvent) == true)
|
||||||
|
{
|
||||||
|
DiagnosticSource.Write(
|
||||||
|
EndContextEvent,
|
||||||
|
new
|
||||||
|
{
|
||||||
|
httpContext = ViewContext,
|
||||||
|
path = Path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Href(string contentPath)
|
||||||
|
{
|
||||||
|
if (contentPath == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(contentPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_urlHelper == null)
|
||||||
|
{
|
||||||
|
var services = ViewContext.HttpContext.RequestServices;
|
||||||
|
var factory = services.GetRequiredService<IUrlHelperFactory>();
|
||||||
|
_urlHelper = factory.GetUrlHelper(ViewContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _urlHelper.Content(contentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DefineSection(string name, RenderAsyncDelegate section)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@
|
||||||
"Microsoft.AspNetCore.Mvc.Razor": {
|
"Microsoft.AspNetCore.Mvc.Razor": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Razor.Evolution": "1.2.0-*",
|
|
||||||
"Microsoft.Extensions.PropertyActivator.Sources": {
|
"Microsoft.Extensions.PropertyActivator.Sources": {
|
||||||
"version": "1.2.0-*",
|
"version": "1.2.0-*",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
builder.AddFormatterMappings();
|
builder.AddFormatterMappings();
|
||||||
builder.AddViews();
|
builder.AddViews();
|
||||||
builder.AddRazorViewEngine();
|
builder.AddRazorViewEngine();
|
||||||
|
builder.AddRazorPages();
|
||||||
builder.AddCacheTagHelper();
|
builder.AddCacheTagHelper();
|
||||||
|
|
||||||
// +1 order
|
// +1 order
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
"Microsoft.AspNetCore.Mvc.Localization": {
|
"Microsoft.AspNetCore.Mvc.Localization": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Mvc.Razor": {
|
"Microsoft.AspNetCore.Mvc.RazorPages": {
|
||||||
"target": "project"
|
"target": "project"
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Mvc.TagHelpers": {
|
"Microsoft.AspNetCore.Mvc.TagHelpers": {
|
||||||
|
|
|
||||||
|
|
@ -25,5 +25,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Contains("This sandbox should give you a quick view of a basic MVC application.", response);
|
Assert.Contains("This sandbox should give you a quick view of a basic MVC application.", response);
|
||||||
}
|
}
|
||||||
|
[Fact]
|
||||||
|
public async Task RazorPages_ReturnSuccess()
|
||||||
|
{
|
||||||
|
// Arrange & Act
|
||||||
|
var response = await Client.GetStringAsync("http://localhost/Pages/Test");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains("This file should give you a quick view of a Mvc Raor Page in action.", response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1518,7 +1518,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var bufferedWriter = Assert.IsType<ViewBufferTextWriter>(Output);
|
var bufferedWriter = Assert.IsType<ViewBufferTextWriter>(Writer);
|
||||||
using (var stringWriter = new StringWriter())
|
using (var stringWriter = new StringWriter())
|
||||||
{
|
{
|
||||||
bufferedWriter.Buffer.WriteTo(stringWriter, HtmlEncoder);
|
bufferedWriter.Buffer.WriteTo(stringWriter, HtmlEncoder);
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
public IUrlHelper UrlHelper { get; set; }
|
public IUrlHelper UrlHelper { get; set; }
|
||||||
|
|
||||||
[RazorInject]
|
[RazorInject]
|
||||||
public HtmlEncoder HtmlEncoder { get; set; }
|
public new HtmlEncoder HtmlEncoder { get; set; }
|
||||||
|
|
||||||
[RazorInject]
|
[RazorInject]
|
||||||
public ViewDataDictionary<TestPage> ViewData { get; set; }
|
public ViewDataDictionary<TestPage> ViewData { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor;
|
using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
using Microsoft.AspNetCore.Razor.Evolution;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Microsoft.AspNetCore.Razor.Evolution;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
|
|
@ -128,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||||
.Returns(new[]
|
.Returns(new[]
|
||||||
{
|
{
|
||||||
GetProjectItem("/About", "/Index.cshtml", $"@page {Environment.NewLine}"),
|
GetProjectItem("", "/About/Index.cshtml", $"@page {Environment.NewLine}"),
|
||||||
});
|
});
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
razorProject.Object,
|
||||||
|
|
@ -145,14 +144,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath);
|
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Index", descriptor.RouteValues["page"]);
|
Assert.Equal("/About/Index", descriptor.RouteValues["page"]);
|
||||||
Assert.Equal("About/Index", descriptor.AttributeRouteInfo.Template);
|
Assert.Equal("About/Index", descriptor.AttributeRouteInfo.Template);
|
||||||
},
|
},
|
||||||
result =>
|
result =>
|
||||||
{
|
{
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath);
|
Assert.Equal("/About/Index.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Index", descriptor.RouteValues["page"]);
|
Assert.Equal("/About/Index", descriptor.RouteValues["page"]);
|
||||||
Assert.Equal("About", descriptor.AttributeRouteInfo.Template);
|
Assert.Equal("About", descriptor.AttributeRouteInfo.Template);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +164,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
razorProject.Setup(p => p.EnumerateItems("/"))
|
razorProject.Setup(p => p.EnumerateItems("/"))
|
||||||
.Returns(new[]
|
.Returns(new[]
|
||||||
{
|
{
|
||||||
GetProjectItem("/Catalog/Details", "/Index.cshtml", $"@page {{id:int?}} {Environment.NewLine}"),
|
GetProjectItem("", "/Catalog/Details/Index.cshtml", $"@page {{id:int?}} {Environment.NewLine}"),
|
||||||
});
|
});
|
||||||
var provider = new PageActionDescriptorProvider(
|
var provider = new PageActionDescriptorProvider(
|
||||||
razorProject.Object,
|
razorProject.Object,
|
||||||
|
|
@ -182,14 +181,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Index", descriptor.RouteValues["page"]);
|
Assert.Equal("/Catalog/Details/Index", descriptor.RouteValues["page"]);
|
||||||
Assert.Equal("Catalog/Details/Index/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
Assert.Equal("Catalog/Details/Index/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||||
},
|
},
|
||||||
result =>
|
result =>
|
||||||
{
|
{
|
||||||
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
var descriptor = Assert.IsType<PageActionDescriptor>(result);
|
||||||
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
Assert.Equal("/Catalog/Details/Index.cshtml", descriptor.RelativePath);
|
||||||
Assert.Equal("/Index", descriptor.RouteValues["page"]);
|
Assert.Equal("/Catalog/Details/Index", descriptor.RouteValues["page"]);
|
||||||
Assert.Equal("Catalog/Details/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
Assert.Equal("Catalog/Details/{id:int?}", descriptor.AttributeRouteInfo.Template);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ using Microsoft.AspNetCore.Mvc.Razor;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
using Microsoft.AspNetCore.Mvc.Razor.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
|
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.TagHelpers;
|
using Microsoft.AspNetCore.Mvc.TagHelpers;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
|
@ -374,6 +376,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
new Type[]
|
new Type[]
|
||||||
{
|
{
|
||||||
typeof(ControllerActionDescriptorProvider),
|
typeof(ControllerActionDescriptorProvider),
|
||||||
|
typeof(PageActionDescriptorProvider),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -381,6 +384,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
new Type[]
|
new Type[]
|
||||||
{
|
{
|
||||||
typeof(ControllerActionInvokerProvider),
|
typeof(ControllerActionInvokerProvider),
|
||||||
|
typeof(PageActionInvokerProvider),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,7 @@ namespace RazorWebSite
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
public IDirectoryContents GetDirectoryContents(string subpath) => new NotFoundDirectoryContents();
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateContent(string subpath, string content)
|
public void UpdateContent(string subpath, string content)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue