我一直在寻找 WebApi 属性的非参数注入选项。
我的问题很简单,这是否真的可以使用 Structuremap?
我一直在谷歌搜索,但不断提出属性注入(我不喜欢使用)或构造函数注入的假设实现,到目前为止我无法复制。
我选择的容器是 Structuremap,但是任何示例都足够了,因为我能够转换它。
有人管理过这个吗?
我一直在寻找 WebApi 属性的非参数注入选项。
我的问题很简单,这是否真的可以使用 Structuremap?
我一直在谷歌搜索,但不断提出属性注入(我不喜欢使用)或构造函数注入的假设实现,到目前为止我无法复制。
我选择的容器是 Structuremap,但是任何示例都足够了,因为我能够转换它。
有人管理过这个吗?
是的,有可能。你(和大多数人一样)被微软的动作过滤器属性营销所吸引,这些属性被方便地放入一个类中,但对 DI 一点也不友好。
解决方案是将动作过滤器属性分成两部分,如本文所示:
方法是使用 IActionFilter 来测试属性是否存在,然后执行所需的行为。动作过滤器可以提供所有依赖项(通过构造函数),然后在组合应用程序时注入。
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
config.Filters.Add(filter);
注意:如果您需要过滤器的任何依赖项的生命周期短于单例,则需要使用 a
GlobalFilterProvider
as in this answer。
要将其与 StructureMap 连接起来,您需要从 DI 配置模块返回一个容器实例。Application_Start 方法仍然是组合根的一部分,因此您可以在此方法中的任何位置使用容器,它仍然不被视为服务定位器模式。请注意,我在这里没有展示完整的 WebApi 设置,因为我假设您已经使用 WebApi 进行了有效的 DI 配置。如果你需要一个,那是另一个问题。
public class DIConfig()
{
public static IContainer Register()
{
// Create the DI container
var container = new Container();
// Setup configuration of DI
container.Configure(r => r.AddRegistry<SomeRegistry>());
// Add additional registries here...
#if DEBUG
container.AssertConfigurationIsValid();
#endif
// Return our DI container instance to the composition root
return container;
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Hang on to the container instance so you can resolve
// instances while still in the composition root
IContainer container = DIConfig.Register();
AreaRegistration.RegisterAllAreas();
// Pass the container so we can resolve our IActionFilter
WebApiConfig.Register(GlobalConfiguration.Configuration, container);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}
public static class WebApiConfig
{
// Add a parameter for IContainer
public static void Register(HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// Add our action filter
config.Filters.Add(container.GetInstance<IMaxLengthActionFilter>());
// Add additional filters here that look for other attributes...
}
}
MaxLengthActionFilter 的实现看起来像这样:
// Used to uniquely identify the filter in StructureMap
public interface IMaxLengthActionFilter : System.Web.Http.Filters.IActionFilter
{
}
public class MaxLengthActionFitler : IMaxLengthActionFilter
{
public readonly IConfigProvider configProvider;
public MaxLengthActionFilter(IConfigProvider configProvider)
{
if (configProvider == null)
throw new ArgumentNullException("configProvider");
this.configProvider = configProvider;
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here (before the continuation),
// and use the configProvider as needed
return continuation().ContinueWith(t =>
{
// Execute your behavior here (after the continuation),
// and use the configProvider as needed
return t.Result;
});
}
return continuation();
}
public bool AllowMultiple
{
get { return true; }
}
public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
{
MaxLengthAttribute result = null;
// Check if the attribute exists on the action method
result = (MaxLengthAttribute)actionDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (MaxLengthAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
return result;
}
}
而且,不应包含任何行为的属性应如下所示:
// This attribute should contain no behavior. No behavior, nothing needs to be injected.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
this.MaxLength = maxLength;
}
public int MaxLength { get; private set; }
}
我在使用自定义操作过滤器提供程序时遇到了困难,但没有让它适用于我的身份验证属性。我还尝试了构造函数和属性注入的各种方法,但没有找到感觉不错的解决方案。
我最终将函数注入到我的属性中。这样,单元测试可以注入一个返回假或模拟的函数,而应用程序可以注入一个函数来解决与 IoC 容器的依赖关系。
我刚刚在这里写过这种方法:http: //danielsaidi.com/blog/2015/09/11/asp-net-and-webapi-attributes-with-structuremap
它在我的项目中非常有效,并解决了我在其他方法中遇到的所有问题。