Using NHibernate.AspNet.Identity

2019-02-06 16:00发布

问题:

I am attempting to use Asp.net identity and NHibernate.

I have created a new blank Asp.net MVC site using .NET framework 4.5.1 and I have installed and followed the instructions for using nuget package NHibernate.AspNet.Identity as described here:

https://github.com/milesibastos/NHibernate.AspNet.Identity

which involves making the following changes to the AccountController class default constructor:

var mapper = new ModelMapper();
mapper.AddMapping<IdentityUserMap>();
mapper.AddMapping<IdentityRoleMap>();
mapper.AddMapping<IdentityUserClaimMap>();
mapper.AddMapping<IdentityUserLoginMap>();

var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
var configuration = new Configuration();
configuration.Configure(System.Web.HttpContext.Current.Server.MapPath(@"~\Models\hibernate.cfg.xml"));
configuration.AddDeserializedMapping(mapping, null);

var schema = new SchemaExport(configuration);
schema.Create(true, true);

var factory = configuration.BuildSessionFactory();
var session = factory.OpenSession();

UserManager = new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(session));

I am getting the following exception:

No persister for: IdentityTest.Models.ApplicationUser

The ApplicationUser class doesn't have any additional properties to IdentityUser (which works fine for a Entity Framework implementation of Asp.net Identity).

Can anyone offer suggestions as to how I can get Asp.net identity to work with this NuGet package?

回答1:

I have struggled very much with this library, which is making me question why this is the recommended library for using OWIN with NHibernate.

Anyway, to answer your question, the code you provided that you got from the github website adds NHibernate mappings for the library's classes. NHibernate doesn't have a mapping for ApplicationUser, it only has a mapping for it's base class. NHibernate needs a mapping for the instantiated class. This is problematic because you don't have access to the mapping code in the library's assembly, so you can't change it to use the ApplicationUser class instead. So the only way to get past this using the library as it is, is to remove the ApplicationUser class and use the library's IdentityUser class. Or, you could copy the mapping code from github and try using the same mapping for ApplicationUser.

Also, the library code and the code he gives for the AccountController does not ever open an NHibernate transaction, so even though the library calls Session.Save and Session.Update the data won't ultimately be saved in the database. After you open the session you need to open a transaction and save it as a private field on the class:

transaction = session.BeginTransaction(IsolationLevel.ReadCommitted);

Then you need to call transaction.Commit() after your action in the AccountController finishes executing, so you will need to override OnResultExecuted:

protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
    transaction.Commit();
}

Keep in mind this example is oversimplified, and in a production application you need to have error checking where you will Rollback instead of Commit if there are errors, and you need to properly close/dispose of everything, etc.

Furthermore, even after you solve those problems, there are other issues with the library. I ended up having to download the source from github so I could modify the library in order to use it. There are at least 3 other blatant errors in the library's code:

1) In NHibernate.AspNet.Identity.UserStore:

public virtual async Task<TUser> FindAsync(UserLoginInfo login)
{
    this.ThrowIfDisposed();
    if (login == null)
        throw new ArgumentNullException("login");

    IdentityUser entity = await Task.FromResult(Queryable
        .FirstOrDefault<IdentityUser>(
            (IQueryable<IdentityUser>)Queryable.Select<IdentityUserLogin, IdentityUser>(
                Queryable.Where<IdentityUserLogin>(
                    // This line attempts to query nhibernate for the built in asp.net
                    // UserLoginInfo class and then cast it to the NHibernate version IdentityUserLogin, 
                    // which always causes a runtime error. UserLoginInfo needs to be replaced 
                    // with IdentityUserLogin
                    (IQueryable<IdentityUserLogin>)this.Context.Query<UserLoginInfo>(), (Expression<Func<IdentityUserLogin, bool>>)(l => l.LoginProvider == login.LoginProvider && l.ProviderKey == login.ProviderKey)),
                    (Expression<Func<IdentityUserLogin, IdentityUser>>)(l => l.User))));

    return entity as TUser;
}

2) In NHibernate.AspNet.Identity.DomainModel.ValueObject:

protected override IEnumerable<PropertyInfo> GetTypeSpecificSignatureProperties()
{
    var invalidlyDecoratedProperties =
        this.GetType().GetProperties().Where(
            p => Attribute.IsDefined(p, typeof(DomainSignatureAttribute), true));

    string message = "Properties were found within " + this.GetType() +
                                        @" having the
            [DomainSignature] attribute. The domain signature of a value object includes all
            of the properties of the object by convention; consequently, adding [DomainSignature]
            to the properties of a value object's properties is misleading and should be removed. 
            Alternatively, you can inherit from Entity if that fits your needs better.";

    // This line is saying, 'If there are no invalidly decorated properties, 
    // throw an exception'..... which obviously should be the opposite, 
    // remove the negation (!)
    if (!invalidlyDecoratedProperties.Any())
        throw new InvalidOperationException(message);

    return this.GetType().GetProperties();
}

3) In NHibernate.AspNet.Identity.UserStore: For some reason, at least when creating a user/user login using an external provider like facebook, when the user/user login is initially created, the Update method is called instead of the Add/Create causing NHibernate to try to update an entity that doesn't exist. For now, without looking more into it, in the UserStore update methods I changed the library's code to call SaveOrUpdate on the NHibernate session instead of Update which fixed the problem.

I have only ran simple tests with the library that have worked after my changes, so there is no telling how many other runtime / logic errors are in this library. After finding those errors, it makes me really nervous using it now. It seems there was absolutely no testing done with even simple scenarios. Take caution using this library.



回答2:

I also struggled to use NHibernate.AspNet.Identity. I found it was much easier just to make my own implementation using NHibernate, which I've turned into a minimal worked example here:

https://github.com/MartinEden/NHibernate.AspNet.Identity.Example

They key parts are a simple implementation of IUserStore<TUser, TKey> and IUserPasswordStore<TUser, TKey> using an NHibernate session for persistence. Then it's just a matter of writing a bit of glue to tell Owin to use that code.