I think it's a good idea to review the user system. (UserManager, Custom membership provider, ...)
We customized the user system several times to fetch users from an external source... SAP, Microsoft Dynamics... and it's not that straight forward...
The current implementation of the Custom Membership Providers in Sitefinity is not optimized and queries all the users in memory before any filtering or sorting can be applied. In our membership users API we use LINQ to query the users from the membership provider, however the standard membership API does not support LINQ. This requires loading all users and filtering them in the memory, which causes performance overhead with many membership users.
We plan want to address this problem by providing a simple LINQ translation implementation and an interface that has to be implemented with that LINQ translation.
We are going to provide an example with the standard .net SqlMembershipProvider.
Due to the complexity of supporting many types of queries, we are thinking of implementing basic filtering and sorting operations.
Below are some examples:
var manager = UserManager.GetManager("CustomMembershipProvider");
// Where clauses -> only one Where clause by query will be supported.
// array.Contains clauses
var guidArray = new Guid Guid.NewGuid() ;
manager.GetUsers().Where(x => guidArray.Contains(x.Id));
// string filters.
manager.GetUsers().Where(x => x.UserName.StartsWith("testUser"));
manager.GetUsers().Where(x => x.UserName.EndsWith("testUser"));
manager.GetUsers().Where(x => x.UserName.Contains("testUser"));
manager.GetUsers().Where(x => x.UserName == "testUser"); /*or*/ manager.GetUsers().Where(x => x.UserName.Equals("testUser"));
// the above queries can be combined with a single OrderBy/Take/Skip clause as in this following examples
// it will not be possible to call Skip or Take twice.
manager.GetUsers().Where(x => x.UserName.StartsWith("testUser"))
.OrderBy(x => x.LastActivityDate).Skip(10).Take(10).ToList();
manager.GetUsers().Where(x => guidArray.Contains(x.Id))
Before the query is executed against the database, we will invoke an Interface method on your custom membership provider with the gathered parameters from the LINQ query, from which your can build your own custom query against the database.
Note that your provider will have to implement this interface and return the queried users. The above examples will be sufficient to make the UI responsive and not load all the users in memory.
Do you have any suggestions of useful and more practical queries in your custom code that you would like us to be able to support?
It will be very helpful to know the data source you use for the users - you can provide a description or send us the data base so we can test with it. If you are using a web service, what is the protocol?
Any additional feedback on your particular problems and cases will be appreciated.
We have Sitefinity integrated with Dynamics CRM as our back-end user store. We previously had this set up using the basic .NET Membership classes and though it lacked some of the modern finesse and polish, it was straightforward and simple to implement and use. While implementing this integration in Sitefinity we ran into two major problems that caused the implementation to drag out for weeks longer than expected as we had to "guess and check" our way into a solution.
1. We cannot return a proprietary IQueryable<T> without pulling our entire user store into memory. This is infeasible as we have 70k+ users. With the core .NET solution or a custom solution this is not a problem. I'm not sure why Sitefinity requires this as it severely cripples your ability to use an external credentials store efficiently if it has more than a handful of users. This is the number one problem with the Sitefinity membership system. Please please please fix this! Requiring any provider to return an IQueryable<T> for a proprietary T completely breaks the extensibility of that provider because that contract cannot be fulfilled without the use of LINQ to Objects.
2. There are very limited events and information about what is happening during the login process. For example, one thing we previously did was to log all of the login failed events along with a reason for the failure (bad password, user does not exist, etc.) This helps immensely when a user contacts us about login issues. We can review the logs for their particular account and pinpoint the exact issue very quickly. Although Sitefinity has an event that notifys us if a login attempt failed, we do not get any information about why it failed. This makes troubleshooting quite difficult. Trying to bake this functionality into Sitefinity was surprisingly difficult, while adding it in to the core .NET provider was simple.
I can see what your intentions were when forming this membership layer, but it seems that the team really didn't focus on extensibility as much as they did usability. The user manger is quite usable and consumable, and I don't have any particular issues with it. I think making this user manager easy to use was done at the sacrifice of extensibility, however, and it would be nice to see a compromise where the user system was both easy to use and easy to extend.
What kind of problems did you have? Do you think the solution we are going to provide will address them?
Thank you for the feedback Jeff.
1. We use IQueryable<T> for everything in our system, and to be consistent we use it for UserManagement as well. With LINQ the API is much more simple and flexible. But, this causes performance issues with membership providers that does not support LINQ. We beleave that we can address this problemswith the solution we are suggesting - translating the LINQ expression into much simple object containing information about filter, sorting and paging. Additionally, the solution should be integrated in the membership provider with minimal efforts.
2. You are right here - when the User management was implemented the main focus was the usablity. But now we are returning back to fix the extensibility and performance issues we have. Even we haven't planned too much time for the implementation, we will review the login process.
Hi Vladimir - any updates on this? Is this actively being worked on, or still under consideration?
A new API for integrating a custom membership provider with the Sitefinity UI has been introduced and released with Sitefinity 8.0
You can find more details on how to integrate and optimize your custom membership provider here -> docs.sitefinity.com/create-custom-membership-provider-optimize-the-provider-to-use-sitefinity-ui
An example implementation with the standard AspNetMembershipPrivider can be found here -> github.com/.../custom-membership-provider