6

The problem Membership.GetUser works fine if I use the GUID, but fails if I try it without parameters or with the name:

//This works and adds records to aspnet_user & aspnet_Membership tables
MembershipUser membershipUser = Membership.CreateUser(_name, _pwd, _email, null, null, true, null, out createStatus); 

//These fail with an InvalidCastException
MembershipUser membershipUser2 = Membership.GetUser(_name, true);
MembershipUser membershipUser3 = Membership.GetUser();

//If I go look up the GUID for this user (userId), this works!
MembershipUser membershipUser4 = Membership.GetUser(new Guid("E1428CD3-CF17-494E-AB77-CF8F6010F585"), true);

Other Mysteries

//This works
Int32 count = Membership.GetNumberOfUsersOnline();    

//this fails (but totalRecords has the right value)
Int32 totalRecords;
MembershipUserCollection col = Membership.GetAllUsers(0,100, out totalRecords);

The Setup

  • MVC
  • .NET 4.0
  • Default SQL Membership Provider (no custom or overrides)

ex:

<membership defaultProvider="SqlProvider">
    <providers>
      <clear />
      <add name="SqlProvider" 
           type="System.Web.Security.SqlMembershipProvider" 
           connectionStringName="MyDB" 
           enablePasswordRetrieval="false" 
           enablePasswordReset="true" 
           requiresQuestionAndAnswer="false" 
           applicationName="myapp" 
           requiresUniqueEmail="false" 
           passwordFormat="Hashed" 
           maxInvalidPasswordAttempts="5" 
           minRequiredPasswordLength="2" 
           minRequiredNonalphanumericCharacters="0" 
           passwordAttemptWindow="10"
           passwordStrengthRegularExpression="" />
    </providers>
</membership>
  • Standard aspnet_* database tables with no customization or extra keys
  • aspnet_Membership_GetAllUsers runs fine manually from the DB. Redirected output shows the expected results are being returned
  • aspnet_Membership_GetUserByName is working as well from the DB

Exception Details

Specified cast is not valid.
   at System.Data.SqlClient.SqlBuffer.get_SqlGuid()
   at System.Data.SqlClient.SqlDataReader.GetGuid(Int32 i)
   at System.Web.Security.SqlMembershipProvider.GetUser(String username, Boolean userIsOnline)
   at System.Web.Security.Membership.GetUser(String username, Boolean userIsOnline)

Thoughts

It's almost like there's something wrong inside the Membership.GetUser method. I reflected out the code for the System.Web.Security.SqlMembershipProvider.GetUser for my System.Web.dll (version 4.0) and I get the following:

public override MembershipUser GetUser(string username, bool userIsOnline)
{
  SecUtility.CheckParameter(ref username, true, false, true, 256, "username");
  SqlDataReader reader = (SqlDataReader) null;
  try
  {
    SqlConnectionHolder connectionHolder = (SqlConnectionHolder) null;
    try
    {
      connectionHolder = SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);
      this.CheckSchemaVersion(connectionHolder.Connection);
      SqlCommand sqlCommand = new SqlCommand("dbo.aspnet_Membership_GetUserByName", connectionHolder.Connection);
      sqlCommand.CommandTimeout = this.CommandTimeout;
      sqlCommand.CommandType = CommandType.StoredProcedure;
      sqlCommand.Parameters.Add(this.CreateInputParam("@ApplicationName", SqlDbType.NVarChar, (object) this.ApplicationName));
      sqlCommand.Parameters.Add(this.CreateInputParam("@UserName", SqlDbType.NVarChar, (object) username));
      sqlCommand.Parameters.Add(this.CreateInputParam("@UpdateLastActivity", SqlDbType.Bit, (object) (bool) (userIsOnline ? 1 : 0)));
      sqlCommand.Parameters.Add(this.CreateInputParam("@CurrentTimeUtc", SqlDbType.DateTime, (object) DateTime.UtcNow));
      SqlParameter sqlParameter = new SqlParameter("@ReturnValue", SqlDbType.Int);
      sqlParameter.Direction = ParameterDirection.ReturnValue;
      sqlCommand.Parameters.Add(sqlParameter);
      reader = sqlCommand.ExecuteReader();
      if (!reader.Read())
        return (MembershipUser) null;
      string nullableString1 = this.GetNullableString(reader, 0);
      string nullableString2 = this.GetNullableString(reader, 1);
      string nullableString3 = this.GetNullableString(reader, 2);
      bool boolean1 = reader.GetBoolean(3);
      DateTime creationDate = reader.GetDateTime(4).ToLocalTime();
      DateTime lastLoginDate = reader.GetDateTime(5).ToLocalTime();
      DateTime lastActivityDate = reader.GetDateTime(6).ToLocalTime();
      DateTime lastPasswordChangedDate = reader.GetDateTime(7).ToLocalTime();
      Guid guid = reader.GetGuid(8);
      bool boolean2 = reader.GetBoolean(9);
      DateTime lastLockoutDate = reader.GetDateTime(10).ToLocalTime();
      return new MembershipUser(this.Name, username, (object) guid, nullableString1, nullableString2, nullableString3, boolean1, boolean2, creationDate, lastLoginDate, lastActivityDate, lastPasswordChangedDate, lastLockoutDate);
    }
    finally
    {
      if (reader != null)
        reader.Close();
      if (connectionHolder != null)
        connectionHolder.Close();
    }
  }
  catch
  {
    throw;
  }
}

Next Step

I'm hoping to get some direction from the SO crowd on where I should go next. I figure I could either override this method and put together my own provider just to debug it, or I could just go around the darned thing and call out to the DB myself directly. This seems like a lot of heartache from some basic DB CRUD.

Current Status

  • 3/5/2013: I discovered this morning that my aspnet_Users table has the UserId as an nvarchar(256) instead of a uniqueidentifier. Perhaps the SqlDataReader.GetGuid() is choking there. I'll run some tests this evening to see if that's the problem. I'm wondering if my table structure is outdated, because online documentation shows this field as a uniqueidentifier.
maulkye
  • 427
  • 5
  • 13
  • How does it fail? Exception Details please. – Glenn Ferrie Mar 05 '13 at 04:45
  • Added exception details. The line: System.Data.SqlClient.SqlDataReader.GetGuid(Int32 i) sure seems odd. – maulkye Mar 05 '13 at 04:49
  • Also, if you are going to "reflect-out" / reverse-engineer / decompile-for-insight the code, you should look at the method GetUser() on the static Membership class -- Membership.GetUser() -- it is THERE where assumptions are being made about the current user's identity. GetUser() assumes that it is retrieving detail about the CURRENT USER in a web application – Glenn Ferrie Mar 05 '13 at 04:49
  • Good idea, I tried that. The Membership class has this code: `return Membership.GetUser(Membership.GetCurrentUserName(), true);` Executing the following in the Immediate window yielded the expected username: `System.Web.Security.Membership.GetCurrentUserName() //Good result` However... `System.Web.Security.Membership.GetUser( System.Web.Security.Membership.GetCurrentUserName(), true)` Still seems to be returning the InvalidCast. :( – maulkye Mar 05 '13 at 04:57
  • Get us that stack trace if you can... specicallly Invalid cast details. – Glenn Ferrie Mar 05 '13 at 05:00
  • I added the stack trace to the Exception Details section above. It's quite short: `Specified cast is not valid. at System.Data.SqlClient.SqlBuffer.get_SqlGuid() at System.Data.SqlClient.SqlDataReader.GetGuid(Int32 i) at System.Web.Security.SqlMembershipProvider.GetUser(String username, Boolean userIsOnline) at System.Web.Security.Membership.GetUser(String username, Boolean userIsOnline)` – maulkye Mar 05 '13 at 05:01
  • I looks like the aspnet_Users table has the UserId as nvarchar(256) instead of uniqueidentifier. This may be the problem. I'll run some tests this evening to confirm. – maulkye Mar 05 '13 at 15:29
  • I assure you that there isn't a problem with the methods in question. If anything your expecting the wrong results from them. The exception is telling you exactly where its failing. – Security Hound Mar 05 '13 at 17:19

2 Answers2

1

Why do you use membership.CurrentUserName()? You can just use HttpContext.Current.User.Identity.Name;

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
1

Answer

My aspnet_User and aspnet_Membership tables had the UserId set to a type of nvarchar(256).

Discussion

This appears to have been causing the error during the following line from the SqlMembershipProvider.GetUser method:

Guid guid = reader.GetGuid(8);

I ran aspnet_regsql.exe from both .NET frameworks 2 and 4, and both set the UserId to a uniqueidentifier, leading me to think I did not (after all) have a standard ASPNET DB.

A little digging in the project's history reveals that this membership database was originally generated via a custom provider for MySql. I can only conclude that it was never regenerated when we switched back to the default provider.

Thanks to everyone for their feedback.

maulkye
  • 427
  • 5
  • 13