Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,50 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Web.Caching;
using Microsoft.AspNetCore.SystemWebAdapters;

namespace Microsoft.Extensions.DependencyInjection;

internal static class HttpRuntimeFactory
{
public static IHttpRuntime Create()
public static IHttpRuntime Create(IServiceProvider serviceProvider)
{
if (NativeMethods.IsAspNetCoreModuleLoaded())
{
var config = NativeMethods.HttpGetApplicationProperties();
return new IISHttpRuntime(config, serviceProvider);
}

return new DefaultHttpRuntime(serviceProvider);
}

return new IISHttpRuntime(config);
internal abstract class BaseHttpRuntime
{
protected BaseHttpRuntime(IServiceProvider serviceProvider)
{
Cache = serviceProvider.GetRequiredService<Cache>();
}

return new DefaultHttpRuntime();
public Cache Cache { get; }
}

internal class DefaultHttpRuntime : IHttpRuntime
internal class DefaultHttpRuntime : BaseHttpRuntime, IHttpRuntime
{
public DefaultHttpRuntime(IServiceProvider sp) : base(sp)
{
}

public string AppDomainAppVirtualPath => "/";

public string AppDomainAppPath => AppContext.BaseDirectory;
}

internal class IISHttpRuntime : IHttpRuntime
internal class IISHttpRuntime : BaseHttpRuntime, IHttpRuntime
{
private readonly NativeMethods.IISConfigurationData _config;

public IISHttpRuntime(NativeMethods.IISConfigurationData config)
public IISHttpRuntime(NativeMethods.IISConfigurationData config, IServiceProvider sp) : base(sp)
{
_config = config;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static class SystemWebAdaptersExtensions
public static ISystemWebAdapterBuilder AddSystemWebAdapters(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<IHttpRuntime>(_ => HttpRuntimeFactory.Create());
services.AddSingleton<IHttpRuntime>(sp => HttpRuntimeFactory.Create(sp));
services.AddSingleton<Cache>();
services.AddSingleton<BrowserCapabilitiesFactory>();
services.AddTransient<IStartupFilter, HttpContextStartupFilter>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ internal interface IHttpRuntime
{
string AppDomainAppVirtualPath { get; }
string AppDomainAppPath { get; }

System.Web.Caching.Cache Cache { get; }
}
10 changes: 10 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/Caching/Cache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ public object Add(string key, object value, CacheDependency? dependencies, DateT
return _cache.AddOrGetExisting(key, value, policy);
}

private static void AddChangeMonitors(CacheDependency? dependencies, CacheItemPolicy policy)
{
if (dependencies?.ChangeMonitors is not null)
{
policy.ChangeMonitors.Add(dependencies.GetChangeMonitor());
}
}

public object Get(string key) => _cache.Get(key);

public void Insert(string key, object value) => _cache.Set(key, value, new CacheItemPolicy());
Expand Down Expand Up @@ -159,5 +167,7 @@ public void Insert(string key, object value, CacheDependency? dependencies, Date

public int Count => (int)_cache.GetCount();

internal ObjectCache ObjectCache => _cache;

public IEnumerator GetEnumerator() => ((IEnumerable)_cache).GetEnumerator();
}
175 changes: 174 additions & 1 deletion src/Microsoft.AspNetCore.SystemWebAdapters/Caching/CacheDependency.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,184 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;

namespace System.Web.Caching;

public class CacheDependency
public class CacheDependency : IDisposable
{
private readonly List<ChangeMonitor> changeMonitors = new();
private bool hasChanged;
private bool disposedValue;
private DateTime utcLastModified;
private Action<object, EventArgs>? dependencyChangedAction;
private readonly DateTime utcStart;
private bool initCompleted;
private string? uniqueId;
private bool uniqueIdInitialized;

internal CacheDependency()
{
FinishInit();
}

public CacheDependency(string filename) : this(filename, DateTime.MaxValue) { }

public CacheDependency(string filename, DateTime start) : this(new[] { filename }, null, null, start) { }

public CacheDependency(string[] filenames) : this(filenames, null, null, DateTime.MaxValue) { }

public CacheDependency(string[] filenames, DateTime start) : this(filenames, null, null, start) { }

public CacheDependency(string[]? filenames, string[]? cachekeys, DateTime start) :
this(filenames, cachekeys, null, start)
{ }

public CacheDependency(
string[]? filenames,
string[]? cachekeys,
CacheDependency? dependency,
DateTime start)
{
utcLastModified = DateTime.MinValue;
if (start != DateTime.MaxValue && start.Kind != DateTimeKind.Utc)
{
start = start.ToUniversalTime();
}
utcStart = start;

if (filenames is not null && filenames.Length != 0)
{
changeMonitors.Add(new HostFileChangeMonitor(filenames.ToList()));
}

if (cachekeys is not null && cachekeys.Length != 0)
{
changeMonitors.Add(HttpRuntime.Cache.ObjectCache
.CreateCacheEntryChangeMonitor(cachekeys));
}

if (dependency is not null)
{
changeMonitors.Add(dependency.GetChangeMonitor());
}

FinishInit();
}

protected internal void FinishInit()
{
hasChanged = changeMonitors.Any(cm => cm.HasChanged && (cm.GetLastModifiedUtc() > utcStart));
utcLastModified = changeMonitors.Max(cm => cm.GetLastModifiedUtc());
if (hasChanged)
{
NotifyDependencyChanged(this, EventArgs.Empty);
}
changeMonitors.ForEach(cm => cm.NotifyOnChanged(NotifyOnChanged));
initCompleted = true;
}

private void NotifyOnChanged(object state) => NotifyDependencyChanged(this, new ChangeNotificationEventArgs(state));

private class ChangeNotificationEventArgs : EventArgs
{
public ChangeNotificationEventArgs(object? state) => State = state;

public object? State { get; }
}

protected void NotifyDependencyChanged(object sender, EventArgs e)
{
if (initCompleted && DateTime.UtcNow > utcStart)
{
hasChanged = true;
utcLastModified = DateTime.UtcNow;
dependencyChangedAction?.Invoke(sender, e);
}
}

protected void SetUtcLastModified(DateTime utcLastModified) => this.utcLastModified = utcLastModified;

public void SetCacheDependencyChanged(Action<object, EventArgs> dependencyChangedAction) =>
this.dependencyChangedAction = dependencyChangedAction;

public virtual string[] GetFileDependencies() => changeMonitors.OfType<FileChangeMonitor>().SelectMany(cm=>cm.FilePaths).ToArray();

public bool HasChanged => hasChanged;

public DateTime UtcLastModified => changeMonitors
.OfType<FileChangeMonitor>()
.Select(fcm => fcm.LastModified.DateTime)
.Concat(new[] { utcLastModified })
.Max();

public virtual string? GetUniqueID()
{
if (!uniqueIdInitialized) {
uniqueId = changeMonitors.Any(cm => cm.UniqueId is null) ?
null :
string.Join(":", changeMonitors.Select(cm => cm.UniqueId));
uniqueIdInitialized = true;
}
return uniqueId;
}


#region "IDisposable"
protected virtual void DependencyDispose() { }

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
foreach (var changeMonitor in changeMonitors)
{
changeMonitor?.Dispose();
}
changeMonitors.Clear();

DependencyDispose();
}
disposedValue = true;
}
}

public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion

internal IEnumerable<ChangeMonitor> ChangeMonitors { get => changeMonitors; }
internal ChangeMonitor GetChangeMonitor() => new CacheDependencyChangeMonitor(this);

internal class CacheDependencyChangeMonitor : ChangeMonitor
{
private readonly CacheDependency cacheDependency;

internal CacheDependencyChangeMonitor(CacheDependency cacheDependency)
{
this.cacheDependency = cacheDependency;
cacheDependency.SetCacheDependencyChanged((state, _) => OnChanged(state));
InitializationComplete();
}

public override string? UniqueId => cacheDependency.GetUniqueID();

public DateTimeOffset LastModified => cacheDependency.UtcLastModified;

protected override void Dispose(bool disposing)
{
if (disposing)
{
cacheDependency?.Dispose();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using static System.Web.Caching.CacheDependency;

namespace System.Runtime.Caching;

internal static class ChangeMonitorExtensions
{
internal static DateTimeOffset GetLastModified(this ChangeMonitor changeMonitor) => changeMonitor switch
{
FileChangeMonitor fcm => fcm.LastModified,
CacheEntryChangeMonitor cecm => cecm.LastModified,
CacheDependencyChangeMonitor cdcm => cdcm.LastModified,
_ => DateTimeOffset.MinValue
};

internal static DateTime GetLastModifiedUtc(this ChangeMonitor changeMonitor) => changeMonitor.GetLastModified().UtcDateTime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
#pragma warning disable CA1716 // Using a reserved keyword as the name of a virtual/interface member makes it harder for consumers in other languages to override/implement the member.
#pragma warning disable CA1054 // URI parameters should not be strings
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member

#pragma warning disable CA1063 // Implement IDisposable Correctly
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
#pragma warning disable CA1716 // Using a reserved keyword as the name of a virtual/interface member makes it harder for consumers in other languages to override/implement the member.
#pragma warning disable CA1054 // URI parameters should not be strings
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member

#pragma warning disable CA1063 // Implement IDisposable Correctly
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
namespace System.Web
{
public partial class HttpBrowserCapabilities : System.Web.Configuration.HttpCapabilitiesBase
Expand Down Expand Up @@ -64,6 +65,7 @@ internal HttpContext() { }
public partial class HttpContextBase : System.IServiceProvider
{
protected HttpContextBase() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public virtual System.Web.Caching.Cache Cache { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual bool IsDebuggingEnabled { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual System.Collections.IDictionary Items { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public virtual System.Web.HttpRequestBase Request { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
Expand All @@ -77,6 +79,7 @@ public partial class HttpContextBase : System.IServiceProvider
public partial class HttpContextWrapper : System.Web.HttpContextBase
{
public HttpContextWrapper(System.Web.HttpContext httpContext) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public override System.Web.Caching.Cache Cache { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override bool IsDebuggingEnabled { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override System.Collections.IDictionary Items { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public override System.Web.HttpRequestBase Request { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
Expand Down Expand Up @@ -374,6 +377,7 @@ public sealed partial class HttpRuntime
internal HttpRuntime() { }
public static string AppDomainAppPath { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public static string AppDomainAppVirtualPath { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public static System.Web.Caching.Cache Cache { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
}
public partial class HttpServerUtility
{
Expand Down Expand Up @@ -488,9 +492,25 @@ public sealed partial class Cache : System.Collections.IEnumerable
public void Insert(string key, object value, System.Web.Caching.CacheDependency dependencies, System.DateTime absoluteExpiration, System.TimeSpan slidingExpiration, System.Web.Caching.CacheItemUpdateCallback onUpdateCallback) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public object Remove(string key) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public partial class CacheDependency
{
internal CacheDependency() { }
public partial class CacheDependency : System.IDisposable
{
public CacheDependency(string filename) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public CacheDependency(string filename, System.DateTime start) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public CacheDependency(string[] filenames) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public CacheDependency(string[] filenames, System.DateTime start) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public CacheDependency(string[] filenames, string[] cachekeys, System.DateTime start) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public CacheDependency(string[] filenames, string[] cachekeys, System.Web.Caching.CacheDependency dependency, System.DateTime start) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public bool HasChanged { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public System.DateTime UtcLastModified { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
protected virtual void DependencyDispose() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
protected internal void FinishInit() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public virtual string[] GetFileDependencies() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public virtual string GetUniqueID() { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
protected void NotifyDependencyChanged(object sender, System.EventArgs e) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
public void SetCacheDependencyChanged(System.Action<object, System.EventArgs> dependencyChangedAction) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
protected void SetUtcLastModified(System.DateTime utcLastModified) { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");}
}
public enum CacheItemPriority
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
#pragma warning disable CA1716 // Using a reserved keyword as the name of a virtual/interface member makes it harder for consumers in other languages to override/implement the member.
#pragma warning disable CA1054 // URI parameters should not be strings
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member

#pragma warning disable CA1063 // Implement IDisposable Correctly
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpBrowserCapabilities))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpBrowserCapabilitiesBase))]
[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Web.HttpBrowserCapabilitiesWrapper))]
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpContextBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ public virtual IPrincipal User

[return: NotNullIfNotNull("context")]
public static implicit operator HttpContextBase?(HttpContextCore? context) => context?.GetAdapterBase();

public virtual System.Web.Caching.Cache Cache => throw new NotImplementedException();
}
}
Loading