Unityを使ってASP.NET MVCでリポジトリパターンを実装する時のお話です。
KVSのデータを操作するこんなインターフェイスと実装があるとします。
public interface IKeyValueStore実装クラスのコンストラクタ引数で格納先が指定できるとします。
{
Task StoreAsync(string key, object value);
TaskGetAsync (string key);
}
public class AzureTableStore : IKeyValueStore
{
private strint tableName;
public AzureTableStore(string tableName)
{
this.tableName = tableName;
}
var shopStore = new AzureTableStore("shops");Unityを使うことを考えてASP.NET MVCのControllerを実装します。
var productStore = new AzureTableStore("products");
public class HomeController : ControllerUnityへの型の登録を記述します。
{
public HomeController(IKeyValueStore shopStore, IKeyValueStore productStore)
{
}
public static class UnityConfigで、実行してみると…
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType(
new InjectionConstructor("shop"));
container.RegisterType(
new InjectionConstructor("product"));
HomeControllerコンストラクタのshopStore、productStoreには両方ともTableNameが"product"のAzureTableStoreインスタンスが渡ってきます。
まぁ、同じ型を登録してるから上書きされて"product"になるのは納得できる。
では、どうするか。
それぞれのインターフェイスを用意してそれぞれ登録すると…
interface IShopStore : IKeyValueStore {}これも結果は同じ。
interface IProductStore : IKeyValueStore {}
public class AzureTableStore : IShopStore, IProductStore {...}
public HomeController(IShopStore shopStore, IProductStore productStore) {...}
container.RegisterType(new InjectionConstructor("shop"));
container.RegisterType(new InjectionConstructor("product"));
両方ともTableNameが"product"のAzureTableStoreインスタンスが渡ってきます。
ちょっと不自然な動きに思えるが、そういうものらしい。
ならば実装クラスを別々に…
interface IShopStore : IKeyValueStore {}これならHomeControllerコンストラクタのshopStore、productStoreにはShopStoreとProductStoreのインスタンスが渡ってきます。
interface IProductStore : IKeyValueStore {}
public class ShopStore : AzureTableStore, IShopStore {
public ShopStore() : base("shop") {...}
public class ProductStore : AzureTableStore, IProductStore {
public ProductStore() : base("product") {...}
public HomeController(IShopStore shopStore, IProductStore productStore) {...}
container.RegisterType();
container.RegisterType();
でも、インターフェイスとクラスが無駄に増えた。
次は考え方を変えて、HomeControllerをUnityに登録してみます。
container.RegisterTypeResolvedParameter(
"shopStore",
new InjectionConstructor("shop"));
container.RegisterType(
"productStore",
new InjectionConstructor("product"));
container.RegisterType(
new InjectionConstructor(
new ResolvedParameter("shopStore"),
new ResolvedParameter("productStore")));
また、型の登録時に名前を付けられるので"shopStore"と"productStore"とし、ResolvedParameterに指定します。
これでHomeControllerコンストラクタの第1引数がnew AzureTableStore("shop")、第2引数にnew AzureTableStore("product")でインスタンスが生成されます。
これがUnityの正しい使い方なのかしら?
でも、Controllerが沢山あったら登録するのが怠いよね...
という事で、この辺りの動作を変更してやります。
UnityにはIConstructorSelectorPolicyインターフェイスがあり、実装すべきはCreateResolverメソッドで、既定の動作となるDefaultUnityConstructorSelectorPolicyクラスは以下のように実装されてます。 ※Codeplex参照
protected override IDependencyResolverPolicy CreateResolver(ParameterInfo parameter)最後のNamedTypeDependencyResolverPolicyをnewする際に第2引数に渡す文字列が型の解決で名前として使用されるので、ParameterInfo.Nameを渡しちゃいます。
{
var attrs = parameter.GetCustomAttributes(false)
.OfType().ToList();
if(attrs.Count > 0)
{
return attrs[0].CreateResolver(parameter.ParameterType);
}
return new NamedTypeDependencyResolverPolicy(parameter.ParameterType, null);
}
ParameterInfo.Nameはコンストラクタ引数の変数名です。つまり ctor(string x, int y)であれば"x"や"y"です。
つまり、コンストラクタ引数の変数名で型登録時の名前を指定するという少し無茶な仕様となります。
class CustomUnityConstructorSelectorPolicy : DefaultUnityConstructorSelectorPolicyあとは用意したCustomUnityConstructorSelectorPolicyが既定の動作となるようにゴニョゴニョと…
{
protected override IDependencyResolverPolicy CreateResolver(ParameterInfo parameter)
{
// 今回はDependencyResolutionAttributeの処理は無視
return new NamedTypeDependencyResolverPolicy(
parameter.ParameterType,
parameter.Name);
}
}
class CustomUnityContainerExtension : UnityContainerExtension
{
protected override void Initialize()
{
Context.Policies.SetDefault(
new CustomUnityConstructorSelectorPolicy());
}
}
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.AddNewExtension();
container.RegisterType
"shopStore",
new InjectionConstructor("shop"));
container.RegisterType
"productStore",
new InjectionConstructor("product"));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
}
public class HomeController : Controller
{
public HomeController(IKeyValueStore shopStore, IKeyValueStore productStore)これで、Controllerのインスタンスが生成される時にIKeyValueStore型で変数名と同じ名前で登録されているクラスを探して解決されるようになります。
変数名で指定するのは俺々ルール感たっぷりですが、コード的にはスッキリしました。