protected RouteEndpoint CreateEndpoint( string template, object defaults = null, object constraints = null, object requiredValues = null, int order = 0, string displayName = null, string routeName = null, params object[] metadata) { var endpointMetadata = new List <object>(metadata ?? Array.Empty <object>()); if (routeName != null) { endpointMetadata.Add(new RouteNameMetadata(routeName)); } return(new RouteEndpoint( (context) => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, constraints, requiredValues), order, new EndpointMetadataCollection(endpointMetadata), displayName)); }
public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndMultipleServices() { // Arrange var options = new RouteOptions(); options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultipleArguments)); var services = new ServiceCollection(); services.AddTransient <ITestService, TestService>(); var factory = GetParameterPolicyFactory(options, services); // Act var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20,-1)"); // Assert var constraint = Assert.IsType <CustomParameterPolicyWithMultipleArguments>(parameterPolicy); Assert.Equal(20, constraint.First); Assert.Equal(-1, constraint.Second); Assert.NotNull(constraint.TestService1); Assert.NotNull(constraint.TestService2); }
/// <summary> /// Adds a GraphQL endpoint to the endpoint configurations. /// </summary> /// <param name="endpointRouteBuilder"> /// The <see cref="IEndpointConventionBuilder"/>. /// </param> /// <param name="path"> /// The path to which the GraphQL endpoint shall be mapped. /// </param> /// <param name="schemaName"> /// The name of the schema that shall be used by this endpoint. /// </param> /// <returns> /// Returns the <see cref="IEndpointConventionBuilder"/> so that /// configuration can be chained. /// </returns> /// <exception cref="ArgumentNullException"> /// The <paramref name="endpointRouteBuilder" /> is <c>null</c>. /// </exception> public static GraphQLEndpointConventionBuilder MapGraphQL( this IEndpointRouteBuilder endpointRouteBuilder, PathString path, NameString schemaName = default) { if (endpointRouteBuilder is null) { throw new ArgumentNullException(nameof(endpointRouteBuilder)); } path = path.ToString().TrimEnd('/'); RoutePattern pattern = RoutePatternFactory.Parse(path + "/{**slug}"); IApplicationBuilder requestPipeline = endpointRouteBuilder.CreateApplicationBuilder(); NameString schemaNameOrDefault = schemaName.HasValue ? schemaName : Schema.DefaultName; IFileProvider fileProvider = CreateFileProvider(); requestPipeline .UseMiddleware <WebSocketSubscriptionMiddleware>(schemaNameOrDefault) .UseMiddleware <HttpPostMiddleware>(schemaNameOrDefault) .UseMiddleware <HttpGetSchemaMiddleware>(schemaNameOrDefault) .UseMiddleware <ToolDefaultFileMiddleware>(fileProvider, path) .UseMiddleware <ToolOptionsFileMiddleware>(schemaNameOrDefault, path) .UseMiddleware <ToolStaticFileMiddleware>(fileProvider, path) .UseMiddleware <HttpGetMiddleware>(schemaNameOrDefault) .Use(next => context => { context.Response.StatusCode = 404; return(Task.CompletedTask); }); return(new GraphQLEndpointConventionBuilder( endpointRouteBuilder .Map(pattern, requestPipeline.Build()) .WithDisplayName("Hot Chocolate GraphQL Pipeline"))); }
public void Matcher_Reinitializes_WhenDataSourceChanges() { // Arrange var dataSource = new DynamicEndpointDataSource(); var matcher = new DataSourceDependentMatcher(dataSource, TestMatcherBuilder.Create); var endpoint = new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse("a/b/c"), new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, "test"); // Act dataSource.AddEndpoint(endpoint); // Assert var inner = Assert.IsType <TestMatcher>(matcher.CurrentMatcher); Assert.Collection( inner.Endpoints, e => Assert.Same(endpoint, e)); }
public void ConfigureServices(IServiceCollection services) { services.AddRouting(); var endpointDataSource = new DefaultEndpointDataSource(new[] { new MatcherEndpoint( invoker: (next) => (httpContext) => { var response = httpContext.Response; var payloadLength = _helloWorldPayload.Length; response.StatusCode = 200; response.ContentType = "text/plain"; response.ContentLength = payloadLength; return(response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength)); }, routePattern: RoutePatternFactory.Parse("/plaintext"), order: 0, metadata: EndpointMetadataCollection.Empty, displayName: "Plaintext"), }); services.TryAddEnumerable(ServiceDescriptor.Singleton <EndpointDataSource>(endpointDataSource)); }
internal static RouteEndpoint CreateEndpoint( string template, object defaults = null, object constraints = null, int order = 0, string[] httpMethods = null, bool acceptCorsPreflight = false) { var metadata = new List <object>(); if (httpMethods != null) { metadata.Add(new HttpMethodMetadata(httpMethods ?? Array.Empty <string>(), acceptCorsPreflight)); } var displayName = "endpoint: " + template + " " + string.Join(", ", httpMethods ?? new[] { "(any)" }); return(new RouteEndpoint( TestConstants.EmptyRequestDelegate, RoutePatternFactory.Parse(template, defaults, constraints), order, new EndpointMetadataCollection(metadata), displayName)); }
public void SubstituteRequiredValues_AllowRequiredValueAnyForParameter() { // Arrange var template = "{controller=Home}/{action=Index}/{id?}"; var defaults = new { }; var policies = new { }; var original = RoutePatternFactory.Parse(template, defaults, policies); var requiredValues = new { controller = RoutePattern.RequiredValueAny, }; // Act var actual = Transformer.SubstituteRequiredValues(original, requiredValues); // Assert Assert.Collection( actual.Defaults.OrderBy(kvp => kvp.Key), kvp => Assert.Equal(new KeyValuePair <string, object>("action", "Index"), kvp), kvp => Assert.Equal(new KeyValuePair <string, object>("controller", "Home"), kvp)); // default is preserved Assert.Collection( actual.RequiredValues.OrderBy(kvp => kvp.Key), kvp => Assert.Equal(new KeyValuePair <string, object>("controller", RoutePattern.RequiredValueAny), kvp)); }
public static MatcherEndpoint CreateMatcherEndpoint( string template, object defaults = null, object constraints = null, object requiredValues = null, int order = 0, string displayName = null, params object[] metadata) { var metadataCollection = EndpointMetadataCollection.Empty; if (metadata != null) { metadataCollection = new EndpointMetadataCollection(metadata); } return(new MatcherEndpoint( MatcherEndpoint.EmptyInvoker, RoutePatternFactory.Parse(template, defaults, constraints), new RouteValueDictionary(requiredValues), order, metadataCollection, displayName)); }
public void Matcher_Reinitializes_WhenDataSourceChanges() { // Arrange var dataSource = new DynamicEndpointDataSource(); var lifetime = new DataSourceDependentMatcher.Lifetime(); var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create); var endpoint = new RouteEndpoint( TestConstants.EmptyRequestDelegate, RoutePatternFactory.Parse("a/b/c"), 0, EndpointMetadataCollection.Empty, "test"); // Act dataSource.AddEndpoint(endpoint); // Assert var inner = Assert.IsType <TestMatcher>(matcher.CurrentMatcher); Assert.Collection( inner.Endpoints, e => Assert.Same(endpoint, e)); }
public static void MapMediatR(this IEndpointRouteBuilder endpointsBuilder) { var mediator = endpointsBuilder.ServiceProvider.GetService <IMediator>(); if (mediator == null) { throw new InvalidOperationException($"IMediator has not added to IServiceCollection. You can add it with services.AddMediatR(...);"); } var mediatorEndpointCollections = endpointsBuilder.ServiceProvider.GetService <MediatorEndpointCollections>(); foreach (var endpoint in mediatorEndpointCollections.Endpoints) { var routePattern = RoutePatternFactory.Parse(endpoint.Uri); var builder = endpointsBuilder.Map(routePattern, MediatorRequestDelegate); builder.WithDisplayName(endpoint.RequestType.Name); for (var i = 0; i < endpoint.Metadata.Count; i++) { builder.WithMetadata(endpoint.Metadata[i]); } } }
public void Matcher_IgnoresUpdate_WhenDisposed() { // Arrange var dataSource = new DynamicEndpointDataSource(); var lifetime = new DataSourceDependentMatcher.Lifetime(); var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create); var endpoint = new RouteEndpoint( TestConstants.EmptyRequestDelegate, RoutePatternFactory.Parse("a/b/c"), 0, EndpointMetadataCollection.Empty, "test"); lifetime.Dispose(); // Act dataSource.AddEndpoint(endpoint); // Assert var inner = Assert.IsType <TestMatcher>(matcher.CurrentMatcher); Assert.Empty(inner.Endpoints); }
public void AddEndpoints( List <Endpoint> endpoints, HashSet <string> routeNames, ActionDescriptor action, IReadOnlyList <ConventionalRouteEntry> routes, IReadOnlyList <Action <EndpointBuilder> > conventions) { 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 (action.AttributeRouteInfo == 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; } // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route // to handle link generation. var builder = CreateEndpoint( routeNames, action, updatedRoutePattern, route.RouteName, route.Order, route.DataTokens, suppressLinkGeneration: true, suppressPathMatching: false, conventions, route.Conventions); endpoints.Add(builder); } } else { 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) { throw new InvalidOperationException("Failed to update route pattern with required values."); } var endpoint = CreateEndpoint( routeNames, action, updatedRoutePattern, action.AttributeRouteInfo.Name, action.AttributeRouteInfo.Order, dataTokens: null, action.AttributeRouteInfo.SuppressLinkGeneration, action.AttributeRouteInfo.SuppressPathMatching, conventions, perRouteConventions: Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(endpoint); } }
public LinkGeneratorIntegrationTest() { var endpoints = new List <Endpoint>() { // Attribute routed endpoint 1 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "api/Pets/{id}", defaults: new { controller = "Pets", action = "GetById", }, parameterPolicies: null, requiredValues: new { controller = "Pets", action = "GetById", area = (string)null, page = (string)null, }), order: 0), // Attribute routed endpoint 2 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "api/Pets", defaults: new { controller = "Pets", action = "GetAll", }, parameterPolicies: null, requiredValues: new { controller = "Pets", action = "GetAll", area = (string)null, page = (string)null, }), order: 0), // Attribute routed endpoint 2 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "api/Pets/{id}", defaults: new { controller = "Pets", action = "Update", }, parameterPolicies: null, requiredValues: new { controller = "Pets", action = "Update", area = (string)null, page = (string)null, }), order: 0), // Attribute routed endpoint 4 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "api/Inventory/{searchTerm}/{page}", defaults: new { controller = "Inventory", action = "Search", }, parameterPolicies: null, requiredValues: new { controller = "Inventory", action = "Search", area = (string)null, page = (string)null, }), order: 0), // Conventional routed endpoint 1 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "{controller=Home}/{action=Index}/{id?}", defaults: null, parameterPolicies: null, requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null, }), order: 2000, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), // Conventional routed endpoint 2 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "{controller=Home}/{action=Index}/{id?}", defaults: null, parameterPolicies: null, requiredValues: new { controller = "Home", action = "About", area = (string)null, page = (string)null, }), order: 2000, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), // Conventional routed endpoint 3 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "{controller=Home}/{action=Index}/{id?}", defaults: null, parameterPolicies: null, requiredValues: new { controller = "Store", action = "Browse", area = (string)null, page = (string)null, }), order: 2000, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), // Conventional routed link generation route 1 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "{controller=Home}/{action=Index}/{id?}", defaults: null, parameterPolicies: null, requiredValues: new { controller = RoutePattern.RequiredValueAny, action = RoutePattern.RequiredValueAny, area = (string)null, page = (string)null, }), order: 2000, metadata: new object[] { new SuppressMatchingMetadata(), }), // Conventional routed endpoint 4 (with area) EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Admin/{controller=Home}/{action=Index}/{id?}", defaults: new { area = "Admin", }, parameterPolicies: new { controller = "Admin", }, requiredValues: new { area = "Admin", controller = "Users", action = "Add", page = (string)null, }), order: 1000, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), // Conventional routed endpoint 5 (with area) EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Admin/{controller=Home}/{action=Index}/{id?}", defaults: new { area = "Admin", }, parameterPolicies: new { controller = "Admin", }, requiredValues: new { area = "Admin", controller = "Users", action = "Remove", page = (string)null, }), order: 1000, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), // Conventional routed link generation route 2 EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Admin/{controller=Home}/{action=Index}/{id?}", defaults: new { area = "Admin", }, parameterPolicies: new { area = "Admin", }, requiredValues: new { controller = RoutePattern.RequiredValueAny, action = RoutePattern.RequiredValueAny, area = "Admin", page = (string)null, }), order: 1000, metadata: new object[] { new SuppressMatchingMetadata(), }), // Conventional routed link generation route 3 - this doesn't match any actions. EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "api/{controller}/{id?}", defaults: new { }, parameterPolicies: new { }, requiredValues: new { controller = RoutePattern.RequiredValueAny, action = (string)null, area = (string)null, page = (string)null, }), order: 3000, metadata: new object[] { new SuppressMatchingMetadata(), new RouteNameMetadata("custom"), }), // Conventional routed link generation route 3 - this doesn't match any actions. EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "api/Foo/{custom2}", defaults: new { }, parameterPolicies: new { }, requiredValues: new { controller = (string)null, action = (string)null, area = (string)null, page = (string)null, }), order: 3000, metadata: new object[] { new SuppressMatchingMetadata(), new RouteNameMetadata("custom2"), }), // Razor Page 1 primary endpoint EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Pages", defaults: new { page = "/Pages/Index", }, parameterPolicies: null, requiredValues: new { controller = (string)null, action = (string)null, area = (string)null, page = "/Pages/Index", }), order: 0), // Razor Page 1 secondary endpoint EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Pages/Index", defaults: new { page = "/Pages/Index", }, parameterPolicies: null, requiredValues: new { controller = (string)null, action = (string)null, area = (string)null, page = "/Pages/Index", }), order: 0, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), // Razor Page 2 primary endpoint EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Pages/Help/{id?}", defaults: new { page = "/Pages/Help", }, parameterPolicies: null, requiredValues: new { controller = (string)null, action = (string)null, area = (string)null, page = "/Pages/Help", }), order: 0), // Razor Page 3 primary endpoint EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Pages/About/{id?}", defaults: new { page = "/Pages/About", }, parameterPolicies: null, requiredValues: new { controller = (string)null, action = (string)null, area = (string)null, page = "/Pages/About", }), order: 0), // Razor Page 4 with area primary endpoint EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Admin/Pages", defaults: new { page = "/Pages/Index", area = "Admin", }, parameterPolicies: null, requiredValues: new { controller = (string)null, action = (string)null, area = "Admin", page = "/Pages/Index", }), order: 0), // Razor Page 4 with area secondary endpoint EndpointFactory.CreateRouteEndpoint( RoutePatternFactory.Parse( "Admin/Pages/Index", defaults: new { page = "/Pages/Index", area = "Admin", }, parameterPolicies: null, requiredValues: new { controller = (string)null, action = (string)null, area = "Admin", page = "/Pages/Index", }), order: 0, metadata: new object[] { new SuppressLinkGenerationMetadata(), }), }; Endpoints = endpoints; LinkGenerator = CreateLinkGenerator(endpoints.ToArray()); }
private void UpdatePathSegments( int i, ActionDescriptor action, IDictionary <string, string> resolvedRequiredValues, RoutePattern routePattern, List <RoutePatternPathSegment> newPathSegments, ref IDictionary <string, IList <IParameterPolicy> > allParameterPolicies) { List <RoutePatternPart> segmentParts = null; // Initialize only as needed var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part is RoutePatternParameterPart parameterPart) { if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } if (allParameterPolicies == null) { allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } // Route value could be null if it is a "known" route value. // Do not use the null value to de-normalize the route pattern, // instead leave the parameter unchanged. // e.g. // RouteValues will contain a null "page" value if there are Razor pages // Skip replacing the {page} parameter if (parameterRouteValue != null) { if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) { // Check if the parameter has a transformer policy // Use the first transformer policy for (var k = 0; k < parameterPolicies.Count; k++) { if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) { parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); break; } } } segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); } } } } // A parameter part was replaced so replace segment with updated parts if (segmentParts != null) { newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } }
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 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) },
public RoutePattern ToRoutePattern() { var segments = Segments.Select(s => s.ToRoutePatternPathSegment()); return(RoutePatternFactory.Pattern(TemplateText, segments)); }
public static MethodModel Route(this MethodModel model, string template) { template = template ?? throw new ArgumentNullException(nameof(template)); model.RoutePattern = template == null ? null : RoutePatternFactory.Parse(template); return(model); }
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_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]); }
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 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]); }
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 RoutePatternPathSegment ToRoutePatternPathSegment() { var parts = Parts.Select(p => p.ToRoutePatternPart()); return(RoutePatternFactory.Segment(parts)); }
public Endpoint CreateEndpoint(RouteModel route, IReadOnlyList <Action <EndpointBuilder> > conventions) { var config = route.Config; var match = config.Match; // Catch-all pattern when no path was specified var pathPattern = string.IsNullOrEmpty(match.Path) ? "/{**catchall}" : match.Path; var endpointBuilder = new RouteEndpointBuilder( requestDelegate: _pipeline ?? throw new InvalidOperationException("The pipeline hasn't been provided yet."), routePattern: RoutePatternFactory.Parse(pathPattern), order: config.Order.GetValueOrDefault()) { DisplayName = config.RouteId }; endpointBuilder.Metadata.Add(route); if (match.Hosts != null && match.Hosts.Count != 0) { endpointBuilder.Metadata.Add(new HostAttribute(match.Hosts.ToArray())); } if (match.Headers != null && match.Headers.Count > 0) { var matchers = new List <HeaderMatcher>(match.Headers.Count); foreach (var header in match.Headers) { matchers.Add(new HeaderMatcher(header.Name, header.Values, header.Mode, header.IsCaseSensitive)); } endpointBuilder.Metadata.Add(new HeaderMetadata(matchers)); } if (match.QueryParameters != null && match.QueryParameters.Count > 0) { var matchers = new List <QueryParameterMatcher>(match.QueryParameters.Count); foreach (var queryparam in match.QueryParameters) { matchers.Add(new QueryParameterMatcher(queryparam.Name, queryparam.Values, queryparam.Mode, queryparam.IsCaseSensitive)); } endpointBuilder.Metadata.Add(new QueryParameterMetadata(matchers)); } bool acceptCorsPreflight; if (string.Equals(CorsConstants.Default, config.CorsPolicy, StringComparison.OrdinalIgnoreCase)) { endpointBuilder.Metadata.Add(_defaultCors); acceptCorsPreflight = true; } else if (string.Equals(CorsConstants.Disable, config.CorsPolicy, StringComparison.OrdinalIgnoreCase)) { endpointBuilder.Metadata.Add(_disableCors); acceptCorsPreflight = true; } else if (!string.IsNullOrEmpty(config.CorsPolicy)) { endpointBuilder.Metadata.Add(new EnableCorsAttribute(config.CorsPolicy)); acceptCorsPreflight = true; } else { acceptCorsPreflight = false; } if (match.Methods != null && match.Methods.Count > 0) { endpointBuilder.Metadata.Add(new HttpMethodMetadata(match.Methods, acceptCorsPreflight)); } if (string.Equals(AuthorizationConstants.Default, config.AuthorizationPolicy, StringComparison.OrdinalIgnoreCase)) { endpointBuilder.Metadata.Add(_defaultAuthorization); } else if (string.Equals(AuthorizationConstants.Anonymous, config.AuthorizationPolicy, StringComparison.OrdinalIgnoreCase)) { endpointBuilder.Metadata.Add(_allowAnonymous); } else if (!string.IsNullOrEmpty(config.AuthorizationPolicy)) { endpointBuilder.Metadata.Add(new AuthorizeAttribute(config.AuthorizationPolicy)); } for (var i = 0; i < conventions.Count; i++) { conventions[i](endpointBuilder); } return(endpointBuilder.Build()); }
public IEnumerable <Endpoint> FindEndpoints(RouteValuesAddress address) { if (address.AmbientValues == null || address.ExplicitValues == null) { return(Enumerable.Empty <Endpoint>()); } // Try to get the contained item first, then the container content item string contentItemId = address.ExplicitValues[_options.ContainedContentItemIdKey]?.ToString(); if (string.IsNullOrEmpty(contentItemId)) { contentItemId = address.ExplicitValues[_options.ContentItemIdKey]?.ToString(); } if (string.IsNullOrEmpty(contentItemId)) { return(Enumerable.Empty <Endpoint>()); } (var found, var autorouteEntry) = _entries.TryGetEntryByContentItemIdAsync(contentItemId).GetAwaiter().GetResult(); if (!found) { return(Enumerable.Empty <Endpoint>()); } if (Match(address.ExplicitValues)) { // Once we have the contained content item id value we no longer want it in the route values. address.ExplicitValues.Remove(_options.ContainedContentItemIdKey); var routeValues = new RouteValueDictionary(address.ExplicitValues); if (address.ExplicitValues.Count > _options.GlobalRouteValues.Count + 1) { foreach (var entry in address.ExplicitValues) { if (String.Equals(entry.Key, _options.ContentItemIdKey, StringComparison.OrdinalIgnoreCase)) { continue; } if (!_options.GlobalRouteValues.ContainsKey(entry.Key)) { routeValues.Remove(entry.Key); } } } var endpoint = new RouteEndpoint ( c => null, RoutePatternFactory.Parse(autorouteEntry.Path, routeValues, null), 0, null, null ); return(new[] { endpoint }); } return(Enumerable.Empty <Endpoint>()); }
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()); } }
private void UpdateEndpoints() { lock (_lock) { var endpoints = new List <Endpoint>(); StringBuilder patternStringBuilder = null; foreach (var action in _actions.ActionDescriptors.Items) { if (action.AttributeRouteInfo == null) { // In traditional conventional routing setup, the routes defined by a user have a static order // defined by how they are added into the list. We would like to maintain the same order when building // up the endpoints too. // // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. var conventionalRouteOrder = 1; // 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 the // area/controller/action parameter parts replaced with literals // // e.g. {controller}/{action} with HomeController.Index and HomeController.Login // would result in endpoints: // - Home/Index // - 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 var isApplicable = true; foreach (var routeKey in action.RouteValues.Keys) { if (!MatchRouteValue(action, endpointInfo, routeKey)) { isApplicable = false; break; } } if (!isApplicable) { continue; } conventionalRouteOrder = CreateEndpoints( endpoints, ref patternStringBuilder, action, conventionalRouteOrder, endpointInfo.ParsedPattern, endpointInfo.MergedDefaults, endpointInfo.Defaults, endpointInfo.Name, endpointInfo.DataTokens, endpointInfo.ParameterPolicies, suppressLinkGeneration: 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( endpoints, ref patternStringBuilder, action, action.AttributeRouteInfo.Order, attributeRoutePattern, attributeRoutePattern.Defaults, nonInlineDefaults: null, action.AttributeRouteInfo.Name, dataTokens: null, allParameterPolicies: null, action.AttributeRouteInfo.SuppressLinkGeneration, action.AttributeRouteInfo.SuppressPathMatching, conventionBuilder.Conventions); } } // See comments in DefaultActionDescriptorCollectionProvider. These steps are done // in a specific order to ensure callers always see a consistent state. // Step 1 - capture old token var oldCancellationTokenSource = _cancellationTokenSource; // Step 2 - update endpoints _endpoints = endpoints; // Step 3 - create new change token _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); // Step 4 - trigger old token oldCancellationTokenSource?.Cancel(); } }
public void RequireAuthorization_Default() { // Arrange var builder = new TestEndpointConventionBuilder(); // Act builder.RequireAuthorization(); // 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()); }