/// <summary> /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests /// for the specified pattern. /// </summary> /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param> /// <param name="pattern">The route pattern.</param> /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param> public static IEndpointRouteBuilder Map(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } if (string.IsNullOrEmpty(pattern)) { throw new ArgumentNullException(nameof(pattern)); } if (requestDelegate == null) { throw new ArgumentNullException(nameof(requestDelegate)); } // Route pattern should never include leading or trailing slashes var routePattern = pattern.TrimStart('/').TrimEnd('/').ToLower(); var builder = new RouteEndpointBuilder(requestDelegate, routePattern) { DisplayName = pattern, }; // Catch error and log when same endpoint is used. Endpoints must have unique pattern. key exists endpoints.DataSources.Add(routePattern, builder.Build()); return(endpoints); }
public void RequireAuthorization_PolicyCallback() { // Arrange var builder = new TestEndpointConventionBuilder(); var requirement = new TestRequirement(); // Act builder.RequireAuthorization(policyBuilder => policyBuilder.Requirements.Add(requirement)); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); Assert.Equal(2, endpointModel.Metadata.Count); var authMetadata = Assert.IsAssignableFrom <IAuthorizeData>(endpointModel.Metadata[0]); Assert.Null(authMetadata.Policy); var policy = Assert.IsAssignableFrom <AuthorizationPolicy>(endpointModel.Metadata[1]); Assert.Equal(1, policy.Requirements.Count); Assert.Equal(requirement, policy.Requirements[0]); }
/// <summary> /// Adds Easy.Endpoints routes to EndpointRouteBuilder /// </summary> /// <param name="endpoints">Microsoft.AspNetCore.Routing.IEndpointRouteBuilder to add Easy.Endpoints to</param> /// <returns>Returns Microsoft.AspNetCore.Builder.IEndpointConventionBuilder for endpoints</returns> public static IEndpointConventionBuilder MapEasyEndpoints( this IEndpointRouteBuilder endpoints) { var requestEndPoints = endpoints.ServiceProvider.GetRequiredService <IEndpointManifest>().ToArray(); var dataSource = endpoints.DataSources.OfType <EasyEndpointDataSource>().FirstOrDefault(); if (dataSource is null) { dataSource = new EasyEndpointDataSource(); endpoints.DataSources.Add(dataSource); } var results = new IEndpointConventionBuilder[requestEndPoints.Length]; var i = 0; foreach (var endPoint in requestEndPoints) { var builder = new RouteEndpointBuilder( BuildDelegate(endPoint), endPoint.Pattern, endPoint.Order) { DisplayName = endPoint.Name, }; foreach (var meta in endPoint.Meta) { builder.Metadata.Add(meta); } results[i] = dataSource.AddEndpointBuilder(builder); i++; } return(new GroupedEasyEndpointConventionBuilder(results)); }
public void RequireAuthorization_PolicyCallbackWithAuthorize() { // Arrange var builder = new TestEndpointConventionBuilder(); var authorize = new AuthorizeAttribute(); var requirement = new TestRequirement(); // Act builder.RequireAuthorization(policyBuilder => policyBuilder.Requirements.Add(requirement)); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); endpointModel.Metadata.Add(authorize); convention(endpointModel); // Confirm that we don't add another authorize if one already exists Assert.Equal(2, endpointModel.Metadata.Count); Assert.Equal(authorize, endpointModel.Metadata[0]); var policy = Assert.IsAssignableFrom <AuthorizationPolicy>(endpointModel.Metadata[1]); Assert.Equal(1, policy.Requirements.Count); Assert.Equal(requirement, policy.Requirements[0]); }
public MvcEndpointDataSource(IActionInvokerFactory actionInvokerFactory, IActionDescriptorProvider actionDiscriptorProvider, RoutePatternTransformer transformer) { _actionInvokerFactory = actionInvokerFactory; _endpoints = new Lazy <IReadOnlyList <Endpoint> >(Create()); List <Endpoint> Create() { var Descriptors = actionDiscriptorProvider.GetActionDescriptors; return(Descriptors.Select(CreateEndpoint).ToList()); } Endpoint CreateEndpoint(ActionDescriptor actionDiscriptor) { var routePattern = RoutePatternFactory.Parse(actionDiscriptor.RouteInfo.Template); var newRoutePattern = transformer.SubstituteRequiredValues(routePattern, new Dictionary <string, string> { ["controller"] = actionDiscriptor.ControllerName, ["action"] = actionDiscriptor.ActionName }); routePattern = routePattern ?? newRoutePattern; var endPointBuilder = new RouteEndpointBuilder(InvokeAsync, routePattern, actionDiscriptor.RouteInfo.Order ?? 0); endPointBuilder.Metadata.Add(actionDiscriptor); return(endPointBuilder.Build()); } }
protected override List <Endpoint> CreateEndpoints(IReadOnlyList <ActionDescriptor> actions, IReadOnlyList <Action <EndpointBuilder> > conventions) { var endpoints = new List <Endpoint>(); foreach (var action in actions) { var attributeInfo = action.AttributeRouteInfo; if (attributeInfo == null) //Conventional Routing { foreach (var route in _conventionalRoutes) { var pattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues); if (pattern != null) { RouteEndpointBuilder builder = new RouteEndpointBuilder(_requestDelegate, pattern, route.Order); builder.Metadata.Add(action); endpoints.Add(builder.Build()); } } } else //Attribute Routing { var original = RoutePatternFactory.Parse(attributeInfo.Template); var pattern = _routePatternTransformer.SubstituteRequiredValues(original, action.RouteValues); if (pattern != null) { RouteEndpointBuilder builder = new RouteEndpointBuilder(_requestDelegate, pattern, attributeInfo.Order); builder.Metadata.Add(action); endpoints.Add(builder.Build()); } } } return(endpoints); }
private List <Endpoint> BuildEndpoints() { List <Endpoint> endpoints = new List <Endpoint>(); foreach (var hubMethod in HubMethods) { var requiredValues = new { hub = hubMethod.Hub, method = hubMethod.Method }; var order = 1; foreach (var pattern in Patterns) { var resolvedPattern = _routePatternTransformer.SubstituteRequiredValues(pattern, requiredValues); if (resolvedPattern == null) { continue; } var endpointBuilder = new RouteEndpointBuilder( hubMethod.RequestDelegate, resolvedPattern, order++); endpointBuilder.DisplayName = $"{hubMethod.Hub}.{hubMethod.Method}"; foreach (var convention in _conventions) { convention(endpointBuilder); } endpoints.Add(endpointBuilder.Build()); } } return(endpoints); }
/// <summary> /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests /// for the specified pattern. /// </summary> /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param> /// <param name="pattern">The route pattern.</param> /// <param name="action">The delegate executed when the endpoint is matched.</param> /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns> public static MinimalActionEndpointConventionBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate action) { if (endpoints is null) { throw new ArgumentNullException(nameof(endpoints)); } if (pattern is null) { throw new ArgumentNullException(nameof(pattern)); } if (action is null) { throw new ArgumentNullException(nameof(action)); } const int defaultOrder = 0; var builder = new RouteEndpointBuilder( RequestDelegateFactory.Create(action, endpoints.ServiceProvider), pattern, defaultOrder) { DisplayName = pattern.RawText ?? pattern.DebuggerToString(), }; // REVIEW: Should we add an IActionMethodMetadata with just MethodInfo on it so we are // explicit about the MethodInfo representing the "action" and not the RequestDelegate? // Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint. builder.Metadata.Add(action.Method); // Add delegate attributes as metadata var attributes = action.Method.GetCustomAttributes(); // This can be null if the delegate is a dynamic method or compiled from an expression tree if (attributes is not null) { foreach (var attribute in attributes) { builder.Metadata.Add(attribute); } } var dataSource = endpoints.DataSources.OfType <ModelEndpointDataSource>().FirstOrDefault(); if (dataSource is null) { dataSource = new ModelEndpointDataSource(); endpoints.DataSources.Add(dataSource); } return(new MinimalActionEndpointConventionBuilder(dataSource.AddEndpointBuilder(builder))); }
private static RouteEndpointBuilder CreateEndpointBuilder(ProxyRoute proxyRoute, Cluster cluster) { var endpointBuilder = new RouteEndpointBuilder(context => Task.CompletedTask, RoutePatternFactory.Parse(""), 0); var routeConfig = new RouteConfig( proxyRoute, new ClusterInfo("cluster-1") { Config = new ClusterConfig(cluster, default) },
/// <summary> /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests /// for the specified pattern. /// </summary> /// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param> /// <param name="pattern">The route pattern.</param> /// <param name="displayName">The display name for the endpoint.</param> /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param> /// <param name="metadata">Metadata that is added to the endpoint.</param> /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns> public static IEndpointConventionBuilder Map( this IEndpointRouteBuilder builder, RoutePattern pattern, string displayName, RequestDelegate requestDelegate, params object[] metadata) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (pattern == null) { throw new ArgumentNullException(nameof(pattern)); } if (requestDelegate == null) { throw new ArgumentNullException(nameof(requestDelegate)); } const int defaultOrder = 0; var routeEndpointBuilder = new RouteEndpointBuilder( requestDelegate, pattern, defaultOrder) { DisplayName = displayName }; // Add delegate attributes as metadata foreach (var attribute in requestDelegate.Method.GetCustomAttributes()) { routeEndpointBuilder.Metadata.Add(attribute); } if (metadata != null) { foreach (var item in metadata) { routeEndpointBuilder.Metadata.Add(item); } } var modelEndpointDataSource = builder.DataSources.OfType <ModelEndpointDataSource>().FirstOrDefault(); if (modelEndpointDataSource == null) { modelEndpointDataSource = new ModelEndpointDataSource(); builder.DataSources.Add(modelEndpointDataSource); } return(modelEndpointDataSource.AddEndpointBuilder(routeEndpointBuilder)); }
private static Endpoint CreateAspNetCoreEndpoint(RouteConfig routeConfig) { var endpointBuilder = new RouteEndpointBuilder( requestDelegate: httpContext => Task.CompletedTask, routePattern: RoutePatternFactory.Parse("/"), order: 0); endpointBuilder.Metadata.Add(routeConfig); return(endpointBuilder.Build()); }
/// <summary> /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests /// for the specified pattern. /// </summary> /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param> /// <param name="pattern">The route pattern.</param> /// <param name="action">The delegate executed when the endpoint is matched.</param> /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns> public static MinimalActionEndpointConventionBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate action) { if (endpoints is null) { throw new ArgumentNullException(nameof(endpoints)); } if (pattern is null) { throw new ArgumentNullException(nameof(pattern)); } if (action is null) { throw new ArgumentNullException(nameof(action)); } const int defaultOrder = 0; var builder = new RouteEndpointBuilder( RequestDelegateFactory.Create(action), pattern, defaultOrder) { DisplayName = pattern.RawText ?? pattern.DebuggerToString(), }; // Add delegate attributes as metadata var attributes = action.Method.GetCustomAttributes(); // This can be null if the delegate is a dynamic method or compiled from an expression tree if (attributes is not null) { foreach (var attribute in attributes) { builder.Metadata.Add(attribute); } } var dataSource = endpoints.DataSources.OfType <ModelEndpointDataSource>().FirstOrDefault(); if (dataSource is null) { dataSource = new ModelEndpointDataSource(); endpoints.DataSources.Add(dataSource); } return(new MinimalActionEndpointConventionBuilder(dataSource.AddEndpointBuilder(builder))); }
/// <summary> /// 生成终结点规则 /// </summary> /// <param name="template"></param> /// <param name="areaName"></param> /// <param name="defaultsDictionary"></param> /// <param name="constraintsDictionary"></param> /// <param name="dataTokens"></param> public void MapRoute(string template, string areaName, RouteValueDictionary defaultsDictionary, RouteValueDictionary constraintsDictionary, object dataTokens) { foreach (var keys in _AshxBuilder.RouteDefaults) { if (true) { string DisplayName = $"Ashx:{keys.Value.AshxType.FullName}.default"; if (EndpointExists(DisplayName)) { continue; } var _routePattern1 = RoutePatternFactory.Parse(template);//"{controller=" + keys.Key + "}/{action}/{id?}" _routePattern1.Defaults.TryGetValue("controller", out object controller); _routePattern1.Defaults.TryGetValue("action", out object action); if (keys.Key.EqualsNotCase(controller.ToString()) && keys.Value.Ashxes.TryGetValue(action.ToString(), out var ashx)) { string _template = template; foreach (var item in _routePattern1.Parameters) { _template = _template.Replace($"{{{item.Name}{(item.IsOptional ? "?" : $"={item.Default}")}}}", "").Replace("//", "/"); } bool iscontroller = false; A: if (iscontroller) { DisplayName += $".{controller}"; _template = $"{_template}{controller}"; } //string _template = $"{_routePattern1.PathSegments}"; //template.Replace($"{{controller={controller}}}/", controller).Replace($"{{action={action}}}", ""); var routePattern = RoutePatternFactory.Parse(_template, defaultsDictionary, null, null); //RoutePatternFactory.Pattern(null, parameterPolicies: null, _routePattern1.PathSegments);//_defaultsDictionary var routeEndpointBuilder = new RouteEndpointBuilder(requestDelegate: InitialLoadAsync, routePattern: routePattern, order: 0) { DisplayName = DisplayName }; routeEndpointBuilder.Metadata.Add(keys.Value); routeEndpointBuilder.Metadata.Add(ashx); routeEndpointBuilder.Metadata.Add(new RouteValueDictionary(dataTokens)); routeEndpointBuilder.Metadata.Add(new { areaName }); _endpointBuilders.Add(routeEndpointBuilder); if (!iscontroller) { iscontroller = true; goto A; } }
private List <Endpoint> BuildEndpoints() { var endpoints = new List <Endpoint>(); if (!_commandTypes.Any()) { return(endpoints); } var dispatcherFactory = _serviceProvider.GetRequiredService <ICommandDispatcherFactory>(); // Step 1: Locate all of the commands foreach (var cmdType in _commandTypes) { var routeAttribute = cmdType.GetCustomAttribute <CommandRouteAttribute>(); if (routeAttribute == null) { _logger.LogTrace($"No routing attributes found for command: {cmdType.Name}"); continue; } var authorizeAttribute = cmdType.GetCustomAttribute <AuthorizeAttribute>(); var commandDispatcher = dispatcherFactory.GetDispatcher(cmdType); var endpointBuilder = new RouteEndpointBuilder( context => commandDispatcher.Dispatch(context), RoutePatternFactory.Parse(routeAttribute.Template), routeAttribute.Order) { DisplayName = $"Command: {cmdType.Name}" }; endpointBuilder.Metadata.Add(new HttpMethodMetadata(new[] { routeAttribute.Method })); if (authorizeAttribute != null) { endpointBuilder.Metadata.Add(authorizeAttribute); } foreach (var convention in _conventions) { convention(endpointBuilder); } endpoints.Add(endpointBuilder.Build()); } return(endpoints); }
private static RouteEndpointBuilder CreateEndpointBuilder(RouteConfig routeConfig, ClusterConfig cluster) { var endpointBuilder = new RouteEndpointBuilder(context => Task.CompletedTask, RoutePatternFactory.Parse(""), 0); var routeModel = new RouteModel( routeConfig, new ClusterState("cluster-1") { Model = new ClusterModel(cluster, new HttpMessageInvoker(new HttpClientHandler())) }, HttpTransformer.Default); endpointBuilder.Metadata.Add(routeModel); return(endpointBuilder); }
public void AllowAnonymous_Default() { // Arrange var builder = new TestEndpointConventionBuilder(); // Act builder.AllowAnonymous(); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); Assert.IsAssignableFrom <IAllowAnonymous>(Assert.Single(endpointModel.Metadata)); }
public void RequireAuthorization_PolicyName() { // Arrange var builder = new TestEndpointConventionBuilder(); // Act builder.RequireAuthorization("policy"); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); Assert.Equal("policy", Assert.IsAssignableFrom <IAuthorizeData>(Assert.Single(endpointModel.Metadata)).Policy); }
public void RequireAuthorization_IAuthorizeData() { // Arrange var builder = new TestEndpointConventionBuilder(); var metadata = new AuthorizeAttribute(); // Act builder.RequireAuthorization(metadata); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); Assert.Equal(metadata, Assert.Single(endpointModel.Metadata)); }
private static OpenApiOperation?GetOperationForEndpoint(RouteEndpointBuilder routeEndpointBuilder) { var pattern = routeEndpointBuilder.RoutePattern; var metadata = new EndpointMetadataCollection(routeEndpointBuilder.Metadata); var methodInfo = metadata.OfType <MethodInfo>().SingleOrDefault(); var serviceProvider = routeEndpointBuilder.ServiceProvider; if (methodInfo == null || serviceProvider == null) { return(null); } var hostEnvironment = serviceProvider.GetService <IHostEnvironment>(); var serviceProviderIsService = serviceProvider.GetService <IServiceProviderIsService>(); var generator = new OpenApiGenerator(hostEnvironment, serviceProviderIsService); return(generator.GetOpenApiOperation(methodInfo, metadata, pattern)); }
private RouteEndpoint CreateEndpoint( ActionDescriptor action, RoutePattern routePattern, string routeName, int order, RouteValueDictionary dataTokens, bool suppressLinkGeneration, bool suppressPathMatching, List <Action <EndpointBuilder> > conventions) { RequestDelegate requestDelegate = (context) => { var routeData = context.GetRouteData(); var actionContext = new ActionContext(context, routeData, action); var invoker = _invokerFactory.CreateInvoker(actionContext); return(invoker.InvokeAsync()); }; var endpointBuilder = new RouteEndpointBuilder(requestDelegate, routePattern, order); AddEndpointMetadata( endpointBuilder.Metadata, action, routeName, dataTokens, suppressLinkGeneration, suppressPathMatching); endpointBuilder.DisplayName = action.DisplayName; // REVIEW: When should conventions be run // Metadata should have lower precedence that data source metadata if (conventions != null) { foreach (var convention in conventions) { convention(endpointBuilder); } } return((RouteEndpoint)endpointBuilder.Build()); }
public void RequireAuthorization_IAuthorizeData_Empty() { // Arrange var builder = new TestEndpointConventionBuilder(); // Act builder.RequireAuthorization(Array.Empty <IAuthorizeData>()); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); var authMetadata = Assert.IsAssignableFrom <IAuthorizeData>(Assert.Single(endpointModel.Metadata)); Assert.Null(authMetadata.Policy); }
public void RequireHost_HostNames() { // Arrange var builder = new TestEndpointConventionBuilder(); // Act builder.RequireHost("contoso.com:8080"); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); var hostMetadata = Assert.IsType <HostAttribute>(Assert.Single(endpointModel.Metadata)); Assert.Equal("contoso.com:8080", hostMetadata.Hosts.Single()); }
private void UpdateEndpoints() { lock (_locker) { var endpoints = new List <HttpEndpoint>(); var order = 0; foreach (var endpoint in _idsendpoints) { RequestDelegate handler = context => HandleEndpoint(context, endpoint); var pattern = RoutePatternFactory.Parse(endpoint.Path.Value); var builder = new RouteEndpointBuilder(handler, pattern, ++order); _conventions.ForEach(a => a.Invoke(builder)); builder.DisplayName = endpoint.Name; endpoints.Add(builder.Build()); } _endpoints = endpoints; } }
public void Build_AllValuesSet_EndpointCreated() { const int defaultOrder = 0; var metadata = new object(); RequestDelegate requestDelegate = (d) => null; var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder) { DisplayName = "Display name!", Metadata = { metadata } }; var endpoint = Assert.IsType <RouteEndpoint>(builder.Build()); Assert.Equal("Display name!", endpoint.DisplayName); Assert.Equal(defaultOrder, endpoint.Order); Assert.Equal(requestDelegate, endpoint.RequestDelegate); Assert.Equal("/", endpoint.RoutePattern.RawText); Assert.Equal(metadata, Assert.Single(endpoint.Metadata)); }
private void Initialize() { int i = 0; _endpoints = new List <Endpoint>(); foreach (var item in _options.Value.SwaggerGeneratorOptions.SwaggerDocs) { var executor = new SwaggerExecutor(item.Value, _asV2, _options.Value.HtmlTemplate); var builder = new RouteEndpointBuilder( requestDelegate: _asHtml ? (RequestDelegate)executor.InvokeAsHtml : executor.Invoke, routePattern: RoutePatternFactory.Parse(_routeTemplate.Replace("{documentName}", item.Key)), order: ++i); builder.DisplayName = $"Swagger.Document.{item.Key} ({item.Value.Title})"; foreach (var convention in _conventions) { convention.Invoke(builder); } _endpoints.Add(builder.Build()); } }
public void RequireAuthorization_Policy() { // Arrange var builder = new TestEndpointConventionBuilder(); var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); // Act builder.RequireAuthorization(policy); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); convention(endpointModel); Assert.Equal(2, endpointModel.Metadata.Count); var authMetadata = Assert.IsAssignableFrom <IAuthorizeData>(endpointModel.Metadata[0]); Assert.Null(authMetadata.Policy); Assert.Equal(policy, endpointModel.Metadata[1]); }
public void RequireAuthorization_PolicyWithAuthorize() { // Arrange var builder = new TestEndpointConventionBuilder(); var policy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); var authorize = new AuthorizeAttribute(); // Act builder.RequireAuthorization(policy); // Assert var convention = Assert.Single(builder.Conventions); var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0); endpointModel.Metadata.Add(authorize); convention(endpointModel); // Confirm that we don't add another authorize if one already exists Assert.Equal(2, endpointModel.Metadata.Count); Assert.Equal(authorize, endpointModel.Metadata[0]); Assert.Equal(policy, endpointModel.Metadata[1]); }
/// <summary> /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests /// for the specified pattern. /// </summary> /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param> /// <param name="pattern">The route pattern.</param> /// <param name="handler">The delegate executed when the endpoint is matched.</param> /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns> public static DelegateEndpointConventionBuilder Map( this IEndpointRouteBuilder endpoints, RoutePattern pattern, Delegate handler) { if (endpoints is null) { throw new ArgumentNullException(nameof(endpoints)); } if (pattern is null) { throw new ArgumentNullException(nameof(pattern)); } if (handler is null) { throw new ArgumentNullException(nameof(handler)); } const int defaultOrder = 0; var routeParams = new List <string>(pattern.Parameters.Count); foreach (var part in pattern.Parameters) { routeParams.Add(part.Name); } var routeHandlerOptions = endpoints.ServiceProvider?.GetService <IOptions <RouteHandlerOptions> >(); var options = new RequestDelegateFactoryOptions { ServiceProvider = endpoints.ServiceProvider, RouteParameterNames = routeParams, ThrowOnBadRequest = routeHandlerOptions?.Value.ThrowOnBadRequest ?? false, }; var requestDelegateResult = RequestDelegateFactory.Create(handler, options); var builder = new RouteEndpointBuilder( requestDelegateResult.RequestDelegate, pattern, defaultOrder) { DisplayName = pattern.RawText ?? pattern.DebuggerToString(), }; // REVIEW: Should we add an IActionMethodMetadata with just MethodInfo on it so we are // explicit about the MethodInfo representing the "handler" and not the RequestDelegate? // Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint. builder.Metadata.Add(handler.Method); // Methods defined in a top-level program are generated as statics so the delegate // target will be null. Inline lambdas are compiler generated method so they can // be filtered that way. if (GeneratedNameParser.TryParseLocalFunctionName(handler.Method.Name, out var endpointName) || !TypeHelper.IsCompilerGeneratedMethod(handler.Method)) { endpointName ??= handler.Method.Name; builder.Metadata.Add(new EndpointNameMetadata(endpointName)); builder.Metadata.Add(new RouteNameMetadata(endpointName)); builder.DisplayName = $"{builder.DisplayName} => {endpointName}"; } // Add delegate attributes as metadata var attributes = handler.Method.GetCustomAttributes(); // Add add request delegate metadata foreach (var metadata in requestDelegateResult.EndpointMetadata) { builder.Metadata.Add(metadata); } // This can be null if the delegate is a dynamic method or compiled from an expression tree if (attributes is not null) { foreach (var attribute in attributes) { builder.Metadata.Add(attribute); } } var dataSource = endpoints.DataSources.OfType <ModelEndpointDataSource>().FirstOrDefault(); if (dataSource is null) { dataSource = new ModelEndpointDataSource(); endpoints.DataSources.Add(dataSource); } return(new DelegateEndpointConventionBuilder(dataSource.AddEndpointBuilder(builder))); }
public void AddEndpoints( List <Endpoint> endpoints, HashSet <string> routeNames, ActionDescriptor action, IReadOnlyList <ConventionalRouteEntry> routes, IReadOnlyList <Action <EndpointBuilder> > conventions, bool createInertEndpoints) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } if (routeNames == null) { throw new ArgumentNullException(nameof(routeNames)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } if (routes == null) { throw new ArgumentNullException(nameof(routes)); } if (conventions == null) { throw new ArgumentNullException(nameof(conventions)); } if (createInertEndpoints) { var builder = new InertEndpointBuilder() { DisplayName = action.DisplayName, RequestDelegate = _requestDelegate, }; AddActionDataToBuilder( builder, routeNames, action, routeName: null, dataTokens: null, suppressLinkGeneration: false, suppressPathMatching: false, conventions, Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(builder.Build()); } if (action.AttributeRouteInfo?.Template == null) { // Check each of the conventional patterns to see if the action would be reachable. // If the action and pattern are compatible then create an endpoint with action // route values on the pattern. foreach (var route in routes) { // A route 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 var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues); if (updatedRoutePattern == null) { continue; } var requestDelegate = CreateRequestDelegate(action, route.DataTokens) ?? _requestDelegate; // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route // to handle link generation. var builder = new RouteEndpointBuilder(requestDelegate, updatedRoutePattern, route.Order) { DisplayName = action.DisplayName, }; AddActionDataToBuilder( builder, routeNames, action, route.RouteName, route.DataTokens, suppressLinkGeneration: true, suppressPathMatching: false, conventions, route.Conventions); endpoints.Add(builder.Build()); } } else { var requestDelegate = CreateRequestDelegate(action) ?? _requestDelegate; var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template); // Modify the route and required values to ensure required values can be successfully subsituted. // Subsitituting required values into an attribute route pattern should always succeed. var(resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern); var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues); if (updatedRoutePattern == null) { // This kind of thing can happen when a route pattern uses a *reserved* route value such as `action`. // See: https://github.com/dotnet/aspnetcore/issues/14789 var formattedRouteKeys = string.Join(", ", resolvedRouteValues.Keys.Select(k => $"'{k}'")); throw new InvalidOperationException( $"Failed to update the route pattern '{resolvedRoutePattern.RawText}' with required route values. " + $"This can occur when the route pattern contains parameters with reserved names such as: {formattedRouteKeys} " + $"and also uses route constraints such as '{{action:int}}'. " + "To fix this error, choose a different parameter name."); } var builder = new RouteEndpointBuilder(requestDelegate, updatedRoutePattern, action.AttributeRouteInfo.Order) { DisplayName = action.DisplayName, }; AddActionDataToBuilder( builder, routeNames, action, action.AttributeRouteInfo.Name, dataTokens: null, action.AttributeRouteInfo.SuppressLinkGeneration, action.AttributeRouteInfo.SuppressPathMatching, conventions, perRouteConventions: Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(builder.Build()); } }
public void AddConventionalLinkGenerationRoute( List <Endpoint> endpoints, HashSet <string> routeNames, HashSet <string> keys, ConventionalRouteEntry route, IReadOnlyList <Action <EndpointBuilder> > conventions) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } if (keys == null) { throw new ArgumentNullException(nameof(keys)); } if (conventions == null) { throw new ArgumentNullException(nameof(conventions)); } var requiredValues = new RouteValueDictionary(); foreach (var key in keys) { if (route.Pattern.GetParameter(key) != null) { // Parameter (allow any) requiredValues[key] = RoutePattern.RequiredValueAny; } else if (route.Pattern.Defaults.TryGetValue(key, out var value)) { requiredValues[key] = value; } else { requiredValues[key] = null; } } // We have to do some massaging of the pattern to try and get the // required values to be correct. var pattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, requiredValues); if (pattern == null) { // We don't expect this to happen, but we want to know if it does because it will help diagnose the bug. throw new InvalidOperationException("Failed to create a conventional route for pattern: " + route.Pattern); } var builder = new RouteEndpointBuilder(context => Task.CompletedTask, pattern, route.Order) { DisplayName = "Route: " + route.Pattern.RawText, Metadata = { new SuppressMatchingMetadata(), }, }; if (route.RouteName != null) { builder.Metadata.Add(new RouteNameMetadata(route.RouteName)); } // See comments on the other usage of EndpointNameMetadata in this class. // // The set of cases for a conventional route are much simpler. We don't need to check // for Endpoint Name already exising here because there's no way to add an attribute to // a conventional route. if (route.RouteName != null && routeNames.Add(route.RouteName)) { builder.Metadata.Add(new EndpointNameMetadata(route.RouteName)); } for (var i = 0; i < conventions.Count; i++) { conventions[i](builder); } for (var i = 0; i < route.Conventions.Count; i++) { route.Conventions[i](builder); } endpoints.Add((RouteEndpoint)builder.Build()); }