/migrate-static-to-wrapper
Replace existing static dependency call sites with a wrapper or built-in abstraction that already exists or is registered in DI, across a bounded scope (file, project, namespace). USE FOR: replace DateTime.UtcNow/DateTime.Now with TimeProvider and add the constructor parameter,
$ npx -y skills add managedcode/dotnet-skills --skill migrate-static-to-wrapper --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/migrate-static-to-wrapper
Context preview
The summary Claude sees to decide when to auto-load this skill.
Replace existing static dependency call sites with a wrapper or built-in abstraction that already exists or is registered in DI, across a bounded scope (file, project, namespace). USE FOR: replace DateTime.UtcNow/DateTime.Now with TimeProvider and add the constructor parameter,
SKILL.md
migrate-static-to-wrapper.SKILL.mdname: migrate-static-to-wrapper
description: >
Replace existing static dependency call sites with a wrapper or built-in
abstraction that already exists or is registered in DI, across a bounded scope
(file, project, namespace).
USE FOR: replace DateTime.UtcNow/DateTime.Now with TimeProvider and add the
constructor parameter, migrate static call sites to a wrapper already in DI,
bulk replace File.* with IFileSystem, scoped migration of statics in only
certain files, update unit tests to a fake time source, make an existing
static or utility class testable by adding an ambient
TimeProvider/IFileSystem seam while every current call site keeps compiling,
behavior-preserving time refactors that must keep the same DateTimeKind.
DO NOT USE FOR: detecting statics (use detect-static-dependencies), designing a
brand-new wrapper interface that does not exist yet (use
generate-testability-wrappers), migrating between test frameworks.
license: MIT
Migrate Static to Wrapper
Perform mechanical, codemod-style replacement of static dependency call sites with calls to injected wrapper interfaces or built-in abstractions. Operates on a bounded scope (single file, project, or namespace) so migrations can be done incrementally.
When to Use
- After wrappers have been generated (via `generate-testability-wrappers`) or built-in abstractions identified
- Migrating `DateTime.UtcNow` → `TimeProvider.GetUtcNow()` across a project
- Migrating `File.*` → `IFileSystem.File.*` across a namespace
- Adding constructor injection for the new abstraction to affected classes
- Making a `static` utility class testable by adding an ambient seam (Step 3) while its existing call sites keep
compiling unchanged
- Incremental migration: one project or namespace at a time
When Not to Use
- No wrapper or abstraction exists yet and one must be designed from scratch (use `generate-testability-wrappers` first).
A built-in abstraction such as `TimeProvider` or `IFileSystem` always counts as existing.
- The user wants to detect statics, not migrate them (use `detect-static-dependencies`)
- Migrating between test frameworks (use the appropriate migration skill)
> A class that is `static`, or a project with no DI container, is **not** a reason to skip this skill — that is exactly > what the ambient seam in Step 3 is for. Use it whenever the call sites must keep compiling unchanged.
Inputs
| Input | Required | Description | |-------|----------|-------------| | Static pattern | Yes | What to replace (e.g., `DateTime.UtcNow`, `File.ReadAllText`) | | Replacement abstraction | Yes | What to use instead (e.g., `TimeProvider`, `IFileSystem`) | | Scope | Yes | File path, project (.csproj), namespace, or directory to migrate | | Injection strategy | No | `constructor` (default), `primary-constructor`, or `ambient` |
Workflow
Step 1: Verify prerequisites
Before modifying any code:
1. **Confirm the wrapper/abstraction exists**: Check that the interface or built-in abstraction is available in the project. For `TimeProvider`, verify the target framework is .NET 8+ or `Microsoft.Bcl.TimeProvider` is referenced. For `System.IO.Abstractions`, verify the NuGet package is referenced.
2. **Confirm DI registration exists**: Check `Program.cs` or `Startup.cs` for the service registration. If missing, add it before proceeding.
3. **Identify all files in scope**: List the `.cs` files that will be modified. Exclude test projects, `obj/`, `bin/`, and generated code.
Step 2: Plan the migration for each file
**Migrate exactly what was asked — nothing adjacent.** If the user named a member (`DateTime.UtcNow`), migrate only that member and leave siblings such as `DateTime.Now` untouched. If the user named files, do not touch other files. Never migrate a call site whose comment or name marks it as deliberate (e.g. `// intentional local time`). List everything you deliberately left alone under "Remaining (out of scope)" so the user can ask for it in a follow-up; suggesting is fine, silently widening the scope is not.
For each file containing the static pattern, determine:
1. **Which class(es) contain the call sites** — identify the class declarations 2. **Whether the class already has the dependency injected** — check constructors for existing `TimeProvider`, `IFileSystem`, etc. parameters 3. **The replacement expression** for each call site
Replacement mapping
| Category | Original | DI replacement | |----------|----------|----------------| | Time | `DateTime.Now` | `_timeProvider.GetLocalNow().LocalDateTime` | | Time | `DateTime.UtcNow` | `_timeProvider.GetUtcNow().UtcDateTime` | | Time | `DateTime.Today` | `_timeProvider.GetLocalNow().LocalDateTime.Date` | | Time | `DateTimeOffset.Now` | `_timeProvider.GetLocalNow()` | | Time | `DateTimeOffset.UtcNow` | `_timeProvider.GetUtcNow()` | | File | `File.ReadAllText(path)` | `_fileSystem.File.ReadAllText(path)` | | File | `File.WriteAllText(path, text)` | `_fileSystem.File.WriteAllText(path, text)` | | File | `File.Exists(path)` | `_fileSystem.File.Exists(path)` | | File | `Directory.Exists(path)` | `_fileSystem.Directory.Exists(path)` | | Env | `Environment.GetEnvironmentVariable(name)` | `_env.GetEnvironmentVariable(name)` | | Console | `Console.WriteLine(msg)` | `_console.WriteLine(msg)` | | Process | `Process.Start(info)` | `_processRunner.Start(info)` |
Apply the same pattern for other members in each category.
> **Preserve `DateTimeKind` — this is the most common silent regression.** `TimeProvider.GetUtcNow()` / `GetLocalNow()` return a `DateTimeOffset`. Converting back to `DateTime` **must keep the original `Kind`**, otherwise you introduce a behavioral change even though the code still compiles: > > - `DateTime.UtcNow` has `Kind == Utc` → use `.UtcDateTime` (**not** `.DateTime`, which yields `Kind == Unspecified`). > - `DateTime.Now` has `Kind == Local` → use `.LocalDateTime` (**not** `.DateTime`). > - When a call site consumes a `
Read more
name: migrate-static-to-wrapper description: > Replace existing static dependency call sites with a wrapper or built-in abstraction that already exists or is registered in DI, across a bounded scope (file, project, namespace). USE FOR: replace DateTime.UtcNow/DateTime.Now with TimeProvider and add the constructor parameter, migrate static call sites to a wrapper already in DI, bulk replace File.* with IFileSystem, scoped migration of statics in only certain files, update unit tests to a fake time source, make an existing static or utility class testable by adding an ambient TimeProvider/IFileSystem seam while every current call site keeps compiling, behavior-preserving time refactors that must keep the same DateTimeKind. DO NOT USE FOR: detecting statics (use detect-static-dependencies), designing a brand-new wrapper interface that does not exist yet (use generate-testability-wrappers), migrating between test frameworks. license: MIT
Migrate Static to Wrapper
Perform mechanical, codemod-style replacement of static dependency call sites with calls to injected wrapper interfaces or built-in abstractions. Operates on a bounded scope (single file, project, or namespace) so migrations can be done incrementally.
When to Use
- After wrappers have been generated (via `generate-testability-wrappers`) or built-in abstractions identified
- Migrating `DateTime.UtcNow` → `TimeProvider.GetUtcNow()` across a project
- Migrating `File.*` → `IFileSystem.File.*` across a namespace
- Adding constructor injection for the new abstraction to affected classes
- Making a `static` utility class testable by adding an ambient seam (Step 3) while its existing call sites keep
compiling unchanged
- Incremental migration: one project or namespace at a time
When Not to Use
- No wrapper or abstraction exists yet and one must be designed from scratch (use `generate-testability-wrappers` first).
A built-in abstraction such as `TimeProvider` or `IFileSystem` always counts as existing.
- The user wants to detect statics, not migrate them (use `detect-static-dependencies`)
- Migrating between test frameworks (use the appropriate migration skill)
> A class that is `static`, or a project with no DI container, is **not** a reason to skip this skill — that is exactly > what the ambient seam in Step 3 is for. Use it whenever the call sites must keep compiling unchanged.
Inputs
| Input | Required | Description | |-------|----------|-------------| | Static pattern | Yes | What to replace (e.g., `DateTime.UtcNow`, `File.ReadAllText`) | | Replacement abstraction | Yes | What to use instead (e.g., `TimeProvider`, `IFileSystem`) | | Scope | Yes | File path, project (.csproj), namespace, or directory to migrate | | Injection strategy | No | `constructor` (default), `primary-constructor`, or `ambient` |
Workflow
Step 1: Verify prerequisites
Before modifying any code:
1. **Confirm the wrapper/abstraction exists**: Check that the interface or built-in abstraction is available in the project. For `TimeProvider`, verify the target framework is .NET 8+ or `Microsoft.Bcl.TimeProvider` is referenced. For `System.IO.Abstractions`, verify the NuGet package is referenced.
2. **Confirm DI registration exists**: Check `Program.cs` or `Startup.cs` for the service registration. If missing, add it before proceeding.
3. **Identify all files in scope**: List the `.cs` files that will be modified. Exclude test projects, `obj/`, `bin/`, and generated code.
Step 2: Plan the migration for each file
**Migrate exactly what was asked — nothing adjacent.** If the user named a member (`DateTime.UtcNow`), migrate only that member and leave siblings such as `DateTime.Now` untouched. If the user named files, do not touch other files. Never migrate a call site whose comment or name marks it as deliberate (e.g. `// intentional local time`). List everything you deliberately left alone under "Remaining (out of scope)" so the user can ask for it in a follow-up; suggesting is fine, silently widening the scope is not.
For each file containing the static pattern, determine:
1. **Which class(es) contain the call sites** — identify the class declarations 2. **Whether the class already has the dependency injected** — check constructors for existing `TimeProvider`, `IFileSystem`, etc. parameters 3. **The replacement expression** for each call site
Replacement mapping
| Category | Original | DI replacement | |----------|----------|----------------| | Time | `DateTime.Now` | `_timeProvider.GetLocalNow().LocalDateTime` | | Time | `DateTime.UtcNow` | `_timeProvider.GetUtcNow().UtcDateTime` | | Time | `DateTime.Today` | `_timeProvider.GetLocalNow().LocalDateTime.Date` | | Time | `DateTimeOffset.Now` | `_timeProvider.GetLocalNow()` | | Time | `DateTimeOffset.UtcNow` | `_timeProvider.GetUtcNow()` | | File | `File.ReadAllText(path)` | `_fileSystem.File.ReadAllText(path)` | | File | `File.WriteAllText(path, text)` | `_fileSystem.File.WriteAllText(path, text)` | | File | `File.Exists(path)` | `_fileSystem.File.Exists(path)` | | File | `Directory.Exists(path)` | `_fileSystem.Directory.Exists(path)` | | Env | `Environment.GetEnvironmentVariable(name)` | `_env.GetEnvironmentVariable(name)` | | Console | `Console.WriteLine(msg)` | `_console.WriteLine(msg)` | | Process | `Process.Start(info)` | `_processRunner.Start(info)` |
Apply the same pattern for other members in each category.
> **Preserve `DateTimeKind` — this is the most common silent regression.** `TimeProvider.GetUtcNow()` / `GetLocalNow()` return a `DateTimeOffset`. Converting back to `DateTime` **must keep the original `Kind`**, otherwise you introduce a behavioral change even though the code still compiles: > > - `DateTime.UtcNow` has `Kind == Utc` → use `.UtcDateTime` (**not** `.DateTime`, which yields `Kind == Unspecified`). > - `DateTime.Now` has `Kind == Local` → use `.LocalDateTime` (**not** `.DateTime`). > - When a call site consumes a `
Stop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.
Repo: managedcode/dotnet-skills
Other skills on dotnet-skills.
- /aspnet-core
Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging, and deployment patterns on current .NET. USE FOR: working on ASP.NET Core apps, services, or middleware; changing auth, routing, configuration,
Open skill - /aspire
Build, upgrade, and operate Aspire 13.4.x C# or TypeScript application hosts with the current CLI, AppHost, ServiceDefaults, integrations, dashboard, testing, MCP, and deployment patterns for distributed apps. USE FOR: Aspire.AppHost.Sdk, Aspire.Hosting.*,
Open skill - /azure-functions
Build, review, or migrate Azure Functions in .NET with correct execution model, isolated worker setup, bindings, DI, and Durable Functions patterns. USE FOR: working on Azure Functions in .NET; migrating from the in-process model to the isolated worker model; adding Durable
Open skill - /blazor
Build and review Blazor applications across server, WebAssembly, web app, and hybrid scenarios with correct component design, state flow, rendering, and hosting choices. USE FOR: building interactive web UIs with C# instead of JavaScript; choosing between Server, WebAssembly, or
Open skill - /entity-framework6
Maintain or migrate EF6-based applications with realistic guidance on what to keep, what to modernize, and when EF Core is or is not the right next step. USE FOR: EF6 codebases; runtime versus ORM migration decisions; EDMX, code-first, ObjectContext, and legacy data-access
Open skill - /entity-framework-core
Design, tune, or review EF Core data access with proper modeling, migrations, query translation, performance, and lifetime management for modern .NET applications. USE FOR: DbContext, migrations, model configuration, EF queries, tracking, loading, performance, transactions, and
Open skill

