| File: RouteData.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing.Abstractions\src\Microsoft.AspNetCore.Routing.Abstractions.csproj (Microsoft.AspNetCore.Routing.Abstractions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Shared; namespace Microsoft.AspNetCore.Routing; /// <summary> /// Information about the current routing path. /// </summary> [DebuggerDisplay("Count = {Values.Count}")] [DebuggerTypeProxy(typeof(RouteDataDebugView))] public class RouteData { private RouteValueDictionary? _dataTokens; private List<IRouter>? _routers; private RouteValueDictionary? _values; /// <summary> /// Creates a new instance of <see cref="RouteData"/> instance. /// </summary> public RouteData() { // Perf: Avoid allocating collections unless needed. } /// <summary> /// Creates a new instance of <see cref="RouteData"/> instance with values copied from <paramref name="other"/>. /// </summary> /// <param name="other">The other <see cref="RouteData"/> instance to copy.</param> public RouteData(RouteData other) { ArgumentNullException.ThrowIfNull(other); // Perf: Avoid allocating collections unless we need to make a copy. if (other._routers != null) { _routers = new List<IRouter>(other.Routers); } if (other._dataTokens != null) { _dataTokens = new RouteValueDictionary(other._dataTokens); } if (other._values != null) { _values = new RouteValueDictionary(other._values); } } /// <summary> /// Creates a new instance of <see cref="RouteData"/> instance with the specified values. /// </summary> /// <param name="values">The <see cref="RouteValueDictionary"/> values.</param> public RouteData(RouteValueDictionary values) { ArgumentNullException.ThrowIfNull(values); _values = values; } /// <summary> /// Gets the data tokens produced by routes on the current routing path. /// </summary> public RouteValueDictionary DataTokens { get { if (_dataTokens == null) { _dataTokens = new RouteValueDictionary(); } return _dataTokens; } } /// <summary> /// Gets the list of <see cref="IRouter"/> instances on the current routing path. /// </summary> public IList<IRouter> Routers { get { if (_routers == null) { _routers = new List<IRouter>(); } return _routers; } } /// <summary> /// Gets the values produced by routes on the current routing path. /// </summary> public RouteValueDictionary Values { get { if (_values == null) { _values = new RouteValueDictionary(); } return _values; } } /// <summary> /// <para> /// Creates a snapshot of the current state of the <see cref="RouteData"/> before appending /// <paramref name="router"/> to <see cref="Routers"/>, merging <paramref name="values"/> into /// <see cref="Values"/>, and merging <paramref name="dataTokens"/> into <see cref="DataTokens"/>. /// </para> /// <para> /// Call <see cref="RouteDataSnapshot.Restore"/> to restore the state of this <see cref="RouteData"/> /// to the state at the time of calling /// <see cref="PushState(IRouter, RouteValueDictionary, RouteValueDictionary)"/>. /// </para> /// </summary> /// <param name="router"> /// An <see cref="IRouter"/> to append to <see cref="Routers"/>. If <c>null</c>, then <see cref="Routers"/> /// will not be changed. /// </param> /// <param name="values"> /// A <see cref="RouteValueDictionary"/> to merge into <see cref="Values"/>. If <c>null</c>, then /// <see cref="Values"/> will not be changed. /// </param> /// <param name="dataTokens"> /// A <see cref="RouteValueDictionary"/> to merge into <see cref="DataTokens"/>. If <c>null</c>, then /// <see cref="DataTokens"/> will not be changed. /// </param> /// <returns>A <see cref="RouteDataSnapshot"/> that captures the current state.</returns> public RouteDataSnapshot PushState(IRouter? router, RouteValueDictionary? values, RouteValueDictionary? dataTokens) { // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in // Array.CopyTo inside the List(IEnumerable<T>) constructor. List<IRouter>? routers = null; var count = _routers?.Count; if (count > 0) { Debug.Assert(_routers != null); routers = new List<IRouter>(count.Value); for (var i = 0; i < count.Value; i++) { routers.Add(_routers[i]); } } var snapshot = new RouteDataSnapshot( this, _dataTokens?.Count > 0 ? new RouteValueDictionary(_dataTokens) : null, routers, _values?.Count > 0 ? new RouteValueDictionary(_values) : null); if (router != null) { Routers.Add(router); } if (values != null) { foreach (var kvp in values) { if (kvp.Value != null) { Values[kvp.Key] = kvp.Value; } } } if (dataTokens != null) { foreach (var kvp in dataTokens) { DataTokens[kvp.Key] = kvp.Value; } } return snapshot; } private sealed class RouteDataDebugView(RouteData routeData) { private readonly RouteData _routeData = routeData; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public DictionaryItemDebugView<string, object?>[] Items => _routeData.Values.Select(pair => new DictionaryItemDebugView<string, object>(pair)).ToArray(); } /// <summary> /// A snapshot of the state of a <see cref="RouteData"/> instance. /// </summary> public readonly struct RouteDataSnapshot { private readonly RouteData _routeData; private readonly RouteValueDictionary? _dataTokens; private readonly IList<IRouter>? _routers; private readonly RouteValueDictionary? _values; /// <summary> /// Creates a new instance of <see cref="RouteDataSnapshot"/> for <paramref name="routeData"/>. /// </summary> /// <param name="routeData">The <see cref="RouteData"/>.</param> /// <param name="dataTokens">The data tokens.</param> /// <param name="routers">The routers.</param> /// <param name="values">The route values.</param> public RouteDataSnapshot( RouteData routeData, RouteValueDictionary? dataTokens, IList<IRouter>? routers, RouteValueDictionary? values) { ArgumentNullException.ThrowIfNull(routeData); _routeData = routeData; _dataTokens = dataTokens; _routers = routers; _values = values; } /// <summary> /// Restores the <see cref="RouteData"/> to the captured state. /// </summary> public void Restore() { if (_routeData._dataTokens == null && _dataTokens == null) { // Do nothing } else if (_dataTokens == null) { _routeData._dataTokens!.Clear(); } else { _routeData._dataTokens!.Clear(); foreach (var kvp in _dataTokens) { _routeData._dataTokens.Add(kvp.Key, kvp.Value); } } if (_routeData._routers == null && _routers == null) { // Do nothing } else if (_routers == null) { // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in // Array.Clear inside the List.Clear() method. var routers = _routeData._routers!; for (var i = routers.Count - 1; i >= 0; i--) { routers.RemoveAt(i); } } else { // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in // Array.Clear inside the List.Clear() method. // // We want to basically copy the contents of _routers in _routeData._routers - this change does // that with the minimal number of reads/writes and without calling Clear(). var routers = _routeData._routers!; var snapshotRouters = _routers; // This is made more complicated by the fact that List[int] throws if i == Count, so we have // to do two loops and call Add for those cases. var i = 0; for (; i < snapshotRouters.Count && i < routers.Count; i++) { routers[i] = snapshotRouters[i]; } for (; i < snapshotRouters.Count; i++) { routers.Add(snapshotRouters[i]); } // Trim excess - again avoiding RemoveRange because it uses native methods. for (i = routers.Count - 1; i >= snapshotRouters.Count; i--) { routers.RemoveAt(i); } } if (_routeData._values == null && _values == null) { // Do nothing } else if (_values == null) { _routeData._values!.Clear(); } else { _routeData._values!.Clear(); foreach (var kvp in _values) { _routeData._values.Add(kvp.Key, kvp.Value); } } } } }