public async Task ApplyAsync_PrefersEndpointsWithODataRoutingMetadata() { // Arrange IEdmModel model = EdmCoreModel.Instance; IODataRoutingMetadata routingMetadata = new ODataRoutingMetadata("odata", model, new ODataPathTemplate()); Endpoint[] endpoints = new[] { CreateEndpoint("/", routingMetadata, new HttpMethodMetadata(new[] { "get" })), CreateEndpoint("/", routingMetadata, new HttpMethodMetadata(new[] { "post" })), CreateEndpoint("/", routingMetadata, new HttpMethodMetadata(new[] { "delete" })) }; CandidateSet candidateSet = CreateCandidateSet(endpoints); HttpContext httpContext = CreateHttpContext("POST"); HttpMethodMatcherPolicy httpMethodPolicy = new HttpMethodMatcherPolicy(); ODataRoutingMatcherPolicy policy = CreatePolicy(); // Act await httpMethodPolicy.ApplyAsync(httpContext, candidateSet); await policy.ApplyAsync(httpContext, candidateSet); // Assert Assert.False(candidateSet.IsValidCandidate(0)); Assert.True(candidateSet.IsValidCandidate(1)); Assert.False(candidateSet.IsValidCandidate(2)); }
/// <summary> /// Adds the OData selector model to the action. /// </summary> /// <param name="action">The given action model.</param> /// <param name="httpMethods">The supported http methods, if mulitple, using ',' to separate.</param> /// <param name="prefix">The prefix.</param> /// <param name="model">The Edm model.</param> /// <param name="path">The OData path template.</param> /// <param name="options">The route build options.</param> public static void AddSelector(this ActionModel action, string httpMethods, string prefix, IEdmModel model, ODataPathTemplate path, ODataRouteOptions options = null) { if (action == null) { throw Error.ArgumentNull(nameof(action)); } if (string.IsNullOrEmpty(httpMethods)) { throw Error.ArgumentNullOrEmpty(nameof(httpMethods)); } if (model == null) { throw Error.ArgumentNull(nameof(model)); } if (path == null) { throw Error.ArgumentNull(nameof(path)); } string[] methods = httpMethods.Split(','); foreach (string template in path.GetTemplates(options)) { // Be noted: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ApplicationModels/ActionAttributeRouteModel.cs#L74-L75 // No matter whether the action selector model is absolute route template, the controller's attribute will apply automatically // So, let's only create/update the action selector model SelectorModel selectorModel = action.Selectors.FirstOrDefault(s => s.AttributeRouteModel == null); if (selectorModel == null) { // Create a new selector model. selectorModel = CreateSelectorModel(action, methods); action.Selectors.Add(selectorModel); } else { // Update the existing non attribute routing selector model. selectorModel = UpdateSelectorModel(selectorModel, methods); } ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, path); selectorModel.EndpointMetadata.Add(odataMetadata); string templateStr = string.IsNullOrEmpty(prefix) ? template : $"{prefix}/{template}"; selectorModel.AttributeRouteModel = new AttributeRouteModel { // OData convention route template doesn't get combined with the route template applied to the controller. // Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller. Template = $"/{templateStr}", Name = templateStr // do we need this? }; // Check with .NET Team whether the "Endpoint name metadata" needed? selectorModel.EndpointMetadata.Add(new EndpointNameMetadata(Guid.NewGuid().ToString())); // Do we need this? } }
private static void AddHttpMethod(ODataRoutingMetadata metadata, string httpMethod) { if (string.IsNullOrEmpty(httpMethod)) { return; } string[] methods = httpMethod.Split(','); foreach (var method in methods) { metadata.HttpMethods.Add(method); } }
/// <summary> /// Adds the OData selector model to the action. /// </summary> /// <param name="action">The given action model.</param> /// <param name="httpMethod">The supported http methods, if mulitple, using ',' to separate.</param> /// <param name="prefix">The prefix.</param> /// <param name="model">The Edm model.</param> /// <param name="path">The OData path template.</param> /// <param name="options">The route build options.</param> public static void AddSelector(this ActionModel action, string httpMethod, string prefix, IEdmModel model, ODataPathTemplate path, ODataRouteOptions options = null) { if (action == null) { throw Error.ArgumentNull(nameof(action)); } if (model == null) { throw Error.ArgumentNull(nameof(model)); } if (path == null) { throw Error.ArgumentNull(nameof(path)); } foreach ((string template, string display) in path.GetTemplates(options)) { // We have to check the selector model on controller? SelectorModel selectorModel = action.Selectors.FirstOrDefault(s => s.AttributeRouteModel == null); if (selectorModel == null) { selectorModel = CreateSelectorModel(action.Attributes); action.Selectors.Add(selectorModel); } ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, path) { TemplateDisplayName = string.IsNullOrEmpty(prefix) ? display : $"{prefix}/{display}" }; AddHttpMethod(odataMetadata, httpMethod); selectorModel.EndpointMetadata.Add(odataMetadata); string templateStr = string.IsNullOrEmpty(prefix) ? template : $"{prefix}/{template}"; // OData convention route template doesn't get combined with the route template applied to the controller. // Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller. templateStr = "/" + templateStr; selectorModel.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(templateStr) { Name = templateStr }); // Check with .NET Team whether the "Endpoint name metadata" selectorModel.EndpointMetadata.Add(new EndpointNameMetadata(Guid.NewGuid().ToString())); } }
public void Ctor_SetPropertiesCorrectly() { // Assert IEdmModel model = EdmCoreModel.Instance; ODataPathTemplate path = new ODataPathTemplate(); // & Act & Assert ODataRoutingMetadata metadata = new ODataRoutingMetadata("prefix", model, path); // Assert Assert.Equal("prefix", metadata.Prefix); Assert.Same(model, metadata.Model); Assert.Same(path, metadata.Template); }
/// <summary> /// Adds the OData selector model to the action. /// </summary> /// <param name="action">The given action model.</param> /// <param name="httpMethod">The supported http methods, if mulitple, using ',' to separate.</param> /// <param name="prefix">The prefix.</param> /// <param name="model">The Edm model.</param> /// <param name="path">The OData path template.</param> /// <param name="options">The route build options.</param> public static void AddSelector(this ActionModel action, string httpMethod, string prefix, IEdmModel model, ODataPathTemplate path, ODataRouteOptions options = null) { if (action == null) { throw Error.ArgumentNull(nameof(action)); } if (model == null) { throw Error.ArgumentNull(nameof(model)); } if (path == null) { throw Error.ArgumentNull(nameof(path)); } foreach ((string template, string display) in path.GetTemplates(options)) { SelectorModel selectorModel = action.Selectors.FirstOrDefault(s => s.AttributeRouteModel == null); if (selectorModel == null) { selectorModel = CreateSelectorModel(action.Attributes); action.Selectors.Add(selectorModel); } ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, path) { TemplateDisplayName = string.IsNullOrEmpty(prefix) ? display : $"{prefix}/{display}" }; AddHttpMethod(odataMetadata, httpMethod); selectorModel.EndpointMetadata.Add(odataMetadata); string templateStr = string.IsNullOrEmpty(prefix) ? template : $"{prefix}/{template}"; selectorModel.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(templateStr) { Name = templateStr }); // Check with .NET Team whether the "Endpoint name metadata" selectorModel.EndpointMetadata.Add(new EndpointNameMetadata(Guid.NewGuid().ToString())); } }
/// <summary> /// Adds the OData selector model to the action. /// </summary> /// <param name="action">The given action model.</param> /// <param name="prefix">The prefix.</param> /// <param name="model">The Edm model.</param> /// <param name="path">The OData path template.</param> public static void AddSelector(this ActionModel action, string prefix, IEdmModel model, ODataPathTemplate path) { if (action == null) { throw Error.ArgumentNull(nameof(action)); } if (model == null) { throw Error.ArgumentNull(nameof(model)); } if (path == null) { throw new ArgumentNullException(nameof(path)); } var httpMethods = action.GetSupportedHttpMethods(); foreach (var template in path.GetTemplates()) { SelectorModel selectorModel = action.Selectors.FirstOrDefault(s => s.AttributeRouteModel == null); if (selectorModel == null) { selectorModel = new SelectorModel(); action.Selectors.Add(selectorModel); } string templateStr = string.IsNullOrEmpty(prefix) ? template : $"{prefix}/{template}"; selectorModel.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(templateStr) { Name = templateStr }); ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, path); selectorModel.EndpointMetadata.Add(odataMetadata); // Check with .NET Team whether the "Endpoint name metadata" // selectorModel.EndpointMetadata.Add(new EndpointNameMetadata(templateStr)); foreach (var httpMethod in httpMethods) { odataMetadata.HttpMethods.Add(httpMethod); } } }
public void AppliesToEndpoints_EndpointHasODataRoutingMetadata_ReturnsTrue() { // Arrange IODataRoutingMetadata routingMetadata = new ODataRoutingMetadata(); Endpoint[] endpoints = new[] { CreateEndpoint("/", routingMetadata), CreateEndpoint("/", null), }; ODataRoutingMatcherPolicy policy = CreatePolicy(); // Act bool result = policy.AppliesToEndpoints(endpoints); // Assert Assert.True(result); }
private SelectorModel CreateActionSelectorModel(string prefix, IEdmModel model, IServiceProvider sp, string routeTemplate, SelectorModel actionSelectorModel, string originalTemplate, string actionName, string controllerName) { try { // Do the uri parser, it will throw exception if the route template is not a OData path. ODataPathTemplate pathTemplate = _templateParser.Parse(model, routeTemplate, sp); if (pathTemplate != null) { // Create a new selector model? SelectorModel newSelectorModel = new SelectorModel(actionSelectorModel); // Shall we remove any certain attributes/metadata? ClearMetadata(newSelectorModel); // Add OData routing metadata ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, pathTemplate); newSelectorModel.EndpointMetadata.Add(odataMetadata); // replace the attribute routing template using absolute routing template to avoid appending any controller route template newSelectorModel.AttributeRouteModel = new AttributeRouteModel() { Template = $"/{originalTemplate}" // add a "/" to make sure it's absolute template, don't combine with controller }; return(newSelectorModel); } return(null); } catch (ODataException ex) { // use the logger to log the wrong odata attribute template. Shall we log the others? string warning = string.Format(CultureInfo.CurrentCulture, SRResources.InvalidODataRouteOnAction, originalTemplate, actionName, controllerName, ex.Message); // Whether we throw exception or mark it as warning is a design pattern. // throw new ODataException(warning); _logger.LogWarning(warning); return(null); } }
/// <summary> /// Adds the OData selector model to the action. /// </summary> /// <param name="action">The given action model.</param> /// <param name="httpMethods">The supported http methods, if multiple, using ',' to separate.</param> /// <param name="prefix">The prefix.</param> /// <param name="model">The Edm model.</param> /// <param name="path">The OData path template.</param> /// <param name="options">The route build options.</param> public static void AddSelector(this ActionModel action, string httpMethods, string prefix, IEdmModel model, ODataPathTemplate path, ODataRouteOptions options = null) { if (action == null) { throw Error.ArgumentNull(nameof(action)); } if (string.IsNullOrEmpty(httpMethods)) { throw Error.ArgumentNullOrEmpty(nameof(httpMethods)); } if (model == null) { throw Error.ArgumentNull(nameof(model)); } if (path == null) { throw Error.ArgumentNull(nameof(path)); } // if the controller has attribute route decorated, for example: // [Route("api/[controller]")] // public class CustomersController : Controller // {} // let's always create new selector model for action. // Since the new created selector model is absolute attribute route, the controller attribute route doesn't apply to this selector model. bool hasAttributeRouteOnController = action.Controller.Selectors.Any(s => s.AttributeRouteModel != null); // If the methods have different case sensitive, for example, "get", "Get", in the ASP.NET Core 3.1, // It will throw "An item with the same key has already been added. Key: GET", in // HttpMethodMatcherPolicy.BuildJumpTable(Int32 exitDestination, IReadOnlyList`1 edges) // Another root cause is that in attribute routing, we reuse the HttpMethodMetadata, the method name is always "upper" case. // Therefore, we upper the http method name always. string[] methods = httpMethods.ToUpperInvariant().Split(','); foreach (string template in path.GetTemplates(options)) { // Be noted: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/ApplicationModels/ActionAttributeRouteModel.cs#L74-L75 // No matter whether the action selector model is absolute route template, the controller's attribute will apply automatically // So, let's only create/update the action selector model SelectorModel selectorModel = action.Selectors.FirstOrDefault(s => s.AttributeRouteModel == null); if (hasAttributeRouteOnController || selectorModel == null) { // Create a new selector model. selectorModel = CreateSelectorModel(action, methods); action.Selectors.Add(selectorModel); } else { // Update the existing non attribute routing selector model. selectorModel = UpdateSelectorModel(selectorModel, methods); } ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, path); selectorModel.EndpointMetadata.Add(odataMetadata); string templateStr = string.IsNullOrEmpty(prefix) ? template : $"{prefix}/{template}"; selectorModel.AttributeRouteModel = new AttributeRouteModel { // OData convention route template doesn't get combined with the route template applied to the controller. // Route templates applied to an action that begin with / or ~/ don't get combined with route templates applied to the controller. Template = $"/{templateStr}", Name = templateStr // do we need this? }; // Check with .NET Team whether the "Endpoint name metadata" needed? selectorModel.EndpointMetadata.Add(new EndpointNameMetadata(Guid.NewGuid().ToString())); // Do we need this? } }