diff --git a/.appveyor.yml b/.appveyor.yml
index 711303fa0f..018881c604 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -2,7 +2,7 @@ init:
- git config --global core.autocrlf true
branches:
only:
- - dev
+ - master
- /^release\/.*$/
- /^(.*\/)?ci-.*$/
build_script:
diff --git a/.travis.yml b/.travis.yml
index e75fe73221..d56301c453 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ os:
osx_image: xcode8.2
branches:
only:
- - dev
+ - master
- /^release\/.*$/
- /^(.*\/)?ci-.*$/
before_install:
diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml
index dc7b8a3cb9..c2c5336fd0 100644
--- a/.vsts-pipelines/builds/ci-internal.yml
+++ b/.vsts-pipelines/builds/ci-internal.yml
@@ -7,7 +7,7 @@ resources:
- repository: buildtools
type: git
name: aspnet-BuildTools
- ref: refs/heads/release/2.2
+ ref: refs/heads/master
phases:
- template: .vsts-pipelines/templates/project-ci.yml@buildtools
diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml
index f5087d9c30..507c89b025 100644
--- a/.vsts-pipelines/builds/ci-public.yml
+++ b/.vsts-pipelines/builds/ci-public.yml
@@ -9,7 +9,7 @@ resources:
type: github
endpoint: DotNet-Bot GitHub Connection
name: aspnet/BuildTools
- ref: refs/heads/release/2.2
+ ref: refs/heads/master
phases:
- template: .vsts-pipelines/templates/project-ci.yml@buildtools
diff --git a/README.md b/README.md
index 775ed6bd0c..babc35f1d9 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ ASP.NET Core MVC
**Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack**
-Travis: [](https://travis-ci.org/aspnet/Mvc)
+Travis: [](https://travis-ci.org/aspnet/Mvc)
ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards.
diff --git a/benchmarkapps/BasicApi/benchmarks.json b/benchmarkapps/BasicApi/benchmarks.json
index aff5eb280d..2ebc74dde2 100644
--- a/benchmarkapps/BasicApi/benchmarks.json
+++ b/benchmarkapps/BasicApi/benchmarks.json
@@ -7,7 +7,7 @@
"PresetHeaders": "Json",
"ReadyStateText": "Application started.",
"Source": {
- "BranchOrCommit": "release/2.2",
+ "BranchOrCommit": "dev",
"Project": "benchmarkapps/BasicApi/BasicApi.csproj",
"Repository": "https://github.com/aspnet/mvc.git"
}
@@ -19,20 +19,20 @@
},
"BasicApi.GetUsingQueryString": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/getWithToken.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua"
},
"Path": "/pet/findByStatus",
"Query": "?status=available"
},
"BasicApi.GetUsingRouteValue": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/getWithToken.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua"
},
"Path": "/pet/-1"
},
"BasicApi.GetUsingRouteValueWithoutAuthorization": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/getWithToken.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua"
},
"Path": "/pet/anonymous/-1"
},
@@ -41,7 +41,7 @@
},
"BasicApi.Post": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/postJsonWithToken.lua"
},
"Path": "/pet"
},
diff --git a/benchmarkapps/BasicViews/benchmarks.json b/benchmarkapps/BasicViews/benchmarks.json
index 6a50d9386d..82e0812a21 100644
--- a/benchmarkapps/BasicViews/benchmarks.json
+++ b/benchmarkapps/BasicViews/benchmarks.json
@@ -7,7 +7,7 @@
"PresetHeaders": "Html",
"ReadyStateText": "Application started.",
"Source": {
- "BranchOrCommit": "release/2.2",
+ "BranchOrCommit": "dev",
"Project": "benchmarkapps/BasicViews/BasicViews.csproj",
"Repository": "https://github.com/aspnet/mvc.git"
}
@@ -20,19 +20,19 @@
},
"BasicViews.Post": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicViews/postWithToken.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua"
},
"Path": "/Home/Index"
},
"BasicViews.PostIgnoringToken": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicViews/postWithToken.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua"
},
"Path": "/Home/IndexWithoutToken"
},
"BasicViews.PostWithoutToken": {
"ClientProperties": {
- "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicViews/post.lua"
+ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/post.lua"
},
"Path": "/Home/IndexWithoutToken"
}
diff --git a/build/dependencies.props b/build/dependencies.props
index 063add026b..189943f524 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -16,97 +16,97 @@
0.43.0
2.1.1.1
2.1.1
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-a-rtm-fix-wildcard-16567
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-a-alpha1-w-m-16569
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
2.0.0
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-a-rtm-allow-required-parameters-17081
- 2.2.0-a-rtm-allow-required-parameters-17081
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-a-alpha1-master-builder-17095
+ 3.0.0-a-alpha1-master-builder-17095
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
5.2.6
- 15.6.82
+ 15.8.166
2.8.0
2.8.0
- 2.2.0-rtm-35519
+ 3.0.0-alpha1-10654
1.7.0
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.1.0
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-preview1-26907-05
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
2.0.9
2.1.3
2.2.0-preview3-27014-02
- 2.2.0-rtm-35519
- 2.2.0-rtm-35519
+ 3.0.0-alpha1-10654
+ 3.0.0-alpha1-10654
15.6.1
4.10.0
2.0.3
1.0.1
11.0.2
- 4.5.0
- 4.5.0
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
4.3.2
- 4.5.1
+ 4.6.0-preview1-26907-04
0.10.0
2.3.1
2.4.0
diff --git a/build/repo.props b/build/repo.props
index 3bd17f1b84..d1a3039193 100644
--- a/build/repo.props
+++ b/build/repo.props
@@ -3,6 +3,7 @@
true
+ true
@@ -14,7 +15,6 @@
Internal.AspNetCore.Universe.Lineup
- 2.2.0-*
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
diff --git a/global.json b/global.json
index b5a5299b58..ffa7db4c53 100644
--- a/global.json
+++ b/global.json
@@ -3,6 +3,6 @@
"version": "2.2.100-preview2-009404"
},
"msbuild-sdks": {
- "Internal.AspNetCore.Sdk": "2.2.0-preview2-20181003.2"
+ "Internal.AspNetCore.Sdk": "3.0.0-alpha1-20181011.11"
}
}
diff --git a/korebuild-lock.txt b/korebuild-lock.txt
index 783131ae5e..27b605e3c6 100644
--- a/korebuild-lock.txt
+++ b/korebuild-lock.txt
@@ -1,2 +1,2 @@
-version:2.2.0-preview2-20181003.2
-commithash:41935e62d7853060283c801f49992e2c73a95927
+version:3.0.0-alpha1-20181011.11
+commithash:f57aa8ddda0abdd74ada55853587bedb4f364065
diff --git a/korebuild.json b/korebuild.json
index d217d06e3e..8a276a7f35 100644
--- a/korebuild.json
+++ b/korebuild.json
@@ -1,4 +1,4 @@
{
- "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json",
- "channel": "release/2.2"
+ "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
+ "channel": "master"
}
diff --git a/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs
new file mode 100644
index 0000000000..6a2fa48911
--- /dev/null
+++ b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationAppBuilderExtensions.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using MvcSandbox.AuthorizationMiddleware;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class AuthorizationAppBuilderExtensions
+ {
+ public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ return app.UseMiddleware();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationEndpointConventionBuilder.cs b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationEndpointConventionBuilder.cs
new file mode 100644
index 0000000000..5fb990e1f3
--- /dev/null
+++ b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationEndpointConventionBuilder.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Routing;
+
+namespace MvcSandbox.AuthorizationMiddleware
+{
+ public static class AuthorizationEndpointConventionBuilder
+ {
+ public static T RequireAuthorization(this T builder, params string[] roles) where T : IEndpointConventionBuilder
+ {
+ builder.Apply(model => model.Metadata.Add(new AuthorizeMetadataAttribute(roles)));
+ return builder;
+ }
+ }
+}
diff --git a/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationMiddleware.cs b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationMiddleware.cs
new file mode 100644
index 0000000000..23a5e45f3f
--- /dev/null
+++ b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizationMiddleware.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+
+namespace MvcSandbox.AuthorizationMiddleware
+{
+ public class AuthorizationMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ public AuthorizationMiddleware(RequestDelegate next)
+ {
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ var endpoint = httpContext.Features.Get()?.Endpoint;
+ var metadata = endpoint?.Metadata?.GetMetadata();
+
+ // Only run authorization if endpoint has metadata
+ if (metadata != null)
+ {
+ // Check if role querystring value is a valid role
+ if (!httpContext.Request.Query.TryGetValue("role", out var role) ||
+ !metadata.Roles.Contains(role.ToString(), StringComparer.OrdinalIgnoreCase))
+ {
+ httpContext.Response.StatusCode = 401;
+ httpContext.Response.ContentType = "text/plain";
+ await httpContext.Response.WriteAsync($"Unauthorized access to '{endpoint.DisplayName}'.");
+ return;
+ }
+ }
+
+ await _next(httpContext);
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/MvcSandbox/AuthorizationMiddleware/AuthorizeMetadataAttribute.cs b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizeMetadataAttribute.cs
new file mode 100644
index 0000000000..7c95327d0d
--- /dev/null
+++ b/samples/MvcSandbox/AuthorizationMiddleware/AuthorizeMetadataAttribute.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MvcSandbox.AuthorizationMiddleware
+{
+ public class AuthorizeMetadataAttribute : Attribute
+ {
+ public AuthorizeMetadataAttribute(string[] roles)
+ {
+ Roles = roles;
+ }
+
+ public string[] Roles { get; }
+ }
+}
diff --git a/samples/MvcSandbox/Controllers/HomeController.cs b/samples/MvcSandbox/Controllers/HomeController.cs
index 2aa4ff6829..87406bce53 100644
--- a/samples/MvcSandbox/Controllers/HomeController.cs
+++ b/samples/MvcSandbox/Controllers/HomeController.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc;
+using MvcSandbox.AuthorizationMiddleware;
namespace MvcSandbox.Controllers
{
diff --git a/samples/MvcSandbox/Controllers/LoginController.cs b/samples/MvcSandbox/Controllers/LoginController.cs
new file mode 100644
index 0000000000..5110850fbf
--- /dev/null
+++ b/samples/MvcSandbox/Controllers/LoginController.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Mvc;
+
+namespace MvcSandbox.Controllers
+{
+ [Route("[controller]/[action]")]
+ public class LoginController : Controller
+ {
+ public IActionResult Index()
+ {
+ return View();
+ }
+ }
+}
diff --git a/samples/MvcSandbox/HealthChecks/HealthChecksEndpointRouteBuilderExtensions.cs b/samples/MvcSandbox/HealthChecks/HealthChecksEndpointRouteBuilderExtensions.cs
new file mode 100644
index 0000000000..b89acdd2b4
--- /dev/null
+++ b/samples/MvcSandbox/HealthChecks/HealthChecksEndpointRouteBuilderExtensions.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class HealthChecksEndpointRouteBuilderExtensions
+ {
+ private static readonly Random _random = new Random();
+
+ public static IEndpointConventionBuilder MapHealthChecks(this IEndpointRouteBuilder builder, string pattern)
+ {
+ return builder.MapGet(
+ pattern,
+ async httpContext =>
+ {
+ await httpContext.Response.WriteAsync(_random.Next() % 2 == 0 ? "Up!" : "Down!");
+ });
+ }
+ }
+}
diff --git a/samples/MvcSandbox/Startup.cs b/samples/MvcSandbox/Startup.cs
index d9f96bf08b..12c1291669 100644
--- a/samples/MvcSandbox/Startup.cs
+++ b/samples/MvcSandbox/Startup.cs
@@ -1,11 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using MvcSandbox.AuthorizationMiddleware;
namespace MvcSandbox
{
@@ -20,14 +27,46 @@ namespace MvcSandbox
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
- app.UseDeveloperExceptionPage();
- app.UseStaticFiles();
- app.UseMvc(routes =>
+ app.UseEndpointRouting(builder =>
{
- routes.MapRoute(
+ builder.MapGet(
+ requestDelegate: WriteEndpoints,
+ pattern: "/endpoints",
+ displayName: "Home");
+
+ builder.MapMvcRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
+
+ builder.MapMvcControllers();
+ builder.MapRazorPages();
+
+ builder.MapHealthChecks("/healthz");
});
+
+ app.UseDeveloperExceptionPage();
+ app.UseStaticFiles();
+
+ app.UseAuthorization();
+
+ app.UseEndpoint();
+ }
+
+ private static Task WriteEndpoints(HttpContext httpContext)
+ {
+ var dataSource = httpContext.RequestServices.GetRequiredService();
+
+ var sb = new StringBuilder();
+ sb.AppendLine("Endpoints:");
+ foreach (var endpoint in dataSource.Endpoints.OfType().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
+ {
+ sb.AppendLine($"- {endpoint.RoutePattern.RawText} '{endpoint.DisplayName}'");
+ }
+
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(sb.ToString());
}
public static void Main(string[] args)
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/DefaultEndpointConventionBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/DefaultEndpointConventionBuilder.cs
new file mode 100644
index 0000000000..8427ba5c3b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/DefaultEndpointConventionBuilder.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Routing;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ internal class DefaultEndpointConventionBuilder : IEndpointConventionBuilder
+ {
+ public DefaultEndpointConventionBuilder()
+ {
+ Conventions = new List>();
+ }
+
+ public List> Conventions { get; }
+
+ public void Apply(Action convention)
+ {
+ Conventions.Add(convention);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs
index 06e26ed407..6cde8eccd5 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs
@@ -90,9 +90,7 @@ namespace Microsoft.AspNetCore.Builder
if (options.Value.EnableEndpointRouting)
{
var mvcEndpointDataSource = app.ApplicationServices
- .GetRequiredService>()
- .OfType()
- .First();
+ .GetRequiredService();
var parameterPolicyFactory = app.ApplicationServices
.GetRequiredService();
@@ -122,11 +120,21 @@ namespace Microsoft.AspNetCore.Builder
}
}
+ // Include all controllers with attribute routing and Razor pages
+ var defaultEndpointConventionBuilder = new DefaultEndpointConventionBuilder();
+ mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add((actionDescriptor) =>
+ {
+ return defaultEndpointConventionBuilder;
+ });
+
if (!app.Properties.TryGetValue(EndpointRoutingRegisteredKey, out _))
{
// Matching middleware has not been registered yet
// For back-compat register middleware so an endpoint is matched and then immediately used
- app.UseEndpointRouting();
+ app.UseEndpointRouting(routerBuilder =>
+ {
+ routerBuilder.DataSources.Add(mvcEndpointDataSource);
+ });
}
return app.UseEndpoint();
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs
index 161189b871..289a1a2f34 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs
@@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Routing.Patterns;
namespace Microsoft.AspNetCore.Builder
{
- internal class MvcEndpointInfo
+ internal class MvcEndpointInfo : DefaultEndpointConventionBuilder
{
public MvcEndpointInfo(
string name,
@@ -43,6 +43,7 @@ namespace Microsoft.AspNetCore.Builder
public string Name { get; }
public string Pattern { get; }
+ public Type ControllerType { get; set; }
// Non-inline defaults
public RouteValueDictionary Defaults { get; }
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointRouteBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointRouteBuilderExtensions.cs
new file mode 100644
index 0000000000..a3bd2d9951
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointRouteBuilderExtensions.cs
@@ -0,0 +1,148 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Internal;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class MvcEndpointRouteBuilderExtensions
+ {
+ public static IEndpointConventionBuilder MapMvcControllers(
+ this IEndpointRouteBuilder routeBuilder)
+ {
+ return MapMvcControllers(routeBuilder);
+ }
+
+ public static IEndpointConventionBuilder MapMvcControllers(
+ this IEndpointRouteBuilder routeBuilder) where TController : ControllerBase
+ {
+ var mvcEndpointDataSource = routeBuilder.DataSources.OfType().FirstOrDefault();
+
+ if (mvcEndpointDataSource == null)
+ {
+ mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService();
+ routeBuilder.DataSources.Add(mvcEndpointDataSource);
+ }
+
+ var conventionBuilder = new DefaultEndpointConventionBuilder();
+
+ mvcEndpointDataSource.AttributeRoutingConventionResolvers.Add(actionDescriptor =>
+ {
+ if (actionDescriptor is ControllerActionDescriptor controllerActionDescriptor &&
+ typeof(TController).IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo))
+ {
+ return conventionBuilder;
+ }
+
+ return null;
+ });
+
+ return conventionBuilder;
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template)
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults: null);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template,
+ object defaults)
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults, constraints: null);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template,
+ object defaults,
+ object constraints)
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template,
+ object defaults,
+ object constraints,
+ object dataTokens)
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults, constraints, dataTokens);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template) where TController : ControllerBase
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults: null);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template,
+ object defaults) where TController : ControllerBase
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults, constraints: null);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template,
+ object defaults,
+ object constraints) where TController : ControllerBase
+ {
+ return MapMvcRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
+ }
+
+ public static IEndpointConventionBuilder MapMvcRoute(
+ this IEndpointRouteBuilder routeBuilder,
+ string name,
+ string template,
+ object defaults,
+ object constraints,
+ object dataTokens) where TController : ControllerBase
+ {
+ var mvcEndpointDataSource = routeBuilder.DataSources.OfType().FirstOrDefault();
+
+ if (mvcEndpointDataSource == null)
+ {
+ mvcEndpointDataSource = routeBuilder.ServiceProvider.GetRequiredService();
+ routeBuilder.DataSources.Add(mvcEndpointDataSource);
+ }
+
+ var endpointInfo = new MvcEndpointInfo(
+ name,
+ template,
+ new RouteValueDictionary(defaults),
+ new RouteValueDictionary(constraints),
+ new RouteValueDictionary(dataTokens),
+ routeBuilder.ServiceProvider.GetRequiredService());
+
+ endpointInfo.ControllerType = typeof(TController);
+
+ mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo);
+
+ return endpointInfo;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
index 95a4afb21b..d645322384 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs
@@ -270,8 +270,7 @@ namespace Microsoft.Extensions.DependencyInjection
//
// Endpoint Routing / Endpoints
//
- services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
+ services.TryAddSingleton();
services.TryAddSingleton();
//
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs
index df110bee25..8f4612672b 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs
@@ -10,6 +10,7 @@ using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
+using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
@@ -57,6 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_parameterPolicyFactory = parameterPolicyFactory;
ConventionalEndpointInfos = new List();
+ AttributeRoutingConventionResolvers = new List>();
// IMPORTANT: this needs to be the last thing we do in the constructor. Change notifications can happen immediately!
//
@@ -72,6 +74,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public List ConventionalEndpointInfos { get; }
+ public List> AttributeRoutingConventionResolvers { get; }
+
public override IReadOnlyList Endpoints
{
get
@@ -134,6 +138,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// - Home/Login
foreach (var endpointInfo in ConventionalEndpointInfos)
{
+ if (endpointInfo.ControllerType != null &&
+ endpointInfo.ControllerType != typeof(ControllerBase))
+ {
+ if (!ValidateControllerConstraint(action, endpointInfo))
+ {
+ // Action descriptor does not belong to a controller of the specified type
+ continue;
+ }
+ }
+
// An 'endpointInfo' is applicable if:
// 1. it has a parameter (or default value) for 'required' non-null route value
// 2. it does not have a parameter (or default value) for 'required' null route value
@@ -164,11 +178,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
endpointInfo.DataTokens,
endpointInfo.ParameterPolicies,
suppressLinkGeneration: false,
- suppressPathMatching: false);
+ suppressPathMatching: false,
+ endpointInfo.Conventions);
}
}
else
{
+ var conventionBuilder = ResolveActionConventionBuilder(action);
+ if (conventionBuilder == null)
+ {
+ // No convention builder for this action
+ // Do not create an endpoint for it
+ continue;
+ }
+
var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template);
CreateEndpoints(
@@ -183,7 +206,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataTokens: null,
allParameterPolicies: null,
action.AttributeRouteInfo.SuppressLinkGeneration,
- action.AttributeRouteInfo.SuppressPathMatching);
+ action.AttributeRouteInfo.SuppressPathMatching,
+ conventionBuilder.Conventions);
}
}
@@ -205,6 +229,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
+ private DefaultEndpointConventionBuilder ResolveActionConventionBuilder(ActionDescriptor action)
+ {
+ foreach (var filter in AttributeRoutingConventionResolvers)
+ {
+ var conventionBuilder = filter(action);
+ if (conventionBuilder != null)
+ {
+ return conventionBuilder;
+ }
+ }
+
+ return null;
+ }
+
+ private static bool ValidateControllerConstraint(ActionDescriptor action, MvcEndpointInfo endpointInfo)
+ {
+ if (action is ControllerActionDescriptor controllerActionDescriptor)
+ {
+ return endpointInfo.ControllerType.IsAssignableFrom(controllerActionDescriptor.ControllerTypeInfo);
+ }
+
+ return false;
+ }
+
// CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values
// Because of default values it is possible for a route pattern to resolve to multiple endpoints
private int CreateEndpoints(
@@ -219,7 +267,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
RouteValueDictionary dataTokens,
IDictionary> allParameterPolicies,
bool suppressLinkGeneration,
- bool suppressPathMatching)
+ bool suppressPathMatching,
+ List> conventions)
{
var newPathSegments = routePattern.PathSegments.ToList();
var hasLinkGenerationEndpoint = false;
@@ -264,7 +313,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
routeOrder++,
dataTokens,
suppressLinkGeneration,
- true);
+ true,
+ conventions);
endpoints.Add(ep);
hasLinkGenerationEndpoint = true;
@@ -282,7 +332,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
routeOrder++,
dataTokens,
suppressLinkGeneration,
- suppressPathMatching);
+ suppressPathMatching,
+ conventions);
endpoints.Add(subEndpoint);
}
@@ -299,7 +350,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
routeOrder++,
dataTokens,
suppressLinkGeneration,
- suppressPathMatching);
+ suppressPathMatching,
+ conventions);
endpoints.Add(finalEndpoint);
return routeOrder;
@@ -406,6 +458,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
}
+
}
private bool UseDefaultValuePlusRemainingSegmentsOptional(
@@ -536,7 +589,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
int order,
RouteValueDictionary dataTokens,
bool suppressLinkGeneration,
- bool suppressPathMatching)
+ bool suppressPathMatching,
+ List> conventions)
{
RequestDelegate requestDelegate = (context) =>
{
@@ -551,7 +605,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var defaults = new RouteValueDictionary(nonInlineDefaults);
EnsureRequiredValuesInDefaults(actionRouteValues, defaults, segments);
- var metadataCollection = BuildEndpointMetadata(
+ var model = new RouteEndpointModel(requestDelegate, RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments), order);
+
+ AddEndpointMetadata(
+ model.Metadata,
action,
routeName,
new RouteValueDictionary(actionRouteValues),
@@ -559,17 +616,23 @@ namespace Microsoft.AspNetCore.Mvc.Internal
suppressLinkGeneration,
suppressPathMatching);
- var endpoint = new RouteEndpoint(
- requestDelegate,
- RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments),
- order,
- metadataCollection,
- action.DisplayName);
+ model.DisplayName = action.DisplayName;
- return endpoint;
+ // REVIEW: When should conventions be run
+ // Metadata should have lower precedence that data source metadata
+ if (conventions != null)
+ {
+ foreach (var convention in conventions)
+ {
+ convention(model);
+ }
+ }
+
+ return (RouteEndpoint)model.Build();
}
- private static EndpointMetadataCollection BuildEndpointMetadata(
+ private static void AddEndpointMetadata(
+ IList