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.

ASP.NET session state still makes database calls when disabled

MVC3 offered us the ability to disable session state in a controller. This allows the elimination when not using session state. So, for example - if you have a controller that manages images for you, previously the session would be loaded on each call. This turns out to be quite expensive. With MVC3, we can now disable that load.

However, even if you use
[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
on your controller, it
turns out database calls are still made!

Deep inside the built in SessionStateModule class, it determines if it should load session state. Even if it does not load session state, it makes a call to ResetItemTimeout(). This then turns around and updates the session timeout value in the database.

Example of the problem

You create an 'Image' controller and that controller has actions to return the actual images to the client. Inside that action, you of course manage client caching, create thumbnails, pull images from databases, etc. The url to access this might be /Image/SomeFile.jpg or /Image/StandardThumbNail/SomeFile.jpg. (Note many people do things like this, as well as have controllers that return optimized/compressed scripts, stylesheets, etc)

Now, suppose you have a basic web page that has 20 images on it. Imagine you have optimized this page such that it only requires one database call. You turn on SQL Profiler, load up this webpage and are shocked to find 21 database calls! Why? One for the page and one for each image as it 'resets' the session timeout on each image request.

This is probably not considered a bug, as the asp.net team does not want a session to timeout even though the user is accessing controllers with the session disabled. But, if you think about most session scenarios - what we store in them is temporary. They expire in whatever time we setup (20 minutes, 1 hour, etc). So, if a user does NOT visit a page that requires the session inside our session time - perhaps it is ok the session has timed out? In MVC3 pages, you may only end up using session state as the 'tempdataprovider' for your modelstate in validation. In that case, you only use the session for the NEXT request - so it's OK if a timeout occurred.

Fixing the problem

How do we fix this? I have come up with several ways to solve this.

1) Change the behavior of the sql session provider's ResetItemTimeout to not reset the timeout if the session is disabled. Each developer would have to decide if this works for them, based on how the session was used in the site. I think for many cases, this would be valid.

2) Do #1 and add a new attribute of [EnableUpdateSessionTimeout]. This would go on controllers where you want the session timeout updated even though session state is disabled.

3) Do the inverse of #2. Add a new attribute of [DisableUpdateSessionTimeout]. This would go on any controller that you did not want to update session timeout and change the behavior of #1 - to always update it unless this is set.

I think #2 would be best if designing from the ground up - but it changes the default behavior of what asp.net always has done. For that reason, I think #3 would be best. This would require you to consider which controllers could be modified without breaking any existing code. That is an easy decision you say.....however, guess what? Did you know that EVERY request that comes into your application - even for static image files, scripts, etc that are delivered by IIS make this same call?? Crazy, huh? IIS indicates no session is necessary, just as we do in our controller - but the session provider still makes the call to the ResetItemTimeout. This translates into this call being made for EVERY FILE you serve no matter what! If you average 500 visitors a day and each visitor goes to 3 pages, where each page has 20 assets (images, scripts, etc) - that alone would translate to 30,000 calls to this stored procedure on any given day. Worse yet, you probably didn't need it to be called. Ultimately, if you want to get rid of all these unnecessary calls - we have to implement it like #2 so the default behavior is to eliminate the call (when session is disabled) unless [EnableUpdateSessionTimeout] is used. I will discuss both implementations below, lets start with #3.


Implementation

Well, this actually could be a really easy fix - but many of the things we need to extend or access are marked as internal! Yuck. If the built in SqlSessionStateStore was not marked internal - we could just extend and change this. But, as it is we have to copy and paste the code for this into our own project! Why Microsoft, why?

So, I took the source code from the default providers and put that in a project. Next, I found that the property I needed to read in HttpContext was also internal (why oh why!?), so I had to use reflection to access it (cached the reflection information for speed). With this in place, I can successfully avoid the database call in ResetItemTimeout. Ugh...Copy and paste 2000 lines of code what could have been a simple override of an already virtual method....




Next, we need the [DisableUpdateSessionTimeout] attribute. The way I envisioned this to work was to put something in HttpContext.Items indicating you wanted to skip the session timeout database call. This should be a simple solution, but it is not. The issue is that the controller factory determines the session state and it does so before it even instantiates the controller! So, no attributes, controller methods or anything have a chance to run any code. This means you have to do this in a new ControllerFactory. This new controller factory will be pretty simple and inherit from the DefaultControllerFactory, so should not be many issues. The attribute is trivial:


And the new controller factory is very easy (since I inherited from the default one)


Next update ResetItemTimeout to use this new option:


Finally, update your web.config to use our new sql provider and register the new controller factory in your global.asax:


Now we are finished. Put [SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)] on your controller, simply add the new [DisableUpdateSessionTimeout] to stop the database call from being made. In my example above, you go from having 21 database calls down to only 1. However, as discussed above, requests for static files still make this call. So, to change the above implementation to be like #2, is easy. Just reverse our logic and create a new attribute. Here are the changes:






Normally, he solution to this problem would not have been much code. However, it actually turns out to be much more code because Microsoft decided to make some methods and classes internal. The solution attached is 2300+ lines of code. If the classes were not marked internal, the solution would be less than 100 lines! (Most of it was from the default provider implementation source). Download source here Download source here.

Monday, January 17, 2011

MVC3 unobtrusive AJAX and default loading image

With MVC3's new unobtrusive AJAX and jquery.unobtrusive-ajax.js, all AJAX calls are mapped through jquery. You end up with lots of "data-ajax-xxxxxxx" attributes on your Form tag. Overall this works pretty well.

However, one issue is that _generally_ I want a 'loading' image to show on the start of each ajax call and hide at the end. In order to do that, you must provide the loadingElementId and optional loadingElementDuration to the ajax calls. Those get translated into attributes on each form call like this:

data-ajax-loading="myloadingpanel" data-ajax-loading-duration="3"


Of course I would be repeating that same information all over my code and that same information is output into each form element. You could of course create your own AjaxOptions class that provided these defaults to help with DRY. However, there are other features - what if you wanted to using one of the easing functions when showing and hiding your image? What if you wanted to center it on the screen?


JQuery has a function called ajaxSetup which sets defaults for all ajax calls. This turns out to be an easy place to attach default beforeSend and complete event that handles showing and hiding your loading image. This does not help in this scenario, because jquery.unobtrusive-ajax.js provides its own beforeSend and complete events and the default ones above will not be called.

So, I propose a new solution. Add two new entries to the ajaxSetup defaults. A showLoading and hideLoading event. You can then assign those to do whatever you want. If you do not pass in a 'loadingElementId' to the AjaxOptions then these events will be used - otherwise your will get the current behavior implemented today. (That way, this should not break any existing code). This would be something you setup and initialize in your master page/layout master.

jQuery.ajaxSetup({
   showLoading: function (XMLHttpRequest) {
    centerElement($('#loadingImage'));
    $('#loadingImage').show();
   },
   hideLoading: function (XMLHttpRequest) {
    $('#loadingImage').hide();
   }

Now you have complete flexibility in what happens when ajax calls start and stop with only one place to change it should you need to. Of course this requires a change to the jquery.unobtrusive-ajax.js code. The code addition is quite small and is shown here on lines 86 - 100. The lines with //wb are the ones I added.

Your feedback is welcomed!

Sunday, January 16, 2011

MVC3 CompareAttribute displays wrong default error message

I am glad to see new compare attribute in MVC3.  If you do not specify the optional error message for the compare attribute, it will display everything correctly, except for the name of the field you are comparing to.  It does not respect the DisplayName attribute of the compared field.  For example, if you have this:



The error message displayed will be:


Notice it does not use the DisplayName of 'New Password', but instead uses the property name.

As a workaround to this, you will just have to specify an error message to the Compare attribute. I will report this as a bug to the MVC team, but it may not be something they can easily fix.

Saturday, January 15, 2011

MVC3 requires unobtrusive validation if you want jquery validation

Reading all the new things about MVC3, it appeared to me they had all these options to 'not' enable Unobtrusive javascript.   Turns out you really have two choices.  You can use msajax style validation (regular or unobtrusive) or you can use jquery validation - unobtrusive style.

If you are like me, you have been doing jquery validation all along, using the alternative script MicrosoftMvcJQueryValidation.js provided by Microsoft futures library.   Well, there does not appear to be a 'new version' of that script and that no longer works.

So, if you currently use MVC2 and jquery validation - the first thing you need to do is turn on unobtrusive validation in your web.config:

<appSettings>
  <add key="ClientValidationEnabled" value="true"/> 
  <add key="UnobtrusiveJavaScriptEnabled" value="true"/> 
</appSettings>

And then replace your included script of MicrosoftMvcJqueryValidation.js with jquery.validate.unobtrusive.min.js.

MVC3 breaks any manual use of jquery validate

If MVC client side validation using model attributes just would not do what you needed it to, you could always turn of client side validation and call validate yourself:

$('#myForm').validate({
            rules: {
                myField: {
                    minlength: 5, maxlength: 5, required: true, digits: true
                }
            },
            submitHandler: function (form) {
                $.post($(form).attr("action"), $(form).serializeArray(), function (data) {
                    $("#myPostResults").html(data);
                });
                return false;
            }
        });

Well, not anymore! MVC3 and jquery unobtrusive (which is become VERY obtrusive) validation breaks this, even if you disable client validation on this form!

Why? The jquery.validate.unobtrusive.js code calls form.validate() even if no rules were found or created - destroying whatever you had done! So, here is another fix to that code.  Simply add the one line, shown here on line 100.  Note this has my other MVC3 validates hidden fields using jquery validation fix


MVC3 validates hidden fields using jquery validation

I am not sure about you, but I rarely, if ever, want to validate a form field that is hidden from view!

Consider the case where you have a several required fields on the screen and the user can take some action that causes some fields to then be hidden.  Well, using the default unobtrusive jquery validation, it will still validate those fields.   If you do not have a 'SummaryValidation', this means the form just will not submit - no errors or anything because the error is displayed on a hidden field!

How do we get it to stop doing this?   For now, I know of only one way.  You need to add one line of code into the jquery.validate.unobtrusive.js file.

Add line 97 as shown below (and of course the comma at the end of line 96):

Wednesday, July 21, 2010

T4MVC add-in and resource compression update

The T4MVC add-in and the dot less css add-in add-ins have been officially merged into the chirpy codeplex project.   Really love the design time javascript/stylesheet minimization and combination. This is really a great project and one of my favorite add-ins...of course I am not biased in any way.  :-)

You can read all about the features of this with Evan Nagle's latest post here.  In addition to his post on how to install, don't forget this add-in is in the VS2010 extension manager - so that should make it really easy to install and update.

One of the new T4MVC related features put into the latest release is 'Smart Run T4MVC'.   If you turn on this option in the chirpy settings, it will only run the T4MVC template when it should.    That means it will only run it if you change a MVC file that would cause it to be refreshed - instead of on every save operation.  Of course it relies strongly on convention locations to determine this.   It runs the template if any of the following happen:

Add/Remove/Rename any file in /Content or /Scripts
Add/Remove/Rename/Edit any file in /Controllers or /Areas/{anyarea}/Controllers
Add/Remove/Rename any file in /Views or /Areas/{anyarea}/Views

For those people with _really_ large project and/or slower computers, this should help with any speed issues and in general is just a good optimization.  If you turn this feature off, it runs the template when you save any file like it used to.

Wednesday, March 17, 2010

Style sheet compression and .less add-in...updated with source



6/2/2010 Update - Project enhanced and now on codeplex!   http://chirpy.codeplex.com/

Design time minification and .net less for style sheets.
Read my previous post on this subject.  






Known Issues
It has been reported that this does not work in 'web site project'.  I do not  use those anymore, not since they brought back our 'web application project'.  If anyone wants to try and make it work, the code is attached.



Source Code
I finally got around to updating this to the latest version of the less engine and api.   In addition, I had been promising source code for a while now.

Click to download

Shout it
kick it on DotNetKicks.com

Tuesday, March 16, 2010

T4MVC Add-In to auto run template

6/2/2010 Update - Project enhanced and now on codeplex!   http://chirpy.codeplex.com/

T4MVC is a fantastic solution to avoid 'Magic Strings' in ASP.NET MVC.   





Thanks to David Ebbo for this contribution which has made its way to MVCContrib.  


Must keep T4 template open and save it once.
This has been the only negative thing about the template.  I thought about writing an Add-In for VS to do this and even taked to David about doing it.  Well, his latest post has inspired me to write one.  I took my dot net less plug in and ripped nearly everything out of it, threw in about 5 lines of new code and it is done.

Configuration
There isn't any.  Just install the add-in and it should work.  It watches for any save, add, or remove event to the solution and runs the T4 template at that time.  As long as you have the T4MVC.tt file in  your project it will run.






Source
I have attached the source and binaries.  I am sure there are some improvements we all can make.


Installation
Simply download the file and double click to install into your Visual Studio Add-in folder.  Currently I only have tested this for VS2008, however should be easy to make work on 2010.  Full Source

What do you think?

Shout it
kick it on DotNetKicks.com