6

I started an ASP.NET MVC 2 project and I am building off of the code that was generated automatically.

The problem that I am experiencing is that after a user is logged in, it appears that the profile of the newly-logged-in user is not loaded into the HttpContext, so I get a ProviderException with the message "This property cannot be set for anonymous users" when attempting to set a property value in the current user's profile.

For the POST-only LogOn action, Visual Web Developer 2010 Express basically generated:

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (MembershipService.ValidateUser(model.UserName, model.Password))
            {
                FormsService.SignIn(model.UserName, model.RememberMe);
        //...

where FormsService is a property of the controller of type FormsAuthenticationService (also generated):

public class FormsAuthenticationService : IFormsAuthenticationService
{
    public void SignIn(string userName, bool createPersistentCookie)
    {
        if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");

        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }

    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

After the FormsService.SignIn(model.UserName, model.RememberMe) line I was assuming that information for the newly-logged-in account would be immediately available to the controller, but this does not appear to be the case. If, for example, I add profile.SetPropertyValue("MyProfileProperty", "test") below the call to FormsService#SignIn, then I get the ProviderException "This property cannot be set for anonymous users".

How do I load the newly-logged-in user's profile into the HttpContext so that I can set a property value in his or her profile before the next request?

Daniel Trebbien
  • 38,421
  • 18
  • 121
  • 193

2 Answers2

7

The naming of the function is counter intuitive, but ProfileBase.Create(username) will load the existing profile for an existing user or create a new profile if none exists.

var profile = ProfileBase.Create(userName);

This will not load the profile into ControllerContext.HttpContext.Profile but at least you can access or alter the user profile.

Steven K.
  • 2,097
  • 16
  • 15
  • No, you're wrong to assume that, it will load an existing profile. Try it out. – Steven K. Jan 19 '11 at 16:14
  • 1
    "It took some time until I realized that calling ProfileBase.Create() with a username as argument performs a lookup against TableStorage and actually retrieves the data associated with that username. As far as I'm concerned, calling this method Create() is misleading, I would expect Load() or Get()." see the answer to this question: http://stackoverflow.com/questions/1126719/asp-net-sql-profile-provider-does-the-profilebase-create-method-hit-db – Steven K. Jan 19 '11 at 16:17
  • 1
    This worked like a charm! Thank you Steven! (To anyone who might read this seeking a similar solution, be sure to call [`Save`](http://msdn.microsoft.com/en-us/library/system.web.profile.profilebase.save.aspx) on the profile object if you make changes.) – Daniel Trebbien Jan 19 '11 at 16:51
1

The "raw" Forms authentication built into the MVC template does not automatically load the profile. All it does is validate the user against the Membership tables and set the login cookie. If you want profile data persisted in Session state on login, you'll have to do that explicitly:

[HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (MembershipService.ValidateUser(model.UserName, model.Password))
            {
                FormsService.SignIn(model.UserName, model.RememberMe);
                LoadProfile(model.UserName);
            }
     }

     private void LoadProfile(string UserName)
     {
          MyModelContext ctx = new MyModelContext(); //EF or LINQ2SQL context
          var user = ctx.Users.Where(u => u.UserName == UserName).FirstOrDefault();
          Session.Add("CurrentUser", user);
     }

UPDATE:

I misunderstood the original question. You have to create and save a profile of type ProfileCommon for anonymous users. See this post:

http://forums.asp.net/p/1150958/2619264.aspx

Dave Swersky
  • 34,502
  • 9
  • 78
  • 118
  • I'm assuming the profile then is loaded into Session when the "next" page is loaded after logging in. Is that correct? – Nick DeVore Jan 19 '11 at 16:00
  • @Nick: In this implementation, the profile is loaded into session immediately upon validation, before the redirect. – Dave Swersky Jan 19 '11 at 16:03
  • This may be a dumb question, but how does this load the profile (`ControllerContext.HttpContext.Profile`) of the newly-logged-in user? – Daniel Trebbien Jan 19 '11 at 16:04
  • @Daniel: I'm assuming that the OP is connecting to the Membership tables in a SQL database. The sample code above demonstrates the idea of using an Entity Framework or LINQ2SQL context to connect to that database and load the profile. If the profile is being stored somewhere else, a different method would be used to load it. – Dave Swersky Jan 19 '11 at 16:12