// 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 Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { /// /// Extension methods for to add MVC to the request execution pipeline. /// public static class MvcApplicationBuilderExtensions { // Property key set in routing package by UseEndpointRouting to indicate middleware is registered private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered"; /// /// Adds MVC to the request execution pipeline. /// /// The . /// A reference to this instance after the operation has completed. /// This method only supports attribute routing. To add conventional routes use /// . public static IApplicationBuilder UseMvc(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMvc(routes => { }); } /// /// Adds MVC to the request execution pipeline /// with a default route named 'default' and the following template: /// '{controller=Home}/{action=Index}/{id?}'. /// /// The . /// A reference to this instance after the operation has completed. public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } /// /// Adds MVC to the request execution pipeline. /// /// The . /// A callback to configure MVC routes. /// A reference to this instance after the operation has completed. public static IApplicationBuilder UseMvc( this IApplicationBuilder app, Action configureRoutes) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (configureRoutes == null) { throw new ArgumentNullException(nameof(configureRoutes)); } VerifyMvcIsRegistered(app); var options = app.ApplicationServices.GetRequiredService>(); if (options.Value.EnableEndpointRouting) { var mvcEndpointDataSource = app.ApplicationServices .GetRequiredService(); var parameterPolicyFactory = app.ApplicationServices .GetRequiredService(); var endpointRouteBuilder = new EndpointRouteBuilder(app); configureRoutes(endpointRouteBuilder); foreach (var router in endpointRouteBuilder.Routes) { // Only accept Microsoft.AspNetCore.Routing.Route when converting to endpoint // Sub-types could have additional customization that we can't knowingly convert if (router is Route route && router.GetType() == typeof(Route)) { var endpointInfo = new MvcEndpointInfo( route.Name, route.RouteTemplate, route.Defaults, route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value), route.DataTokens, parameterPolicyFactory); mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo); } else { throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Endpoint Routing."); } } // 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(routerBuilder => { routerBuilder.DataSources.Add(mvcEndpointDataSource); }); } return app.UseEndpoint(); } else { var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService(), }; configureRoutes(routes); routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); return app.UseRouter(routes.Build()); } } private class EndpointRouteBuilder : IRouteBuilder { public EndpointRouteBuilder(IApplicationBuilder applicationBuilder) { ApplicationBuilder = applicationBuilder; Routes = new List(); DefaultHandler = NullRouter.Instance; } public IApplicationBuilder ApplicationBuilder { get; } public IRouter DefaultHandler { get; set; } public IServiceProvider ServiceProvider { get { return ApplicationBuilder.ApplicationServices; } } public IList Routes { get; } public IRouter Build() { throw new NotSupportedException(); } } private static void VerifyMvcIsRegistered(IApplicationBuilder app) { // Verify if AddMvc was done before calling UseMvc // We use the MvcMarkerService to make sure if all the services were added. if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), "AddMvc", "ConfigureServices(...)")); } } } }