WebApi 属性の非パラメーター注入オプションを探しています。
私の質問は、Structuremap を使用してこれが実際に可能かどうかです。
私はぐるぐる回っていますが、プロパティ注入 (私は使用したくない) またはこれまで複製できなかったコンストラクター注入の想定される実装のいずれかを考え続けています。
私の選択したコンテナはStructuremapですが、変換できるので、これの例で十分です。
これを管理したことのある人はいますか?
WebApi 属性の非パラメーター注入オプションを探しています。
私の質問は、Structuremap を使用してこれが実際に可能かどうかです。
私はぐるぐる回っていますが、プロパティ注入 (私は使用したくない) またはこれまで複製できなかったコンストラクター注入の想定される実装のいずれかを考え続けています。
私の選択したコンテナはStructuremapですが、変換できるので、これの例で十分です。
これを管理したことのある人はいますか?
はい、可能です。あなた (ほとんどの人と同じように) は、Microsoft のアクション フィルター属性のマーケティングによって放り出されています。これは、単一のクラスに便利に配置されていますが、DI にはまったく適していません。
解決策は、この投稿で示されているように、アクション フィルター属性を 2 つの部分に分割することです。
アプローチは、IActionFilter を使用して属性の存在をテストしてから、目的の動作を実行することです。アクション フィルターは、(コンストラクターを介して) すべての依存関係と共に提供され、アプリケーションの構成時に挿入されます。
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
config.Filters.Add(filter);
注:フィルターの依存関係のいずれかの寿命をシングルトンよりも短くする必要がある場合は、
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
それは私のプロジェクトで非常にうまく機能し、他のアプローチで私が抱えていたすべての問題を解決します.