Strange behaviour in MultiplexingMembershipProvider

Vote:
 

Hi,

The MultiplexingMembershipProvider does this in the CurrentProvider getter:

MembershipUser currentMembershipUser = this.CurrentMembershipUser;
if (currentMembershipUser != null)
{
  defaultProvider = this.MembershipProviders[currentMembershipUser.ProviderName];
}
else
{
   defaultProvider = this.DefaultProvider;
}

 

CurrentMembershipUser checks if the user exists in the provider and if it does not, it defaults to the first provider in the list. This makes me unable to hook logic into Validate() on my own provider, since it never enters.

Will there be a fix for this?

Thanks!
Magnus

#20869
Jun 17, 2008 11:10
Vote:
 

I'm on thin ice here, but looking at the code, it seems like the CurrentProvider getter should not be called unless there is a validated user. The else part looks like a neccessary fallback.

/Steve

#20914
Jun 17, 2008 14:21
Vote:
 

Indeed it is so, but it only checks if a user exists and if it does not, it defaults to the first provider in the list.

I would like to do things in Validate() if a user does not exist (create one if it exists in another internal system), but since it defaults to the first provider in the list, I can not do that.

As a temporary workaround, I put my own provider first in the list. But if I'd like to make two custom providers that does kind of the same thing, I'll be in trouble. :/

#20915
Jun 17, 2008 14:27
Vote:
 

Just to make sure, you want to implement ValidateUser in your own membership provider, and you use the MultiPlexingProvider because you have other providers that also needs to work.

Are you calling the MultiplexingMembershipProvider.CurrentProvider from your code?

When implementing a membership provider, you implement the ValidateUser method (among lots of other methods and properties), and it should not really need to know anything about the MultiPlexing provider at all.

Or, am I missing the point here? Sorry if I'm asking questions with obvious answers Smile.

/Steve

#21017
Jun 18, 2008 14:44
jo
Vote:
 

I suspect the problem is that the registered providers are first called in their GetUser implementations, and then the provider that first returns a valid user is called in *its* ValidateUser. However, if the user doesnt exist in any provider, this call is fallbacked to the defaultProvider.

Without changing the code in the MultiplexingProvider the only "workaround" is to let your provider be the first in the chain, as already stated in the op.

/johan

#21023
Jun 18, 2008 21:36
Vote:
 
The problem is exactly as Johan described.
#21047
Jun 19, 2008 13:50
Vote:
 

Ok, then I think I got it.

I haven't checked, but I will assume that ValidateUser will be called first instead of the GetUser method if your provider is the only provider available (not using the multiplexing provider).

Given that, the implementation of the multiplexing provider require the participating providers to actually return a user from the GetUser method in order for it to try to validate it.

If you return a (possibly invalid) user in the GetUser method, ValidateUser will be called afterwards. Even if the MembershipUser returned by GetUser is not a real user. You can do it like this because you know how the multiplexing provider works. 

I'm not sure I will call it a bug, more of a design issue. I do think it is unfortunate that the multiplexing provider call methods on the provider that normally would not happen if configured without it (if that made any sense.) It means that a provider, like in this case, must be written to know about the multiplexing provider.

Instead of doing something like this, I'd rely on your provider to be the first one in the list, as you've discovered, or put the whole logic of creating users outside the GetUser/ValidateUser methods. It your login page cannot log the user in, you can see it from code, and you can go ahead creating the user from there. If your architecture allows for this of course.

So, is this something that can be fixed? Should it be fixed? Not up to me, but I'll vote for it. ValidateUser should call ValidateUser on all providers, and not GetUser. The design goal for the Multiplexing provider should be to be as transparent as possible, and not call any methods on the underlying providers that would not be called if it had not been in use in the first place.

/Steve

 

#21200
Jun 20, 2008 0:28
Vote:
 

Another thing, assuming ValidateUser is called instead of GetUser, how would you know that your provider is the one that should handle it if the user does not exists?

What if there is another provider configured to run after your provider? If the answer is that you control the order of providers used by the multiplexing provider, then you can just as well put your provider as the first one in the list.

The point is that even if the multiplexing provider was written in the way you'd like it to, you'll be short circuiting it in your ValidateUser method.

/Steve

 

#21201
Jun 20, 2008 0:36
Vote:
 

Hi again,

I also think the multiplexing provider should be as transparent as possible. It does not call provider methods in the "usual" way as it is built now, thus forcing me to make my provider "know" about the multiplexing provider and build it different from how I'd do otherwise, should I want tohave it second in the list.

I've put the logic for creating users in the Validate() method, since it should import user data from an external system if it exists there, but not in Star Community. 

It works now that I have my provider first in the list, but what if I'd like to make another provider that does things in Validate()? :)

I know that you validate against the provider returned from the user in GetUser(), but there surely must be another way? Wouldn't just trying Validate() on all of the providers in the list work?

I also vote for it to be "fixed" and would be very happy if it was!

#21204
Jun 20, 2008 10:21
jo
Vote:
 

I think the whole idea behind the CurrentProvider/CurrentMembershipUser in the MultiplexingProvider is to improve performance as calls tends to be made in " bursts". When logging in for example, the framework first calls ValidateUser(), and if it succeeds then follows up right away with a call to GetUser(). To avoid having to iterate over all providers for every call, the retrieved membershipuser is stashed away in the current context.

Though, I agree that the implementation of ValidateUser() probably could be rewritten to iterate over all providers implementations, and upon the first that returns true *then* set the CurrentProvider.

However, to save server roundtrips then each and every provider would have to perform this context "caching" (remember the framework first calls ValidateUser() , then GetUser() ) themselves.

The SqlMembershipProvider does not do this, and every method call will result in one hit on the backend sql server....

/johan

#21220
Jun 23, 2008 11:59