Merge branch 'merge/release/2.2-to-master' of https://github.com/dotnet-maestro-bot/Mvc into dotnet-maestro-bot-merge/release/2.2-to-master

This commit is contained in:
Ryan Nowak 2018-09-06 16:09:34 -07:00
commit f7d9271d71
161 changed files with 1597 additions and 433 deletions

17
Mvc.sln
View File

@ -178,7 +178,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicViews", "benchmarkapps
EndProject 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}" 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|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.ActiveCfg = Release|Any CPU
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1025,6 +1039,7 @@ Global
{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{D7C6A696-F232-4288-BCCD-367407E4A934} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,138 @@
@page
@model Pages.PageA
@using static System.Convert
@section Subcategories {
<partial name="_Subcategories" />
}
@section Tabs {
<span class="dialog-tab@(Model.Condition ? " selected" : "")"><span class="link" data-url="/Category/PageA">Sub cat A</span></span>
<span class="dialog-tab"><span class="link" data-url="/Category/PageB/@Model.Value1/@Model.Value2">Sub cat B</span></span>
<span class="dialog-tab"><span class="link" data-url="/Category/PageC/@((int)Model.Value3)">Sub cat C</span></span>
<span class="dialog-tab@(!Model.Condition ? " selected" : "")"><span class="link" data-url="/Category/PageD/@((int)Model.Value3)">Sub cat D</span></span>
}
@switch (Model.Value)
{
case 0:
<h1>@Model.Name Type A</h1>
break;
case 1:
<h1>@Model.Name Type B</h1>
break;
case 2:
<h1>@Model.Name Type C</h1>
break;
}
<h3 style="clear:both;">Something Something</h3>
<div>
<table class="generic small oddeven middle center">
<tr>
<th style="width: 24px;"></th>
<th>Something</th>
<th style="width: 270px;">Something Something</th>
@if (Model.Value3 != 0)
{
<th style="width: 70px;" class="dull right">Something</th>
}
<th style="width: 130px;"></th>
</tr>
@{
foreach (var data in Model.Data1)
{
<tr>
<td class="left">@data.Icon</td>
<td class="left">@data.Name</td>
<td style="text-align:center">@data.Html</td>
@if (Model.Value3 != 0)
{
<td class="dull right">
@(new TimeSpan(0, 0, (int)data.Seconds)) <br />
(@data.PerHour.ToString("N2") p/h)
</td>
}
<td>
<form asp-page-handler="Make">
<input type="hidden" name="Id" value="@((int)Model.Value3)" />
<input type="hidden" name="ResType" value="@data.Id" />
<input type="text" name="Quantity" style="width: 35px; font-size: 11px; text-align: left;" placeholder="@data.Max.ToString(System.Globalization.NumberFormatInfo.InvariantInfo)" />
<a href="#" class="max">max</a>
<button class="small">Make</button>
</form>
</td>
</tr>
}
}
</table>
</div>
<h3 style="clear:both;">Something something something</h3>
<div>
@{
if (Model.Data2.Count > 0)
{
<table class="oddeven" style="font-size: 11px;">
<tr>
<th style="width:20px"></th>
<th class="left">SomethingA</th>
<th style="width: 80px;">SomethingB</th>
<th style="width: 60px">SomethingC</th>
<th>SomethingD</th>
<th style="width: 60px">SomethingE</th>
<th style="width: 90px;">SomethingF</th>
<th style="width: 56px;"></th>
</tr>
@foreach (var data in Model.Data2)
{
var StartDate = data.StartDate;
var CompleteDate = data.CompleteDate;
<tr>
<td>@data.Icon</td>
<td class="left">@data.Name</td>
<td>@data.Value</td>
<td class="center">@StartDate.ToString("dd MMM HH:mm:ss")</td>
@{
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);
}
<td>
<div class="progBar">
<div class="progProgress" data-start="@startDate" data-end="@endDate" style="width: @(percentage.ToString("N0"))%;"></div>
</div>
</td>
<td class="center">@CompleteDate.ToString("dd MMM HH:mm:ss")</td>
<td>
<span class="progTime" data-start="@startDate" data-end="@endDate">@(CompleteDate.Subtract(DateTime.UtcNow).ToString())</span>
</td>
<td>
<form method="post" asp-page-handler="Cancel">
<input type="hidden" name="id" value="@data.Id" />
<button class="small">Cancel</button>
</form>
</td>
</tr>
}
</table>
}
else
{
<span>Something @Model.Name something something something something.</span>
}
}
</div>

View File

@ -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<DataA> Data1 { get; }
public List<DataB> Data2 { get; }
public PageA(List<DataA> dataA, List<DataB> dataB, ILogger<PageA> logger) : base(logger)
{
Data1 = dataA;
Data2 = dataB;
}
public async Task OnGetAsync()
{
PageTitle = "PageA Title";
PageIcon = "sicon dialogue_pagea";
await Task.Delay(0);
}
}
}

View File

@ -0,0 +1,12 @@
@model Pages.Page
<div class="dialog-subcat">
<div data-url="/Category/PageA" title="PageA Title" class="sicon dialogue_pagea"></div>
<div data-url="/Category/PageB" title="PageB Title" class="sicon dialogue_pageb"></div>
<div data-url="/Category/PageC" title="PageC Title" class="sicon dialogue_pagec"></div>
<div data-url="/Category/PageD" title="PageD Title" class="sicon dialogue_paged"></div>
<div data-url="/Category/PageE" title="PageE Title" class="sicon dialogue_pagee"></div>
<div data-url="/Category/PageF" title="PageF Title" class="sicon dialogue_pagef"></div>
<div data-url="/Category/PageG" title="PageG Title" class="sicon dialogue_pageg"></div>
</div>
<div class="licon micon dialogue_category"></div>

View File

@ -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));
}
}
}

View File

@ -0,0 +1,31 @@
@model Pages.Page
@RenderSection("Subcategories")
<div class="dialog-title-icon @Model.PageIcon"></div>
<div class="dialog-title">@Model.PageTitle</div>
@{
var hasTabs = IsSectionDefined("Tabs");
}
<div class="dialog-body@(hasTabs ? "-tabs" : "")">
@if (hasTabs)
{
<div class="dialog-tabs">
@RenderSection("Tabs", required: false)
</div>
}
<div class="dialog-parch-bottom@(hasTabs ? " tabs" : "")">
<div id="contentScroll" tabindex="20" class="dialog-parch-scroll@(hasTabs ? " tabs" : "")">
<div class="dialog-parch-left">
<div class="dialog-parch-right">
<div id="contentWindow" class="dialog-parch@(hasTabs ? " tabs" : "")">
@RenderBody()
</div>
</div>
</div>
</div>
<div class="dialog-parch-shadow@(hasTabs ? " tabs" : "")"></div>
</div>
</div>
<div class="dialog-close-icon icon_close"></div>
<div class="dialog-bottom-left-edge"></div>
<div class="dialog-bottom-edge"></div>

View File

@ -0,0 +1,2 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.2</TargetFrameworks>
</PropertyGroup>
<!-- These references are used when running locally -->
<ItemGroup Condition="'$(BenchmarksTargetFramework)' == ''">
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="$(MicrosoftAspNetCoreServerKestrelPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj" />
</ItemGroup>
<!--
These references are used when running on the Benchmarks Server.
-->
<ItemGroup Condition="'$(BenchmarksTargetFramework)' != ''">
<PackageReference Include="Microsoft.AspNetCore.App" Version="$(MicrosoftNETCoreApp22PackageVersion)" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1 @@
Url: /Category/PageA

View File

@ -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<List<DataA>>(_ => DataA);
services.AddScoped<List<DataB>>(_ => DataB);
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
}
private static List<DataA> DataA = GenerateDataA();
private static List<DataA> GenerateDataA()
{
var dataA = new List<DataA>();
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> DataB = GenerateDataB();
private static List<DataB> GenerateDataB()
{
var utc = DateTimeOffset.UtcNow;
var dataB = new List<DataB>();
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<Startup>();
}
}

View File

@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
var state = routeContext.RouteData.PushState(MockRouter.Instance, routeValues, null); 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); Verify(expected, actual);
state.Restore(); 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. // A naive implementation we can use to generate match data for inputs, and for a baseline.
private static IReadOnlyList<ActionDescriptor> NaiveSelectCandiates(ActionDescriptor[] actions, RouteValueDictionary routeValues) private static IReadOnlyList<ActionDescriptor> NaiveSelectCandidates(ActionDescriptor[] actions, RouteValueDictionary routeValues)
{ {
var results = new List<ActionDescriptor>(); var results = new List<ActionDescriptor>();
for (var i = 0; i < actions.Length; i++) for (var i = 0; i < actions.Length; i++)
@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance
{ {
var action = actions[i]; var action = actions[i];
var routeValues = new RouteValueDictionary(action.RouteValues); var routeValues = new RouteValueDictionary(action.RouteValues);
var matches = NaiveSelectCandiates(actions, routeValues); var matches = NaiveSelectCandidates(actions, routeValues);
if (matches.Count == 0) if (matches.Count == 0)
{ {
throw new InvalidOperationException("This should have at least one match."); 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. // Make one of the route values not match.
routeValues[routeValues.First().Key] = ((string)routeValues.First().Value) + "fkdkfdkkf"; routeValues[routeValues.First().Key] = ((string)routeValues.First().Value) + "fkdkfdkkf";
var matches = NaiveSelectCandiates(actions, routeValues); var matches = NaiveSelectCandidates(actions, routeValues);
if (matches.Count != 0) if (matches.Count != 0)
{ {
throw new InvalidOperationException("This should have 0 matches."); throw new InvalidOperationException("This should have 0 matches.");

View File

@ -48,8 +48,8 @@
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion> <MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion> <MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreResponseCachingPackageVersion> <MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreResponseCachingPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion> <MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-a-alpha1-link-generator-master-16955</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreRoutingPackageVersion> <MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-a-alpha1-link-generator-master-16955</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreServerIISIntegrationPackageVersion> <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreServerKestrelPackageVersion> <MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreSessionPackageVersion> <MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10400</MicrosoftAspNetCoreSessionPackageVersion>

View File

@ -36,6 +36,9 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
/// </summary> /// </summary>
public IList<IActionConstraintMetadata> ActionConstraints { get; set; } public IList<IActionConstraintMetadata> ActionConstraints { get; set; }
/// <summary>
/// Gets or sets the endpoint metadata for this action.
/// </summary>
public IList<object> EndpointMetadata { get; set; } public IList<object> EndpointMetadata { get; set; }
/// <summary> /// <summary>

View File

@ -31,15 +31,11 @@ namespace Microsoft.AspNetCore.Mvc
/// <param name="actionContext">The <see cref="ActionContext"/> to copy.</param> /// <param name="actionContext">The <see cref="ActionContext"/> to copy.</param>
public ActionContext(ActionContext actionContext) public ActionContext(ActionContext actionContext)
: this( : this(
actionContext.HttpContext, actionContext?.HttpContext,
actionContext.RouteData, actionContext?.RouteData,
actionContext.ActionDescriptor, actionContext?.ActionDescriptor,
actionContext.ModelState) actionContext?.ModelState)
{ {
if (actionContext == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
} }
/// <summary> /// <summary>

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc
} }
/// <summary> /// <summary>
/// Intializes a new instance of <see cref="ActionResult{TValue}"/> using the specified <see cref="ActionResult"/>. /// Initializes a new instance of <see cref="ActionResult{TValue}"/> using the specified <see cref="ActionResult"/>.
/// </summary> /// </summary>
/// <param name="result">The <see cref="ActionResult"/>.</param> /// <param name="result">The <see cref="ActionResult"/>.</param>
public ActionResult(ActionResult result) public ActionResult(ActionResult result)

View File

@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc
} }
else if (methods.Length > 1) else if (methods.Length > 1)
{ {
throw new ArgumentException(Resources.FormatApiConventionMethod_AmbigiousMethodName(methodName, conventionType), nameof(methodName)); throw new ArgumentException(Resources.FormatApiConventionMethod_AmbiguousMethodName(methodName, conventionType), nameof(methodName));
} }
return methods[0]; return methods[0];

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
/// Gets the list of <see cref="ApplicationPart"/> instances. /// Gets the list of <see cref="ApplicationPart"/> instances.
/// <para> /// <para>
/// Instances in this collection are stored in precedence order. An <see cref="ApplicationPart"/> that appears /// Instances in this collection are stored in precedence order. An <see cref="ApplicationPart"/> that appears
/// earlier in the list has a higher precendence. /// earlier in the list has a higher precedence.
/// An <see cref="IApplicationFeatureProvider"/> may choose to use this an interface as a way to resolve conflicts when /// An <see cref="IApplicationFeatureProvider"/> may choose to use this an interface as a way to resolve conflicts when
/// multiple <see cref="ApplicationPart"/> instances resolve equivalent feature values. /// multiple <see cref="ApplicationPart"/> instances resolve equivalent feature values.
/// </para> /// </para>

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// By default, this maps to <see cref="ProblemDetails.Title"/> and should not change /// By default, this maps to <see cref="ProblemDetails.Title"/> and should not change
/// between multiple occurences of the same error. /// between multiple occurrences of the same error.
/// </remarks> /// </remarks>
public string Title { get; set; } public string Title { get; set; }
} }

View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc
/// </example> /// </example>
/// </para> /// </para>
/// <para> /// <para>
/// Setting compatiblity version to a specific version will change the default values of various /// Setting compatibility version to a specific version will change the default values of various
/// settings to match a particular minor release of ASP.NET Core MVC. /// settings to match a particular minor release of ASP.NET Core MVC.
/// </para> /// </para>
/// </remarks> /// </remarks>

View File

@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Mvc
// If there are multiple IConsumeActionConstraints which are defined at the class and // If there are multiple IConsumeActionConstraints which are defined at the class and
// at the action level, the one closest to the action overrides the others. To ensure this // at the action level, the one closest to the action overrides the others. To ensure this
// we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an // we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an
// IConsumeActionConstraint. Since filterdescriptor collection is ordered (the last filter is the one // IConsumeActionConstraint. Since FilterDescriptor collection is ordered (the last filter is the one
// closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this. // closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this.
return actionDescriptor.FilterDescriptors.Last( return actionDescriptor.FilterDescriptors.Last(
filter => filter.Filter is IConsumesActionConstraint).Filter == this; filter => filter.Filter is IConsumesActionConstraint).Filter == this;

View File

@ -16,9 +16,9 @@ namespace Microsoft.AspNetCore.Mvc
/// ///
/// <code> /// <code>
/// [HttpGet] /// [HttpGet]
/// public ProductModel GetProduct([FromServices] IProductModelRequestService productModelReqest) /// public ProductModel GetProduct([FromServices] IProductModelRequestService productModelRequest)
/// { /// {
/// return productModelReqest.Value; /// return productModelRequest.Value;
/// } /// }
/// </code> /// </code>
/// </example> /// </example>

View File

@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
// Evaluate DefaultValues onces so subclasses don't have to cache. // Evaluate DefaultValues once so subclasses don't have to cache.
var defaultValues = DefaultValues; var defaultValues = DefaultValues;
foreach (var @switch in options) foreach (var @switch in options)

View File

@ -1,7 +1,6 @@
// 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.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
@ -134,7 +133,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
// Consumers who poll will observe a new action descriptor collection at step 2 - they will see // Consumers who poll will observe a new action descriptor collection at step 2 - they will see
// the new collection and ignore the change token. // the new collection and ignore the change token.
// //
// Consumers who listen to the change token will requery at step 4 - they will see the new collection // Consumers who listen to the change token will re-query at step 4 - they will see the new collection
// and new change token. // and new change token.
// //
// Anyone who acquires the collection and change token between steps 2 and 3 will be notified of // Anyone who acquires the collection and change token between steps 2 and 3 will be notified of

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
///</para> ///</para>
///<para> ///<para>
/// To be reactively notified of changes, downcast to <see cref="ActionDescriptorCollectionProvider"/> and /// To be reactively notified of changes, downcast to <see cref="ActionDescriptorCollectionProvider"/> and
/// subcribe to the change token returned from <see cref="ActionDescriptorCollectionProvider.GetChangeToken"/> /// subscribe to the change token returned from <see cref="ActionDescriptorCollectionProvider.GetChangeToken"/>
/// using <see cref="ChangeToken.OnChange(System.Func{IChangeToken}, System.Action)"/>. /// using <see cref="ChangeToken.OnChange(System.Func{IChangeToken}, System.Action)"/>.
/// </para> /// </para>
/// <para> /// <para>

View File

@ -12,14 +12,14 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
/// </summary> /// </summary>
/// <typeparam name="TResult">The type of <see cref="IActionResult"/>.</typeparam> /// <typeparam name="TResult">The type of <see cref="IActionResult"/>.</typeparam>
/// <remarks> /// <remarks>
/// Implementions of <see cref="IActionResultExecutor{TResult}"/> are typically called by the /// Implementations of <see cref="IActionResultExecutor{TResult}"/> are typically called by the
/// <see cref="IActionResult.ExecuteResultAsync(ActionContext)"/> method of the corresponding action result type. /// <see cref="IActionResult.ExecuteResultAsync(ActionContext)"/> method of the corresponding action result type.
/// Implementations should be registered as singleton services. /// Implementations should be registered as singleton services.
/// </remarks> /// </remarks>
public interface IActionResultExecutor<in TResult> where TResult : IActionResult public interface IActionResultExecutor<in TResult> where TResult : IActionResult
{ {
/// <summary> /// <summary>
/// Asynchronously excecutes the action result, by modifying the <see cref="HttpResponse"/>. /// Asynchronously executes the action result, by modifying the <see cref="HttpResponse"/>.
/// </summary> /// </summary>
/// <param name="context">The <see cref="ActionContext"/> associated with the current request."/></param> /// <param name="context">The <see cref="ActionContext"/> associated with the current request."/></param>
/// <param name="result">The action result to execute.</param> /// <param name="result">The action result to execute.</param>

View File

@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
=> typeof(Task<IActionResult>).IsAssignableFrom(executor.MethodReturnType); => typeof(Task<IActionResult>).IsAssignableFrom(executor.MethodReturnType);
} }
// Task<PhysicalfileResult> DownloadFile(..) // Task<PhysicalFileResult> DownloadFile(..)
// ValueTask<ViewResult> GetViewsAsync(..) // ValueTask<ViewResult> GetViewsAsync(..)
private class TaskOfActionResultExecutor : ActionMethodExecutor private class TaskOfActionResultExecutor : ActionMethodExecutor
{ {

View File

@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// We also want to add the same (as in reference equality) list of actions to the ordinal entries. // We also want to add the same (as in reference equality) list of actions to the ordinal entries.
// We'll keep updating `entries` to include all of the actions in the same equivalence class - // We'll keep updating `entries` to include all of the actions in the same equivalence class -
// meaning, all conventionally routed actions for which the route values are equalignoring case. // meaning, all conventionally routed actions for which the route values are equal ignoring case.
// //
// `entries` will appear in `OrdinalIgnoreCaseEntries` exactly once and in `OrdinalEntries` once // `entries` will appear in `OrdinalIgnoreCaseEntries` exactly once and in `OrdinalEntries` once
// for each variation of casing that we've seen. // for each variation of casing that we've seen.

View File

@ -138,10 +138,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ActionModel action) ActionModel action)
{ {
var defaultControllerConstraints = Enumerable.Empty<IActionConstraintMetadata>(); var defaultControllerConstraints = Enumerable.Empty<IActionConstraintMetadata>();
var defaultControllerEndpointMetadata = Enumerable.Empty<object>();
if (controller.Selectors.Count > 0) if (controller.Selectors.Count > 0)
{ {
defaultControllerConstraints = controller.Selectors[0].ActionConstraints defaultControllerConstraints = controller.Selectors[0].ActionConstraints
.Where(constraint => !(constraint is IRouteTemplateProvider)); .Where(constraint => !(constraint is IRouteTemplateProvider));
defaultControllerEndpointMetadata = controller.Selectors[0].EndpointMetadata;
} }
var actionDescriptors = new List<ControllerActionDescriptor>(); var actionDescriptors = new List<ControllerActionDescriptor>();
@ -164,8 +166,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
AddActionConstraints(actionDescriptor, actionSelector, controllerConstraints); AddActionConstraints(actionDescriptor, actionSelector, controllerConstraints);
// REVIEW: Need to get metadata from controller // Metadata for the action is more significant so order it before the controller metadata
actionDescriptor.EndpointMetadata = actionSelector.EndpointMetadata.ToList(); var actionDescriptorMetadata = actionSelector.EndpointMetadata.ToList();
actionDescriptorMetadata.AddRange(defaultControllerEndpointMetadata);
actionDescriptor.EndpointMetadata = actionDescriptorMetadata;
} }
return actionDescriptors; return actionDescriptors;

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public RequestDelegate GetPipeline(Type configurationType) public RequestDelegate GetPipeline(Type configurationType)
{ {
// Build the pipeline only once. This is similar to how middlewares registered in Startup are constructed. // Build the pipeline only once. This is similar to how middleware registered in Startup are constructed.
var requestDelegate = _pipelinesCache.GetOrAdd( var requestDelegate = _pipelinesCache.GetOrAdd(
configurationType, configurationType,
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
// Ideally we want the experience of a middleware pipeline to behave the same as if it was registered // Ideally we want the experience of a middleware pipeline to behave the same as if it was registered
// in Startup. In this scenario, an Exception thrown in a middelware later in the pipeline gets // in Startup. In this scenario, an Exception thrown in a middleware later in the pipeline gets
// propagated back to earlier middleware. So, check if a later resource filter threw an Exception and // propagated back to earlier middleware. So, check if a later resource filter threw an Exception and
// propagate that back to the middleware pipeline. // propagate that back to the middleware pipeline.
resourceExecutedContext.ExceptionDispatchInfo?.Throw(); resourceExecutedContext.ExceptionDispatchInfo?.Throw();

View File

@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// - /Home/Index/{id?} // - /Home/Index/{id?}
// - /Home // - /Home
// - / // - /
if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, endpointInfo, newPathSegments))
{ {
var subPathSegments = newPathSegments.Take(i); var subPathSegments = newPathSegments.Take(i);
@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
private bool UseDefaultValuePlusRemainingSegementsOptional( private bool UseDefaultValuePlusRemainingSegmentsOptional(
int segmentIndex, int segmentIndex,
ActionDescriptor action, ActionDescriptor action,
MvcEndpointInfo endpointInfo, MvcEndpointInfo endpointInfo,

View File

@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
} }
// If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since // If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since
// that's what we would create. (The cases handled here are IEnumerable<>, IReadOnlyColection<> and // that's what we would create. (The cases handled here are IEnumerable<>, IReadOnlyCollection<> and
// IReadOnlyList<>). // IReadOnlyList<>).
var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
if (enumerableType != null) if (enumerableType != null)

View File

@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// //
// If a property does not have a binding source, then it's fair game for any value provider. // If a property does not have a binding source, then it's fair game for any value provider.
// //
// If any property meets the above conditions and has a value from valueproviders, then we'll // If any property meets the above conditions and has a value from ValueProviders, then we'll
// create the model and try to bind it. OR if ALL properties of the model have a greedy source, // create the model and try to bind it. OR if ALL properties of the model have a greedy source,
// then we go ahead and create it. // then we go ahead and create it.
// //

View File

@ -82,11 +82,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
// enum FlagsEnum { Value1 = 1, Value2 = 2, Value4 = 4 } // enum FlagsEnum { Value1 = 1, Value2 = 2, Value4 = 4 }
// //
// Valid Scenarios: // Valid Scenarios:
// 1. valueproviderresult="Value2,Value4", model=Value2 | Value4, underlying=6, converted=Value2, Value4 // 1. valueProviderResult="Value2,Value4", model=Value2 | Value4, underlying=6, converted=Value2, Value4
// 2. valueproviderresult="2,4", model=Value2 | Value4, underlying=6, converted=Value2, Value4 // 2. valueProviderResult="2,4", model=Value2 | Value4, underlying=6, converted=Value2, Value4
// //
// Invalid Scenarios: // Invalid Scenarios:
// 1. valueproviderresult="2,10", model=12, underlying=12, converted=12 // 1. valueProviderResult="2,10", model=12, underlying=12, converted=12
// //
var underlying = Convert.ChangeType( var underlying = Convert.ChangeType(
model, model,

View File

@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
var headerName = bindingContext.FieldName; var headerName = bindingContext.FieldName;
// Do not set ModelBindingResult to Failed on not finding the value in the header as we want the inner // Do not set ModelBindingResult to Failed on not finding the value in the header as we want the inner
// modelbinder to do that. This would give a chance to the inner binder to add more useful information. // ModelBinder to do that. This would give a chance to the inner binder to add more useful information.
// For example, SimpleTypeModelBinder adds a model error when binding to let's say an integer and the // For example, SimpleTypeModelBinder adds a model error when binding to let's say an integer and the
// model is null. // model is null.
var request = bindingContext.HttpContext.Request; var request = bindingContext.HttpContext.Request;

View File

@ -9,7 +9,7 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
// Normalizes keys, in a keyvaluepair collection, from jQuery format to a format that MVC understands. // Normalizes keys, in a KeyValuePair collection, from jQuery format to a format that MVC understands.
internal static class JQueryKeyValuePairNormalizer internal static class JQueryKeyValuePairNormalizer
{ {
public static IDictionary<string, StringValues> GetValues( public static IDictionary<string, StringValues> GetValues(

View File

@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
var cacheEntry = GetCacheEntry(modelType); var cacheEntry = GetCacheEntry(modelType);
// We're relying on a safe race-condition for Properties - take care only // We're relying on a safe race-condition for Properties - take care only
// to set the value onces the properties are fully-initialized. // to set the value once the properties are fully-initialized.
if (cacheEntry.Details.Properties == null) if (cacheEntry.Details.Properties == null)
{ {
var key = ModelMetadataIdentity.ForType(modelType); var key = ModelMetadataIdentity.ForType(modelType);

View File

@ -1509,18 +1509,18 @@ namespace Microsoft.AspNetCore.Mvc.Core
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiConvention_UnsupportedAttributesOnConvention"), p0, p1, p2); => string.Format(CultureInfo.CurrentCulture, GetString("ApiConvention_UnsupportedAttributesOnConvention"), p0, p1, p2);
/// <summary> /// <summary>
/// Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. /// Method name '{0}' is ambiguous for convention type '{1}'. More than one method found with the name '{0}'.
/// </summary> /// </summary>
internal static string ApiConventionMethod_AmbigiousMethodName internal static string ApiConventionMethod_AmbiguousMethodName
{ {
get => GetString("ApiConventionMethod_AmbigiousMethodName"); get => GetString("ApiConventionMethod_AmbiguousMethodName");
} }
/// <summary> /// <summary>
/// Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. /// Method name '{0}' is ambiguous for convention type '{1}'. More than one method found with the name '{0}'.
/// </summary> /// </summary>
internal static string FormatApiConventionMethod_AmbigiousMethodName(object p0, object p1) internal static string FormatApiConventionMethod_AmbiguousMethodName(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_AmbigiousMethodName"), p0, p1); => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_AmbiguousMethodName"), p0, p1);
/// <summary> /// <summary>
/// A method named '{0}' was not found on convention type '{1}'. /// A method named '{0}' was not found on convention type '{1}'.

View File

@ -451,8 +451,8 @@
<data name="ApiConvention_UnsupportedAttributesOnConvention" xml:space="preserve"> <data name="ApiConvention_UnsupportedAttributesOnConvention" xml:space="preserve">
<value>Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}.</value> <value>Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}.</value>
</data> </data>
<data name="ApiConventionMethod_AmbigiousMethodName" xml:space="preserve"> <data name="ApiConventionMethod_AmbiguousMethodName" xml:space="preserve">
<value>Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'.</value> <value>Method name '{0}' is ambiguous for convention type '{1}'. More than one method found with the name '{0}'.</value>
</data> </data>
<data name="ApiConventionMethod_NoMethodFound" xml:space="preserve"> <data name="ApiConventionMethod_NoMethodFound" xml:space="preserve">
<value>A method named '{0}' was not found on convention type '{1}'.</value> <value>A method named '{0}' was not found on convention type '{1}'.</value>

View File

@ -2,6 +2,7 @@
// 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;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -51,46 +52,41 @@ namespace Microsoft.AspNetCore.Mvc.Routing
throw new ArgumentNullException(nameof(urlActionContext)); throw new ArgumentNullException(nameof(urlActionContext));
} }
var valuesDictionary = GetValuesDictionary(urlActionContext.Values); var values = GetValuesDictionary(urlActionContext.Values);
if (urlActionContext.Action == null) if (urlActionContext.Action == null)
{ {
if (!valuesDictionary.ContainsKey("action") && if (!values.ContainsKey("action") &&
AmbientValues.TryGetValue("action", out var action)) AmbientValues.TryGetValue("action", out var action))
{ {
valuesDictionary["action"] = action; values["action"] = action;
} }
} }
else else
{ {
valuesDictionary["action"] = urlActionContext.Action; values["action"] = urlActionContext.Action;
} }
if (urlActionContext.Controller == null) if (urlActionContext.Controller == null)
{ {
if (!valuesDictionary.ContainsKey("controller") && if (!values.ContainsKey("controller") &&
AmbientValues.TryGetValue("controller", out var controller)) AmbientValues.TryGetValue("controller", out var controller))
{ {
valuesDictionary["controller"] = controller; values["controller"] = controller;
} }
} }
else else
{ {
valuesDictionary["controller"] = urlActionContext.Controller; values["controller"] = urlActionContext.Controller;
} }
var successfullyGeneratedLink = _linkGenerator.TryGetLink(
var path = _linkGenerator.GetPathByRouteValues(
ActionContext.HttpContext, ActionContext.HttpContext,
valuesDictionary, routeName: null,
out var link); values,
if (!successfullyGeneratedLink) new FragmentString(urlActionContext.Fragment == null ? null : "#" + urlActionContext.Fragment));
{ return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, path);
//TODO: log here
return null;
}
return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, link, urlActionContext.Fragment);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -101,20 +97,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing
throw new ArgumentNullException(nameof(routeContext)); throw new ArgumentNullException(nameof(routeContext));
} }
var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); var path = _linkGenerator.GetPathByRouteValues(
var successfullyGeneratedLink = _linkGenerator.TryGetLink(
ActionContext.HttpContext, ActionContext.HttpContext,
routeContext.RouteName, routeContext.RouteName,
valuesDictionary, routeContext.Values,
out var link); new FragmentString(routeContext.Fragment == null ? null : "#" + routeContext.Fragment));
return GenerateUrl(routeContext.Protocol, routeContext.Host, path);
if (!successfullyGeneratedLink)
{
return null;
}
return GenerateUrl(routeContext.Protocol, routeContext.Host, link, routeContext.Fragment);
} }
} }
} }

View File

@ -201,6 +201,68 @@ namespace Microsoft.AspNetCore.Mvc.Routing
} }
} }
/// <summary>
/// Generates a URI from the provided components.
/// </summary>
/// <param name="protocol">The URI scheme/protocol.</param>
/// <param name="host">The URI host.</param>
/// <param name="path">The URI path and remaining portions (path, query, and fragment).</param>
/// <returns>
/// An absolute URI if the <paramref name="protocol"/> or <paramref name="host"/> is specified, otherwise generates a
/// URI with an absolute path.
/// </returns>
protected string GenerateUrl(string protocol, string host, string path)
{
// This method is similar to GenerateUrl, but it's used for EndpointRouting. It ignores pathbase and fragment
// because those have already been incorporated.
if (path == null)
{
return null;
}
// Perf: In most of the common cases, GenerateUrl is called with a null protocol, host and fragment.
// In such cases, we might not need to build any URL as the url generated is mostly same as the virtual path available in pathData.
// For such common cases, this FastGenerateUrl method saves a string allocation per GenerateUrl call.
string url;
if (TryFastGenerateUrl(protocol, host, path, fragment: null, out url))
{
return url;
}
var builder = GetStringBuilder();
try
{
if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host))
{
AppendPathAndFragment(builder, pathBase: null, path, fragment: null);
// We're returning a partial URL (just path + query + fragment), but we still want it to be rooted.
if (builder.Length == 0 || builder[0] != '/')
{
builder.Insert(0, '/');
}
}
else
{
protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol;
builder.Append(protocol);
builder.Append("://");
host = string.IsNullOrEmpty(host) ? ActionContext.HttpContext.Request.Host.Value : host;
builder.Append(host);
AppendPathAndFragment(builder, pathBase: null, path, fragment: null);
}
return builder.ToString();
}
finally
{
// Clear the StringBuilder so that it can reused for the next call.
builder.Clear();
}
}
// for unit testing // for unit testing
internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment) internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment)
{ {

View File

@ -462,7 +462,7 @@ namespace Microsoft.AspNetCore.Mvc
if (!routeValues.ContainsKey("handler") && if (!routeValues.ContainsKey("handler") &&
ambientValues.TryGetValue("handler", out var handler)) ambientValues.TryGetValue("handler", out var handler))
{ {
// Clear out formaction unless it's explicitly specified in the routeValues. // Clear out form action unless it's explicitly specified in the routeValues.
routeValues["handler"] = null; routeValues["handler"] = null;
} }
} }

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc
public class ValidationProblemDetails : ProblemDetails public class ValidationProblemDetails : ProblemDetails
{ {
/// <summary> /// <summary>
/// Intializes a new instance of <see cref="ValidationProblemDetails"/>. /// Initializes a new instance of <see cref="ValidationProblemDetails"/>.
/// </summary> /// </summary>
public ValidationProblemDetails() public ValidationProblemDetails()
{ {

View File

@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc
} }
/// <summary> /// <summary>
/// Gets or sets a flag to determine whether error messsages from JSON deserialization by the /// Gets or sets a flag to determine whether error messages from JSON deserialization by the
/// <see cref="JsonInputFormatter"/> will be added to the <see cref="ModelStateDictionary"/>. The default /// <see cref="JsonInputFormatter"/> will be added to the <see cref="ModelStateDictionary"/>. The default
/// value is <c>false</c>, meaning that a generic error message will be used instead. /// value is <c>false</c>, meaning that a generic error message will be used instead.
/// </summary> /// </summary>

View File

@ -25,12 +25,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts
if (duplicates != null) if (duplicates != null)
{ {
var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier)); var viewsDifferingInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier));
var message = string.Join( var message = string.Join(
Environment.NewLine, Environment.NewLine,
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
viewsDiffereningInCase); viewsDifferingInCase);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }

View File

@ -35,12 +35,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation
{ {
// Ensure parts do not specify views with differing cases. This is not supported // Ensure parts do not specify views with differing cases. This is not supported
// at runtime and we should flag at as such for precompiled views. // at runtime and we should flag at as such for precompiled views.
var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.RelativePath)); var viewsDifferingInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.RelativePath));
var message = string.Join( var message = string.Join(
Environment.NewLine, Environment.NewLine,
Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase,
viewsDiffereningInCase); viewsDifferingInCase);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }

View File

@ -81,7 +81,7 @@ namespace Microsoft.Extensions.DependencyInjection
// ViewFeature items have precedence semantics - when two views have the same path \ identifier, // ViewFeature items have precedence semantics - when two views have the same path \ identifier,
// the one that appears earlier in the list wins. Therefore the ordering of // the one that appears earlier in the list wins. Therefore the ordering of
// RazorCompiledItemFeatureProvider and ViewsFeatureProvider is pertinent - any view compiled // RazorCompiledItemFeatureProvider and ViewsFeatureProvider is pertinent - any view compiled
// using the Sdk will be prefered to views compiled using MvcPrecompilation. // using the Sdk will be preferred to views compiled using MvcPrecompilation.
if (!builder.PartManager.FeatureProviders.OfType<RazorCompiledItemFeatureProvider>().Any()) if (!builder.PartManager.FeatureProviders.OfType<RazorCompiledItemFeatureProvider>().Any())
{ {
builder.PartManager.FeatureProviders.Add(new RazorCompiledItemFeatureProvider()); builder.PartManager.FeatureProviders.Add(new RazorCompiledItemFeatureProvider());
@ -159,6 +159,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddEnumerable( services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>()); ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<RazorViewEngineOptions>, RazorViewEngineOptionsSetup>());
services.TryAddSingleton< services.TryAddSingleton<
IRazorViewEngineFileProviderAccessor, IRazorViewEngineFileProviderAccessor,
DefaultRazorViewEngineFileProviderAccessor>(); DefaultRazorViewEngineFileProviderAccessor>();

View File

@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
// from either the set of known precompiled views, or by being compiled. // from either the set of known precompiled views, or by being compiled.
_cache = cache; _cache = cache;
// We need to validate that the all of the precompiled views are unique by path (case-insenstive). // We need to validate that the all of the precompiled views are unique by path (case-insensitive).
// We do this because there's no good way to canonicalize paths on windows, and it will create // We do this because there's no good way to canonicalize paths on windows, and it will create
// problems when deploying to linux. Rather than deal with these issues, we just don't support // problems when deploying to linux. Rather than deal with these issues, we just don't support
// views that differ only by case. // views that differ only by case.
@ -116,6 +116,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
} }
} }
public bool AllowRecompilingViewsOnFileChange { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Task<CompiledViewDescriptor> CompileAsync(string relativePath) public Task<CompiledViewDescriptor> CompileAsync(string relativePath)
{ {
@ -254,16 +256,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
// Used to validate and recompile // Used to validate and recompile
NormalizedPath = normalizedPath, NormalizedPath = normalizedPath,
ExpirationTokens = new List<IChangeToken>(),
};
var checksums = precompiledView.Item.GetChecksumMetadata(); ExpirationTokens = GetExpirationTokens(precompiledView),
for (var i = 0; i < checksums.Count; i++) };
{
// We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job,
// so it probably will.
item.ExpirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier));
}
// We also need to create a new descriptor, because the original one doesn't have expiration tokens on // We also need to create a new descriptor, because the original one doesn't have expiration tokens on
// it. These will be used by the view location cache, which is like an L1 cache for views (this class is // it. These will be used by the view location cache, which is like an L1 cache for views (this class is
@ -282,10 +277,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath) private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath)
{ {
var expirationTokens = new List<IChangeToken>() IList<IChangeToken> expirationTokens = Array.Empty<IChangeToken>();
if (AllowRecompilingViewsOnFileChange)
{ {
_fileProvider.Watch(normalizedPath), var changeToken = _fileProvider.Watch(normalizedPath);
}; expirationTokens = new List<IChangeToken> { changeToken };
}
var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath); var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath);
if (!projectItem.Exists) if (!projectItem.Exists)
@ -293,7 +291,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
_logger.ViewCompilerCouldNotFindFileAtPath(normalizedPath); _logger.ViewCompilerCouldNotFindFileAtPath(normalizedPath);
// If the file doesn't exist, we can't do compilation right now - we still want to cache // If the file doesn't exist, we can't do compilation right now - we still want to cache
// the fact that we tried. This will allow us to retrigger compilation if the view file // the fact that we tried. This will allow us to re-trigger compilation if the view file
// is added. // is added.
return new ViewCompilerWorkItem() return new ViewCompilerWorkItem()
{ {
@ -313,9 +311,46 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
_logger.ViewCompilerFoundFileToCompile(normalizedPath); _logger.ViewCompilerFoundFileToCompile(normalizedPath);
GetChangeTokensFromImports(expirationTokens, projectItem);
return new ViewCompilerWorkItem()
{
SupportsCompilation = true,
NormalizedPath = normalizedPath,
ExpirationTokens = expirationTokens,
};
}
private IList<IChangeToken> GetExpirationTokens(CompiledViewDescriptor precompiledView)
{
if (!AllowRecompilingViewsOnFileChange)
{
return Array.Empty<IChangeToken>();
}
var checksums = precompiledView.Item.GetChecksumMetadata();
var expirationTokens = new List<IChangeToken>(checksums.Count);
for (var i = 0; i < checksums.Count; i++)
{
// We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job,
// so it probably will.
expirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier));
}
return expirationTokens;
}
private void GetChangeTokensFromImports(IList<IChangeToken> expirationTokens, RazorProjectItem projectItem)
{
if (!AllowRecompilingViewsOnFileChange)
{
return;
}
// OK this means we can do compilation. For now let's just identify the other files we need to watch // OK this means we can do compilation. For now let's just identify the other files we need to watch
// so we can create the cache entry. Compilation will happen after we release the lock. // so we can create the cache entry. Compilation will happen after we release the lock.
var importFeature = _projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault(); var importFeature = _projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().FirstOrDefault();
// There should always be an import feature unless someone has misconfigured their RazorProjectEngine. // There should always be an import feature unless someone has misconfigured their RazorProjectEngine.
@ -330,14 +365,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{ {
expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath)); expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath));
} }
return new ViewCompilerWorkItem()
{
SupportsCompilation = true,
NormalizedPath = normalizedPath,
ExpirationTokens = expirationTokens,
};
} }
protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath) protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath)

View File

@ -80,7 +80,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
feature.ViewDescriptors, feature.ViewDescriptors,
_compilationMemoryCacheProvider.CompilationMemoryCache, _compilationMemoryCacheProvider.CompilationMemoryCache,
_logger); _logger)
{
AllowRecompilingViewsOnFileChange = _viewEngineOptions.AllowRecompilingViewsOnFileChange,
};
} }
} }
} }

View File

@ -7,3 +7,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -2,8 +2,10 @@
// 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;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Compilation;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@ -13,10 +15,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// <summary> /// <summary>
/// Provides programmatic configuration for the <see cref="RazorViewEngine"/>. /// Provides programmatic configuration for the <see cref="RazorViewEngine"/>.
/// </summary> /// </summary>
public class RazorViewEngineOptions public class RazorViewEngineOptions : IEnumerable<ICompatibilitySwitch>
{ {
private readonly ICompatibilitySwitch[] _switches;
private readonly CompatibilitySwitch<bool> _allowRecompilingViewsOnFileChange;
private Action<RoslynCompilationContext> _compilationCallback = c => { }; private Action<RoslynCompilationContext> _compilationCallback = c => { };
public RazorViewEngineOptions()
{
_allowRecompilingViewsOnFileChange = new CompatibilitySwitch<bool>(nameof(AllowRecompilingViewsOnFileChange));
_switches = new[]
{
_allowRecompilingViewsOnFileChange,
};
}
/// <summary> /// <summary>
/// Gets a <see cref="IList{IViewLocationExpander}"/> used by the <see cref="RazorViewEngine"/>. /// Gets a <see cref="IList{IViewLocationExpander}"/> used by the <see cref="RazorViewEngine"/>.
/// </summary> /// </summary>
@ -181,5 +194,52 @@ namespace Microsoft.AspNetCore.Mvc.Razor
_compilationCallback = value; _compilationCallback = value;
} }
} }
/// <summary>
/// Gets or sets a value that determines if Razor files (Razor Views and Razor Pages) are recompiled and updated
/// if files change on disk.
/// <para>
/// When <see langword="true"/>, MVC will use <see cref="IFileProvider.Watch(string)"/> to watch for changes to
/// Razor files in configured <see cref="IFileProvider"/> instances.
/// </para>
/// </summary>
/// <value>
/// The default value is <see langword="true"/> if the version is <see cref = "CompatibilityVersion.Version_2_1" />
/// or earlier. If the version is later and <see cref= "IHostingEnvironment.EnvironmentName" /> is <c>Development</c>,
/// the default value is <see langword="true"/>. Otherwise, the default value is <see langword="false" />.
/// </value>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// lower then this setting will have the value <see langword="true"/> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
/// higher then this setting will have the value <see langword="false"/> unless
/// <see cref="IHostingEnvironment.EnvironmentName"/> is <c>Development</c> or the value is explicitly configured.
/// </para>
/// </remarks>
public bool AllowRecompilingViewsOnFileChange
{
// Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property.
get => _allowRecompilingViewsOnFileChange.Value;
set => _allowRecompilingViewsOnFileChange.Value = value;
}
IEnumerator<ICompatibilitySwitch> IEnumerable<ICompatibilitySwitch>.GetEnumerator()
{
return ((IEnumerable<ICompatibilitySwitch>)_switches).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator();
} }
} }

View File

@ -2,27 +2,45 @@
// 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;
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Razor.Internal namespace Microsoft.AspNetCore.Mvc.Razor
{ {
/// <summary> internal class RazorViewEngineOptionsSetup :
/// Sets up default options for <see cref="RazorViewEngineOptions"/>. ConfigureCompatibilityOptions<RazorViewEngineOptions>,
/// </summary> IConfigureOptions<RazorViewEngineOptions>
public class RazorViewEngineOptionsSetup : IConfigureOptions<RazorViewEngineOptions>
{ {
private readonly IHostingEnvironment _hostingEnvironment; private readonly IHostingEnvironment _hostingEnvironment;
/// <summary> public RazorViewEngineOptionsSetup(
/// Initializes a new instance of <see cref="RazorViewEngineOptions"/>. IHostingEnvironment hostingEnvironment,
/// </summary> ILoggerFactory loggerFactory,
/// <param name="hostingEnvironment"><see cref="IHostingEnvironment"/> for the application.</param> IOptions<MvcCompatibilityOptions> compatibilityOptions)
public RazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment) : base(loggerFactory, compatibilityOptions)
{ {
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
} }
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
var values = new Dictionary<string, object>();
if (Version < CompatibilityVersion.Version_2_2)
{
// Default to true in 2.1 or earlier. In 2.2, we have to conditionally enable this
// and consequently this switch has no default value.
values[nameof(RazorViewEngineOptions.AllowRecompilingViewsOnFileChange)] = true;
}
return values;
}
}
public void Configure(RazorViewEngineOptions options) public void Configure(RazorViewEngineOptions options)
{ {
if (options == null) if (options == null)
@ -41,6 +59,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension); options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension); options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension); options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
if (_hostingEnvironment.IsDevelopment())
{
options.AllowRecompilingViewsOnFileChange = true;
}
} }
} }
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
@ -18,11 +19,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private readonly IFileProvider _fileProvider; private readonly IFileProvider _fileProvider;
private readonly string[] _searchPatterns; private readonly string[] _searchPatterns;
private readonly string[] _additionalFilesToTrack; private readonly string[] _additionalFilesToTrack;
private readonly bool _watchForChanges;
public PageActionDescriptorChangeProvider( public PageActionDescriptorChangeProvider(
RazorTemplateEngine templateEngine, RazorTemplateEngine templateEngine,
IRazorViewEngineFileProviderAccessor fileProviderAccessor, IRazorViewEngineFileProviderAccessor fileProviderAccessor,
IOptions<RazorPagesOptions> razorPagesOptions) IOptions<RazorPagesOptions> razorPagesOptions,
IOptions<RazorViewEngineOptions> razorViewEngineOptions)
{ {
if (templateEngine == null) if (templateEngine == null)
{ {
@ -39,6 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
throw new ArgumentNullException(nameof(razorPagesOptions)); throw new ArgumentNullException(nameof(razorPagesOptions));
} }
_watchForChanges = razorViewEngineOptions.Value.AllowRecompilingViewsOnFileChange;
if (!_watchForChanges)
{
// No need to do any additional work if we aren't going to be watching for file changes.
return;
}
_fileProvider = fileProviderAccessor.FileProvider; _fileProvider = fileProviderAccessor.FileProvider;
var rootDirectory = razorPagesOptions.Value.RootDirectory; var rootDirectory = razorPagesOptions.Value.RootDirectory;
@ -84,6 +94,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public IChangeToken GetChangeToken() public IChangeToken GetChangeToken()
{ {
if (!_watchForChanges)
{
return NullChangeToken.Singleton;
}
var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length]; var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length];
for (var i = 0; i < _additionalFilesToTrack.Length; i++) for (var i = 0; i < _additionalFilesToTrack.Length; i++)
{ {

View File

@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public void OnProvidersExecuting(PageRouteModelProviderContext context) public void OnProvidersExecuting(PageRouteModelProviderContext context)
{ {
// When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectoryy = /Areas; // When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectory = /Areas;
// we need to ensure that the page is only route-able via the area route. By adding area routes first, // we need to ensure that the page is only route-able via the area route. By adding area routes first,
// we'll ensure non area routes get skipped when it encounters an IsAlreadyRegistered check. // we'll ensure non area routes get skipped when it encounters an IsAlreadyRegistered check.
@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private static bool IsRouteable(RazorProjectItem item) private static bool IsRouteable(RazorProjectItem item)
{ {
// Pages like _ViewImports should not be routable. // Pages like _ViewImports should not be routeable.
return !item.FileName.StartsWith("_", StringComparison.OrdinalIgnoreCase); return !item.FileName.StartsWith("_", StringComparison.OrdinalIgnoreCase);
} }
} }

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache
public class CacheTagKey : IEquatable<CacheTagKey> public class CacheTagKey : IEquatable<CacheTagKey>
{ {
private static readonly char[] AttributeSeparator = new[] { ',' }; private static readonly char[] AttributeSeparator = new[] { ',' };
private static readonly Func<IRequestCookieCollection, string, string> CookieAcccessor = (c, key) => c[key]; private static readonly Func<IRequestCookieCollection, string, string> CookieAccessor = (c, key) => c[key];
private static readonly Func<IHeaderDictionary, string, string> HeaderAccessor = (c, key) => c[key]; private static readonly Func<IHeaderDictionary, string, string> HeaderAccessor = (c, key) => c[key];
private static readonly Func<IQueryCollection, string, string> QueryAccessor = (c, key) => c[key]; private static readonly Func<IQueryCollection, string, string> QueryAccessor = (c, key) => c[key];
private static readonly Func<RouteValueDictionary, string, string> RouteValueAccessor = (c, key) => c[key]?.ToString(); private static readonly Func<RouteValueDictionary, string, string> RouteValueAccessor = (c, key) => c[key]?.ToString();
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache
_expiresOn = tagHelper.ExpiresOn; _expiresOn = tagHelper.ExpiresOn;
_expiresSliding = tagHelper.ExpiresSliding; _expiresSliding = tagHelper.ExpiresSliding;
_varyBy = tagHelper.VaryBy; _varyBy = tagHelper.VaryBy;
_cookies = ExtractCollection(tagHelper.VaryByCookie, request.Cookies, CookieAcccessor); _cookies = ExtractCollection(tagHelper.VaryByCookie, request.Cookies, CookieAccessor);
_headers = ExtractCollection(tagHelper.VaryByHeader, request.Headers, HeaderAccessor); _headers = ExtractCollection(tagHelper.VaryByHeader, request.Headers, HeaderAccessor);
_queries = ExtractCollection(tagHelper.VaryByQuery, request.Query, QueryAccessor); _queries = ExtractCollection(tagHelper.VaryByQuery, request.Query, QueryAccessor);
_routeValues = ExtractCollection(tagHelper.VaryByRoute, tagHelper.ViewContext.RouteData.Values, RouteValueAccessor); _routeValues = ExtractCollection(tagHelper.VaryByRoute, tagHelper.ViewContext.RouteData.Values, RouteValueAccessor);

View File

@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
// can't be put inside a using block. // can't be put inside a using block.
entry.Dispose(); entry.Dispose();
// Set the result on the TCS once we've commited the entry to the cache since commiting to the cache // Set the result on the TCS once we've committed the entry to the cache since commiting to the cache
// may throw. // may throw.
tcs.SetResult(content); tcs.SetResult(content);
return content; return content;

View File

@ -167,9 +167,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
} }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if user provides an <c>formaction</c> attribute.</remarks> /// <remarks>Does nothing if user provides an <c>FormAction</c> attribute.</remarks>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Thrown if <c>formaction</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/>, /// Thrown if <c>FormAction</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/>,
/// <see cref="Fragment"/> or <see cref="Route"/> are non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes. /// <see cref="Fragment"/> or <see cref="Route"/> are non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes.
/// Also thrown if <see cref="Route"/> and one or both of <see cref="Action"/> and <see cref="Controller"/> /// Also thrown if <see cref="Route"/> and one or both of <see cref="Action"/> and <see cref="Controller"/>
/// are non-<c>null</c> /// are non-<c>null</c>
@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
throw new ArgumentNullException(nameof(output)); throw new ArgumentNullException(nameof(output));
} }
// If "formaction" is already set, it means the user is attempting to use a normal button or input element. // If "FormAction" is already set, it means the user is attempting to use a normal button or input element.
if (output.Attributes.ContainsName(FormAction)) if (output.Attributes.ContainsName(FormAction))
{ {
if (Action != null || if (Action != null ||
@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
Route != null || Route != null ||
(_routeValues != null && _routeValues.Count > 0)) (_routeValues != null && _routeValues.Count > 0))
{ {
// User specified a formaction and one of the bound attributes; can't override that formaction // User specified a FormAction and one of the bound attributes; can't override that FormAction
// attribute. // attribute.
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatFormActionTagHelper_CannotOverrideFormAction( Resources.FormatFormActionTagHelper_CannotOverrideFormAction(

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
public class RenderAtEndOfFormTagHelper : TagHelper public class RenderAtEndOfFormTagHelper : TagHelper
{ {
// This TagHelper's order must be greater than the FormTagHelper's. I.e it must be executed after // This TagHelper's order must be greater than the FormTagHelper's. I.e it must be executed after
// FormTaghelper does. // FormTagHelper does.
/// <inheritdoc /> /// <inheritdoc />
public override int Order => -900; public override int Order => -900;

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
var copiedAttribute = false; var copiedAttribute = false;
// We iterate context.AllAttributes backwards since we prioritize TagHelperOutput values occurring // We iterate context.AllAttributes backwards since we prioritize TagHelperOutput values occurring
// before the current context.AllAttribtes[i]. // before the current context.AllAttributes[i].
for (var i = context.AllAttributes.Count - 1; i >= 0; i--) for (var i = context.AllAttributes.Count - 1; i >= 0; i--)
{ {
// We look for the original attribute so we can restore the exact attribute name the user typed in // We look for the original attribute so we can restore the exact attribute name the user typed in

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers
/// <summary> /// <summary>
/// Creates a new instance of <see cref="RedirectHandler"/>. /// Creates a new instance of <see cref="RedirectHandler"/>.
/// </summary> /// </summary>
/// <param name="maxRedirects">The maximun number of redirect responses to follow. It must be /// <param name="maxRedirects">The maximum number of redirect responses to follow. It must be
/// equal or greater than 0.</param> /// equal or greater than 0.</param>
public RedirectHandler(int maxRedirects) public RedirectHandler(int maxRedirects)
{ {

View File

@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Testing
/// <see cref="WebApplicationFactoryContentRootAttribute"/> on the assembly containing the functional tests with /// <see cref="WebApplicationFactoryContentRootAttribute"/> on the assembly containing the functional tests with
/// a key equal to the <typeparamref name="TEntryPoint"/> assembly <see cref="Assembly.FullName"/>. /// a key equal to the <typeparamref name="TEntryPoint"/> assembly <see cref="Assembly.FullName"/>.
/// In case an attribute with the right key can't be found, <see cref="WebApplicationFactory{TEntryPoint}"/> /// In case an attribute with the right key can't be found, <see cref="WebApplicationFactory{TEntryPoint}"/>
/// will fall back to searching for a solution file (*.sln) and then appending <typeparamref name="TEntryPoint"/> asembly name /// will fall back to searching for a solution file (*.sln) and then appending <typeparamref name="TEntryPoint"/> assembly name
/// to the solution directory. The application root directory will be used to discover views and content files. /// to the solution directory. The application root directory will be used to discover views and content files.
/// </para> /// </para>
/// <para> /// <para>

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc
/// Gets or sets <see cref="ViewDataDictionary"/> used by <see cref="ViewResult"/> and <see cref="ViewBag"/>. /// Gets or sets <see cref="ViewDataDictionary"/> used by <see cref="ViewResult"/> and <see cref="ViewBag"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// By default, this property is intiailized when <see cref="Controllers.IControllerActivator"/> activates /// By default, this property is initialized when <see cref="Controllers.IControllerActivator"/> activates
/// controllers. /// controllers.
/// <para> /// <para>
/// This property can be accessed after the controller has been activated, for example, in a controller action /// This property can be accessed after the controller has been activated, for example, in a controller action

View File

@ -5,6 +5,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -28,14 +29,30 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
// Don't do anything. We'll call FlushAsync. // Don't do anything. We'll call FlushAsync.
} }
public override async Task FlushAsync() public override Task FlushAsync() => FlushAsyncCore();
// private non-virtual for internal calling.
// It first does a fast check to see if async is necessary, we inline this check.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task FlushAsyncCore()
{ {
var length = _charBuffer.Length; var length = _charBuffer.Length;
if (length == 0) if (length == 0)
{ {
return;
// If nothing sync buffered return CompletedTask,
// so we can fast-path skip async state-machine creation
return Task.CompletedTask;
} }
return FlushAsyncAwaited();
}
private async Task FlushAsyncAwaited()
{
var length = _charBuffer.Length;
Debug.Assert(length > 0);
var pages = _charBuffer.Pages; var pages = _charBuffer.Pages;
var count = pages.Count; var count = pages.Count;
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
@ -89,21 +106,54 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
_charBuffer.Append(value); _charBuffer.Append(value);
} }
public override async Task WriteAsync(char value) public override Task WriteAsync(char value)
{ {
await FlushAsync(); var flushTask = FlushAsyncCore();
// FlushAsyncCore will return CompletedTask if nothing sync buffered
// Fast-path and skip async state-machine if only a single async operation
return ReferenceEquals(flushTask, Task.CompletedTask) ?
_inner.WriteAsync(value) :
WriteAsyncAwaited(flushTask, value);
}
private async Task WriteAsyncAwaited(Task flushTask, char value)
{
await flushTask;
await _inner.WriteAsync(value); await _inner.WriteAsync(value);
} }
public override async Task WriteAsync(char[] buffer, int index, int count) public override Task WriteAsync(char[] buffer, int index, int count)
{ {
await FlushAsync(); var flushTask = FlushAsyncCore();
// FlushAsyncCore will return CompletedTask if nothing sync buffered
// Fast-path and skip async state-machine if only a single async operation
return ReferenceEquals(flushTask, Task.CompletedTask) ?
_inner.WriteAsync(buffer, index, count) :
WriteAsyncAwaited(flushTask, buffer, index, count);
}
private async Task WriteAsyncAwaited(Task flushTask, char[] buffer, int index, int count)
{
await flushTask;
await _inner.WriteAsync(buffer, index, count); await _inner.WriteAsync(buffer, index, count);
} }
public override async Task WriteAsync(string value) public override Task WriteAsync(string value)
{ {
await FlushAsync(); var flushTask = FlushAsyncCore();
// FlushAsyncCore will return CompletedTask if nothing sync buffered
// Fast-path and skip async state-machine if only a single async operation
return ReferenceEquals(flushTask, Task.CompletedTask) ?
_inner.WriteAsync(value) :
WriteAsyncAwaited(flushTask, value);
}
private async Task WriteAsyncAwaited(Task flushTask, string value)
{
await flushTask;
await _inner.WriteAsync(value); await _inner.WriteAsync(value);
} }

View File

@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
} }
/// <inheritdoc /> /// <inheritdoc />
// Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public IHtmlContentBuilder Append(string unencoded) public IHtmlContentBuilder Append(string unencoded)
{ {
@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
} }
/// <inheritdoc /> /// <inheritdoc />
// Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public IHtmlContentBuilder AppendHtml(IHtmlContent content) public IHtmlContentBuilder AppendHtml(IHtmlContent content)
{ {
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
} }
/// <inheritdoc /> /// <inheritdoc />
// Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public IHtmlContentBuilder AppendHtml(string encoded) public IHtmlContentBuilder AppendHtml(string encoded)
{ {
@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
return this; return this;
} }
// Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AppendValue(ViewBufferValue value) private void AppendValue(ViewBufferValue value)
{ {
@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
page.Append(value); page.Append(value);
} }
// Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private ViewBufferPage GetCurrentPage() private ViewBufferPage GetCurrentPage()
{ {

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
public bool IsFull => Count == Capacity; public bool IsFull => Count == Capacity;
// Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(ViewBufferValue value) => Buffer[Count++] = value; public void Append(ViewBufferValue value) => Buffer[Count++] = value;
} }

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
Order = int.MinValue + 100; Order = int.MinValue + 100;
} }
// <inheritdoc /> /// <inheritdoc />
public int Order { get; set; } public int Order { get; set; }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1565,10 +1565,10 @@ namespace Microsoft.AspNetCore.Mvc.Description
} }
[Fact] [Fact]
public void GetApiDescription_ParameterDescription_RedundentMetadata_NotMergedWithParent() public void GetApiDescription_ParameterDescription_RedundantMetadata_NotMergedWithParent()
{ {
// Arrange // Arrange
var action = CreateActionDescriptor(nameof(AcceptsRedundentMetadata)); var action = CreateActionDescriptor(nameof(AcceptsRedundantMetadata));
var parameterDescriptor = action.Parameters.Single(); var parameterDescriptor = action.Parameters.Single();
// Act // Act
@ -1590,7 +1590,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
} }
[Fact] [Fact]
public void GetApiDescription_ParameterDescription_RedundentMetadata_WithParameterMetadata() public void GetApiDescription_ParameterDescription_RedundantMetadata_WithParameterMetadata()
{ {
// Arrange // Arrange
var action = CreateActionDescriptor(nameof(AcceptsPerson)); var action = CreateActionDescriptor(nameof(AcceptsPerson));
@ -2081,7 +2081,7 @@ namespace Microsoft.AspNetCore.Mvc.Description
{ {
} }
private void AcceptsRedundentMetadata([FromQuery] RedundentMetadata r) private void AcceptsRedundantMetadata([FromQuery] RedundentMetadata r)
{ {
} }

View File

@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc
} }
[Fact] [Fact]
public void OnFormatting_NullUrlHelperContextNoRequestServices_ThrowsArgumentNullExeption() public void OnFormatting_NullUrlHelperContextNoRequestServices_ThrowsArgumentNullException()
{ {
// Arrange // Arrange
var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());

View File

@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc
public void Convert_InfersDeclaredTypeFromActionResultTypeParameter() public void Convert_InfersDeclaredTypeFromActionResultTypeParameter()
{ {
// Arrange // Arrange
var value = new DeriviedItem(); var value = new DerivedItem();
var actionResultOfT = new ActionResult<BaseItem>(value); var actionResultOfT = new ActionResult<BaseItem>(value);
var convertToActionResult = (IConvertToActionResult)actionResultOfT; var convertToActionResult = (IConvertToActionResult)actionResultOfT;
@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc
{ {
} }
private class DeriviedItem : BaseItem private class DerivedItem : BaseItem
{ {
} }
} }

View File

@ -85,14 +85,14 @@ namespace Microsoft.AspNetCore.Mvc
} }
[Fact] [Fact]
public void Constructor_ThrowsIfMethodIsAmbigous() public void Constructor_ThrowsIfMethodIsAmbiguous()
{ {
// Arrange // Arrange
var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
var attribute = typeof(ProducesAttribute); var attribute = typeof(ProducesAttribute);
var type = typeof(TestConventions); var type = typeof(TestConventions);
var expected = $"Method name 'Method' is ambigous for convention type '{type}'. More than one method found with the name 'Method'."; var expected = $"Method name 'Method' is ambiguous for convention type '{type}'. More than one method found with the name 'Method'.";
// Act & Assert // Act & Assert
ExceptionAssert.ThrowsArgument( ExceptionAssert.ThrowsArgument(

View File

@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
} }
[Fact] [Fact]
public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrefix()
{ {
// Arrange // Arrange
var name = "Postman"; var name = "Postman";
@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
} }
[Fact] [Fact]
public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatches()
{ {
// Arrange // Arrange
var method = typeof(TestController).GetMethod(nameof(TestController.Get)); var method = typeof(TestController).GetMethod(nameof(TestController.Get));

View File

@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc
[InlineData("application/json")] [InlineData("application/json")]
[InlineData("application/json;Parameter1=12")] [InlineData("application/json;Parameter1=12")]
[InlineData("text/xml")] [InlineData("text/xml")]
public void ActionConstraint_Accept_MatchesForMachingRequestContentType(string contentType) public void ActionConstraint_Accept_MatchesForMatchingRequestContentType(string contentType)
{ {
// Arrange // Arrange
var constraint = new ConsumesAttribute("application/json", "text/xml"); var constraint = new ConsumesAttribute("application/json", "text/xml");

View File

@ -1027,10 +1027,10 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test
// Arrange // Arrange
var controller = new TestableController(); var controller = new TestableController();
var pageName = "/Page-Name"; var pageName = "/Page-Name";
var routeVaues = new { key = "value" }; var routeValues = new { key = "value" };
// Act // Act
var result = controller.RedirectToPage(pageName, routeVaues); var result = controller.RedirectToPage(pageName, routeValues);
// Assert // Assert
Assert.IsType<RedirectToPageResult>(result); Assert.IsType<RedirectToPageResult>(result);

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Pipeline1.ConfigurePipeline = (ab) => Pipeline1.ConfigurePipeline = (ab) =>
{ {
configureCallCount++; configureCallCount++;
ab.Use((httpCtxt, next) => ab.Use((httpContext, next) =>
{ {
return next(); return next();
}); });

View File

@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true); httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true);
httpContext.Setup(c => c.Request.Query["format"]).Returns("xml"); httpContext.Setup(c => c.Request.Query["format"]).Returns("xml");
// Routedata contains json // RouteData contains json
var data = new RouteData(); var data = new RouteData();
data.Values.Add("format", "json"); data.Values.Add("format", "json");

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{ {
get get
{ {
// object value, bool useDeclaredTypeAsString, bool expectedCanwriteResult, bool useNonNullContentType // object value, bool useDeclaredTypeAsString, bool expectedCanWriteResult, bool useNonNullContentType
yield return new object[] { "valid value", true, false, true }; yield return new object[] { "valid value", true, false, true };
yield return new object[] { "valid value", false, false, true }; yield return new object[] { "valid value", false, false, true };
yield return new object[] { "", false, false, true }; yield return new object[] { "", false, false, true };

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
} }
[Fact] [Fact]
public void Constructor_WithNameAndInitalValue_IsValueSetIsFalse() public void Constructor_WithNameAndInitialValue_IsValueSetIsFalse()
{ {
// Arrange & Act // Arrange & Act
var @switch = new CompatibilitySwitch<bool>("TestProperty", initialValue: true); var @switch = new CompatibilitySwitch<bool>("TestProperty", initialValue: true);
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
var @switch = new CompatibilitySwitch<bool>("TestProperty"); var @switch = new CompatibilitySwitch<bool>("TestProperty");
// Act // Act
@switch.Value = false; // You don't need to actually change the value, just caling the setting works @switch.Value = false; // You don't need to actually change the value, just calling the setting works
// Assert // Assert
Assert.False(@switch.Value); Assert.False(@switch.Value);

View File

@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
} }
[Fact] [Fact]
public void ActionDescriptors_UpdatesAndResubscripes_WhenChangeTokenTriggers() public void ActionDescriptors_UpdatesAndResubscribes_WhenChangeTokenTriggers()
{ {
// Arrange // Arrange
var actionDescriptorProvider = new Mock<IActionDescriptorProvider>(); var actionDescriptorProvider = new Mock<IActionDescriptorProvider>();

View File

@ -8,7 +8,7 @@ using Xunit;
namespace Microsoft.AspNetCore.Mvc.Infrastructure namespace Microsoft.AspNetCore.Mvc.Infrastructure
{ {
public class ProblemDetalsClientErrorFactoryTest public class ProblemDetailsClientErrorFactoryTest
{ {
[Fact] [Fact]
public void GetClientError_ReturnsProblemDetails_IfNoMappingWasFound() public void GetClientError_ReturnsProblemDetails_IfNoMappingWasFound()

View File

@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Fact] [Fact]
public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MulitpleRoutes() public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MultipleRoutes()
{ {
// Arrange // Arrange
var actionName = nameof(ParameterBindingController.ParameterInMultipleRoutes); var actionName = nameof(ParameterBindingController.ParameterInMultipleRoutes);
@ -1112,7 +1112,7 @@ Environment.NewLine + "int b";
} }
[Fact] [Fact]
public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAsssembly() public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAssembly()
{ {
// Arrange // Arrange
var expected = typeof(InvalidEnumArgumentException); var expected = typeof(InvalidEnumArgumentException);

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApiExplorer;
@ -252,6 +253,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value); Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value);
} }
[Fact]
public void GetDescriptors_EndpointMetadata_ContainsAttributesFromActionAndController()
{
// Arrange & Act
var descriptors = GetDescriptors(
typeof(AuthorizeController).GetTypeInfo());
// Assert
Assert.Equal(2, descriptors.Count());
var anonymousAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AllowAnonymousAction");
Assert.NotNull(anonymousAction.EndpointMetadata);
Assert.Collection(anonymousAction.EndpointMetadata,
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata),
metadata => Assert.IsType<AuthorizeAttribute>(metadata));
var authorizeAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AuthorizeAction");
Assert.NotNull(authorizeAction.EndpointMetadata);
Assert.Collection(authorizeAction.EndpointMetadata,
metadata => Assert.Equal("ActionPolicy", Assert.IsType<AuthorizeAttribute>(metadata).Policy),
metadata => Assert.Equal("ControllerPolicy", Assert.IsType<AuthorizeAttribute>(metadata).Policy));
}
[Fact] [Fact]
public void GetDescriptors_ActionWithHttpMethods_AddedToEndpointMetadata() public void GetDescriptors_ActionWithHttpMethods_AddedToEndpointMetadata()
{ {
@ -272,7 +300,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.False(httpMethodMetadata.AcceptCorsPreflight); Assert.False(httpMethodMetadata.AcceptCorsPreflight);
Assert.Equal("GET", Assert.Single(httpMethodMetadata.HttpMethods)); Assert.Equal("GET", Assert.Single(httpMethodMetadata.HttpMethods));
}); },
metadata => Assert.IsType<RouteAttribute>(metadata));
} }
[Fact] [Fact]
@ -1175,7 +1204,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
[Theory] [Theory]
[InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))] [InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))]
[InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))] [InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))]
public void ApiExplorer_ThrowsForContentionalRouting(string actionName, Type type) public void ApiExplorer_ThrowsForConventionalRouting(string actionName, Type type)
{ {
// Arrange // Arrange
var assemblyName = type.GetTypeInfo().Assembly.GetName().Name; var assemblyName = type.GetTypeInfo().Assembly.GetName().Name;
@ -1865,6 +1894,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public void AttributeRoutedAction() { } public void AttributeRoutedAction() { }
} }
[Authorize("ControllerPolicy")]
private class AuthorizeController
{
[AllowAnonymous]
public void AllowAnonymousAction() { }
[Authorize("ActionPolicy")]
public void AuthorizeAction() { }
}
private class EmptyController private class EmptyController
{ {
} }

View File

@ -1206,7 +1206,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Theory] [Theory]
[InlineData(nameof(TestController.AsynActionMethodWithTestActionResult))] [InlineData(nameof(TestController.AsyncActionMethodWithTestActionResult))]
[InlineData(nameof(TestController.ActionMethodWithTestActionResult))] [InlineData(nameof(TestController.ActionMethodWithTestActionResult))]
public async Task InvokeAction_ReturnTypeAsIActionResult_ReturnsExpected(string methodName) public async Task InvokeAction_ReturnTypeAsIActionResult_ReturnsExpected(string methodName)
{ {
@ -1654,7 +1654,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return new TestActionResult { Value = value }; return new TestActionResult { Value = value };
} }
public async Task<TestActionResult> AsynActionMethodWithTestActionResult(int value) public async Task<TestActionResult> AsyncActionMethodWithTestActionResult(int value)
{ {
return await Task.FromResult<TestActionResult>(new TestActionResult { Value = value }); return await Task.FromResult<TestActionResult>(new TestActionResult { Value = value });
} }

View File

@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Fact] [Fact]
public void EnumerateElements_TwoEnumerableImplemenations() public void EnumerateElements_TwoEnumerableImplementations()
{ {
// Arrange // Arrange
var model = new TwiceEnumerable(new int[] { 2, 3, 5 }); var model = new TwiceEnumerable(new int[] { 2, 3, 5 });

View File

@ -1316,7 +1316,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
{ model, new ValidationStateEntry() } { model, new ValidationStateEntry() }
}; };
var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLeveleMetadataData), BindingFlags.NonPublic | BindingFlags.Instance); var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLevelMetadataData), BindingFlags.NonPublic | BindingFlags.Instance);
var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]); var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]);
// Act & Assert // Act & Assert
@ -1465,7 +1465,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
void DoSomething(); void DoSomething();
} }
private void Validate_Throws_ForTopLeveleMetadataData(DepthObject depthObject) { } private void Validate_Throws_ForTopLevelMetadataData(DepthObject depthObject) { }
// Custom validation attribute that returns multiple entries in ValidationResult.MemberNames and those member // Custom validation attribute that returns multiple entries in ValidationResult.MemberNames and those member
// names are indexers. An example scenario is an attribute that confirms all entries in a list are unique. // names are indexers. An example scenario is an attribute that confirms all entries in a list are unique.

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
// Arrange // Arrange
var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance);
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize); authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize);
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Arrange // Arrange
var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance);
var disableRequestSizeLimitResourceFilterFinal = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); var disableRequestSizeLimitResourceFilterFinal = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance);
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { disableRequestSizeLimitResourceFilter, disableRequestSizeLimitResourceFilterFinal }); new IFilterMetadata[] { disableRequestSizeLimitResourceFilter, disableRequestSizeLimitResourceFilterFinal });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var loggerFactory = new TestLoggerFactory(sink, enabled: true); var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory);
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter });
// Act // Act
disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext);
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var loggerFactory = new TestLoggerFactory(sink, enabled: true); var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory);
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
httpMaxRequestBodySize.IsReadOnly = true; httpMaxRequestBodySize.IsReadOnly = true;
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var loggerFactory = new TestLoggerFactory(sink, enabled: true); var loggerFactory = new TestLoggerFactory(sink, enabled: true);
var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory);
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize); authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize);
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal($"The request body size limit has been disabled.", write.State.ToString()); Assert.Equal($"The request body size limit has been disabled.", write.State.ToString());
} }
private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) private static AuthorizationFilterContext CreateAuthorizationFilterContext(IFilterMetadata[] filters)
{ {
return new AuthorizationFilterContext(CreateActionContext(), filters); return new AuthorizationFilterContext(CreateActionContext(), filters);
} }

View File

@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Fact] [Fact]
public void EnumerateElements_TwoEnumerableImplemenations() public void EnumerateElements_TwoEnumerableImplementations()
{ {
// Arrange // Arrange
var model = new TwiceEnumerable(new int[] { 2, 3, 5 }); var model = new TwiceEnumerable(new int[] { 2, 3, 5 });

View File

@ -36,13 +36,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
// Arrange // Arrange
Task requestDelegate(HttpContext context) => Task.FromResult(true); Task requestDelegate(HttpContext context) => Task.FromResult(true);
var middlwareFilter = new MiddlewareFilter(requestDelegate); var middlewareFilter = new MiddlewareFilter(requestDelegate);
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
var resourceExecutingContext = GetResourceExecutingContext(httpContext); var resourceExecutingContext = GetResourceExecutingContext(httpContext);
var resourceExecutionDelegate = GetResourceExecutionDelegate(httpContext); var resourceExecutionDelegate = GetResourceExecutionDelegate(httpContext);
// Act // Act
await middlwareFilter.OnResourceExecutionAsync(resourceExecutingContext, resourceExecutionDelegate); await middlewareFilter.OnResourceExecutionAsync(resourceExecutingContext, resourceExecutionDelegate);
// Assert // Assert
var feature = resourceExecutingContext.HttpContext.Features.Get<IMiddlewareFilterFeature>(); var feature = resourceExecutingContext.HttpContext.Features.Get<IMiddlewareFilterFeature>();
@ -398,7 +398,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
logger, logger,
diagnosticSource, diagnosticSource,
mapper, mapper,
CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState), CreateControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState),
CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory), CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory),
filters) filters)
{ {
@ -420,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo); return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo);
} }
private static ControllerContext CreatControllerContext( private static ControllerContext CreateControllerContext(
ActionContext actionContext, ActionContext actionContext,
IReadOnlyList<IValueProviderFactory> valueProviderFactories, IReadOnlyList<IValueProviderFactory> valueProviderFactories,
int maxAllowedErrorsInModelState) int maxAllowedErrorsInModelState)

View File

@ -112,9 +112,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var asyncResultFilter = Mock.Of<IAsyncResultFilter>(); var asyncResultFilter = Mock.Of<IAsyncResultFilter>();
var resourceFilter = Mock.Of<IResourceFilter>(); var resourceFilter = Mock.Of<IResourceFilter>();
var asyncResourceFilter = Mock.Of<IAsyncResourceFilter>(); var asyncResourceFilter = Mock.Of<IAsyncResourceFilter>();
var orderedresourceFilterMock = new Mock<IOrderedResourceFilter>(); var orderedResourceFilterMock = new Mock<IOrderedResourceFilter>();
orderedresourceFilterMock.SetupGet(f => f.Order).Returns(-100); orderedResourceFilterMock.SetupGet(f => f.Order).Returns(-100);
var orderedResourceFilter = orderedresourceFilterMock.Object; var orderedResourceFilter = orderedResourceFilterMock.Object;
var filters = new IFilterMetadata[] var filters = new IFilterMetadata[]
{ {
actionFilter, actionFilter,

View File

@ -377,7 +377,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint); var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint);
var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>(); var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
Assert.NotNull(routeValuesAddressNameMetadata); Assert.NotNull(routeValuesAddressNameMetadata);
Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name); Assert.Equal(string.Empty, routeValuesAddressNameMetadata.RouteName);
} }
[Fact] [Fact]
@ -402,7 +402,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep); var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>(); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
Assert.NotNull(routeValuesAddressMetadata); Assert.NotNull(routeValuesAddressMetadata);
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName);
Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
}, },
(ep) => (ep) =>
@ -410,7 +410,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep); var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>(); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
Assert.NotNull(routeValuesAddressMetadata); Assert.NotNull(routeValuesAddressMetadata);
Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName);
Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText);
}); });
} }

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Arrange // Arrange
var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance); var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance);
requestFormLimitsFilter.FormOptions = new FormOptions(); requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter }); new IFilterMetadata[] { requestFormLimitsFilter });
// Set to null explicitly as we want to make sure the filter adds one // Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null); authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null);
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Arrange // Arrange
var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance); var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance);
requestFormLimitsFilter.FormOptions = new FormOptions(); requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter }); new IFilterMetadata[] { requestFormLimitsFilter });
var oldFormFeature = new FormFeature(authorizationFilterContext.HttpContext.Request); var oldFormFeature = new FormFeature(authorizationFilterContext.HttpContext.Request);
// Set to null explicitly as we want to make sure the filter adds one // Set to null explicitly as we want to make sure the filter adds one
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory); var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
requestFormLimitsFilter.FormOptions = new FormOptions(); requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter }); new IFilterMetadata[] { requestFormLimitsFilter });
authorizationFilterContext.HttpContext.Request.Form = new FormCollection(null); authorizationFilterContext.HttpContext.Request.Form = new FormCollection(null);
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory); var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
requestFormLimitsFilter.FormOptions = new FormOptions(); requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter }); new IFilterMetadata[] { requestFormLimitsFilter });
// Set to null explicitly as we want to make sure the filter adds one // Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null); authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(null);
@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory); var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory);
requestFormLimitsFilter.FormOptions = new FormOptions(); requestFormLimitsFilter.FormOptions = new FormOptions();
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { requestFormLimitsFilter }); new IFilterMetadata[] { requestFormLimitsFilter });
// Set to null explicitly as we want to make sure the filter adds one // Set to null explicitly as we want to make sure the filter adds one
authorizationFilterContext.HttpContext.Features.Set<IFormFeature>( authorizationFilterContext.HttpContext.Features.Set<IFormFeature>(
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
write.State.ToString()); write.State.ToString());
} }
private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) private static AuthorizationFilterContext CreateAuthorizationFilterContext(IFilterMetadata[] filters)
{ {
return new AuthorizationFilterContext(CreateActionContext(), filters); return new AuthorizationFilterContext(CreateActionContext(), filters);
} }

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Arrange // Arrange
var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(NullLoggerFactory.Instance); var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(NullLoggerFactory.Instance);
requestSizeLimitResourceFilter.Bytes = 12345; requestSizeLimitResourceFilter.Bytes = 12345;
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize); authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize);
@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
requestSizeLimitResourceFilter.Bytes = 12345; requestSizeLimitResourceFilter.Bytes = 12345;
var requestSizeLimitResourceFilterFinal = new RequestSizeLimitFilter(NullLoggerFactory.Instance); var requestSizeLimitResourceFilterFinal = new RequestSizeLimitFilter(NullLoggerFactory.Instance);
requestSizeLimitResourceFilterFinal.Bytes = 0; requestSizeLimitResourceFilterFinal.Bytes = 0;
var authorizationFilterContext = CreateauthorizationFilterContext( var authorizationFilterContext = CreateAuthorizationFilterContext(
new IFilterMetadata[] { requestSizeLimitResourceFilter, requestSizeLimitResourceFilterFinal }); new IFilterMetadata[] { requestSizeLimitResourceFilter, requestSizeLimitResourceFilterFinal });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory);
requestSizeLimitResourceFilter.Bytes = 12345; requestSizeLimitResourceFilter.Bytes = 12345;
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter });
// Act // Act
requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext);
@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory);
requestSizeLimitResourceFilter.Bytes = 12345; requestSizeLimitResourceFilter.Bytes = 12345;
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
httpMaxRequestBodySize.IsReadOnly = true; httpMaxRequestBodySize.IsReadOnly = true;
@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory);
requestSizeLimitResourceFilter.Bytes = 12345; requestSizeLimitResourceFilter.Bytes = 12345;
var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter });
var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature();
authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize); authorizationFilterContext.HttpContext.Features.Set<IHttpMaxRequestBodySizeFeature>(httpMaxRequestBodySize);
@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal($"The maximum request body size has been set to 12345.", write.State.ToString()); Assert.Equal($"The maximum request body size has been set to 12345.", write.State.ToString());
} }
private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) private static AuthorizationFilterContext CreateAuthorizationFilterContext(IFilterMetadata[] filters)
{ {
return new AuthorizationFilterContext(CreateActionContext(), filters); return new AuthorizationFilterContext(CreateActionContext(), filters);
} }

View File

@ -467,7 +467,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
[Theory] [Theory]
[MemberData(nameof(DerivedInputFormattersThrowingNonInputFormatterException))] [MemberData(nameof(DerivedInputFormattersThrowingNonInputFormatterException))]
public async Task BindModel_DerivedXmlInputFormatters_ThrowingNonInputFormatingException_AddsErrorToModelState( public async Task BindModel_DerivedXmlInputFormatters_ThrowingNonInputFormattingException_AddsErrorToModelState(
IInputFormatter formatter, IInputFormatter formatter,
string contentType, string contentType,
InputFormatterExceptionPolicy inputFormatterExceptionPolicy) InputFormatterExceptionPolicy inputFormatterExceptionPolicy)

View File

@ -42,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
[InlineData(false, typeof(IntEnum))] [InlineData(false, typeof(IntEnum))]
[InlineData(false, typeof(FlagsEnum))] [InlineData(false, typeof(FlagsEnum))]
public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes( public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes(
bool suprressBindingUndefinedValueToEnumType, bool suppressBindingUndefinedValueToEnumType,
Type modelType) Type modelType)
{ {
// Arrange // Arrange
var message = "The value '' is invalid."; var message = "The value '' is invalid.";
var binderInfo = GetBinderAndContext( var binderInfo = GetBinderAndContext(
modelType, modelType,
suprressBindingUndefinedValueToEnumType, suppressBindingUndefinedValueToEnumType,
valueProviderValue: ""); valueProviderValue: "");
var bindingContext = binderInfo.Item1; var bindingContext = binderInfo.Item1;
var binder = binderInfo.Item2; var binder = binderInfo.Item2;

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
[Theory] [Theory]
[InlineData(typeof(FormCollection))] [InlineData(typeof(FormCollection))]
[InlineData(typeof(DerviedFormCollection))] [InlineData(typeof(DerivedFormCollection))]
public void Create_ThrowsException_ForFormCollectionModelType(Type modelType) public void Create_ThrowsException_ForFormCollectionModelType(Type modelType)
{ {
// Arrange // Arrange
@ -62,9 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
{ {
} }
private class DerviedFormCollection : FormCollection private class DerivedFormCollection : FormCollection
{ {
public DerviedFormCollection() : base(fields: null, files: null) { } public DerivedFormCollection() : base(fields: null, files: null) { }
} }
} }
} }

View File

@ -607,7 +607,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
[Fact] [Fact]
public async Task TryUpdataModel_ModelTypeDifferentFromModel_Throws() public async Task TryUpdateModel_ModelTypeDifferentFromModel_Throws()
{ {
// Arrange // Arrange
var metadataProvider = new EmptyModelMetadataProvider(); var metadataProvider = new EmptyModelMetadataProvider();

View File

@ -21,10 +21,10 @@ namespace Microsoft.AspNetCore.Mvc
// Act & Assert // Act & Assert
foreach (var property in formOptionsProperties) foreach (var property in formOptionsProperties)
{ {
var formLimiAttributeProperty = formLimitsAttributeProperties var formLimitAttributeProperty = formLimitsAttributeProperties
.Where(pi => property.Name == pi.Name && pi.PropertyType == property.PropertyType) .Where(pi => property.Name == pi.Name && pi.PropertyType == property.PropertyType)
.SingleOrDefault(); .SingleOrDefault();
Assert.NotNull(formLimiAttributeProperty); Assert.NotNull(formLimitAttributeProperty);
} }
} }

View File

@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Mvc
[InlineData(null, false)] [InlineData(null, false)]
[InlineData(true, false)] [InlineData(true, false)]
[InlineData(false, true)] [InlineData(false, true)]
public void OnAuthorization_RedirectsToHttpsEndpoint_WithSpecifiedStatusCodeAndrequireHttpsPermanentOption(bool? permanent, bool requireHttpsPermanent) public void OnAuthorization_RedirectsToHttpsEndpoint_WithSpecifiedStatusCodeAndRequireHttpsPermanentOption(bool? permanent, bool requireHttpsPermanent)
{ {
var requestContext = new DefaultHttpContext(); var requestContext = new DefaultHttpContext();
requestContext.RequestServices = CreateServices(null, requireHttpsPermanent); requestContext.RequestServices = CreateServices(null, requireHttpsPermanent);

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
"testController", "testController",
"testAction"); "testAction");
actionDescriptor.RouteValues.Add("randomKey", "testRandom"); actionDescriptor.RouteValues.Add("randomKey", "testRandom");
var descriptorCollectionProvider = CreateActionDesciprtorCollectionProvider(actionDescriptor); var descriptorCollectionProvider = CreateActionDescriptorCollectionProvider(actionDescriptor);
var services = new ServiceCollection(); var services = new ServiceCollection();
services.AddRouting(); services.AddRouting();
@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
"testAction"); "testAction");
actionDescriptor.RouteValues.Add("randomKey", "testRandom"); actionDescriptor.RouteValues.Add("randomKey", "testRandom");
var provider = CreateActionDesciprtorCollectionProvider(actionDescriptor); var provider = CreateActionDescriptorCollectionProvider(actionDescriptor);
var constraint = new KnownRouteValueConstraint(provider); var constraint = new KnownRouteValueConstraint(provider);
@ -235,7 +235,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
private static HttpContext GetHttpContext(ActionDescriptor actionDescriptor, bool setupRequestServices = true) private static HttpContext GetHttpContext(ActionDescriptor actionDescriptor, bool setupRequestServices = true)
{ {
var descriptorCollectionProvider = CreateActionDesciprtorCollectionProvider(actionDescriptor); var descriptorCollectionProvider = CreateActionDescriptorCollectionProvider(actionDescriptor);
var context = new Mock<HttpContext>(); var context = new Mock<HttpContext>();
if (setupRequestServices) if (setupRequestServices)
@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
return context.Object; return context.Object;
} }
private static IActionDescriptorCollectionProvider CreateActionDesciprtorCollectionProvider(ActionDescriptor actionDescriptor) private static IActionDescriptorCollectionProvider CreateActionDescriptorCollectionProvider(ActionDescriptor actionDescriptor)
{ {
var actionProvider = new Mock<IActionDescriptorProvider>(MockBehavior.Strict); var actionProvider = new Mock<IActionDescriptorProvider>(MockBehavior.Strict);

Some files were not shown because too many files have changed in this diff Show More