asp.net core authentication with IdentityDbContext<AppUser, AppRole, int, AppUserClaim, AppUserRole, AppUserLogin, AppRoleClaim, AppUserToken> - asp.net-mvc

I am trying to create database from Code first model. When I run dotnet ef migrations add or debug application I get error:
TypeLoadException: GenericArguments[0], 'OpPISWeb.Models.AppUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9[TUser,TRole,TContext,TKey,TUserClaim,TUserRole,TUserLogin,TUserToken,TRoleClaim]' violates the constraint of type parameter 'TUser'.
System.RuntimeTypeHandle.Instantiate(RuntimeTypeHandle handle, IntPtr* pInst, int numGenericArgs, ObjectHandleOnStack type)
in code:
.AddEntityFrameworkStores<AppWebContext, int>()
My code in ConfigureServices is:
var res = services
.AddIdentity<AppUser, AppRole>(config =>
{
config.User.RequireUniqueEmail = true;
config.Password.RequireNonAlphanumeric = false;
config.Cookies.ApplicationCookie.AutomaticChallenge = false;
})
.AddEntityFrameworkStores<AppWebContext, int>()
.AddDefaultTokenProviders();
and my EF models are:
[Table("Roles")]
public partial class AppRole : IdentityRole<int, AppUserRole, AppRoleClaim>
{
}
[Table("RoleClaims")]
public partial class AppRoleClaim : IdentityRoleClaim<int>
{
}
[Table("Users")]
public partial class AppUser : IdentityUser<int, AppUserClaim, AppUserRole, AppUserLogin>
{
}
//same for UserClaim, UserLogin, UserRole, UserToken
and my DBContext:
public partial class AppWebContext : IdentityDbContext<AppUser, AppRole, int, AppUserClaim, AppUserRole, AppUserLogin, AppRoleClaim, AppUserToken>
{
public AppWebContext() : base() {
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=...");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<AppUser>(entity =>
{
entity
.HasKey(u => u.Id);
entity.Property(p => p.Id)
.ValueGeneratedOnAdd();
});
modelBuilder.Entity<AppRole>(entity =>
{
entity
.HasKey(u => u.Id);
entity.Property(p => p.Id)
.ValueGeneratedOnAdd();
});
modelBuilder.Entity<AppUserClaim>(entity =>
{
entity
.HasKey(u => u.Id);
entity.Property(p => p.Id)
.ValueGeneratedOnAdd();
});
modelBuilder.Entity<AppUserRole>(entity =>
{
entity
.HasKey(u => new { u.RoleId, u.UserId });
});
modelBuilder.Entity<AppRoleClaim>(entity =>
{
entity
.HasKey(u => u.Id);
entity.Property(p => p.Id)
.ValueGeneratedOnAdd();
});
}
}
What did I miss? I am new in ASP.net and ASP.NET Core.
I am using version 1.1.
Complete error:
An error occurred while starting the application.
TypeLoadException: GenericArguments[0], 'OpPISWeb.Models.AppUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9[TUser,TRole,TContext,TKey,TUserClaim,TUserRole,TUserLogin,TUserToken,TRoleClaim]' violates the constraint of type parameter 'TUser'.
System.RuntimeTypeHandle.Instantiate(RuntimeTypeHandle handle, IntPtr* pInst, int numGenericArgs, ObjectHandleOnStack type)
ArgumentException: GenericArguments[0], 'OpPISWeb.Models.AppUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4[TUser,TRole,TContext,TKey]' violates the constraint of type 'TUser'.
System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
TypeLoadException: GenericArguments[0], 'OpPISWeb.Models.AppUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9[TUser,TRole,TContext,TKey,TUserClaim,TUserRole,TUserLogin,TUserToken,TRoleClaim]' violates the constraint of type parameter 'TUser'.
System.RuntimeTypeHandle.Instantiate(RuntimeTypeHandle handle, IntPtr* pInst, int numGenericArgs, ObjectHandleOnStack type)
System.RuntimeTypeHandle.Instantiate(Type[] inst)
System.RuntimeType.MakeGenericType(Type[] instantiation)
Show raw exception details
ArgumentException: GenericArguments[0], 'OpPISWeb.Models.AppUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4[TUser,TRole,TContext,TKey]' violates the constraint of type 'TUser'.
System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
System.RuntimeType.MakeGenericType(Type[] instantiation)
Microsoft.Extensions.DependencyInjection.IdentityEntityFrameworkBuilderExtensions.GetDefaultServices(Type userType, Type roleType, Type contextType, Type keyType)
Microsoft.Extensions.DependencyInjection.IdentityEntityFrameworkBuilderExtensions.AddEntityFrameworkStores<TContext, TKey>(IdentityBuilder builder)
OpPISWeb.Startup.ConfigureServices(IServiceCollection services)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
Show raw exception details
.NET Core X64 v4.1.1.0 | Microsoft.AspNetCore.Hosting version 1.1.0-rtm-22752 | Microsoft Windows 10.0.14393 | Need help? - Yes I do. Please.

Your problem is caused by .AddEntityFrameworkStores<AppWebContext, int>(). Take a look at HaoK's responses toward the end of this post:
https://github.com/aspnet/Identity/issues/1001.
Specially these 2
"AddEntityFrameworkStores can only be called with a user that derives from IdentityUser. If you are specifying more generic arguments, use IdentityBuilder.AddUserStore() instead."
"AddEntityFrameworkStores can only be called with a role that derives from IdentityRole. If you are specifying more generic arguments, use IdentityBuilder.AddRoleStore() instead."
So if you want to override UserClaim, UserLogin and all others, you need to implement your own UserStore and RoleStore, and use AddUserStore<T>() and AddRoleStore<T>() instead of AddEntityFrameworkStores.
AppUserStore
public class AppUserStore : UserStore<AppUser, AppRole, AppDbContext, int, AppUserClaim, AppUserRole, AppUserLogin, AppUserToken, AppRoleClaim>
{
public AppUserStore(AppDbContext dbContext)
: base(dbContext)
{
}
protected override AppUserToken CreateUserToken(AppUser user, string loginProvider, string name, string value)
{
throw new NotImplementedException();
}
protected override AppUserLogin CreateUserLogin(AppUser user, UserLoginInfo login)
{
throw new NotImplementedException();
}
protected override AppUserRole CreateUserRole(AppUser user, AppRole role)
{
throw new NotImplementedException();
}
protected override AppUserClaim CreateUserClaim(AppUser user, Claim claim)
{
throw new NotImplementedException();
}
}
AppRoleStore
public class AppRoleStore : RoleStore<AppRole, AppDbContext, int, AppUserRole, AppRoleClaim>
{
public AppRoleStore(AppDbContext dbContext)
: base(dbContext)
{
}
protected override AppRoleClaim CreateRoleClaim(AppRole role, Claim claim)
{
throw new NotImplementedException();
}
}
And in your Startup.cs:
services.AddIdentity<AppUser, AppRole>()
.AddUserStore<AppUserStore>()
.AddRoleStore<AppRoleStore>()
// Components that generate the confirm email and reset password tokens
.AddDefaultTokenProviders();
Now I do think you need to create your own version of UserManager, RoleManager and SignInManager as well. I will update with more codes here later on after I figure everything out.

When you are customizing ASP.NET Core Identity, you should not use
AddEntityFrameworkStores anymore. Because it will override all of your
previous settings and customization to default Identity services.
Quote from Github answer here.

Related

Getting exception suddenly from entityframework

I get this exception from time to time :
The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'. Method Message:, LogException: System.InvalidOperationException: The 'Email' property on 'User' could not be set to a 'System.Int64' value. You must set this property to a non-null value of type 'System.String'.
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader1.GetValue(DbDataReader reader, Int32 ordinal)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.HandleEntityAppendOnly[TEntity](Func2 constructEntityDelegate, EntityKey entityKey, EntitySet entitySet)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper1.SimpleEnumerator.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at Project.Services.UserService.FindById(Int64 userId)
I'm using Asp.net Identity in MVC project.
My User class like :
public class User : IdentityUser<long, IdentityConfig.UserLogin, IdentityConfig.UserRole, IdentityConfig.UserClaim>
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(IdentityConfig.CustomUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[MaxLength(256)]
[Index(IsUnique = true)]
[Required]
public override string Email { get; set; }
[Required]
[MaxLength(256)]
public string FirstName { get; set; }
// rest of properties
....
}
UserManager :
public class CustomUserManager : UserManager<User, long>
{
public CustomUserManager(IUserStore<User, long> store, IdentityFactoryOptions<CustomUserManager> options) : base(store)
{
this.UserValidator = new UserValidator<User, long>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
PasswordValidator = new PasswordValidator
{
RequiredLength = 8,
RequireLowercase = true,
RequireUppercase = true,
RequireDigit = true
};
// Configure user lockout defaults
UserLockoutEnabledByDefault = true;
DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
MaxFailedAccessAttemptsBeforeLockout = 5;
// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
RegisterTwoFactorProvider("Google Authentication", new GoogleAuthenticatorTokenProvider());
var provider = new MachineKeyProtectionProvider();
UserTokenProvider = new DataProtectorTokenProvider<User,long>(provider.Create("ResetPasswordPurpose"));
}
}
UserService:
public class UserService : EntityService<User>, IUserService
{
private readonly IdentityConfig.CustomUserManager _userManager;
public UserService(MyDbContext context, IdentityConfig.CustomUserManager userManager) : base(context)
{
_userManager = userManager;
}
public User FindById(long userId)
{
return _userManager.Users.FirstOrDefault(x => x.Id == userId);
}
// other methods..
}
Register Autofac:
builder.RegisterModule(new ServiceModule());
builder.RegisterModule(new EfModule());
builder.RegisterType<IdentityConfig.RoleStore>().As<IRoleStore<IdentityConfig.Role, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserStore>().As<IUserStore<User, long>>().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomUserManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomSignInManager>().AsSelf().InstancePerRequest();
builder.RegisterType<IdentityConfig.CustomRoleManager>().AsSelf().InstancePerRequest();
builder.Register<IAuthenticationManager>(c => HttpContext.Current.GetOwinContext().Authentication);
builder.Register(c => new IdentityFactoryOptions<IdentityConfig.CustomUserManager>
{
DataProtectionProvider = new DpapiDataProtectionProvider("MyWebAppName"),
Provider = new IdentityFactoryProvider<IdentityConfig.CustomUserManager>()
}).InstancePerRequest();
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(Assembly.Load("Project.Services"))
.Where(t => t.Name.EndsWith("Service") || t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
public class EfModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType(typeof(MyDbContext)).AsSelf().WithParameter("connectionString", ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString).InstancePerRequest();
}
}
What I noticed also is this error affect some of other entities not just the user !
The problem is the application runs for some time and then gives this kind of errors too much, which does not make any sense to me and makes me mad.
I'm using Azure SQL , Azure web services, Autofac.
Same issue here. It happens on medium to high demand. I don't know what to do anymore. I'm recycling 5x/day.
I couldn't find any standard. The exception throws in many different methods. No standard. Looks completely random.
It seems that i'm trying to retrieve an information on DB and it always returns blank data, so an error is thrown when it tries to cast the null data to the model.

How would I pass a constructor parameter at runtime to my dbContext and also register them with Unity as such

I am trying to implement all sorts of good stuff like UnitOfWork, Repository, DI. I am using Unity for DI. Here is my dilemma. I have a few (currently 3) databases with identical schema but obviously with different data for business reasons (I will call them GroupDB1, GroupDB2 and GroupDB3). I also have a Master Database (DifferentDB) that has a different schema. My dbcontext need to use different databases for different scenarios at runtime. I have no clue how to put them all to work together.
Here is my dbContexts
public partial class GroupDB2 : DataContext
{
public GroupDB2() : base( "name=GroupDB2" )
{
}
public IDbSet<T> Set<T>() where T : EntityBase { return base.Set<T>(); }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//......
}
}
public partial class MasterDB : DataContext
{
public MasterDB() : base( "name=MasterDB" )
{
}
public IDbSet<T> Set<T>() where T : EntityBase { return base.Set<T>(); }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//......
}
}
and here are my other interfaces and implementations.
public class DataContext : DbContext, IDataContextAsync
{
private readonly Guid _instanceId;
bool _disposed;
public DataContext(string nameOrConnectionString) : base(nameOrConnectionString)
{
_instanceId = Guid.NewGuid();
//Configuration.LazyLoadingEnabled = false;
//Configuration.ProxyCreationEnabled = false;
}
}
public interface IDataContext : IDisposable
{
int SaveChanges();
}
public interface IDataContextAsync : IDataContext
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
Task<int> SaveChangesAsync();
}
public interface IRepository<T> where T : class
{
IDataContextAsync Context { get; }
IDbSet<T> DbSet { get; }
void Add(T entity);
void Delete(T entity);
void Delete(dynamic id);
T FindOne(Expression<Func<T, bool>> predicate);
IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
IQueryable<T> GetAll();
void Update(T entity);
}
public interface IRepositoryAsync<TEntity> : IRepository<TEntity> where TEntity : class
{
Task<TEntity> FindAsync( params object[] keyValues );
Task<TEntity> FindAsync( CancellationToken cancellationToken, params object[] keyValues );
Task<bool> DeleteAsync( params object[] keyValues );
Task<bool> DeleteAsync( CancellationToken cancellationToken, params object[] keyValues );
}
public static IUnityContainer InitializeContainer( IUnityContainer _container )
{
container = _container;
....
....
container.RegisterType<IDataContextAsync, DataContext>( new InjectionConstructor( "name=MasterDB" ) );
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>();// ("Async");
// Here is where I have no clue how do I register and resolve the correct entity context based on some conditions
// Like ConnectionStringService.GetConnectionString( for some condition );
//container.RegisterType<IDataContextAsync, DataContext>( "GroupDB", new InjectionConstructor( xxxxxx ) );
//container.RegisterType<IDataContextAsync, DataContext>( "DifferentDB", new InjectionConstructor( yyyyyy ) );
....
....
return container;
}
Since I read a lot about anti-patterns I am reluctant to do
var result = container.Resolve<MyObject>(
new ParameterOverride("x", ExpectedValue)
.OnType<MyOtherObject>());
I am stumped. Any help is highly appreciated. Thanks.
Babu.
I came up with an example that might be a bit over engineered, but I believe it gives you the most flexibility. You can see the entire example here. If you only want support for design time or only support for runtime, you can probably clean it up a bit.
For design time resolution, this uses an additional generics parameter as a token to identify the data store you wish to connect to. That allows you to resolve (via constructor injection) a unit of work and/or a repository that is specific to one data store.
MyService(IUnitOfWork<Group2Token> unitOfWork) { /* ... */ }
For runtime resolution, this uses a manager class to retrieve an instance of the desired unit of work with a string token.
MyService(IUnitOfWorkManager unitOfWorkManager)
{
_unitOfWork = unitOfWorkManager.GetUnitOfWork("Group2");
}
The managers use Unity's built-in support for resolving all named registrations into an array. More on that can be found in this question and answer.
Note that I suggest you use HierarchicalLifetimeManager for the registration of anything that's disposable. If you use that in combination with using child containers, you will have an automatic disposal mechanism. More info in this question and answer.

Use ConstructedBy together with the non-generic For

I have this registration in StructureMap
ObjectFactory.Initialize(x => {
x.For<IPageModel>().UseSpecial(y => y.ConstructedBy( r => ((MvcHandler) HttpContext.Current.Handler).RequestContext.RouteData.GetCurrentModel<IPageModel>()));
});
And then I access this object in my constructor like this
public HomeController(IPageModel model) {}
Now I would like to register all concrete types that implements the interface IPageModel and when asked for I want to use the same For<> statement to get the correct instance.
It seems like i could use Scan together with my own convention to do this but I can't figure out exactly how to do it.
This is some example code
x.Scan(scanner =>
{
scanner.AssembliesFromApplicationBaseDirectory();
scanner.Convention<MySpecialConvetion>();
});
public class MySpecialConvetion : IRegistrationConvention {
public void Process(Type type, Registry registry) {
if(type.IsAssignableFrom(typeof(IPageModel))) {
registry.For<CONCRETE IMPLEMENTATION>().UseSpecial(y => y.ConstructedBy( r => ((MvcHandler) HttpContext.Current.Handler).RequestContext.RouteData.GetCurrentModel<CONCRETE IMPLEMENTATION>()));
}
}
}
Edit: It seems like I need to use the non-generic For, but how can I handle the construction my self using the non-generic For?
I got this working by creating a generic method definition, and used reflection to populate the types. Easier to show than explain:
public class MySpecialConvetion : IRegistrationConvention
{
public static readonly MethodInfo RegisterMethod = typeof (MySpecialConvetion)
.GetMethod("Register", BindingFlags.NonPublic | BindingFlags.Static)
.GetGenericMethodDefinition();
public void Process(Type type, Registry registry)
{
if (type.IsAssignableFrom(typeof (IPageModel)))
{
var specificRegisterMethod = RegisterMethod.MakeGenericMethod(new[] { type });
specificRegisterMethod.Invoke(null, new object[] { registry });
}
}
static private void Register<T>(Registry registry)
where T : IPageModel
{
registry
.For<T>()
.UseSpecial(y => y.ConstructedBy(r => GetCurrentPageModel<T>()));
}
static private T GetCurrentPageModel<T>()
where T : IPageModel
{
var handler = (MvcHandler) HttpContext.Current.Handler;
if (handler == null)
return default(T);
return handler.RequestContext.RouteData.GetCurrentModel<T>();
}
}
I added an intermediate step and checked for a null handler since I didn't have one in my site. But this should get you the missing piece you needed.

Registering closed types using StructureMap's scanner

How do I register a closed type so that instances of the generic are created using HybridHttpOrThreadLocalScoped lifecycle?
My classes:
public interface IBaseService
{
}
public interface IAccountService
{
void Save(Account entry);
Account GetById(string id);
List<Account> GetList();
void Delete(string id);
bool Exists(string id);
}
public interface IClientService
{
void Save(Client entry);
Client GetById(string id);
List<Client> GetList();
void Delete(string id);
bool Exists(string id);
}
public class AccountService : IBaseService, IAccountService
{
Some code for managing accounts
}
public class ClientService : IBaseService, IClientService
{
Some code for managing clients
}
Dependency resolver:
public StructureMapContainer(IContainer container)
{
_container = container;
_container.Configure(x => x.Scan(y =>
{
y.AssembliesFromApplicationBaseDirectory();
y.WithDefaultConventions();
y.LookForRegistries();
y.ConnectImplementationsToTypesClosing(typeof(IService<>))
.OnAddedPluginTypes(t => t.HybridHttpOrThreadLocalScoped());
}));
}
What’s the syntax in the resolver for automatically creating instances of IBaseService? Using ConnectImplementationsToTypesClosing only works for open generics. Do I even need to use the resolver? Is there a better way to register the types?
For now, this is how I amhandling registering them:
container.Configure(x =>
{
x.For<IClientService>()
.HybridHttpOrThreadLocalScoped()
.Use(new ClientService());
x.For<IEmailAddressService>()
.HybridHttpOrThreadLocalScoped()
.Use(new EmailAddressService());
x.For<IAccountService>()
.HybridHttpOrThreadLocalScoped()
.Use(new AccountService());
});
Something like:
Scan(y =>
{
y.AssemblyContainingType<IService>();
y.Assembly(Assembly.GetExecutingAssembly().FullName);
y.With(new ServiceScanner());
});
Then you need the Customscanner:
/// <summary>
/// Custom scanner to create Service types based on custom convention
/// In this case any type that implements IService and follows the
/// naming convention of "Name"Service.
/// </summary>
public class ServiceScanner : IRegistrationConvention
{
public void Process(Type type, StructureMap.Configuration.DSL.Registry registry)
{
if (type.BaseType == null) return;
if (type.GetInterface(typeof(IService).Name) != null)
{
var name = type.Name;
var newtype = type.GetInterface(string.Format("I{0}", name));
registry
.For<IService>()
.AddInstances(y => y.Instance(new ConfiguredInstance(type).Named(name)))
.HybridHttpOrThreadLocalScoped();
registry.For(newtype)
.HybridHttpOrThreadLocalScoped().Use(c => c.GetInstance<IService>(name));
}
}
}

Castle Windsor Typed Factory Facility with generics

I'm trying to register a factory that could resolve an array of event handlers defined as follow:
public interface IEvent { }
public class EventA : IEvent { }
public class EventB : IEvent { }
public class EventC : IEvent { }
public interface IHandler<TEvent> where TEvent : IEvent
{
void Handle(TEvent ev);
}
public class HandlerX : IHandler<EventA>, IHandler<EventB>
{
public void Handle(EventA ev)
{
throw new NotImplementedException("handle EventA");
}
public void Handle(EventB ev)
{
throw new NotImplementedException("handle EventB");
}
}
public class HandlerY : IHandler<EventB>, IHandler<EventC>
{
public void Handle(EventB ev)
{
throw new NotImplementedException("handle EventB");
}
public void Handle(EventC ev)
{
throw new NotImplementedException("handle EventC");
}
}
public interface HandlerFactory
{
object[] GetHandlersForEvent(IEvent ev);
}
Basically for each event I can have more handlers and each handler can handle multiple events. I also want the factory to return object[] because at runtime I don't know what closed generic types would be returned.
I tried the approach descirbed by Krzysztof Koźmic http://kozmic.pl/2010/03/11/advanced-castle-windsor-ndash-generic-typed-factories-auto-release-and-more/
but still have problems.
Basically my question boils down to what types to return from my custom type deriving from DefaultTypedFactoryComponentSelector.
I tried many variations of the following:
public class HandlerSelector : DefaultTypedFactoryComponentSelector
{
protected override TypedFactoryComponent BuildFactoryComponent(MethodInfo method, string componentName, Type componentType, System.Collections.IDictionary additionalArguments)
{
Type eventType = null;
foreach (var k in additionalArguments.Values)
{
eventType = k.GetType();
}
var handlerType = typeof(IHandler<>).MakeGenericType(eventType);
var handlerArrayType = handlerType.MakeArrayType();
//return handlerArrayType;
return new TypedFactoryComponentCollection(handlerType, additionalArguments);
}
protected override Type GetComponentType(MethodInfo method, object[] arguments)
{
return typeof (object);
/*
var message = arguments[0];
var handlerType = typeof(IHandler<>).MakeGenericType(message.GetType());
var handlerArrayType = handlerType.MakeArrayType();
return handlerArrayType;
*/
}
/*
public TypedFactoryComponent SelectComponent(MethodInfo method, Type type, object[] arguments)
{
var message = arguments[0];
var handlerType = typeof(IHandler<>).MakeGenericType(message.GetType());
var result = new TypedFactoryComponentCollection(handlerType.MakeArrayType(), new Arguments(arguments));
return result;
}*/
}
with Windsor installer defined as:
public class Installer : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<TypedFactoryFacility>()
.Register(
Component.For<HandlerSelector>().ImplementedBy<HandlerSelector>(),
Component.For<AutoReleaseHandlerInterceptor>(),
AllTypes.FromAssemblyContaining<Program>()
.BasedOn(typeof(IHandler<>))
.WithService.Base()
.Configure(c => c.LifeStyle.Is(LifestyleType.Transient)
.Interceptors<AutoReleaseHandlerInterceptor>()),
Component.For<HandlerFactory>().AsFactory(c => c.SelectedWith<HandlerSelector>()));
}
}
When calling factory.GetHandlersForEvent(ev); I get an exception complaining about array type mismatch:
"Attempted to access an element as a type incompatible with the array."
Stack trace:
at System.Collections.Generic.Dictionary2.ValueCollection.CopyTo(TValue[] array, Int32 index)
at System.Collections.Generic.Dictionary2.ValueCollection.System.Collections.ICollection.CopyTo(Array array, Int32 index)
at Castle.MicroKernel.DefaultKernel.ResolveAll(Type service, IDictionary arguments) in e:\OSS.Code\Castle.Windsor\src\Castle.Windsor\MicroKernel\DefaultKernel_Resolve.cs:line 285
at Castle.Facilities.TypedFactory.TypedFactoryComponentCollection.Resolve(IKernel kernel) in e:\OSS.Code\Castle.Windsor\src\Castle.Windsor\Facilities\TypedFactory\TypedFactoryComponentCollection.cs:line 39
at Castle.Facilities.TypedFactory.Internal.TypedFactoryInterceptor.Resolve(IInvocation invocation) in e:\OSS.Code\Castle.Windsor\src\Castle.Windsor\Facilities\TypedFactory\Internal\TypedFactoryInterceptor.cs:line 173
at Castle.Facilities.TypedFactory.Internal.TypedFactoryInterceptor.Intercept(IInvocation invocation) in e:\OSS.Code\Castle.Windsor\src\Castle.Windsor\Facilities\TypedFactory\Internal\TypedFactoryInterceptor.cs:line 83
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.HandlerFactoryProxy.GetHandlersForEvent(IEvent ev)
at CastleWindsorTests.Program.TryIt(HandlerFactory factory) in c:\users\user\documents\visual studio 2010\Projects
How to implement the HandlerSelector so that it works well with factory defined as returning object[] whereas the real objects at runtime are closed generic types?
I'll be happy to be pointed to some existing documentation with guidelines for implementors of ITypedFactoryComponentSelector / DefaultTypedFactoryComponentSelector. Yes, I tried the http://docs.castleproject.org/(S(kwaa14uzdj55gv55dzgf0vui))/Windsor.Typed-Factory-Facility-interface-based-factories.ashx but here's not much about the above types.
I really don't want to introduce a service locator (instead of factory) ;).
To answer my own question:
I must've been blind. After reading more closely the xmldoc of the methods I override, changing HandlerSelector to the following solved the problem:
public class HandlerSelector : DefaultTypedFactoryComponentSelector
{
protected override TypedFactoryComponent BuildFactoryComponent(MethodInfo method, string componentName, Type componentType, System.Collections.IDictionary additionalArguments)
{
return new TypedFactoryComponentCollection(componentType, additionalArguments);
}
protected override Type GetComponentType(MethodInfo method, object[] arguments)
{
var message = arguments[0];
var handlerType = typeof(IHandler<>).MakeGenericType(message.GetType());
return handlerType;
}
}
#workabyte
For Castle v3, GetComponentType method stays the same but the BuildFactoryComponent method looks like this for me:
protected override Func<IKernelInternal, IReleasePolicy, object> BuildFactoryComponent(MethodInfo method, string componentName, Type componentType, IDictionary additionalArguments)
{
return (kernel, rp) => kernel.ResolveAll(componentType);
}

Resources