| File: Repositories\EphemeralXmlRepository.cs | Web Access |
| Project: src\aspnetcore\src\DataProtection\DataProtection\src\Microsoft.AspNetCore.DataProtection.csproj (Microsoft.AspNetCore.DataProtection) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Microsoft.AspNetCore.Shared; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.DataProtection.Repositories; /// <summary> /// An ephemeral XML repository backed by process memory. This class must not be used for /// anything other than dev scenarios as the keys will not be persisted to storage. /// </summary> internal sealed class EphemeralXmlRepository : IDeletableXmlRepository { private readonly List<XElement> _storedElements = new List<XElement>(); public EphemeralXmlRepository(ILoggerFactory loggerFactory) { var logger = loggerFactory.CreateLogger<EphemeralXmlRepository>(); logger.UsingInmemoryRepository(); } public IReadOnlyCollection<XElement> GetAllElements() { // force complete enumeration under lock for thread safety lock (_storedElements) { return GetAllElementsCore().ToList().AsReadOnly(); } } private IEnumerable<XElement> GetAllElementsCore() { // this method must be called under lock foreach (XElement element in _storedElements) { yield return new XElement(element); // makes a deep copy so caller doesn't inadvertently modify it } } public void StoreElement(XElement element, string friendlyName) { ArgumentNullThrowHelper.ThrowIfNull(element); var cloned = new XElement(element); // makes a deep copy so caller doesn't inadvertently modify it // under lock for thread safety lock (_storedElements) { _storedElements.Add(cloned); } } /// <inheritdoc/> public bool DeleteElements(Action<IReadOnlyCollection<IDeletableElement>> chooseElements) { ArgumentNullThrowHelper.ThrowIfNull(chooseElements); var deletableElements = new List<DeletableElement>(); lock (_storedElements) { foreach (var storedElement in _storedElements) { // Make a deep copy so caller doesn't inadvertently modify it. deletableElements.Add(new DeletableElement(storedElement, new XElement(storedElement))); } } chooseElements(deletableElements); var elementsToDelete = deletableElements .Where(e => e.DeletionOrder.HasValue) .OrderBy(e => e.DeletionOrder.GetValueOrDefault()); lock (_storedElements) { foreach (var deletableElement in elementsToDelete) { var storedElement = deletableElement.StoredElement; var index = _storedElements.FindIndex(e => ReferenceEquals(e, storedElement)); if (index >= 0) // Might not find it if the collection has changed since we started. { // It would be more efficient to remove the larger indices first, but deletion order // is important for correctness. _storedElements.RemoveAt(index); } } } return true; } private sealed class DeletableElement : IDeletableElement { public DeletableElement(XElement storedElement, XElement element) { StoredElement = storedElement; Element = element; } /// <inheritdoc/> public XElement Element { get; } /// <summary>The <see cref="XElement"/> from which <see cref="Element"/> was cloned.</summary> public XElement StoredElement { get; } /// <inheritdoc/> public int? DeletionOrder { get; set; } } }