public void GetTemplatesWorksForODataPathWithDollarRefOnSingleNavigation() { // Arrange EdmEntityType customer = new EdmEntityType("NS", "Customer"); customer.AddKeys(customer.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); var entitySet = container.AddEntitySet("Customers", customer); var navigation = customer.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo { TargetMultiplicity = EdmMultiplicity.One, Name = "SubCustomer", Target = customer }); ODataPathTemplate template = new ODataPathTemplate( new EntitySetSegmentTemplate(entitySet), KeySegmentTemplate.CreateKeySegment(customer, entitySet), new NavigationLinkSegmentTemplate(navigation, entitySet)); // Act IEnumerable <(string, string)> actual = template.GetTemplates(); // Assert Assert.Equal(2, actual.Count()); Assert.Equal(new[] { "Customers({key})/SubCustomer/$ref", "Customers/{key}/SubCustomer/$ref" }, actual.Select(a => a.Item1)); }
/// <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? } }
public void GetTemplatesReturnsCorrectWithMetadataSegment() { // Arrange ODataPathTemplate path = new ODataPathTemplate(MetadataSegmentTemplate.Instance); // Act IEnumerable <string> templates = path.GetTemplates(); // Assert var template = Assert.Single(templates); Assert.Equal("$metadata", template); }
public void GetTemplatesReturnsCorrectWithEmptySegments() { // Arrange ODataPathTemplate path = new ODataPathTemplate(); // Act IEnumerable <string> templates = path.GetTemplates(); // Assert var template = Assert.Single(templates); Assert.Equal("", 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)) { // 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 GetTemplatesWorksForPathWithTypeCastAndFunction() { // Arrange EdmEntityType customer = new EdmEntityType("NS", "Customer"); customer.AddKeys(customer.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); // VipCustomer EdmEntityType vipCustomer = new EdmEntityType("NS", "VipCustomer", customer); EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); var entitySet = container.AddEntitySet("Customers", customer); // function with optional parameters EdmFunction getSalaray = new EdmFunction("NS", "GetWholeSalary", IntType, isBound: true, entitySetPathExpression: null, isComposable: false); getSalaray.AddParameter("entityset", new EdmEntityTypeReference(vipCustomer, false)); getSalaray.AddParameter("salary", IntType); getSalaray.AddOptionalParameter("minSalary", IntType); getSalaray.AddOptionalParameter("maxSalary", IntType, "129"); ODataPathTemplate template = new ODataPathTemplate( new EntitySetSegmentTemplate(entitySet), KeySegmentTemplate.CreateKeySegment(customer, entitySet), new CastSegmentTemplate(vipCustomer, customer, entitySet), new FunctionSegmentTemplate(getSalaray, null)); // Act IEnumerable <(string, string)> actual = template.GetTemplates(); Assert.Equal(4, actual.Count()); Assert.Equal(new[] { "Customers({key})/NS.VipCustomer/NS.GetWholeSalary({salary;minSalary;maxSalary})", "Customers({key})/NS.VipCustomer/GetWholeSalary({salary;minSalary;maxSalary})", "Customers/{key}/NS.VipCustomer/NS.GetWholeSalary({salary;minSalary;maxSalary})", "Customers/{key}/NS.VipCustomer/GetWholeSalary({salary;minSalary;maxSalary})", }, actual.Select(a => a.Item1)); Assert.Equal(new[] { "Customers({key})/NS.VipCustomer/NS.GetWholeSalary(salary={salary},minSalary={minSalary},maxSalary={maxSalary})", "Customers({key})/NS.VipCustomer/GetWholeSalary(salary={salary},minSalary={minSalary},maxSalary={maxSalary})", "Customers/{key}/NS.VipCustomer/NS.GetWholeSalary(salary={salary},minSalary={minSalary},maxSalary={maxSalary})", "Customers/{key}/NS.VipCustomer/GetWholeSalary(salary={salary},minSalary={minSalary},maxSalary={maxSalary})", }, actual.Select(a => a.Item2)); }
/// <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 GetTemplatesWorksForBasicPath() { // Arrange EdmEntityType customer = new EdmEntityType("NS", "Customer"); customer.AddKeys(customer.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); var entitySet = container.AddEntitySet("Customers", customer); ODataPathTemplate template = new ODataPathTemplate( new EntitySetSegmentTemplate(entitySet), KeySegmentTemplate.CreateKeySegment(customer, entitySet)); // Act IEnumerable <(string, string)> actual = template.GetTemplates(); // Assert Assert.Equal(2, actual.Count()); Assert.Equal(new[] { "Customers({key})", "Customers/{key}" }, actual.Select(a => a.Item1)); }
public void GetTemplatesWorksForODataPathWithDollarRefOnCollectionNavigation() { // Arrange EdmEntityType customer = new EdmEntityType("NS", "Customer"); customer.AddKeys(customer.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); var entitySet = container.AddEntitySet("Customers", customer); var navigation = customer.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo { TargetMultiplicity = EdmMultiplicity.Many, Name = "SubCustomers", Target = customer }); KeyValuePair <string, object>[] keys = new KeyValuePair <string, object>[] { new KeyValuePair <string, object>("Id", "{nextKey}") }; KeySegment keySegment = new KeySegment(keys, customer, entitySet); ODataPathTemplate template = new ODataPathTemplate( new EntitySetSegmentTemplate(entitySet), KeySegmentTemplate.CreateKeySegment(customer, entitySet), new NavigationLinkSegmentTemplate(navigation, entitySet) { Key = new KeySegmentTemplate(keySegment) }); // Act IEnumerable <string> actual = template.GetTemplates(); // Assert Assert.Equal(4, actual.Count()); Assert.Equal(new[] { "Customers({key})/SubCustomers({nextKey})/$ref", "Customers({key})/SubCustomers/{nextKey}/$ref", "Customers/{key}/SubCustomers({nextKey})/$ref", "Customers/{key}/SubCustomers/{nextKey}/$ref" }, actual); }
public void GetTemplatesReturnsCorrectWithTwoSegments() { // Arrange EdmEntityType entityType = new EdmEntityType("NS", "entity"); EdmEntityContainer container = new EdmEntityContainer("NS", "default"); EdmEntitySet entitySet = new EdmEntitySet(container, "set", entityType); EdmAction action = new EdmAction("NS", "action", null, true, null); ODataPathTemplate path = new ODataPathTemplate(new EntitySetSegmentTemplate(entitySet), new ActionSegmentTemplate(action, null)); // Act IEnumerable <string> templates = path.GetTemplates(); // Act & Assert Assert.Collection(templates, e => { Assert.Equal("set/NS.action", e); }, e => { Assert.Equal("set/action", e); }); }
/// <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? } }
public static void AddedODataSelector(ILogger logger, ActionModel action, ODataPathTemplate template) { string message = action.DisplayName + ": " + template.GetTemplates().FirstOrDefault(); _addedODataSelector(logger, message, null); }