/// <summary> /// Initializes a new instance of the <see cref="ODataRoutingApplicationModelProvider" /> class. /// </summary> /// <param name="conventions">The registered OData routing conventions.</param> /// <param name="options">The registered OData options.</param> public ODataRoutingApplicationModelProvider( IEnumerable <IODataControllerActionConvention> conventions, IOptions <ODataOptions> options) { _conventions = conventions; _options = options.Value; _controllerActionConventions = conventions.Where(c => c.GetType() != typeof(AttributeRoutingConvention)).OrderBy(p => p.Order).ToArray(); }
/// <summary> /// Parse the odata path. /// </summary> /// <param name="model">The model to use for path parsing.</param> /// <param name="serviceRoot">The service root of the OData path.</param> /// <param name="odataPath">The OData path to parse.</param> /// <param name="template">The flag indicates whether the path is template or not.</param> /// <param name="serviceProvider">The service proivder.</param> /// <param name="httpContext"></param> /// <param name="resolverSettings"></param> /// <returns>A parsed representation of the path, or <c>null</c> if the path does not match the model.</returns> public static ODataPath Parse(IEdmModel model, string serviceRoot, string odataPath, bool template, IServiceProvider serviceProvider, ODataUriResolverSettings resolverSettings) { ODL.ODataUriParser uriParser; Uri serviceRootUri = null; Uri fullUri = null; // TODO: Replace this type. //NameValueCollection queryString = null; ODataOptions options = serviceProvider.GetRequiredService <IOptions <ODataOptions> >().Value; if (template) { uriParser = new ODL.ODataUriParser(model, new Uri(odataPath, UriKind.Relative), serviceProvider); uriParser.EnableUriTemplateParsing = true; } else { Contract.Assert(serviceRoot != null); serviceRootUri = new Uri( serviceRoot.EndsWith("/", StringComparison.Ordinal) ? serviceRoot : serviceRoot + "/"); fullUri = new Uri(serviceRootUri, odataPath); //queryString = fullUri.ParseQueryString(); uriParser = new ODL.ODataUriParser(model, serviceRootUri, fullUri, serviceProvider); } uriParser.Resolver = resolverSettings.CreateResolver(); if (options.UrlKeyDelimiter != null) { uriParser.UrlKeyDelimiter = options.UrlKeyDelimiter; } else { // ODL changes to use ODataUrlKeyDelimiter.Slash as default value. // Web API still uses the ODataUrlKeyDelimiter.Parentheses as default value. // Please remove it after fix: https://github.com/OData/odata.net/issues/642 uriParser.UrlKeyDelimiter = ODataUrlKeyDelimiter.Parentheses; } ODL.ODataPath path; UnresolvedPathSegment unresolvedPathSegment = null; ODL.KeySegment id = null; try { path = uriParser.ParsePath(); } catch (ODL.ODataUnrecognizedPathException ex) { if (ex.ParsedSegments != null && ex.ParsedSegments.Any() && (ex.ParsedSegments.Last().EdmType is IEdmComplexType || ex.ParsedSegments.Last().EdmType is IEdmEntityType) && ex.CurrentSegment != ODataSegmentKinds.Count) { if (!ex.UnparsedSegments.Any()) { path = new ODL.ODataPath(ex.ParsedSegments); unresolvedPathSegment = new UnresolvedPathSegment(ex.CurrentSegment); } else { // Throw ODataException if there is some segment following the unresolved segment. throw new ODataException(Error.Format( SRResources.InvalidPathSegment, ex.UnparsedSegments.First(), ex.CurrentSegment)); } } else { throw; } } if (!template && path.LastSegment is ODL.NavigationPropertyLinkSegment) { IEdmCollectionType lastSegmentEdmType = path.LastSegment.EdmType as IEdmCollectionType; if (lastSegmentEdmType != null) { ODL.EntityIdSegment entityIdSegment = null; bool exceptionThrown = false; try { entityIdSegment = uriParser.ParseEntityId(); if (entityIdSegment != null) { // Create another ODataUriParser to parse $id, which is absolute or relative. ODL.ODataUriParser parser = new ODL.ODataUriParser(model, serviceRootUri, entityIdSegment.Id); id = parser.ParsePath().LastSegment as ODL.KeySegment; } } catch (ODataException) { // Exception was thrown while parsing the $id. // We will throw another exception about the invalid $id. exceptionThrown = true; } if (exceptionThrown || (entityIdSegment != null && (id == null || !(id.EdmType.IsOrInheritsFrom(lastSegmentEdmType.ElementType.Definition) || lastSegmentEdmType.ElementType.Definition.IsOrInheritsFrom(id.EdmType))))) { throw new ODataException(Error.Format(SRResources.InvalidDollarId, "$id" /*queryString.Get("$id")*/)); } } } // do validation for the odata path path.WalkWith(new DefaultODataPathValidator(model)); // do segment translator (for example parameter alias, key & function parameter template, etc) var segments = ODataPathSegmentTranslator.Translate(model, path, uriParser.ParameterAliasNodes).ToList(); if (unresolvedPathSegment != null) { segments.Add(unresolvedPathSegment); } if (!template) { AppendIdForRef(segments, id); } return(new ODataPath(segments) { ODLPath = path }); }
/// <summary> /// Maps the specified OData route and the OData route attributes. /// </summary> /// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param> /// <param name="routeName">The name of the route to map.</param> /// <param name="routePrefix">The prefix to add to the OData route's path template.</param> /// <param name="configureAction">The configuring action to add the services to the root container.</param> /// <returns>The input <see cref="IEndpointRouteBuilder"/>.</returns> public static IEndpointRouteBuilder MapODataRoute(this IEndpointRouteBuilder builder, string routeName, string routePrefix, Action <IContainerBuilder> configureAction) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (routeName == null) { throw new ArgumentNullException(nameof(routeName)); } // Build and configure the root container. IServiceProvider serviceProvider = builder.ServiceProvider; IPerRouteContainer perRouteContainer = serviceProvider.GetRequiredService <IPerRouteContainer>(); if (perRouteContainer == null) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, SRResources.MissingODataServices, nameof(IPerRouteContainer))); } // Make sure the MetadataController is registered with the ApplicationPartManager. ApplicationPartManager applicationPartManager = serviceProvider.GetRequiredService <ApplicationPartManager>(); applicationPartManager.ApplicationParts.Add(new AssemblyPart(typeof(MetadataController).Assembly)); // Create an service provider for this route. Add the default services to the custom configuration actions. Action <IContainerBuilder> builderAction = ConfigureDefaultServices(builder, configureAction); IServiceProvider subServiceProvider = perRouteContainer.CreateODataRootContainer(routeName, builderAction); // Resolve the path handler and set URI resolver to it. IODataPathHandler pathHandler = subServiceProvider.GetRequiredService <IODataPathHandler>(); // If settings is not on local, use the global configuration settings. ODataOptions options = serviceProvider.GetRequiredService <ODataOptions>(); //if (pathHandler != null && pathHandler.UrlKeyDelimiter == null) //{ // pathHandler.UrlKeyDelimiter = options.UrlKeyDelimiter; //} // Resolve HTTP handler, create the OData route and register it. routePrefix = RemoveTrailingSlash(routePrefix); // If a batch handler is present, register the route with the batch path mapper. This will be used // by the batching middleware to handle the batch request. Batching still requires the injection // of the batching middleware via UseODataBatching(). ODataBatchHandler batchHandler = subServiceProvider.GetService <ODataBatchHandler>(); if (batchHandler != null) { // TODO: for the $batch, need refactor/test it for more. batchHandler.ODataRouteName = routeName; string batchPath = String.IsNullOrEmpty(routePrefix) ? '/' + ODataRouteConstants.Batch : '/' + routePrefix + '/' + ODataRouteConstants.Batch; ODataBatchPathMapping batchMapping = builder.ServiceProvider.GetRequiredService <ODataBatchPathMapping>(); batchMapping.IsEndpointRouting = true; batchMapping.AddRoute(routeName, batchPath); } builder.MapDynamicControllerRoute <ODataEndpointRouteValueTransformer>( ODataEndpointPattern.CreateODataEndpointPattern(routeName, routePrefix)); perRouteContainer.AddRoute(routeName, routePrefix); return(builder); }