// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Net.Http; using System.Web.Http.Properties; namespace System.Web.Http.Routing { /// /// A single route that is the composite of multiple "sub routes". /// /// /// Corresponds to the MVC implementation of attribute routing in System.Web.Mvc.Routing.RouteCollectionRoute. /// internal class RouteCollectionRoute : IHttpRoute, IReadOnlyCollection { // Key for accessing SubRoutes on a RouteData. // We expose this through the RouteData.Values instead of a derived class because // RouteData can get wrapped in another type, but Values still gets persisted through the wrappers. // Prefix with a \0 to protect against conflicts with user keys. public const string SubRouteDataKey = "MS_SubRoutes"; private IReadOnlyCollection _subRoutes; private static readonly IDictionary _empty = EmptyReadOnlyDictionary.Value; public RouteCollectionRoute() { } // This will enumerate all controllers and action descriptors, which will run those // Initialization hooks, which may try to initialize controller-specific config, which // may call back to the initialize hook. So guard against that reentrancy. private bool _beingInitialized; // deferred hook for initializing the sub routes. The composite route can be added during the middle of // intializing, but then the actual sub routes can get populated after initialization has finished. public void EnsureInitialized(Func> initializer) { if (_beingInitialized && _subRoutes == null) { // Avoid reentrant initialization return; } try { _beingInitialized = true; _subRoutes = initializer(); Contract.Assert(_subRoutes != null); } finally { _beingInitialized = false; } } private IReadOnlyCollection SubRoutes { get { // Caller should have already explicitly called EnsureInitialize. // Avoid lazy initilization from within the route table because the route table // is shared resource and init can happen if (_subRoutes == null) { string msg = Error.Format(SRResources.Object_NotYetInitialized); throw new InvalidOperationException(msg); } return _subRoutes; } } public string RouteTemplate { get { return String.Empty; } } public IDictionary Defaults { get { return _empty; } } public IDictionary Constraints { get { return _empty; } } public IDictionary DataTokens { get { return null; } } public HttpMessageHandler Handler { get { return null; } } // Returns null if no match. // Else, returns a composite route data that encapsulates the possible routes this may match against. public IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request) { List matches = new List(); foreach (IHttpRoute route in SubRoutes) { IHttpRouteData match = route.GetRouteData(virtualPathRoot, request); if (match != null) { matches.Add(match); } } if (matches.Count == 0) { return null; // no matches } return new RouteCollectionRouteData(this, matches.ToArray()); } public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary values) { // Use LinkGenerationRoute stubs to get placeholders for all the sub routes. return null; } public int Count { get { return SubRoutes.Count; } } public IEnumerator GetEnumerator() { return SubRoutes.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return SubRoutes.GetEnumerator(); } // Represents a union of multiple IHttpRouteDatas. private class RouteCollectionRouteData : IHttpRouteData { public RouteCollectionRouteData(IHttpRoute parent, IHttpRouteData[] subRouteDatas) { Route = parent; // Each sub route may have different values. Callers need to enumerate the subroutes // and individually query each. // Find sub-routes via the SubRouteDataKey; don't expose as a property since the RouteData // can be wrapped in an outer type that doesn't propagate properties. Values = new HttpRouteValueDictionary() { { SubRouteDataKey, subRouteDatas } }; } public IHttpRoute Route { get; private set; } public IDictionary Values { get; private set; } } } }