Loading...
Loading...
Compare original and translation side by side
TimeProviderIHttpClientFactoryTimeProviderIHttpClientFactorydetect-static-dependenciesTimeProviderSystem.IO.AbstractionsEnvironment.*Console.*Process.*detect-static-dependenciesTimeProviderSystem.IO.AbstractionsEnvironment.*Console.*Process.*detect-static-dependenciesmigrate-static-to-wrapperdetect-static-dependenciesmigrate-static-to-wrapper| Input | Required | Description |
|---|---|---|
| Static category | Yes | Which category: |
| Target framework | Yes | The |
| DI container | No | Which DI framework: |
| Namespace | No | Target namespace for generated wrapper code |
| 输入项 | 是否必填 | 描述 |
|---|---|---|
| 静态类别 | 是 | 类别选项: |
| 目标框架 | 是 | 来自 |
| DI容器 | 否 | DI框架选项: |
| 命名空间 | 否 | 生成的包装代码的目标命名空间 |
| Category | .NET 8+ | .NET 6-7 | .NET Framework |
|---|---|---|---|
| Time | | | Custom |
| File system | | Same | Same |
| HTTP | | Same | Same |
| Environment | Custom | Same | Same |
| Console | Custom | Same | Same |
| Process | Custom | Same | Same |
| 类别 | .NET 8+ | .NET 6-7 | .NET Framework |
|---|---|---|---|
| 时间 | | 通过 | 自定义 |
| 文件系统 | | 同上 | 同上 |
| HTTP | | 同上 | 同上 |
| 环境 | 自定义 | 同上 | 同上 |
| 控制台 | 自定义 | 同上 | 同上 |
| 进程 | 自定义 | 同上 | 同上 |
builder.Services.AddSingleton(TimeProvider.System);public class OrderProcessor(TimeProvider timeProvider)
{
public bool IsExpired(Order order)
=> timeProvider.GetUtcNow() > order.ExpiresAt;
}FakeTimeProvider// Requires Microsoft.Extensions.TimeProvider.Testing NuGet
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 1, 15, 0, 0, 0, TimeSpan.Zero));
var processor = new OrderProcessor(fakeTime);
fakeTime.Advance(TimeSpan.FromDays(1));
Assert.True(processor.IsExpired(order));builder.Services.AddSingleton(TimeProvider.System);public class OrderProcessor(TimeProvider timeProvider)
{
public bool IsExpired(Order order)
=> timeProvider.GetUtcNow() > order.ExpiresAt;
}FakeTimeProvider// Requires Microsoft.Extensions.TimeProvider.Testing NuGet
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 1, 15, 0, 0, 0, TimeSpan.Zero));
var processor = new OrderProcessor(fakeTime);
fakeTime.Advance(TimeSpan.FromDays(1));
Assert.True(processor.IsExpired(order));Microsoft.Bcl.TimeProviderMicrosoft.Bcl.TimeProviderbuilder.Services.AddHttpClient<MyService>()HttpClientbuilder.Services.AddHttpClient<MyService>()HttpClientnamespace <Namespace>;
/// <summary>
/// Abstraction over <static class> for testability.
/// </summary>
public interface I<WrapperName>
{
// One method per detected static call
<return type> <MethodName>(<parameters>);
}namespace <Namespace>;
/// <summary>
/// Abstraction over <static class> for testability.
/// </summary>
public interface I<WrapperName>
{
// One method per detected static call
<return type> <MethodName>(<parameters>);
}namespace <Namespace>;
/// <summary>
/// Default implementation that delegates to <static class>.
/// </summary>
public sealed class <WrapperName> : I<WrapperName>
{
public <return type> <MethodName>(<parameters>)
=> <StaticClass>.<Method>(<arguments>);
}namespace <Namespace>;
/// <summary>
/// Default implementation that delegates to <static class>.
/// </summary>
public sealed class <WrapperName> : I<WrapperName>
{
public <return type> <MethodName>(<parameters>)
=> <StaticClass>.<Method>(<arguments>);
}// In Program.cs or Startup.cs:
builder.Services.AddSingleton<I<WrapperName>, <WrapperName>>();// In Program.cs or Startup.cs:
builder.Services.AddSingleton<I<WrapperName>, <WrapperName>>();System.IO.Abstractionsdotnet add package System.IO.Abstractionsbuilder.Services.AddSingleton<IFileSystem, FileSystem>();IFileSystempublic class ConfigLoader(IFileSystem fileSystem)
{
public string LoadConfig(string path)
=> fileSystem.File.ReadAllText(path);
}MockFileSystemdotnet add <TestProject> package System.IO.Abstractions.TestingHelpersvar mockFs = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ "/config.json", new MockFileData("{\"key\": \"value\"}") }
});
var loader = new ConfigLoader(mockFs);
Assert.Equal("{\"key\": \"value\"}", loader.LoadConfig("/config.json"));System.IO.Abstractionsdotnet add package System.IO.Abstractionsbuilder.Services.AddSingleton<IFileSystem, FileSystem>();IFileSystempublic class ConfigLoader(IFileSystem fileSystem)
{
public string LoadConfig(string path)
=> fileSystem.File.ReadAllText(path);
}MockFileSystemdotnet add <TestProject> package System.IO.Abstractions.TestingHelpersvar mockFs = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ "/config.json", new MockFileData("{\"key\": \"value\"}") }
});
var loader = new ConfigLoader(mockFs);
Assert.Equal("{\"key\": \"value\"}", loader.LoadConfig("/config.json"));public static class Clock
{
private static readonly AsyncLocal<Func<DateTimeOffset>?> s_override = new();
public static DateTimeOffset UtcNow
=> s_override.Value?.Invoke() ?? TimeProvider.System.GetUtcNow();
public static IDisposable Override(DateTimeOffset fixedTime)
{
s_override.Value = () => fixedTime;
return new Scope();
}
private sealed class Scope : IDisposable
{
public void Dispose() => s_override.Value = null;
}
}AsyncLocal<T>static readonlypublic static class Clock
{
private static readonly AsyncLocal<Func<DateTimeOffset>?> s_override = new();
public static DateTimeOffset UtcNow
=> s_override.Value?.Invoke() ?? TimeProvider.System.GetUtcNow();
public static IDisposable Override(DateTimeOffset fixedTime)
{
s_override.Value = () => fixedTime;
return new Scope();
}
private sealed class Scope : IDisposable
{
public void Dispose() => s_override.Value = null;
}
}AsyncLocal<T>static readonlyAbstractions/Interfaces/Infrastructure/Services/Abstractions/Interfaces/Infrastructure/Services/AddSingletonAddTransientTimeProviderISystemClockAsyncLocal<T>AddSingletonAddTransientTimeProviderISystemClockAsyncLocal<T>| Pitfall | Solution |
|---|---|
| Wrapping ALL members of a static class | Only wrap methods actually called in the codebase |
| Custom time wrapper on .NET 8+ | Use built-in |
| Custom file system wrapper | Prefer |
| Registering scoped when singleton suffices | Stateless wrappers should be |
| Forgetting test helper packages | |
Ambient context without | Non-async |
| 陷阱 | 解决方案 |
|---|---|
| 包装静态类的所有成员 | 仅包装代码库中实际调用的方法 |
| 在.NET 8+中使用自定义时间包装类 | 改用内置的 |
| 使用自定义文件系统包装类 | 优先采用 |
| 当单例足够时仍注册为作用域 | 无状态包装类应使用 |
| 忘记使用测试辅助包 | 时间测试使用 |
环境上下文未使用 | 非异步的 |