C# Web API 2中使用StructureMap将请求数据注入依赖构造函数的疑问
Great question! When working with StructureMap in WebApi2, injecting request-specific dynamic values like clientCode or bodyData into dependency constructors requires a bit of extra work because IoC containers typically handle static dependencies by default. Let's break down the best approaches for your scenario:
Approach 1: Encapsulate Logic in a Service (Recommended)
The simplest and most maintainable approach is to wrap your business logic in a service class, inject that service into your controller, and pass the dynamic request parameters directly to the service's methods. This avoids tying your dependencies to the HTTP context and keeps your code testable.
Step 1: Define a Service Interface and Implementation
First, extract your logic into a service:
public interface ISomeDataService { SomeData CreateSomeData(string clientCode, MyDto bodyData); } public class SomeDataService : ISomeDataService { private readonly IClientManager _clientManager; // Inject static dependencies (like IClientManager) via constructor public SomeDataService(IClientManager clientManager) { _clientManager = clientManager; } public SomeData CreateSomeData(string clientCode, MyDto bodyData) { var client = _clientManager.GetClientByCode(clientCode); return new SomeData { User = bodyData.User, ClientCode = clientCode, SomeField = client.SomeField }; } }
Step 2: Refactor Your Controller to Use Dependency Injection
Update your controller to inject the service instead of manually instantiating ClientManager:
public class MyController : ApiController { private readonly ISomeDataService _someDataService; // Inject the service via constructor public MyController(ISomeDataService someDataService) { _someDataService = someDataService; } public IHttpActionResult MyAction(string clientCode, [FromBody]MyDto bodyData) { var someData = _someDataService.CreateSomeData(clientCode, bodyData); // Continue with your logic... return Ok(someData); } }
Step 3: Register Dependencies in StructureMap
Add these registrations to your StructureMap registry:
public class WebApiRegistry : Registry { public WebApiRegistry() { For<ISomeDataService>().Use<SomeDataService>(); For<IClientManager>().Use<ClientManager>(); // Register your ClientManager here } }
Approach 2: Factory Pattern (For Dependencies Needing Dynamic Parameters)
If your ClientManager (or another dependency) requires the clientCode directly in its constructor, use a factory pattern to create instances with the dynamic parameter at runtime. This keeps your dependencies decoupled from the HTTP context.
Step 1: Create a Factory Interface and Implementation
public interface IClientManagerFactory { IClientManager Create(string clientCode); } public class ClientManagerFactory : IClientManagerFactory { // Inject any static dependencies your ClientManager needs here private readonly IDatabaseService _dbService; public ClientManagerFactory(IDatabaseService dbService) { _dbService = dbService; } public IClientManager Create(string clientCode) { return new ClientManager(clientCode, _dbService); // Pass dynamic + static dependencies } }
Step 2: Use the Factory in Your Controller
public class MyController : ApiController { private readonly IClientManagerFactory _clientManagerFactory; public MyController(IClientManagerFactory clientManagerFactory) { _clientManagerFactory = clientManagerFactory; } public IHttpActionResult MyAction(string clientCode, [FromBody]MyDto bodyData) { var clientManager = _clientManagerFactory.Create(clientCode); var client = clientManager.GetClientByCode(clientCode); var someData = new SomeData { User = bodyData.User, ClientCode = clientCode, SomeField = client.SomeField }; // Continue with your logic... return Ok(someData); } }
Step 3: Register the Factory in StructureMap
public class WebApiRegistry : Registry { public WebApiRegistry() { For<IClientManagerFactory>().Use<ClientManagerFactory>(); For<IDatabaseService>().Use<DatabaseService>(); // Register static dependencies } }
Approach 3: Inject via HTTP Context (Less Recommended)
If you absolutely need to inject request-specific values directly into constructor parameters, you can store the values in the HTTP request properties and resolve them via StructureMap. However, this ties your dependencies to the HTTP context, making unit testing harder.
Step 1: Capture Request Data with an Action Filter
public class CaptureRequestDataFilter : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { // Store clientCode in request properties if (actionContext.ActionArguments.TryGetValue("clientCode", out var clientCode)) { actionContext.Request.Properties["ClientCode"] = clientCode.ToString(); } // Store bodyData if needed if (actionContext.ActionArguments.TryGetValue("bodyData", out var bodyData)) { actionContext.Request.Properties["BodyData"] = bodyData as MyDto; } base.OnActionExecuting(actionContext); } }
Register the filter in WebApiConfig.cs:
config.Filters.Add(new CaptureRequestDataFilter());
Step 2: Resolve Values in StructureMap
Create a helper to access the current request:
public static class HttpContextHelper { public static HttpRequestMessage CurrentRequest => HttpContext.Current?.Items["MS_HttpRequestMessage"] as HttpRequestMessage; }
Then register your dependency with the dynamic constructor parameter:
public class WebApiRegistry : Registry { public WebApiRegistry() { For<IClientManager>().Use<ClientManager>() .LifecycleIs(new HttpRequestLifecycle()) // Ensure per-request instance .Ctor<string>("clientCode").Is(() => { var request = HttpContextHelper.CurrentRequest; if (request?.Properties.TryGetValue("ClientCode", out var clientCode) ?? false) { return clientCode.ToString(); } throw new InvalidOperationException("ClientCode not found in request properties."); }); } }
Final Notes
- Prefer Approach 1 or 2 over Approach 3: They keep your code cleaner, more testable, and decoupled from the HTTP context.
- Always register request-scoped dependencies with
HttpRequestLifecycleto avoid sharing instances across requests. - Don't forget to set up StructureMap as your WebApi dependency resolver in
WebApiConfig.cs:var container = new Container(new WebApiRegistry()); config.DependencyResolver = new StructureMapDependencyResolver(container);
内容的提问来源于stack exchange,提问作者Bruno

