Doug
Doug

Reputation: 71

ASP.NET MVC 2 and SQL Table Profile Provider

I'm trying to add the sample table profile provider from http://www.asp.net/downloads/sandbox/table-profile-provider-samples to a new MVC 2 site.

After a bit of research and fiddling around, I've arrived at a profile class that looks like this.

namespace MyNamespace.Models
{
    public class UserProfile : ProfileBase
    {
        [SettingsAllowAnonymous(false),CustomProviderData("FirstName;nvarchar")]
        public string FirstName
        {
            get { return base["FirstName"] as string; }
            set { base["FirstName"] = value; }
        }

        [SettingsAllowAnonymous(false),CustomProviderData("LastName;nvarchar")]
        public string LastName
        {
            get { return base["LastName"] as string; }
            set { base["LastName"] = value; }
        }

        public static UserProfile GetUserProfile(string username)
        {
            return Create(username,false) as UserProfile;
        }

        public static UserProfile GetUserProfile()
        {
            return Create(Membership.GetUser().UserName,true) as UserProfile;
        }
    }
}

And a web.config like

<profile enabled="true" defaultProvider="TableProfileProvider" inherits="MyNamespace.Models.UserProfile">
  <providers>
    <clear />
    <add name="TableProfileProvider" type="Microsoft.Samples.SqlTableProfileProvider" connectionStringName="ContentDB" table="aspnet_UserProfiles" applicationName="/"/>
  </providers>
</profile>

Things I think I've found out along the way are

It all seems to work OK once a user is logged in. However, I want to capture some profile data as part of the new user registration process, and I cannot seem to access the profile object until the user has logged in.

I've tried adding a call to save profile data in the new user registration section of the MVC template code:

FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
UserProfile profile = UserProfile.GetUserProfile(Membership.GetUser().UserName);
profile.FirstName = "Doug";
Profile.Save();
return RedirectToAction("Index", "Home");

However it seems that Membership.GetUser() is null until the user actually logs in. I also tried using the user's name from the model.

FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
UserProfile profile = UserProfile.GetUserProfile(model.UserName);
profile.FirstName = "Doug";
profile.Save();
return RedirectToAction("Index", "Home");

This gets a bit further, but fails when trying to set the FirstName profile field, with an error message along the lines of "trying to set an attribute as an anonymous user, but this is not allowed" (sorry, don't have access to the exact message as I'm typing this).

Is there any way round this? It looks like the FormsServer.SignIn method does not actually log the user in as far as forms authentication is concerned, and it needs a round trip to be fully logged in, presumably needing the cookie to be submitted back to the server.

If there's no easy way round this I could populate the profile table directly using data access methods (insert into aspnet_UserProfiles ....). Is this a bridge too far, or is it a viable solution?


Hasn't anyone got this problem? No? Just me then!

Just to update, I've tried the suggestion Franci Penov makes in his answer to this posting.

So, now my code looks like this.

            FormsService.SignIn(model.UserName, false /* createPersistentCookie */);

            GenericIdentity id = new GenericIdentity(model.UserName);
            HttpContext.User = new GenericPrincipal(id, null);

            UserProfile profile = UserProfile.GetUserProfile(Membership.GetUser().UserName) as UserProfile;
            profile.FirstName = "Doug";
            profile.Save();

            return RedirectToAction("Index", "Home");

Now, at least the call to Membership.GetUser() returns a valid MembershipUser object, but trying to set the FirstName profile property still results in the message This property cannot be set for anonymous users.

So, the user is logged on as far as Membership is concerned, but the Profile system still thinks not.

Any ideas?

Upvotes: 3

Views: 1782

Answers (2)

Timothy Dooling
Timothy Dooling

Reputation: 510

I have a slightly simpler approach to the problem.

I set the following in my web.config file:

<profile enabled="true">
  <providers>
    <clear />
    <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
  </providers>
  <properties>
    <add name="InfoID" />
    <add name="FirstName" />
    <add name="LastName" />
    <add name="Address" />
    <add name="City" />
    <add name="State" />
    <add name="PostalCode" />
    <add name="Country" />
    <add name="Phone" />
  </properties>
</profile>

To update it in my AccountController class, I used the following:

var user = db.UserInformations.FirstOrDefault(u => u.InfoID == infoID);
var profile = Request.RequestContext.HttpContext.Profile;
profile.Initialize(register.UserName, true);
profile.SetPropertyValue("InfoID", user.InfoID.ToString());
profile.SetPropertyValue("LastName", user.LastName);
profile.SetPropertyValue("FirstName", user.FirstName);
profile.SetPropertyValue("Address", user.Address);
profile.SetPropertyValue("City", user.City);
profile.SetPropertyValue("State", user.State);
profile.SetPropertyValue("Country", user.Country);
profile.SetPropertyValue("PostalCode", user.PostalCode);
profile.SetPropertyValue("Phone", user.Phone);
profile.Save();

Instead of using string keys, you could create a dynamic wrapper and initialize it with the profile variable.

This works fine for me. It updates the standard aspnet_Profile database for the user indicated. I am converting from one login system to the standard login, so I have the extra InfoID field (it links to another database). I do this at login to transfer the information over.

You should probably check to see if the information for the specific user exists for any of the fields before unnecessarily setting them upon login or update only those fields that need changing.

NOTE: you must initialize the profile FIRST, or any changes will cause a yellow screen error.

Upvotes: 0

Doug
Doug

Reputation: 71

Hurrah!

Reading this posting even more closely, I thought I'd try calling the profile Initialize method explicitly, and it worked!

Final full code for the register action method is:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);

        if (createStatus == MembershipCreateStatus.Success)
        {
            FormsService.SignIn(model.UserName, false /* createPersistentCookie */);

            GenericIdentity id = new GenericIdentity(model.UserName);
            HttpContext.User = new GenericPrincipal(id, null);

            UserProfile profile = UserProfile.GetUserProfile(Membership.GetUser().UserName) as UserProfile;
            profile.Initialize(Membership.GetUser().UserName, true);
            profile.FirstName = "Doug";
            profile.Save();

            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
        }
    }

    // If we got this far, something failed, redisplay form
    ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
    return View(model);
}

Hope this helps.

Doug

Upvotes: 4

Related Questions