How to manage multiple calls to Membership.GetUser() efficiently - c#

I have multiple calls to Membership.GetUser() and can't seem to find anything built in to cope with this (without hitting the database multiple times).
I first looked into static variables before realising this was not suitable. I know I can use sessions but I would need to hash/encrypt the userID to make it safe, which is fine but I didn't want to do this without checking if there was a built in way to do this within the .net membership provider
Is there a built in way of remembering a value like the UserID and safely storing that for the current user?

I recommend using the Singleton-per-Request pattern which uses the HttpContext.Items property.
public class UserPerRequest
{
/// <summary>
/// Returns the result of Membership.GetUser(), but will cache the results within the
/// current request so it's only called once per request.
/// </summary>
public static MembershipUser Current
{
get
{
const string key = "UserPerRequest";
if (HttpContext.Current.Items[key] == null)
HttpContext.Current.Items[key] = Membership.GetUser();
return (MembershipUser)HttpContext.Current.Items[key];
}
}
}
So everywhere you'd call Membership.GetUser() just call UserPerRequest.Current instead. The only time you'll still get multiple calls to the database during a request is if Membership.GetUser() returns null.

Membership.GetUser() is an indirect call to the class MembershipProvider's method:
public abstract MembershipUser GetUser(string username, bool userIsOnline);
So, you can write a class that derives from the MembershipProvider you're using (for example SqlMembershipProvider or ActiveDirectoryMembershipProvider, or a custom one) and override this method using some clever cache mechanism.
Then you will need to change the web.config to declare this new MembershipProvider class:
<membership defaultProvider="MyProvider">
<providers>
<clear/>
<add name="MyProvider" type="...MyProvider" ... />
</providers>
</membership>

Related

Changing membership connection string

Iam new to asp.net membership & I need help to change its connection string programmatically.
What I have tried till now is
I have create a class project name Sample as namespace** and extends the System.Web.Security.SqlMembershipProvider
code as
namespace Sample
{
public class Connectionstring : SqlMembershipProvider
{
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
string connectionString = "server=xx.xx.xx;database=db;user id=un;password=pwd";
// Set private property of Membership provider.
FieldInfo connectionStringField = GetType().BaseType
.GetField("_sqlConnectionString", BindingFlags.Instance |
BindingFlags.NonPublic);
connectionStringField.SetValue(this, connectionString);
}
}
}
and altered web config file in membership tag as
<membership defaultProvider="SQLMembershipProvider">
<providers>
<add name="SQLMembershipProvider" type="sample.Connectionstring,sample" connectionStringName="SQLMembershipConnString" applicationName="#######" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed" />
</providers>
</membership>
and while running the web application project the connection string which i am changing is not get altered?
waiting for your valuable responses and comments
What i have also found on the net about problem is this here:
A simpler, albeit somewhat eyebrow-raising solution is just modifying
the connection string in the providers early enough in the request's
lifecycle:
private void SetProviderConnectionString(string connectionString)
{
var connectionStringField =
Membership.Provider.GetType().GetField("_sqlConnectionString",
BindingFlags.Instance | BindingFlags.NonPublic);
if (connectionStringField != null)
connectionStringField.SetValue(Membership.Provider, connectionString);
}
Calling this method from Global.asax.cs inside
Application_PreRequestHandlerExecute does the job. Haven't tested it
too much, but even if something doesn't work, it just means it needs
to be done earlier. No guarantees this will work with future versions
of the framework, although most likely it will.
So, the 'SetProviderConnectionString' method can be called manually (after the Initialize method is finished), instead of expecting the framework to call the overwritten Initialize method when the Membership.Provider is referenced for a first time.
You don't appear to be calling base.Initialize from your Initialize override. I'm surprised anything works in this case.
In .NET 4 (not sure about earlier versions), you should be able to add the connection string to the configuration collection before calling base.Initialize as follows:
public override void Initialize(string name, NameValueCollection config)
{
config["connectionString"] = ... whatever ...;
base.Initialize(name, config);
}
This avoid the need to use reflection to access a private field.
For what reason are you not using a connection string in the web config?
Typically the web server will access the database using the web server's credentials, not an individual user's credentials.
This is a generic setup guide for asp.net authentication.
http://vstudiojourney.blogspot.com/2008/06/choosing-another-database-for.html

Membership provider in Umbraco

I'm trying to create a custom membership provider to authenticate users in Umbraco to an already existing database outside Umbraco. From what I've learned so far it should not be much more work than create a class that inherits from umbraco.providers.members.UmbracoMembershipProvider and overrides the ValidateUser() function.
What I'm wondering is how I'm supposed to do when I wish to include this class to my Umbraco project other than specify it in the web.config file? Is it possible to extend Umbraco in this way whitout re-compile the source code?
EDIT - I wrote an article about how I implemented this on http://marcus-abrahamsson.se/post/Membership-Provider-in-Umbraco
You can create and build your own module and then copy the .dll into the bin folder of the Umbraco install. No need to recompile Umbraco itself.
I had a very similar issue, but perhaps with a smaller requirement. I was trying to implement the "last logged in" functionality as described in an Umbraco TV video, to display the last logged in date of members. As the code base has changed, the described method was no longer valid. I found there were 2 general ways to get it to work:
Create your own membership provider. Inherit from UmbracoMembershipProvider and update web config to use your provider. I overrode one method, and my provider code looked like:
using umbraco.BusinessLogic;
using System.Web.Profile;
using System;
namespace zo.Umb.LastLogin
{
// this approac works, and it may be necessary to extend the membership provider in the future, so that's why I'm
// leaving it here. But for now I'm using the ApplicationStartupHandler event subscription method
// in MemberEvent.cs
/// <summary>
/// Inherit the default membership provider and substitute my own method that's fired when a member tries
/// to log in. Note that you must also replace the UmbracoMembershipProvider reference in the web.config
/// with a reference to this one. eg:
/// <add name="UmbracoMembershipProvider" type="zo.Umb.LastLogin.MyMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Another Type" passwordFormat="Hashed" />
///
/// also note that, to have custom profile properties appear, they must also be added in the web.config
/// like so:
/// <profile defaultProvider="UmbracoMemberProfileProvider" enabled="true">
/// <providers>
/// <clear />
/// <add name="UmbracoMemberProfileProvider" type="umbraco.providers.members.UmbracoProfileProvider, umbraco.providers" />
/// </providers>
/// <properties>
/// <clear />
/// <add name="lastLogin" allowAnonymous ="false" provider="UmbracoMemberProfileProvider" type="System.DateTime" />
/// </properties>
///</profile>
/// </summary>
public class MyMembershipProvider : umbraco.providers.members.UmbracoMembershipProvider
{
public override bool ValidateUser(string username, string password)
{
var success = base.ValidateUser(username, password);
if (success)
{
var user = GetUser(username, true);
var profile = ProfileBase.Create(user.UserName);
profile["lastLogin"] = DateTime.Now;
profile.Save();
}
return success;
}
}
}
ValidateUser is fired when the user tries to get to secured content.
Note that you now have to update the web.config to point to your custom membership provider.
While this worked, I didn't like moving off of the default provider, editing web.config, etc. and wanted to use the more "standard" approach of the previous version. So I used method 2:
Wire into ApplicationStartupHandler, and subscribe to Member.BeforeSave
using System;
//using umbraco.BusinessLogic;
using umbraco.businesslogic;
using umbraco.cms.businesslogic.member;
namespace zo.Umb.LastLogin
{
public class MemberEvent : ApplicationStartupHandler
{
public MemberEvent()
{
Member.BeforeSave += new Member.SaveEventHandler(Member_BeforeSave);
}
void Member_BeforeSave(Member sender, umbraco.cms.businesslogic.SaveEventArgs e)
{
//Log.Add(LogTypes.Debug, sender.Id, "Member_AfterAddToCache");
sender.getProperty("lastLogin").Value = DateTime.Now;
}
}
}
If I recall, member.beforesave is not something that's fired when a user is created and saved to the membership store; it's actually fired when a user logs in.

Membership.GetNumberOfUsersOnline() does not return correct values

I basically need to restrict the number of logged in users in my application. What I am doing is using Membership.GetNumberOfUsersOnline()
I am getting the number of logged in users while authenticating a new user I check the number of online users to the number I want to restrict.
Membership.GetNumberOfUsersOnline() works fine when a new user logs in, the problem occurs when a user signs out the value of Membership.GetNumberOfUsersOnline() does not automatically decrements. I searched over the msdn and found that this method checks the last user activity time, which doesnt support signout event.
Is there any other way in Membership providers to restrict the number of users.
I am using Silverlight and REST services. I am using Membership.GetNumberOfUsersOnline() at the server side.
Thanx in advance.
You can try ASP.NET C# Visitor Real-time Session Tracker by Sarin at http://www.sarin.mobi/2008/11/aspnet-csharp-visitor-real-time-session-tracker/
I'm assuming you are using an SqlMembershipProvider. If so, this is the direction I would take...
Create a class that inherits SqlMembershipProvider and override the GetNumberOfUsersOnline() and ValidateUser() methods...
using System.Web.Security;
public class MyMembershipProvider : SqlMembershipProvider
{
public override bool ValidateUser(string username, string password)
{
if (base.ValidateUser(username, password))
{
// successfully logged in. add logic to increment online user count.
return true;
}
return false;
}
public override int GetNumberOfUsersOnline()
{
// add logic to get online user count and return it.
}
}
Now if you're using a LoginStatus control to allow users to sign out, you can use the LoggedOut event to add the decrement online user count logic there.
You will have to use your new custom membership provider in the web.config. For the type property of your membership, change it from whatever it says, something like type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" to something like type="MyNameSpace.MyMembershipProvider". I think that's all there is to it.
This solution allows you to keep using your SqlMembershipProvider provider with just a couple additions.
Add a new column named status in your user table in data base.The data type of that status column bit (1/0).In every 15 min check no of users are online. Use a Timer in code behind page.Check the timer and in every 15 min raise that paricular event

IsUserInRole calls GetRolesForUser?

When I implement the RoleProvider class and call Roles.IsUserInRole(string username, string roleName), code execution first goes to the method 'GetRolesForUser(string username)'. Why is this? I don't want to iterate all roles when I am just looking for the single value of whether that user belongs in one role. Is this a limitation of .NET's role provider class or is there something I can do to control the execution of code a bit more?
Here's the calling code
if (Roles.IsUserInRole(CurrentUser.UserName, "Teacher")) {
And here's the implementation of IsUserInRole
public override bool IsUserInRole(string username, string roleName) { return true; }
But the code GetRolesForUser always gets implemented first:
public override string[] GetRolesForUser(string username) {
string[] roles = GetAllRoles();
List<string> userRoles = new List<string>();
foreach (string role in roles) {
if (IsUserInRole(username, role)) {
userRoles.Add(role);
}
}
return userRoles.ToArray();
}
The RoleProvider.IsUserInRole(username, password) is used for checking roles for a given user which is not the current loggon user(for current logon user, it also use the Principal.IsInRole instead). And for RolePrincipal, it always use the GetRolesForUser to cache the roles and do the role checking among the cached role list. (source)
can user Roles.Provider.IsUserInRole instead of Roles.IsUserInRole
There's a layer Microsoft's role provider solution that enables caching a user's roles in a cookie so it doesn't need to call the provider's GetRolesForUser method. I believe the cookie caching is part of the Roles class, so as long as you implement from the RoleProvider base class, it should be compatible. It's worth a look at the code in reflector to get an idea of how MS implements their own abstract classes, and what the static helper classes do (Roles and Membership)
Try adding cacheRolesInCookie="true" to the roleManager element in your config file, and see if the flow changes.
Since you're using your own implementation of a RoleProvider, you can also override the IsUserInRole method and provide your own implementation of checking if a user is in a role.
UPDATE: This block of code gets called inside the Roles.IsUserInRole method:
IPrincipal currentUser = GetCurrentUser();
if (((currentUser != null) && (currentUser is RolePrincipal)) && ((((RolePrincipal) currentUser).ProviderName == Provider.Name) && StringUtil.EqualsIgnoreCase(username, currentUser.Identity.Name)))
{
flag = currentUser.IsInRole(roleName);
}
else
{
flag = Provider.IsUserInRole(username, roleName);
}
The else block is what will call your custom provider's IsUserInRole method.
So the roles for your user have not yet been added to the Principal object. If you just haven't gotten around to that step yet, OK. If not, make sure you do that. It will make sure that every time you call Roles.IsUserInRole, or User.IsInRole that those functions will use an in-memory cache of the roles for the user (once loaded) instead of having to go to the database every time. (Although the base role provider and Roles manager class should take care of this for you.)
Can you verify the config file settings for the role provider? Also, what version of .net are you using? Are you manually managing the login process or are you using the .net login control? Have you implemented a custom Roles class? Or are you using the System.Web.Security.Roles?

ASP.NET MVC3 Custom Membership Provider - The membership provider name specified is invalid

I'm implementing a custom membership provider, and everything seems to go swimmingly until I create a MembershipUser object. At that point, I receive the error:
The membership provider name specified
is invalid. Parameter name:
providerName
In web.config the membership key is
<membership defaultProvider="MembersProvider">
<providers>
<clear/>
<add name="MembersProvider" type="Members.Providers.MembersProvider" connectionStringName="ApplicationServices"
enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="DeviceDatabase" />
</providers>
</membership>
When creating the MembershipUser object from my custom User class:
public static MembershipUser ToMembershipUser(User user)
{
MembershipUser member = new MembershipUser
("MembersProvider"
, user.Name
, user.Id
, user.EmailAddress
, user.PasswordQuestion
, user.Comment
, user.IsApproved
, user.IsLockedOut
, user.DateCreated
, user.LastLoginDate ?? DateTime.MinValue
, user.LastActivityDate ?? DateTime.MinValue
, user.LastPasswordChangedDate ?? DateTime.MinValue
, user.LastLockoutDate ?? DateTime.MinValue
);
return member;
}
(I realize I could probably just inherit my User class from MembershipUser, but it's already part of an existing class hierarchy. I honestly think this is the first time I've encountered a legitimate need for for multiple inheritance!)
My feeling is that the new MembershipUser(...) providerName parameter is supposed to match what's set in web.config, but, since they match already, I'm at a loss as to how to proceed.
Is there a convenient way to get the name of the active membership provider in code?
I'm starting to think that using the built-in membership system is overkill and more trouble than it's worth.
Edit
Not sure if it's relevant, but the custom membership provider class is in a class library, not the main WAP project.
Update
Here's the contents of the System.Web.Security.Membership.Provider object as show in the VS2010 command window:
>eval System.Web.Security.Membership.Provider
{Members.Providers.MembersProvider}
[Members.Providers.MembersProvider]: {Members.Providers.MembersProvider}
base {System.Configuration.Provider.ProviderBase}: {Members.Providers.MembersProvider}
ApplicationName: null
EnablePasswordReset: true
EnablePasswordRetrieval: false
MaxInvalidPasswordAttempts: 5
MinRequiredNonAlphanumericCharacters: 0
MinRequiredPasswordLength: 6
PasswordAttemptWindow: 10
PasswordFormat: Function evaluation was aborted.
PasswordStrengthRegularExpression: Cannot evaluate expression because debugging information has been optimized away .
RequiresQuestionAndAnswer: Cannot evaluate expression because debugging information has been optimized away .
RequiresUniqueEmail: Cannot evaluate expression because debugging information has been optimized away .
Update 2
This just randomly started working, which means I changed something but can't remember what it was. Stupid brain. I'll accept the only answer that's been posted and update this if I figure out what the problem was.
I used Membership.Provider.Name to get the correct name parameter
public static MembershipUser GetUserFromEntity(this UserEntity userEntity)
{
return new MembershipUser(
Membership.Provider.Name,
userEntity.Username,
userEntity.PartitionKey,
userEntity.Email,
userEntity.PasswordQuestion,
userEntity.Comment,
userEntity.IsApproved,
userEntity.IsLockedOut,
userEntity.CreationDate,
userEntity.LastLoginDate,
userEntity.LastActivityDate,
userEntity.LastPasswordChangedDate,
userEntity.LastLockedOutDate
);
}
Not the solution yet, but with the two following functions you can at least get an idea what is registered:
Membership.Provider
Membership.Providers
I did inherit my User class from MembershipUser for some reason, but I'm pretty sure I had a good reason for it :-)

Categories

Resources