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
45 changes: 45 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,26 @@
"stopAtEntry": false,
"type": "coreclr"
},
{
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src/DotGraphExample",
"name": "DotGraphExample",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/DotGraphExample/bin/Debug/net10.0/DotGraphExample.dll",
"request": "launch",
"stopAtEntry": false,
"type": "coreclr"
},
{
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src/DynamicProxyExample",
"name": "DynamicProxyExample",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/DynamicProxyExample/bin/Debug/net10.0/DynamicProxyExample.dll",
"request": "launch",
"stopAtEntry": false,
"type": "coreclr"
},
{
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src/GenericHostBuilderExample",
Expand All @@ -75,6 +95,21 @@
"stopAtEntry": false,
"type": "coreclr"
},
{
"cwd": "${workspaceFolder}/src/MultitenantExample.AspNetCore",
"launchSettingsProfile": "MultitenantExample.AspNetCore",
"name": "MultitenantExample.AspNetCore",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/MultitenantExample.AspNetCore/bin/Debug/net10.0/MultitenantExample.AspNetCore.dll",
"request": "launch",
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\\\bNow listening on:\\\\s+(https?://\\\\S+)",
"uriFormat": "%s/api/tenant?tenant=alpha"
},
"stopAtEntry": false,
"type": "coreclr"
},
{
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src/MultitenantExample.ConsoleApplication",
Expand All @@ -84,6 +119,16 @@
"request": "launch",
"stopAtEntry": false,
"type": "coreclr"
},
{
"console": "integratedTerminal",
"cwd": "${workspaceFolder}/src/PoolingExample",
"name": "PoolingExample",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/PoolingExample/bin/Debug/net10.0/PoolingExample.dll",
"request": "launch",
"stopAtEntry": false,
"type": "coreclr"
}
],
"version": "0.2.0"
Expand Down
4 changes: 4 additions & 0 deletions Examples.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
<Project Path="src/AspNetCoreChildLifetimeScope/AspNetCoreChildLifetimeScope.csproj" />
<Project Path="src/AspNetCoreExample/AspNetCoreExample.csproj" />
<Project Path="src/AspNetCoreNoStartupExample/AspNetCoreNoStartupExample.csproj" />
<Project Path="src/MultitenantExample.AspNetCore/MultitenantExample.AspNetCore.csproj" />
</Folder>
<Folder Name="/Hosting and Core Features/">
<Project Path="src/AttributeMetadataExample/AttributeMetadataExample.csproj" />
<Project Path="src/ConfigurationExample/ConfigurationExample.csproj" />
<Project Path="src/ConfigurationExampleInterface/ConfigurationExampleInterface.csproj" />
<Project Path="src/ConfigurationExamplePlugin/ConfigurationExamplePlugin.csproj" />
<Project Path="src/DotGraphExample/DotGraphExample.csproj" />
<Project Path="src/DynamicProxyExample/DynamicProxyExample.csproj" />
<Project Path="src/GenericHostBuilderExample/GenericHostBuilderExample.csproj" />
<Project Path="src/MultitenantExample.ConsoleApplication/MultitenantExample.ConsoleApplication.csproj" />
<Project Path="src/PoolingExample/PoolingExample.csproj" />
</Folder>
<Folder Name="/NET Framework/">
<Project Path="src/MultitenantExample.MvcApplication/MultitenantExample.MvcApplication.csproj" />
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Each example has its own README with what it shows and how to run it.
| [AspNetCoreExample](src/AspNetCoreExample/README.md) | A `Startup` class whose `ConfigureContainer` takes a `ContainerBuilder` | [`Autofac.Extensions.DependencyInjection`](https://github.com/autofac/Autofac.Extensions.DependencyInjection) |
| [AspNetCoreNoStartupExample](src/AspNetCoreNoStartupExample/README.md) | The same wiring in the minimal hosting model, with no `Startup` class | [`Autofac.Extensions.DependencyInjection`](https://github.com/autofac/Autofac.Extensions.DependencyInjection) |
| [AspNetCoreChildLifetimeScope](src/AspNetCoreChildLifetimeScope/README.md) | Two hosts sharing one container, each rooted in its own child scope | [`Autofac.Extensions.DependencyInjection`](https://github.com/autofac/Autofac.Extensions.DependencyInjection) |
| [MultitenantExample.AspNetCore](src/MultitenantExample.AspNetCore/README.md) | Per-tenant registration overrides, with the tenant read from the query string | [`Autofac.AspNetCore.Multitenant`](https://github.com/autofac/Autofac.AspNetCore.Multitenant) |

### Hosting and core features

Expand All @@ -24,6 +25,9 @@ Each example has its own README with what it shows and how to run it.
| [ConfigurationExample](src/ConfigurationExample/README.md) | Registering from `autofac.json`, including an unreferenced plugin assembly | [`Autofac.Configuration`](https://github.com/autofac/Autofac.Configuration) |
| [AttributeMetadataExample](src/AttributeMetadataExample/README.md) | Metadata by string, class, interface, and attribute, then filtering on it | [`Autofac.Extras.AttributeMetadata`](https://github.com/autofac/Autofac.Extras.AttributeMetadata) |
| [MultitenantExample.ConsoleApplication](src/MultitenantExample.ConsoleApplication/README.md) | Per-tenant overrides with no web request in sight | [`Autofac.Multitenant`](https://github.com/autofac/Autofac.Multitenant) |
| [DynamicProxyExample](src/DynamicProxyExample/README.md) | Method interception, wired both on the registration and by attribute | [`Autofac.Extras.DynamicProxy`](https://github.com/autofac/Autofac.Extras.DynamicProxy) |
| [PoolingExample](src/PoolingExample/README.md) | Reusing expensive instances across scopes, with a reset-on-return policy | [`Autofac.Pooling`](https://github.com/autofac/Autofac.Pooling) |
| [DotGraphExample](src/DotGraphExample/README.md) | Capturing a resolve operation as a Graphviz graph for troubleshooting | [`Autofac.Diagnostics.DotGraph`](https://github.com/autofac/Autofac.Diagnostics.DotGraph) |

### .NET Framework

Expand Down
14 changes: 14 additions & 0 deletions src/DotGraphExample/BracketFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace DotGraphExample;

/// <summary>
/// A decorator, so the captured graph shows an adapter chain rather than a flat
/// list of unrelated services.
/// </summary>
public sealed class BracketFormatter : IFormatter
{
private readonly IFormatter _inner;

public BracketFormatter(IFormatter inner) => _inner = inner;

public string Format(string value) => $"[{_inner.Format(value)}]";
}
6 changes: 6 additions & 0 deletions src/DotGraphExample/DataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DotGraphExample;

public sealed class DataSource : IDataSource
{
public string Read() => "42";
}
14 changes: 14 additions & 0 deletions src/DotGraphExample/DotGraphExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Autofac" Version="9.3.2" />
<PackageReference Include="Autofac.Diagnostics.DotGraph" Version="3.0.1" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/DotGraphExample/IDataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DotGraphExample;

public interface IDataSource
{
string Read();
}
6 changes: 6 additions & 0 deletions src/DotGraphExample/IFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DotGraphExample;

public interface IFormatter
{
string Format(string value);
}
6 changes: 6 additions & 0 deletions src/DotGraphExample/IReportGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DotGraphExample;

public interface IReportGenerator
{
string Generate();
}
42 changes: 42 additions & 0 deletions src/DotGraphExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Autofac;
using Autofac.Diagnostics.DotGraph;

namespace DotGraphExample;

internal static class Program
{
public static void Main()
{
var builder = new ContainerBuilder();
builder.RegisterType<DataSource>().As<IDataSource>().SingleInstance();
builder.RegisterType<UppercaseFormatter>().As<IFormatter>().InstancePerLifetimeScope();
builder.RegisterDecorator<BracketFormatter, IFormatter>();
builder.RegisterType<ReportGenerator>().As<IReportGenerator>();

using var container = builder.Build();

// The tracer raises an event per completed resolve operation, and the
// trace content is a Graphviz DOT document describing that operation.
var tracer = new DotDiagnosticTracer();
var graphs = new List<string>();
tracer.OperationCompleted += (sender, args) => graphs.Add(args.TraceContent);
container.SubscribeToDiagnostics(tracer);

using (var scope = container.BeginLifetimeScope())
{
Console.WriteLine($"Report: {scope.Resolve<IReportGenerator>().Generate()}");
}

var output = Path.Combine(AppContext.BaseDirectory, "resolve-graph.dot");
File.WriteAllText(output, string.Join(Environment.NewLine, graphs));

Console.WriteLine();
Console.WriteLine($"Captured {graphs.Count} resolve operation(s), written to:");
Console.WriteLine($" {output}");
Console.WriteLine();
Console.WriteLine("Render it with Graphviz:");
Console.WriteLine($" dot -T png -O \"{output}\"");
Console.WriteLine();
Console.WriteLine("Tracing is expensive. Turn it on while troubleshooting, not in production.");
}
}
9 changes: 9 additions & 0 deletions src/DotGraphExample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DotGraphExample

Capturing a resolve operation as a Graphviz graph, which is the fastest way to see what Autofac actually did when a resolve surprises you. The registrations include a decorator so the graph has an adapter chain worth looking at.

Packages: [`Autofac`](https://github.com/autofac/Autofac), [`Autofac.Diagnostics.DotGraph`](https://github.com/autofac/Autofac.Diagnostics.DotGraph)

Run `dotnet run --project src/DotGraphExample`. It writes a `.dot` file next to the built assembly and prints the Graphviz command to render it.

See [Tracing](https://autofac.readthedocs.io/en/latest/troubleshooting/tracing.html) for the documentation this example follows.
19 changes: 19 additions & 0 deletions src/DotGraphExample/ReportGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace DotGraphExample;

/// <summary>
/// Sits at the top of the graph, depending on a source and a formatter so the
/// trace has more than one level to draw.
/// </summary>
public sealed class ReportGenerator : IReportGenerator
{
private readonly IDataSource _source;
private readonly IFormatter _formatter;

public ReportGenerator(IDataSource source, IFormatter formatter)
{
_source = source;
_formatter = formatter;
}

public string Generate() => _formatter.Format(_source.Read());
}
6 changes: 6 additions & 0 deletions src/DotGraphExample/UppercaseFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DotGraphExample;

public sealed class UppercaseFormatter : IFormatter
{
public string Format(string value) => value.ToUpperInvariant();
}
6 changes: 6 additions & 0 deletions src/DynamicProxyExample/Calculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DynamicProxyExample;

public sealed class Calculator : ICalculator
{
public int Add(int left, int right) => left + right;
}
22 changes: 22 additions & 0 deletions src/DynamicProxyExample/CallLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Castle.DynamicProxy;

namespace DynamicProxyExample;

/// <summary>
/// An interceptor sees every call made through the proxied interface.
/// <see cref="IInvocation.Proceed"/> runs the real implementation; anything
/// before or after that call is yours to do.
/// </summary>
public sealed class CallLogger : IInterceptor
{
private readonly TextWriter _output;

public CallLogger(TextWriter output) => _output = output;

public void Intercept(IInvocation invocation)
{
_output.WriteLine($" -> {invocation.Method.Name}({string.Join(", ", invocation.Arguments)})");
invocation.Proceed();
_output.WriteLine($" <- {invocation.Method.Name} returned {invocation.ReturnValue}");
}
}
14 changes: 14 additions & 0 deletions src/DynamicProxyExample/DynamicProxyExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Autofac" Version="9.3.2" />
<PackageReference Include="Autofac.Extras.DynamicProxy" Version="8.1.0" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/DynamicProxyExample/Greeter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace DynamicProxyExample;

public sealed class Greeter : IGreeter
{
public string Greet(string name) => $"Hello, {name}!";
}
10 changes: 10 additions & 0 deletions src/DynamicProxyExample/ICalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DynamicProxyExample;

/// <summary>
/// Interception is wired up on the registration for this service, so the
/// interface itself needs to know nothing about it.
/// </summary>
public interface ICalculator
{
int Add(int left, int right);
}
13 changes: 13 additions & 0 deletions src/DynamicProxyExample/IGreeter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Autofac.Extras.DynamicProxy;

namespace DynamicProxyExample;

/// <summary>
/// The attribute names the interceptor here instead, which keeps the wiring next
/// to the contract rather than in the container configuration.
/// </summary>
[Intercept(typeof(CallLogger))]
public interface IGreeter
{
string Greet(string name);
}
39 changes: 39 additions & 0 deletions src/DynamicProxyExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Autofac;
using Autofac.Extras.DynamicProxy;

namespace DynamicProxyExample;

internal static class Program
{
public static void Main()
{
var builder = new ContainerBuilder();

// The interceptor is an ordinary registration, so it can take
// dependencies like anything else in the container.
builder.Register(_ => new CallLogger(Console.Out));

// Style one: the registration names the interceptor.
builder.RegisterType<Calculator>()
.As<ICalculator>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(CallLogger));

// Style two: IGreeter carries [Intercept], so the registration only has
// to opt in to interception at all.
builder.RegisterType<Greeter>()
.As<IGreeter>()
.EnableInterfaceInterceptors();

using var container = builder.Build();

Console.WriteLine("Calling ICalculator.Add, intercepted by registration:");
var sum = container.Resolve<ICalculator>().Add(2, 3);
Console.WriteLine($"Result: {sum}");

Console.WriteLine();
Console.WriteLine("Calling IGreeter.Greet, intercepted by attribute:");
var greeting = container.Resolve<IGreeter>().Greet("Autofac");
Console.WriteLine($"Result: {greeting}");
}
}
9 changes: 9 additions & 0 deletions src/DynamicProxyExample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DynamicProxyExample

Method interception with Castle DynamicProxy, wired up two ways: named on the registration, and declared with an `[Intercept]` attribute on the contract. The interceptor is a normal registration, so it can take dependencies of its own.

Packages: [`Autofac`](https://github.com/autofac/Autofac), [`Autofac.Extras.DynamicProxy`](https://github.com/autofac/Autofac.Extras.DynamicProxy)

Run `dotnet run --project src/DynamicProxyExample`. It prints the intercepted calls around each method.

See [Type Interceptors](https://autofac.readthedocs.io/en/latest/advanced/interceptors.html) for the documentation this example follows.
30 changes: 30 additions & 0 deletions src/MultitenantExample.AspNetCore/Controllers/TenantController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Autofac.Multitenant;
using Microsoft.AspNetCore.Mvc;

namespace MultitenantExample.AspNetCore.Controllers;

[ApiController]
[Route("api/[controller]")]
public sealed class TenantController : ControllerBase
{
private readonly ITenantDependency _dependency;
private readonly ITenantIdentificationStrategy _strategy;

public TenantController(ITenantDependency dependency, ITenantIdentificationStrategy strategy)
{
_dependency = dependency;
_strategy = strategy;
}

[HttpGet]
public IActionResult Get()
{
_strategy.TryIdentifyTenant(out var tenantId);

return Ok(new
{
Tenant = tenantId as string ?? "(default)",
Resolved = _dependency.Describe(),
});
}
}
Loading
Loading