Tuesday, March 08, 2011

MVC3 control Profile with action filter

Asp.net profile providers are very useful for storing information about a particular user on your website. When you reference HttpContext.Profile, it is automatically loaded from the database. If you do not reference the profile, it will not be loaded, which works out great.

Changing and saving the profile is another story. You setup your profile provider in the web.config. In that configuration, there is an automaticSaveEnabled setting. When this is true, the profile provider automatically saves the profile when the page request is finished. It also is smart enough to only save the profile if it is 'dirty' (you have changed something in it). If this is false, you are responsible for saving the profile anytime it changes.

All sounds great. There is one big catch. If you store any non primitive type in the profile, it does not know if you changed it or not. So, as soon as you read the profile property, the provider assumes you changed it and saves it back out. For me, I typically always have some basic object I am storing, so that means my profile would be saved on every request if I had automaticSaveEnabled.

Introducing a MVC [Profile] action filter. Inspired by the new SessionState attribute (see [LINK HERE]optimization issue on that one too), this action filter lets you take control over exactly what happens to the profile with a filter attribute.

This filter has 4 ProfileBehavior options:
1) Default - this is basically a no-op. Just do what .net has always done.
2) ReadOnly - this will not save the profile, even if you access a complex property and automaticSaveEnabled=true.
3) Save - this will call save on the profile provider at the end of the request no matter what. It is up to the profile provider to skip the save if isDirty=false.
4) SaveIfDirty - this will call save on the profile provider at the end of the request if the profile is marked dirty.

This lets you use this attribute in several ways.

Set automaticSaveEnbled=true in your web.config and mark all controllers and/or actions with the profile filter set to ReadOnly when you know you do not want it saved.

Set automaticSaveEnbled=false in your web.config and mark all controllers and/or actions with the profile filter set to Save/SaveIfDirty where you know you want the profile saved at the end of the request.

Either way, this helps optimize the number of database calls made by the profile provider.

Download source here.


Brant Burnett said...

This definitely seems like a nice touch and a good idea. However, I feel like it has an issue.

Correct me if I'm wrong, but it looks like the event handler module_ProfileAutoSaving is being attached for every result, and never detached. This will cause it to add a reference to the method for each individual ProfileAttribute instance, and all of them will be called for each request.

Instead, should you used a static method, and then only attach it once?

Wayne Brantley said...

As I recall, I tested this and if it was not attached each time, it was not called - guess we need to test this. If that is not true, you could make it static, but the hard question would be to know if it was attached. To manage this, we would need another static property for if it had been attached (since it is not always necessary to attach it). Alternatively, since this is a 'request based' item, we could leave the event handler 'non-static' and then remove the event handler at the end of the event? Thoughts?