Project-level AI context for the AspectCore-Framework repository. Generated from the current code tree (version 3.0.0-rc.1). Keep this file lean; link to external docs instead of inlining them.
AspectCore-Framework is an Aspect-Oriented Programming (AOP) framework for .NET. It weaves interceptors into service methods through two equivalent proxy engines that share one contract (AspectCore.Abstractions):
- DynamicProxy (runtime, IL emit via
System.Reflection.Emit) — lives inAspectCore.Core. - Source Generator (compile-time, Roslyn
IIncrementalGenerator) — lives inAspectCore.SourceGenerator.
Tech stack (concrete):
- .NET target frameworks (libraries):
net10.0;net9.0;net8.0;net6.0(SourceGenerator isnetstandard2.0only, as required by Roslyn). - .NET target frameworks (tests):
net10.0;net9.0;net8.0;net6.0. - C# language version:
13.0forsrc/(set inbuild/common.props);13.0for tests. - Test framework: xUnit
2.9.2+Microsoft.NET.Test.Sdk 17.12.0. - Coverage:
coverlet.msbuild 6.0.2(Cobertura), thresholds enforced in CI (unit 95%, E2E 80%). - DI integrations: MsDI, Autofac
[7.0.0, 8.0.0), Castle.Windsor6.0.0, LightInject6.6.4, plus Generic Host and ASP.NET Core adapters. - Benchmarks: BenchmarkDotNet
0.14.0. - Version source of truth:
build/version.props(VersionMajor=3,VersionMinor=0,VersionPatch=0,VersionQuality=rc.1).
| Directory | Purpose | Local Documentation |
|---|---|---|
src/AspectCore.Abstractions/ |
Pure contracts: interfaces, attributes, enums. No implementation. Namespaces AspectCore.DynamicProxy, AspectCore.Configuration, AspectCore.DependencyInjection. |
– |
src/AspectCore.Core/ |
Runtime DynamicProxy engine (IL emit), built-in IoC container (ServiceContext/ServiceResolver), interceptor pipeline, configuration. AllowUnsafeBlocks=true. |
– |
src/AspectCore.Extensions.Reflection/ |
Standalone high-performance reflection library. No AspectCore project references; consumed by Core. | – |
src/AspectCore.SourceGenerator/ |
Roslyn compile-time proxy generator (AspectCoreProxyGenerator). IsRoslynComponent=true, OutputItemType=Analyzer. No project references. |
– |
src/AspectCore.Extensions.DependencyInjection/ |
Microsoft.Extensions.DependencyInjection (MsDI) adapter. | – |
src/AspectCore.Extensions.Autofac/ |
Autofac adapter. | – |
src/AspectCore.Extensions.Windsor/ |
Castle.Windsor adapter. | – |
src/AspectCore.Extensions.LightInject/ |
LightInject adapter. | – |
src/AspectCore.Extensions.Hosting/ |
Generic Host integration. | – |
src/AspectCore.Extensions.AspNetCore/ |
ASP.NET Core web integration (FrameworkReference Microsoft.AspNetCore.App). |
– |
src/AspectCore.Extensions.AspectScope/ |
ScopedContext / aspect scope extension. | – |
src/AspectCore.Extensions.Configuration/ |
Configuration injection via Microsoft.Extensions.Configuration. |
– |
src/AspectCore.Extensions.DataAnnotations/ |
DataAnnotations-based validation extension. | – |
src/AspectCore.Extensions.DataValidation/ |
Data validation extension. | – |
src/AspectCore.Extensions.CastleCompat/ |
Castle DynamicProxy compatibility shim for gradual migration to AspectCore. Depends on Castle.Core. Targets net10.0;net9.0;net8.0. |
– |
tests/ |
10 xUnit test projects + AspectCore.NativeAot.E2E (a PublishAot executable, not xUnit). tests/Directory.Build.props injects coverlet.msbuild. |
– |
sample/ |
4 runnable sample projects (DI console, AspectScope, Autofac, DataAnnotations). | – |
benchmark/ benchmarks/ |
BenchmarkDotNet projects. | – |
docs/ |
Architecture, guide, getting-started, development, testing docs (bilingual; docs/en/ for English). |
docs/README.md |
build/ |
common.props, version.props, sign.props, aspectcore.snk. |
– |
.github/workflows/ |
build-ci.yml, build-pr-ci.yml, release.yml. |
– |
Dependency direction (acyclic, bottom-up):
Abstractions + Extensions.Reflection ◄── Core ◄── all Extensions. SourceGenerator is independent (no project refs; generated code references Core/Abstractions at runtime).
No
global.jsonexists — SDK is not pinned. CI installs6.0.x / 8.0.x / 9.0.x / 10.0.x. Locally you need an SDK that can build the target frameworks you care about.
# Whole solution
dotnet build AspectCore-Framework.sln --configuration Release
# Per-project (matches CI behavior)
for project in $(find ./src -name "*.csproj"); do
dotnet build --configuration Release "$project"
done
# Build with explicit version (CI release flow)
dotnet build --configuration Release ./src/AspectCore.Core/AspectCore.Core.csproj -p:Version=3.0.0-rc.1
# Format check (PR CI gate — currently warns, does not fail)
dotnet format AspectCore-Framework.sln --verify-no-changes
# Auto-format locally before pushing
dotnet format AspectCore-Framework.slnPack / publish NuGet:
# Pack all src projects (CI build flow, includes source/symbols)
for project in $(find ./src -name "*.csproj"); do
dotnet pack --configuration Release --no-build "$project" \
-p:PackageVersion=3.0.0-rc.1 --include-source --output ./artifacts/packages
done
# Pack single project
dotnet pack --configuration Release --no-build \
./src/AspectCore.Core/AspectCore.Core.csproj \
-p:PackageVersion=3.0.0-rc.1 --output ./artifacts/packagesRun a sample:
dotnet run --configuration Release -f net9.0 \
--project ./sample/AspectCore.Extensions.DependencyInjection.ConsoleSample/AspectCore.Extensions.DependencyInjection.ConsoleSample.csprojRun a benchmark:
dotnet run --configuration Release -f net9.0 \
--project ./benchmark/AspectCore.Core.Benchmark/AspectCore.Core.Benchmark.csproj# All test projects (CI behavior)
for project in $(find ./tests -name "*.csproj"); do
dotnet test --configuration Release "$project"
done
# Single test project
dotnet test --configuration Release ./tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj
# Single target framework (tests target net10.0;net9.0;net8.0;net6.0)
dotnet test --configuration Release -f net9.0 ./tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj
# Filter a single test (xUnit filter syntax)
dotnet test --configuration Release ./tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj \
--filter "FullyQualifiedName~AspectCore.Core.Tests.ProxyTests"
# With coverage (matches .github/scripts/check-coverage.sh)
dotnet test ./tests/AspectCore.Core.Tests/AspectCore.Core.Tests.csproj \
--configuration Release -f net9.0 \
/p:CollectCoverage=true /p:CoverletOutputFormat=cobertura \
/p:CoverletOutput=./TestResults/ \
"/p:Include=[AspectCore.Core]*"Coverage thresholds (blocking CI gates): unit tests 95%, E2E tests 80%. E2E coverage filters to [AspectCore.Core]* and [AspectCore.Abstractions]*. The coverage script sets DOTNET_ROLL_FORWARD=Major to run on newer runtimes.
Engine parity: tests/AspectCore.Core.Tests/EngineParity/ enforces that DynamicProxy and the Source Generator behave identically. Any change to the core interceptor/proxy engine MUST keep both engines in sync and pass these tests.
- Default branch:
master(PR merge target). There is nomainbranch. - Branch naming:
feat/<short-description>fromorigin/master. Also used:fix/,ci/,chore/,docs/,test/,feature/. - Commit messages (CRITICAL): Conventional Commits —
feat:,fix:,docs:,test:,ci:,chore:. Example:fix: implement keyed service resolution in IServiceResolver (#387). - PR merge: squash-merge; the merge commit title includes the PR number
(#number). - Committer identity (CRITICAL): must be
Haoyang Liu/liuhaoyang1221@hotmail.com. - Avoid
Co-Authored-Bytrailers: recently adopted policy (most recent commits follow this; older commits may still contain them). Prefer commits without these trailers. - Release flow: tag
v*→release.ymlbuilds, tests, packs, publishes to NuGet.org + MyGet, creates a GitHub Release, then auto-bumpsbuild/version.propsto the next minor via an auto-PR. Patch bumps are manual.
Formatting is enforced by dotnet format (no .editorconfig; uses .NET SDK defaults). src/Directory.Build.props enables .NET analyzers at informational level (non-blocking). LangVersion=13.0 for src/.
Namespaces — block-scoped, not file-scoped:
// ✅
namespace AspectCore.DynamicProxy
{
public interface IInterceptor { ... }
}
// ❌
namespace AspectCore.DynamicProxy;
public interface IInterceptor { ... }Private fields prefixed with _:
// ✅
private readonly IInterceptorSelector[] _interceptorSelectors;Public API contract/implementation split (CRITICAL):
- Interfaces and attributes go in
AspectCore.Abstractions(no implementation dependencies). - Implementations go in
AspectCore.Coreor the relevant extension package. - Dependency direction is
Abstractions ◄── Core ◄── Extensions— never reverse it, never add horizontal coupling between extension packages.
Naming conventions:
- Interfaces:
I-prefixed —IInterceptor,IServiceResolver,IAspectBuilder,IProxyGenerator. - Attribute-based interceptors: derive from
AbstractInterceptorAttribute; name ends withInterceptorAttribute(e.g.ServiceInterceptorAttribute,DataValidationInterceptorAttribute). - Non-attribute interceptors: derive from
AbstractInterceptor; name ends withInterceptor. - Other attributes: end with
Attribute(e.g.NonAspectAttribute,FromServiceContextAttribute,AspectCoreGenerateProxyAttribute). - Namespaces match the package/feature.
XML doc comments (/// <summary>) are required on public APIs.
Constructor null guards use ArgumentNullException(nameof(param)).
Async interceptor pattern:
public async Task Invoke(AspectContext context, AspectDelegate next)
{
// before
await next(context);
// after
}- ✅ Always do (MANDATORY, before any work) — read the development guidelines in full before starting any code change, and walk through the code review guidelines self-check checklist before opening a PR. The
aspectcore-dev-reviewskill (.agents/skills/aspectcore-dev-review/SKILL.md) is a quick reference only and does not replace reading the full guidelines. - ✅ Always do — keep DynamicProxy and the Source Generator behaviorally in sync; run the
EngineParity/tests after any core engine change. - ✅ Always do — run
dotnet formatlocally before pushing to avoid the CI lint gate. - ✅ Always do — put new public interfaces/attributes in
AspectCore.Abstractions; implementations inCoreor the relevant extension. - ✅ Always do — use Conventional Commits and the fixed committer identity (
Haoyang Liu). - ✅ Always do — after changing code functionality, check whether
AGENTS.md,README,ROADMAP, anddocs/have drifted from the new behavior; if so, update the affected docs in the same change so documentation stays in sync with the code. ⚠️ Ask first — bumpingbuild/version.props(release flow auto-bumps minor only; patch bumps need explicit approval).⚠️ Ask first — changing target frameworks orLangVersioninbuild/common.props(affects all packages and CI matrix).⚠️ Ask first — adding a new DI container integration or a new top-level package.⚠️ Ask first — addingCo-Authored-Bytrailers to commit messages (recently adopted policy; check with maintainer before including).- 🚫 Never do — commit generated proxy source from
AspectCore.SourceGenerator(it is emitted at compile time intoobj/, which is gitignored). - 🚫 Never do — reverse the
Abstractions ◄── Core ◄── Extensionsdependency direction, or add horizontal references between extension packages. - 🚫 Never do — commit secrets, NuGet API keys, or
artifacts/output. - 🚫 Never do — skip the 95% unit / 80% E2E coverage thresholds; they are blocking CI gates.
README.md— project overview, NuGet install table, quick start.ROADMAP.md— current roadmap and planned work.docs/README.md— documentation index.docs/architecture/overview.md— architecture and module design (Chinese).docs/architecture/module-design.md— contract/implementation split and dependency rules.docs/development/contributing.md— contribution rules, commit conventions, CI gates (Chinese).docs/development/development-guidelines.md— development standards: project structure recognition, command granularity, testing, performance, design principles.docs/development/code-review-guidelines.md— code review standards: review dimensions, blocking issues, self-check checklist..agents/skills/aspectcore-dev-review/SKILL.md— skill for AI agents: quick reference for development and review guidelines.docs/guide/interceptor.md— how to write interceptors.docs/testing/— testing guidance.docs/en/— English documentation mirror..github/workflows/build-ci.yml,build-pr-ci.yml,release.yml— CI/CD definitions.
No top-level deepwiki/ directory exists in this repository.