Thursday, December 31, 2009

Ultimate automatic stylesheet combining, minification and .less integration

3/17/2010
Update to article here: http://blog.waynebrantley.com/2010/03/style-sheet-compression-and-less-add.html 


Combine your stylesheet css files into one file to improve site performance.
We all know this rule, but how do we go about doing this?   We could automate this in our build script, but then that means we have to do the same thing in development.   So,  if you reference 'combined.css' in your webpage in development - because the build server will make that for you - you have to maintain that combined file in development.  Or, you are looking at the debug setting and properly outputting a reference to the correct script(s).  Either way, this is a pain and makes something that works in development potentially broken at deployment.  What if the deployment script combined them in a different order than you had done in development?  This would be a difficult problem to find.  If we can avoid the development environment being different than the deployment environment, we should.


Minify your stylesheet css files into one file to improve site performance.
We also know this rule, but how do we go about doing this?   This has many of the same considerations as the combine problem above.  Difference is usually, your build process would just minify a stylesheet replacing the regular one with the new one.  This makes for MUCH less difference between development and deployment, so this one is not quite as painful.    However, what if the minification process breaks your stylesheet?  When and how will you find this?  Again, deployment different than development will open its jaws and bite you here.



Writing stylesheets violates the DRY principle, so try Less Css for .net.
This has it roots in ruby.  It lets you have variables, reference styles from other styles and other painful things you have to do in css.  As a quick example - here is a screenshot directly from their website:

As you can see, this is very useful.  It uses existing CSS syntax, so it will just work on all your existing stylesheets!  As an additional feature, when running through the less processor it can minify the stylesheet.  To implement this, you can run into the exact same problems as the 'minification' above.



Use an http handler to combine, minify, less, etc...
This is exactly how the Less Css for .net project implements the less processor.  However, sometimes people, like Phil Haack, just want a static css file.  So, Phil implemented a T4 template to take files with the name of .less and create a .css file.  Now, this is getting close to a good solution!   The build does not have to do anything but copy the stylesheet and our build vs development environment is closer alike.


Use Phil Haacks T4 template to minify
This definitively works.  However, you  have to remember to run the T4 template after each modification of a stylesheet, which means it is easy to break both the development and deployment environments!  So, I took his template and modified it to support the 'always keep template dirty' workaround.  This requires you to open the T4 template with your solution and then save the template once.  After that on each build/save it will regenerate the stylesheet.   This is an improvement, but not that much of one.  Plus this does not handle any style sheet combining.


Finally, the ultimate solution is built
I decided to write an add-in.   Never having written an add-in, I got a great head start from Martin From who sent me an add-in that captured some ide events and it showed me basic usage.  From there, I started enhancing.  I ended up with an add in that combines any stylesheets together, minifies and runs them through the less engine.  It can just combine or just minify or both and in any combination.   It is also totally automatic and requires no additional steps.   Additionally, if you have a combination of several stylesheets and one of those stylesheets change, it will automatically rebuild the combined one.   This solves all build/development environment concerns and gives us a great end result. 


How to use and configure it

Basic less processing:  First and easiest thing you can do is to create or rename your stylesheets as .less.css.   So, if you have a mysite.css, just rename it to mysite.less.css and instantly it will produce mysite.css that has been run through the less processor and is minified:



Style sheet combining:  To accomplish this, you create an XML file that defines how and what to combine.  If you want the combined stylesheet to be called final.css, then create an XML file called final.less.config.  Here is a screenshot and an example configuration.



<?xml version="1.0" encoding="utf-8"?>
<root>
    <CssFile Name="LessBased.less.css"/>
    <CssFile Name="MainSite.css"/>
    <CssFile Name="KeepAsIs.css" FilterThroughLess="False"/>
</root>

Let's just say for some reason you needed the "LessBased.css" file to be included by itself somewhere AND you needed that same file included in some of your combined css files.  The configuration above will do that.  It takes LessBased.less.css and MainSite.css, runs them through less processor, minifies them and combines that with an unmodified version of KeepAsIs.css.   Additionally, you could have included LessBased.css and marked it to NOT be filtered through less and minification - because that file already is.

In general, I think you would just put your 'less' syntax into your regular .css files, not renaming or doing anything.  Then using a final.less.config file, let them all be combined together.

Installation
Simply download the zip file and copy the contents into your Visual Studio Addin's folder (normally My Documents\Visual Studio 2008\AddIns).  Start visual studio and enjoy.  Currently I only have this for VS2008, however should be easy to make work on 2010.

What do you think?

1-2-2010 Update - dependency with css files in folders bug.

kick it on DotNetKicks.com
Shout it

Monday, December 07, 2009

ASP.NET MVC 2 Beta EditorFor improvements

ASP.NET MVC 2 Beta has some great improvements.   One of these improvements is the Html.EditorFor extension.  Basically, this extension takes data annotations and types information from your model and uses those to generate editors.  You MUST use a strongly typed view to take advantage of this!

I am going to give a brief introduction, just to get to my enhancement - for more information see this article.

So, if you have this:

<%= Html.EditorFor( mymodel => mymodel.SomeField %>

If SomeField is a boolean, it will generate a checkbox.  If SomeField is a string, it will generate a textbox.  When it generates the editor it uses a new template system that you can change.  So, if you define a partial view called 'string.ascx' in an EditorTemplates folder, it will use that when it encounters a string that needs an editor.  You can also specify a [UIHint("myspecialui")] in the data model and it will look for myspecialui.ascx to generate the editor.

One of the recognized shortcomings of EditorFor is the lack of an additional method parameter to specify additional html options, such as the class.   So that means you have to go back to TextBoxFor, which does not use any of the new templates. 

There are several annotations you can put on your model, some annotations are for use in DisplayFor, EditorFor and ValidatorMessageFor extensions. What is interesting is if you have a [StringLength(20)] attribute on your model, this is used by the default ValidationMessageFor, but not by the default EditorFor template.  The MVC 2 beta does not set the maxlength attribute of the input box.   So, here is a new implementation of string editor for that does!

Create string.ascx in Views/Shared/EditorTemplates with the following code:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    Dictionary attributes = new Dictionary() {
                { "class", "text-box single-line " }
            };
    IEnumerable validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
    ModelClientValidationRule rule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault( m=> m.ValidationType=="stringLength");
    if (rule.ValidationParameters.ContainsKey("maximumLength"))
    {
        attributes.Add("maxlength", rule.ValidationParameters["maximumLength"]);
    }
%>
<%=Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, attributes)%>

Presto!  All strings on the site that use EditorFor extension will now have stringlength support in the input box.  Templates are a very powerful way to extend.  I am not sure the above was the best way to get to the StringLength data annotation - so, if anyone has any improvements let me know!

Edit 12/19: Latest version of above code:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    Dictionary attributes = new Dictionary() {
                { "class", "text-box single-line " }
            };
    IEnumerable validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
    ModelClientValidationRule rule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault(m => m.ValidationType == "stringLength");
    if (rule != null && rule.ValidationParameters.ContainsKey("maximumLength"))
    {
        attributes.Add("maxlength", rule.ValidationParameters["maximumLength"]);
    }
%>
<%=Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, attributes)%>
<%
    rule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault(m => m.ValidationType == "required");
    if (rule != null)
    {
%>
*
<%}%> 

ASP.NET AJAX calling webservice has exception handling bug

[This is a re-post from my previous blog]

When using Microsoft ASP.NET AJAX, you can call any webservice you have enabled with the [ScriptService] attribute. This webservice could throw an exception, which ends up on the webpage as a javascript alert. This all works quite nicely, UNTIL you actually deploy such a solution!

Let's say you have a webservice that takes an item and returns some status about that item. When coding your webservice, if you cannot find the item (client passed in an invalid number), you throw an InvalidItemException with a message of 'Item Not Found'. You test this and everything works in development, but when you deploy you get a generic message of 'There was an error processing the request' when the item is not found.

Turns out, this is because you have (Of course you do, this is a real live site!). When this is set to off, the exception message from the webservice is not shown also. This is of course unacceptable, because you intended that message to show. It turns out, this is by design. The asp.net team is trying to prevent internal details from showing up to users. This is all great and fine, but what in the world are you supposed to do to handle this situation? You could return a custom type, that had an error flag, description, and regular result data - what...are you kidding me...that would be crazy. Or you could do as some others and just not use it!

Here is an easy solution to this problem. Place all your webservices where you WANT the exception message to be shown in their own separate directory. Then in that directory, put a web.config file that contains and presto, your thrown exceptions now show the correct message! (Be careful and make sure other exceptions thrown by your service would not expose internal details. You could wrap the service method in a try/catch block allowing your custom exceptions to escape and suppressing the 'internal data' on the others)

Here are a couple of related blog entries on this same problem.

http://blog.hackedbrain.com/archive/2007/08/03/6121.aspx

http://jlchereau.blogspot.com/2007/03/ajax-extensions-services-always-report.html

Good luck!