diff --git a/Mvc.sln b/Mvc.sln
index 7478c7b22e..2bcf1d1580 100644
--- a/Mvc.sln
+++ b/Mvc.sln
@@ -178,7 +178,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicViews", "benchmarkapps
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Api.Analyzers", "src\Microsoft.AspNetCore.Mvc.Api.Analyzers\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", "{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Api.Analyzers", "src\Microsoft.AspNetCore.Mvc.Api.Analyzers\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", "{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorRendering", "benchmarkapps\RazorRendering\RazorRendering.csproj", "{D7C6A696-F232-4288-BCCD-367407E4A934}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -952,6 +954,18 @@ Global
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.ActiveCfg = Release|Any CPU
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.Build.0 = Release|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1025,6 +1039,7 @@ Global
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
+ {D7C6A696-F232-4288-BCCD-367407E4A934} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}
diff --git a/benchmarkapps/RazorRendering/Data/DataA.cs b/benchmarkapps/RazorRendering/Data/DataA.cs
new file mode 100644
index 0000000000..42cbf300ee
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Data/DataA.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.Html;
+
+namespace Data
+{
+ public class DataA
+ {
+ public DataA(int id, HtmlString icon, HtmlString html, string name, int seconds, int max, float perHour)
+ {
+ Id = id;
+ Icon = icon;
+ Html = html;
+ Name = name;
+ Seconds = seconds;
+ Max = max;
+ PerHour = perHour;
+ }
+
+ public int Id { get; }
+ public HtmlString Icon { get; }
+ public HtmlString Html { get; }
+ public string Name { get; }
+ public int Seconds { get; }
+ public int Max { get; }
+ public float PerHour { get; }
+ }
+}
diff --git a/benchmarkapps/RazorRendering/Data/DataB.cs b/benchmarkapps/RazorRendering/Data/DataB.cs
new file mode 100644
index 0000000000..be2830e67f
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Data/DataB.cs
@@ -0,0 +1,25 @@
+using System;
+using Microsoft.AspNetCore.Html;
+
+namespace Data
+{
+ public class DataB
+ {
+ public DataB(int id, HtmlString icon, string name, int value, DateTimeOffset startDate, DateTimeOffset completeDate)
+ {
+ Id = id;
+ Icon = icon;
+ Name = name;
+ Value = value;
+ StartDate = startDate;
+ CompleteDate = completeDate;
+ }
+
+ public int Id { get; }
+ public HtmlString Icon { get; }
+ public string Name { get; }
+ public int Value { get; }
+ public DateTimeOffset StartDate { get; }
+ public DateTimeOffset CompleteDate { get; }
+ }
+}
diff --git a/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml
new file mode 100644
index 0000000000..036af8cb1f
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml
@@ -0,0 +1,138 @@
+@page
+@model Pages.PageA
+@using static System.Convert
+
+@section Subcategories {
+
+}
+
+@section Tabs {
+ Sub cat A
+ Sub cat B
+ Sub cat C
+ Sub cat D
+}
+
+@switch (Model.Value)
+{
+ case 0:
+
@Model.Name Type A
+ break;
+ case 1:
+ @Model.Name Type B
+ break;
+ case 2:
+ @Model.Name Type C
+ break;
+}
+
+
+Something Something
+
+
+
+ |
+ Something |
+ Something Something |
+ @if (Model.Value3 != 0)
+ {
+ Something |
+ }
+ |
+
+ @{
+ foreach (var data in Model.Data1)
+ {
+
+ | @data.Icon |
+ @data.Name |
+ @data.Html |
+ @if (Model.Value3 != 0)
+ {
+
+ @(new TimeSpan(0, 0, (int)data.Seconds))
+ (@data.PerHour.ToString("N2") p/h)
+ |
+ }
+
+
+ |
+
+ }
+ }
+
+
+
+Something something something
+
+@{
+ if (Model.Data2.Count > 0)
+ {
+
+
+ |
+ SomethingA |
+ SomethingB |
+ SomethingC |
+ SomethingD |
+ SomethingE |
+ SomethingF |
+ |
+
+
+ @foreach (var data in Model.Data2)
+ {
+ var StartDate = data.StartDate;
+ var CompleteDate = data.CompleteDate;
+
+
+ | @data.Icon |
+ @data.Name |
+ @data.Value |
+ @StartDate.ToString("dd MMM HH:mm:ss") |
+ @{
+ float percentage = 100f;
+
+ var totalTime = CompleteDate.Subtract(StartDate).TotalMilliseconds;
+ if (totalTime > 1000)
+ {
+ percentage = 100f * (float)DateTimeOffset.UtcNow.Subtract(StartDate).TotalMilliseconds / (float)totalTime;
+ }
+
+ percentage = MathF.Min(100f, MathF.Max(0f, percentage));
+
+ var startDate = ToInt64(StartDate.Subtract(DateTime.UnixEpoch).Ticks / (double)10000);
+ var endDate = ToInt64(CompleteDate.Subtract(DateTime.UnixEpoch).Ticks / (double)10000);
+ }
+
+
+ |
+ @CompleteDate.ToString("dd MMM HH:mm:ss") |
+
+ @(CompleteDate.Subtract(DateTime.UtcNow).ToString())
+ |
+
+
+ |
+
+ }
+
+ }
+ else
+ {
+
Something @Model.Name something something something something.
+ }
+
+}
+
\ No newline at end of file
diff --git a/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs
new file mode 100644
index 0000000000..8f2d98dc78
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs
@@ -0,0 +1,35 @@
+using Data;
+using Microsoft.Extensions.Logging;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Pages
+{
+ public class PageA : Page
+ {
+ public int Value { get; } = 0;
+ public int Value1 { get; } = 0;
+ public int Value2 { get; } = 0;
+ public int Value3 { get; } = 1;
+ public bool Condition { get; } = true;
+
+ public string Name { get; } = "A Name";
+
+ public List Data1 { get; }
+ public List Data2 { get; }
+
+ public PageA(List dataA, List dataB, ILogger logger) : base(logger)
+ {
+ Data1 = dataA;
+ Data2 = dataB;
+ }
+
+ public async Task OnGetAsync()
+ {
+ PageTitle = "PageA Title";
+ PageIcon = "sicon dialogue_pagea";
+ await Task.Delay(0);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml b/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml
new file mode 100644
index 0000000000..3681e4e9cd
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml
@@ -0,0 +1,12 @@
+@model Pages.Page
+
+
+
diff --git a/benchmarkapps/RazorRendering/Pages/Page.cs b/benchmarkapps/RazorRendering/Pages/Page.cs
new file mode 100644
index 0000000000..e02d03b5de
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/Page.cs
@@ -0,0 +1,26 @@
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace Pages
+{
+ public class Page : PageModel
+ {
+ public ILogger Logger { get; }
+
+ public string PageIcon { get; protected set; }
+ public string PageTitle { get; protected set; }
+ public string PageUrl { get; protected set; }
+
+ public Page(ILogger logger)
+ {
+ Logger = logger;
+ }
+
+
+ public void AddErrorMessage(string message)
+ {
+ Response.Headers.Add("X-Error-Message", UrlEncoder.Default.Encode(message));
+ }
+ }
+}
\ No newline at end of file
diff --git a/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml b/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml
new file mode 100644
index 0000000000..6e3fbe8720
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml
@@ -0,0 +1,31 @@
+@model Pages.Page
+
+@RenderSection("Subcategories")
+
+@Model.PageTitle
+@{
+ var hasTabs = IsSectionDefined("Tabs");
+}
+
+ @if (hasTabs)
+ {
+
+ @RenderSection("Tabs", required: false)
+
+ }
+
+
+
+
+
diff --git a/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml b/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml
new file mode 100644
index 0000000000..b0ad3a90b5
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml
@@ -0,0 +1,2 @@
+
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
\ No newline at end of file
diff --git a/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml b/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml
new file mode 100644
index 0000000000..d641c67f33
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml
@@ -0,0 +1,3 @@
+@{
+ Layout = "_Layout";
+}
\ No newline at end of file
diff --git a/benchmarkapps/RazorRendering/RazorRendering.csproj b/benchmarkapps/RazorRendering/RazorRendering.csproj
new file mode 100644
index 0000000000..dbbbfc0027
--- /dev/null
+++ b/benchmarkapps/RazorRendering/RazorRendering.csproj
@@ -0,0 +1,23 @@
+
+
+ netcoreapp2.2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmarkapps/RazorRendering/Readme.md b/benchmarkapps/RazorRendering/Readme.md
new file mode 100644
index 0000000000..15640cadc8
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Readme.md
@@ -0,0 +1 @@
+Url: /Category/PageA
\ No newline at end of file
diff --git a/benchmarkapps/RazorRendering/Startup.cs b/benchmarkapps/RazorRendering/Startup.cs
new file mode 100644
index 0000000000..3092f1639c
--- /dev/null
+++ b/benchmarkapps/RazorRendering/Startup.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Data;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Html;
+using Microsoft.Extensions.Configuration;
+using System.IO;
+
+public class Startup
+{
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddScoped>(_ => DataA);
+ services.AddScoped>(_ => DataB);
+ services.AddMvc()
+ .SetCompatibilityVersion(CompatibilityVersion.Latest);
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ app.UseMvc();
+ }
+
+ private static List DataA = GenerateDataA();
+
+ private static List GenerateDataA()
+ {
+ var dataA = new List();
+
+ foreach (var i in Enumerable.Range(0, 100))
+ {
+ dataA.Add(new DataA(i, new HtmlString(i.ToString()), new HtmlString(i.ToString()), i.ToString(), i, i, 60f / i));
+ }
+
+ return dataA;
+ }
+
+ private static List DataB = GenerateDataB();
+
+ private static List GenerateDataB()
+ {
+ var utc = DateTimeOffset.UtcNow;
+ var dataB = new List();
+ foreach (var i in Enumerable.Range(0, 100))
+ {
+ dataB.Add(new DataB(i, new HtmlString(i.ToString()), i.ToString(), i, utc, utc));
+ }
+
+ return dataB;
+ }
+
+ public static void Main(string[] args)
+ {
+ var host = CreateWebHostBuilder(args)
+ .Build();
+
+ host.Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
+ {
+ var configuration = new ConfigurationBuilder()
+ .AddEnvironmentVariables()
+ .AddCommandLine(args)
+ .Build();
+
+ return new WebHostBuilder()
+ .UseKestrel()
+ .UseUrls("http://+:5000")
+ .UseConfiguration(configuration)
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseStartup();
+ }
+}
\ No newline at end of file
diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs
index ec9c2c5a4b..5b4cebad21 100644
--- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs
+++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs
@@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
var state = routeContext.RouteData.PushState(MockRouter.Instance, routeValues, null);
- var actual = NaiveSelectCandiates(_actions, routeContext.RouteData.Values);
+ var actual = NaiveSelectCandidates(_actions, routeContext.RouteData.Values);
Verify(expected, actual);
state.Restore();
@@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
}
// A naive implementation we can use to generate match data for inputs, and for a baseline.
- private static IReadOnlyList NaiveSelectCandiates(ActionDescriptor[] actions, RouteValueDictionary routeValues)
+ private static IReadOnlyList NaiveSelectCandidates(ActionDescriptor[] actions, RouteValueDictionary routeValues)
{
var results = new List();
for (var i = 0; i < actions.Length; i++)
@@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
{
var action = actions[i];
var routeValues = new RouteValueDictionary(action.RouteValues);
- var matches = NaiveSelectCandiates(actions, routeValues);
+ var matches = NaiveSelectCandidates(actions, routeValues);
if (matches.Count == 0)
{
throw new InvalidOperationException("This should have at least one match.");
@@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
// Make one of the route values not match.
routeValues[routeValues.First().Key] = ((string)routeValues.First().Value) + "fkdkfdkkf";
- var matches = NaiveSelectCandiates(actions, routeValues);
+ var matches = NaiveSelectCandidates(actions, routeValues);
if (matches.Count != 0)
{
throw new InvalidOperationException("This should have 0 matches.");
diff --git a/build/dependencies.props b/build/dependencies.props
index 67761a1add..ea25042d20 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -48,8 +48,8 @@
3.0.0-alpha1-10400
3.0.0-alpha1-10400
3.0.0-alpha1-10400
- 3.0.0-alpha1-10400
- 3.0.0-alpha1-10400
+ 3.0.0-a-alpha1-link-generator-master-16955
+ 3.0.0-a-alpha1-link-generator-master-16955
3.0.0-alpha1-10400
3.0.0-alpha1-10400
3.0.0-alpha1-10400
diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs
index 20ab53e773..276f3179f3 100644
--- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs
@@ -36,6 +36,9 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
///
public IList ActionConstraints { get; set; }
+ ///
+ /// Gets or sets the endpoint metadata for this action.
+ ///
public IList