[FromServices]
Inject a DI service into a single action or handler parameter, understand inference since .NET 7, and avoid body-inference and missing-registration errors.
public IActionResult Render(int id, [FromServices] IInvoicePdfRenderer renderer)- Added in .NET ASP.NET Core 1.0
Return value
n/aResolves the parameter from the request's service provider (HttpContext.RequestServices), so scoped services share the request scope.
Errors
InvalidOperationException at request time when a non-nullable [FromServices] type is not registered. Without the attribute, an unregistered interface on a minimal API GET handler fails when the endpoint is built: "Body was inferred but the method does not allow inferred body parameters".
Changelog
- PHP ASP.NET Core 6.0Minimal APIs infer services: a parameter whose type is registered in DI is resolved without the attribute.
- PHP ASP.NET Core 7.0Controllers infer services the same way; ApiBehaviorOptions.DisableImplicitFromServicesParameters turns it off. A nullable [FromServices] parameter is optional.
[FromServices] makes a handler parameter a dependency: it is resolved from HttpContext.RequestServices, the request's DI scope, instead of from the HTTP request. In a minimal API this is the normal way to get services. In a controller it is action injection: a service that only one action uses comes in through that action instead of the constructor, so the other actions do not pay for creating it.
Inference and why the attribute still matters
Both models infer services: if the parameter type is registered in the container, it is resolved from DI without the attribute (minimal APIs since .NET 6, controllers since .NET 7). Inference checks the registration when the endpoint is built, which creates two traps:
- Forget to register
IInvoicePdfRendererand the parameter is no longer a service. A minimal API GET handler then fails when its endpoint is built (normally the first request, not app start) with "Body was inferred but the method does not allow inferred body parameters"; a POST handler silently tries to read the service from the JSON body and returns 400 or 415. - A type that is both registered and plausible as a body (a settings record you also registered) binds from DI where you expected JSON.
With [FromServices] the source is fixed: a missing registration fails the request with a clear InvalidOperationException naming the type, and nobody mistakes the parameter for input. Teams that prefer zero ambiguity set DisableImplicitFromServicesParameters for controllers and write the attribute everywhere.
Optional services
Mark the parameter nullable ([FromServices] IFraudScorer? scorer) and it binds null when nothing is registered, which suits features switched on by configuration.
Lifetimes
| Registration | What the action gets |
|---|---|
| Singleton | The shared instance. |
| Scoped | The request's instance, the same one the constructor, filters and other services in the request see. |
| Transient | A new instance, disposed when the request scope ends. |
Choosing between constructor and action injection
Use constructor injection for what most actions need; it keeps dependencies visible in one place and unit tests simple. Use [FromServices] for expensive or rarely used services (PDF rendering, an export client). Never resolve services manually with HttpContext.RequestServices.GetService in handlers: it hides the dependency and turns a clear resolution error into a runtime null.
Example
When to use it
- Resolve a heavy PDF renderer only in GET /api/invoices/{id}/pdf instead of in every request the controller handles
- Bind an optional fraud scorer as null when the feature is not registered in this environment
- Make a missing DI registration fail with a message naming the type rather than a confusing 415 from body inference
Frequently asked questions
Do I need [FromServices] in minimal APIs?
Not when the type is registered: it is inferred. Use it anyway for interfaces whose registration might be conditional, so a missing registration produces a clear error instead of body inference.
Is the service scoped to the request?
Yes. It comes from HttpContext.RequestServices, so a scoped service is the same instance the controller constructor, filters and other services in that request receive.
Constructor injection or [FromServices]?
Constructor injection for dependencies most actions use; [FromServices] for expensive or single-action dependencies. A controller with ten constructor parameters is usually a sign it should be split, not that everything should move to action injection.
More examples
Turn off inference for controllers
With inference off, a parameter typed as a registered class is treated like any other complex type, so it would be inferred from the body. The explicit attribute documents the source and survives a registration being removed without changing the endpoint's contract.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<CustomerDirectory>();
// Services must now be declared with [FromServices] (or come through the constructor).
builder.Services.Configure<ApiBehaviorOptions>(o => o.DisableImplicitFromServicesParameters = true);
var app = builder.Build();
app.MapControllers();
app.Run();
public class CustomerDirectory
{
public string NameOf(string id) => id == "CUST-1042" ? "Northwind Traders" : "unknown";
}
[ApiController]
[Route("api/customers")]
public class CustomersController : ControllerBase
{
[HttpGet("{id}")]
public IActionResult Get(string id, [FromServices] CustomerDirectory directory) =>
Ok(new { id, name = directory.NameOf(id) });
}
The error from a missing registration
Neither failure is good, but both point at the same fix: register the service. Both appear only when a request arrives, not at app start, and <code>ValidateOnBuild</code> cannot help because it checks constructor graphs of registered services, never handler parameters. An integration test that calls each endpoint is what makes a missing registration fail the build pipeline rather than the first customer request.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
// Missing: builder.Services.AddSingleton<IExchangeRates, FixedExchangeRates>();
var app = builder.Build();
// Without the attribute this GET handler fails when endpoints are built:
// "Body was inferred but the method does not allow inferred body parameters."
// app.MapGet("/prices/{sku}", (string sku, IExchangeRates rates) => ...);
// With it, the endpoint builds and the request fails with a clear message instead:
// "No service for type 'IExchangeRates' has been registered."
app.MapGet("/prices/{sku}", (string sku, [FromServices] IExchangeRates rates) =>
TypedResults.Ok(new { sku, eur = 19.99m, usd = 19.99m * rates.EurToUsd() }));
app.Run();
public interface IExchangeRates { decimal EurToUsd(); }
public class FixedExchangeRates : IExchangeRates { public decimal EurToUsd() => 1.08m; }
Unit-testing an action that uses action injection
An action-injected service is just a method argument in a unit test, so the controller needs no constructor setup for the dependency that only one action uses. NSubstitute supplies the fake renderer.
using Microsoft.AspNetCore.Mvc;
using NSubstitute;
using Xunit;
namespace Shop.Api.Tests;
public interface IInvoicePdfRenderer { byte[] Render(int invoiceId); }
[ApiController]
[Route("api/invoices")]
public class InvoicesController : ControllerBase
{
[HttpGet("{id:int}/pdf")]
public IActionResult Pdf(int id, [FromServices] IInvoicePdfRenderer renderer) =>
File(renderer.Render(id), "application/pdf", $"invoice-{id}.pdf");
}
public class InvoicesControllerTests
{
[Fact]
public void Pdf_returns_the_rendered_bytes()
{
var renderer = Substitute.For<IInvoicePdfRenderer>();
renderer.Render(42).Returns([0x25, 0x50, 0x44, 0x46]);
var result = new InvoicesController().Pdf(42, renderer);
var file = Assert.IsType<FileContentResult>(result);
Assert.Equal("invoice-42.pdf", file.FileDownloadName);
Assert.Equal(4, file.FileContents.Length);
}
}
Discussion