public void ConfigOperationOptions_ThrowsODataException() { // Arrange string expect = "The route option disables qualified and unqualified operation call. At least one option should enable."; // Act & Assert Action test = () => { ODataRouteOptions options = new ODataRouteOptions(); options.EnableQualifiedOperationCall = false; options.EnableUnqualifiedOperationCall = false; }; ExceptionAssert.Throws <ODataException>(test, expect); // Act & Assert test = () => { ODataRouteOptions options = new ODataRouteOptions(); options.EnableUnqualifiedOperationCall = false; options.EnableQualifiedOperationCall = false; }; ExceptionAssert.Throws <ODataException>(test, expect); }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { if (RelatedKey != null) { options = options ?? ODataRouteOptions.Default; Contract.Assert(options.EnableKeyInParenthesis || options.EnableKeyAsSegment); if (options.EnableKeyInParenthesis && options.EnableKeyAsSegment) { yield return($"/{{{ParameterName}}}({{{RelatedKey}}})/$ref"); yield return($"/{{{ParameterName}}}/{{{RelatedKey}}}/$ref"); } else if (options.EnableKeyInParenthesis) { yield return($"/{{{ParameterName}}}({{{RelatedKey}}})/$ref"); } else { yield return($"/{{{ParameterName}}}/{{{RelatedKey}}}/$ref"); } } else { yield return($"/{{{ParameterName}}}/$ref"); } }
public void ConfigKeyOptions_ThrowsODataException() { // Arrange string expect = "The route option disables key in parenthesis and key as segment. At least one option should enable."; // Act & Assert Action test = () => { ODataRouteOptions options = new ODataRouteOptions(); options.EnableKeyAsSegment = false; options.EnableKeyInParenthesis = false; }; ExceptionAssert.Throws <ODataException>(test, expect); // Act & Assert test = () => { ODataRouteOptions options = new ODataRouteOptions(); options.EnableKeyInParenthesis = false; options.EnableKeyAsSegment = false; }; ExceptionAssert.Throws <ODataException>(test, expect); }
public void CtorODataRouteOptions_HasDefaultProperties() { // Arrange & Act ODataRouteOptions options = new ODataRouteOptions(); // Assert Assert.True(options.EnableKeyInParenthesis); Assert.True(options.EnableKeyAsSegment); Assert.True(options.EnableQualifiedOperationCall); Assert.True(options.EnableUnqualifiedOperationCall); Assert.False(options.EnableNonParenthsisForEmptyParameterFunction); }
/// <summary> /// Generates all templates for the given <see cref="ODataPathTemplate"/> using the given <see cref="ODataRouteOptions"/>. /// All templates mean: /// 1) for key segment, we have key in parenthesis and key as segment. /// 2) for bound function/action segment, we have qualified function call and unqualified function call. /// All of such might be based on route options. /// </summary> /// <param name="options">The route options.</param> /// <returns>All path templates.</returns> public virtual IEnumerable <string> GetTemplates(ODataRouteOptions options = null) { options = options ?? ODataRouteOptions.Default; Stack <string> stack = new Stack <string>(); IList <string> templates = new List <string>(); ProcessSegment(stack, 0, Count, templates, options); return(templates); }
private static void Verify(Func <ODataRouteOptions, bool> func, Action <ODataRouteOptions, bool> config, bool defValue = true) { // Arrange ODataRouteOptions options = new ODataRouteOptions(); Assert.Equal(defValue, func(options)); // Act config(options, !defValue); // Assert Assert.Equal(!defValue, func(options)); }
public void DefaultODataRouteOptions_HasDefaultProperties() { // Arrange & Act ODataRouteOptions options = ODataRouteOptions.Default; // Assert Assert.True(options.EnableKeyInParenthesis); Assert.True(options.EnableKeyAsSegment); Assert.True(options.EnableQualifiedOperationCall); Assert.True(options.EnableUnqualifiedOperationCall); Assert.False(options.EnableNonParenthsisForEmptyParameterFunction); Assert.False(options.EnableControllerNameCaseInsensitive); }
public void ConfigOperationOptions_DoesNotThrowsODataException() { // Arrange & Act & Assert ODataRouteOptions options = new ODataRouteOptions(); options.EnableQualifiedOperationCall = false; options.EnableUnqualifiedOperationCall = true; // Arrange & Act & Assert options = new ODataRouteOptions(); options.EnableUnqualifiedOperationCall = false; options.EnableQualifiedOperationCall = true; }
public void ConfigKeyOptions_DoesNotThrowsODataException() { // Arrange & Act & Assert ODataRouteOptions options = new ODataRouteOptions(); options.EnableKeyAsSegment = false; options.EnableKeyInParenthesis = true; // Arrange & Act & Assert options = new ODataRouteOptions(); options.EnableKeyInParenthesis = false; options.EnableKeyAsSegment = true; }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { options = options ?? ODataRouteOptions.Default; if (ParameterMappings.Count == 0 && options.EnableNonParenthsisForEmptyParameterFunction) { yield return($"/{FunctionImport.Name}"); } else { string parameters = string.Join(",", ParameterMappings.Select(a => $"{a.Key}={{{a.Value}}}")); yield return($"/{FunctionImport.Name}({parameters})"); } }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { if (Key != null) { IEnumerable <string> keyTemplates = Key.GetTemplates(options); foreach (var keyTemplate in keyTemplates) { yield return($"/{NavigationProperty.Name}{keyTemplate}/$ref"); } } else { yield return($"/{NavigationProperty.Name}/$ref"); } }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { options = options ?? ODataRouteOptions.Default; Contract.Assert(options.EnableQualifiedOperationCall || options.EnableUnqualifiedOperationCall); if (options.EnableQualifiedOperationCall && options.EnableUnqualifiedOperationCall) { yield return($"/{Action.FullName()}"); yield return($"/{Action.Name}"); } else if (options.EnableQualifiedOperationCall) { yield return($"/{Action.FullName()}"); } else { yield return($"/{Action.Name}"); } }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { options = options ?? ODataRouteOptions.Default; Contract.Assert(options.EnableKeyInParenthesis || options.EnableKeyAsSegment); if (options.EnableKeyInParenthesis && options.EnableKeyAsSegment) { yield return($"({_keyLiteral})"); yield return($"/{_keyLiteral}"); } else if (options.EnableKeyInParenthesis) { yield return($"({_keyLiteral})"); } else if (options.EnableKeyAsSegment) { yield return($"/{_keyLiteral}"); } }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { options = options ?? ODataRouteOptions.Default; Contract.Assert(options.EnableQualifiedOperationCall || options.EnableUnqualifiedOperationCall); string unqualifiedIdentifier, qualifiedIdentifier; if (ParameterMappings.Count == 0 && options.EnableNonParenthsisForEmptyParameterFunction) { unqualifiedIdentifier = "/" + Function.Name; qualifiedIdentifier = "/" + Function.FullName(); } else { string parameterStr = "(" + string.Join(",", ParameterMappings.Select(a => $"{a.Key}={{{a.Value}}}")) + ")"; unqualifiedIdentifier = "/" + Function.Name + parameterStr; qualifiedIdentifier = "/" + Function.FullName() + parameterStr; } if (options.EnableQualifiedOperationCall && options.EnableUnqualifiedOperationCall) { // "/NS.Function(...)" yield return(qualifiedIdentifier); // "/Function(...)" yield return(unqualifiedIdentifier); } else if (options.EnableQualifiedOperationCall) { // "/NS.Function(...)" yield return(qualifiedIdentifier); } else { // "/Function(...)" yield return(unqualifiedIdentifier); } }
private void ProcessSegment(Stack <string> stack, int index, int count, IList <string> templates, ODataRouteOptions options) { if (index == count) { string pathTemplate = string.Join("", stack.Reverse()); if (pathTemplate.StartsWith('/')) { pathTemplate = pathTemplate.Substring(1); // remove the first "/" } templates.Add(pathTemplate); return; } ODataSegmentTemplate segment = this[index]; foreach (string template in segment.GetTemplates(options)) { stack.Push(template); ProcessSegment(stack, index + 1, count, templates, options); stack.Pop(); } }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { yield return($"/{Segment.Singleton.Name}"); }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { yield return("/$value"); }
/// <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 override IEnumerable <string> GetTemplates(ODataRouteOptions options) { yield return("/{classname}"); }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { yield return($"/{Segment.LiteralText}"); }
/// <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 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); 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())); } }
/// <summary> /// Gets the templates. It's case-insensitive template. /// It's used to build the routing template in conventional routing. /// It's not used in attribute routing. /// The template string should include the leading "/" if apply. /// </summary> /// <param name="options">The route options.</param> /// <returns>The built segment templates.</returns> public abstract IEnumerable <string> GetTemplates(ODataRouteOptions options);
/// <summary> /// Generates all templates for the given <see cref="ODataPathTemplate"/>. /// All templates mean: /// 1) for key segment, we have key in parenthesis and key as segment. /// 2) for bound function segment, we have qualified function call and unqualified function call. /// </summary> /// <param name="path">The given path template.</param> /// <param name="options">The route options.</param> /// <returns>All path templates.</returns> public static IEnumerable <string> GetTemplates(this ODataPathTemplate path, ODataRouteOptions options = null) { if (path == null) { throw Error.ArgumentNull(nameof(path)); } options = options ?? ODataRouteOptions.Default; IList <StringBuilder> templates = new List <StringBuilder> { new StringBuilder() }; int count = path.Segments.Count; for (int index = 0; index < count; index++) { ODataSegmentTemplate segment = path.Segments[index]; if (segment.Kind == ODataSegmentKind.Key) { // for key segment, if it's single key, let's add key as segment template also // otherwise, we only add the key in parenthesis template. KeySegmentTemplate keySg = segment as KeySegmentTemplate; templates = AppendKeyTemplate(templates, keySg, options); continue; } if (index != 0) { templates = CombinateTemplate(templates, "/"); } // create => ~.../navigation/{key}/$ref if (segment.Kind == ODataSegmentKind.NavigationLink) { NavigationLinkSegmentTemplate navigationLinkSegment = (NavigationLinkSegmentTemplate)segment; if (index == count - 1) { // we don't have the other segment templates = CombinateTemplates(templates, $"{navigationLinkSegment.Segment.NavigationProperty.Name}/$ref"); } else { ODataSegmentTemplate nextSegment = path.Segments[index + 1]; if (nextSegment.Kind == ODataSegmentKind.Key) { // append "navigation property" templates = CombinateTemplates(templates, navigationLinkSegment.Segment.NavigationProperty.Name); // append "key" KeySegmentTemplate keySg = nextSegment as KeySegmentTemplate; templates = AppendKeyTemplate(templates, keySg, options); // append $ref templates = CombinateTemplates(templates, "/$ref"); index++; // skip the key segment after $ref. } else { templates = CombinateTemplates(templates, $"{navigationLinkSegment.Segment.NavigationProperty.Name}/$ref"); } } continue; } if (segment.Kind == ODataSegmentKind.Action) { ActionSegmentTemplate action = (ActionSegmentTemplate)segment; templates = AppendActionTemplate(templates, action, options); } else if (segment.Kind == ODataSegmentKind.Function) { FunctionSegmentTemplate function = (FunctionSegmentTemplate)segment; templates = AppendFunctionTemplate(templates, function, options); } else { templates = CombinateTemplate(templates, segment.Literal); } } return(templates.Select(t => t.ToString())); }
private static IList <StringBuilder> AppendFunctionTemplate(IList <StringBuilder> templates, FunctionSegmentTemplate segment, ODataRouteOptions options) { Contract.Assert(segment != null); Contract.Assert(options != null); if (options.EnableQualifiedOperationCall && options.EnableUnqualifiedOperationCall) { return(CombinateTemplates(templates, segment.Literal, segment.UnqualifiedIdentifier)); } else if (options.EnableQualifiedOperationCall) { return(CombinateTemplate(templates, segment.Literal)); } else if (options.EnableUnqualifiedOperationCall) { return(CombinateTemplate(templates, segment.UnqualifiedIdentifier)); } else { throw new ODataException(Error.Format(SRResources.RouteOptionDisabledOperationSegment, "function")); } }
private static IList <StringBuilder> AppendKeyTemplate(IList <StringBuilder> templates, KeySegmentTemplate segment, ODataRouteOptions options) { Contract.Assert(segment != null); Contract.Assert(options != null); if (options.EnableKeyInParenthesis && options.EnableKeyAsSegment) { return(CombinateTemplates(templates, $"({segment.Literal})", $"/{segment.Literal}")); } else if (options.EnableKeyInParenthesis) { return(CombinateTemplate(templates, $"({segment.Literal})")); } else if (options.EnableKeyAsSegment) { return(CombinateTemplate(templates, $"/{segment.Literal}")); } else { throw new ODataException(SRResources.RouteOptionDisabledKeySegment); } }
private static void AddSelector(IEdmEntitySet entitySet, IEdmEntityType entityType, IEdmStructuredType castType, string prefix, IEdmModel model, ActionModel action, string httpMethod, ODataRouteOptions options) { IList <ODataSegmentTemplate> segments = new List <ODataSegmentTemplate> { new EntitySetSegmentTemplate(entitySet), KeySegmentTemplate.CreateKeySegment(entityType, entitySet) }; // If we have the type cast if (castType != null) { if (castType == entityType) { // If cast type is the entity type of the entity set. // we support two templates // ~/Customers({key}) action.AddSelector(httpMethod, prefix, model, new ODataPathTemplate(segments), options); // ~/Customers({key})/Ns.Customer segments.Add(new CastSegmentTemplate(castType, entityType, entitySet)); action.AddSelector(httpMethod, prefix, model, new ODataPathTemplate(segments), options); } else { // ~/Customers({key})/Ns.VipCustomer segments.Add(new CastSegmentTemplate(castType, entityType, entitySet)); action.AddSelector(httpMethod, prefix, model, new ODataPathTemplate(segments), options); } } else { // ~/Customers({key}) action.AddSelector(httpMethod, prefix, model, new ODataPathTemplate(segments), options); } }
/// <inheritdoc /> public override IEnumerable <string> GetTemplates(ODataRouteOptions options) { yield return($"/{Property.Name}"); }
private static IList <TemplateInfo> AppendActionTemplate(IList <TemplateInfo> templates, ActionSegmentTemplate segment, ODataRouteOptions options) { Contract.Assert(segment != null); Contract.Assert(options != null); string fullName = segment.Action.FullName(); string name = segment.Action.Name; if (options.EnableQualifiedOperationCall && options.EnableUnqualifiedOperationCall) { return(CombinateTemplates(templates, (fullName, fullName), (name, name))); } else if (options.EnableQualifiedOperationCall) { return(CombinateTemplate(templates, (fullName, fullName))); } else if (options.EnableUnqualifiedOperationCall) { return(CombinateTemplate(templates, (name, name))); } else { throw new ODataException(Error.Format(SRResources.RouteOptionDisabledOperationSegment, "action")); } }
/// <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)); } ODataRoutingMetadata odataMetadata = new ODataRoutingMetadata(prefix, model, path); AddHttpMethod(odataMetadata, httpMethod); foreach (string template in path.GetTemplates(options)) { SelectorModel selectorModel = action.Selectors.FirstOrDefault(s => s.AttributeRouteModel == null); if (selectorModel == null) { selectorModel = CreateSelectorModel(action.Attributes); action.Selectors.Add(selectorModel); } 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())); } }
private static IList <TemplateInfo> AppendFunctionTemplate(IList <TemplateInfo> templates, FunctionSegmentTemplate segment, ODataRouteOptions options) { Contract.Assert(segment != null); Contract.Assert(options != null); string qualified = segment.GetDisplayName(true); string unqulified = segment.GetDisplayName(false); if (options.EnableQualifiedOperationCall && options.EnableUnqualifiedOperationCall) { return(CombinateTemplates(templates, (segment.Literal, qualified), (segment.UnqualifiedIdentifier, unqulified))); } else if (options.EnableQualifiedOperationCall) { return(CombinateTemplate(templates, (segment.Literal, qualified))); } else if (options.EnableUnqualifiedOperationCall) { return(CombinateTemplate(templates, (segment.UnqualifiedIdentifier, unqulified))); } else { throw new ODataException(Error.Format(SRResources.RouteOptionDisabledOperationSegment, "function")); } }