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!

Monday, September 21, 2009

Develop on iPhone with C# and .net

If you have seen Objective-C and did not want any part of it, now there is an alternative!

Use C# and .net on the iPhone with MonoTouch.

Of course you will still need the iPhone SDK and a Mac for development.

9/21 - UPDATEScreen cast of hello world.

Thursday, September 17, 2009

Web Deployment Project Enhancements

[Note: This is a republish of a blog I wrote 2/8/2006 on a old blog]

Deploying a web project has always required some external software and sometimes manual changes/copying/etc. Thankfully, Microsoft has seen fit to help us with our web deployment scenarios and has created an add-on to Visual Studio 2005 called Web Deployment Project. If you are not familiar with the product, I would suggest you check it out as the rest of this article assumes you have it installed and are trying to use it for your builds. In attempting to use this add-on I ran across a few shortcomings in what I needed. Here are my workarounds.

Web.config Merging Enhancement

One of the nice things I was looking forward to was web.config file merging. Turns out this feature will not replace any section (due to bugs or shortcomings or something) and it makes you create a separate file for every section you want to merge. If you want to merge 5 sections and then you have a staging and release build, that’s 10 files you have to maintain (and will show up in your website in development and deployment).

A better solution is to extend the MSBuild rules of Web Deployment Project. What we want is one web.config file for each release we are doing and at build time the correct build file would be copied to web.config in the deployment folder. To accomplish this, create your build configurations and configuration files called *.[buildConfiguration].

As an example, let's say you have 2 deployment environments - Staging and Release. You will create two build configurations in VS, one called ‘release’ and one called ‘staging’. Now, create web.config.staging and a web.config.release. The changes below will cause the appropriate web.config.xxxx to be copied over web.config in the deployment folder depending on which build you do. Note this will work for ANY file, configuration files, graphics, anything, as long as the file name ends in the configuration name!

1) Right click the deployment project and choose 'Open Project File'. This will open the MSBuild file into the IDE.

2) At the bottom of the file you will see a commented section where it talks about how to extend this file using pre-built Targets. Add the following code:


<ItemGroup>

<!--list of all files that we are going to copy into the current config files.

So, for example, if we are on a release build, it will get all files called *.release-->

<MySourceConfigFiles Include="$(SourceWebPhysicalPath)\*.$(Configuration)" />

</ItemGroup>



<Target Name="AfterBuild">

<Copy SourceFiles="@(MySourceConfigFiles)" DestinationFiles="@(MySourceConfigFiles->'$(OutputPath)\%(Filename)')" />

</Target>
Note: The above only looks in the root of the web folder. If you need it to look in all folders you would need to modify it to build and copy a recursive list of files. See http://msdn2.microsoft.com/en-us/library/ms171454.aspx for more information


Too many files deployed

Another issue is all files from your project are copied to the deployment folder. You could of course exclude them from your project, but if you do that causes other problems. For example, in the enhancement above, if you do not copy them to the deployment folder, they are not available to copy because web deployment project uses a temporary folder for the build (since it modifies aspx files and such). So, to prevent this, you can add more MSBuild commands. Working off the example above, once the web.config is replaced with the correct version; we no longer need the other versions. To delete add the following:


<ItemGroup>

<!--list of all files that we can delete from the deployment folder-->

<MyUnNeededDestFiles Include="$(SourceWebPhysicalPath)\*.staging;$(SourceWebPhysicalPath)\*.release" />

</ItemGroup>



<Target Name="AfterBuild">

<Delete Files="@(MyUnNeededDestFiles->'$(OutputPath)\%(Filename)%(Extension)')" />

</Target>

Not Enough files deployed

I know I just wrote about too many files being deployed, but believe it or not, not enough files are copied and it happens at the same time! What happens is that any assembly you reference in your project that is in the GAC is not copied to the bin folder of your web application. Since it is not copied there, the web deployment project does not know you need it. Another example, is for some reason license files are not copied either, which you will need to run in the deployment environment! The best solution would be to parse the web.config file looking through the section, then use the GAC to locate the assembly and copy it for you. A much simpler solution is to just put what additional files you want to copy in the MSBuild file:


<PropertyGroup>

<InfragisticsFolder>c:\program files\Infragistics\NetAdvantage 2005 Volume 3 CLR 2.0\ASP.NET\bin</InfragisticsFolder>

</PropertyGroup>



<ItemGroup>

<!--Files registered in the GAC are not copied to the bin folder. List them all here so they will be!-->

<MyGACFiles Include="$(InfragisticsFolder)\Infragistics2.WebUI.Shared.v5.3.dll;

$(InfragisticsFolder)\Infragistics2.WebUI.UltraWebGrid.v5.3.dll;

$(InfragisticsFolder)\Infragistics2.WebUI.UltraWebNavigator.v5.3.dll" />

</ItemGroup>



<Target Name="AfterBuild">

<Copy SourceFiles="@(MyGACFiles)" DestinationFiles="@(MyGACFiles->'$(OutputPath)\bin\%(Filename)%(Extension)')" />

</Target>

If you want to do more than one of the suggestions above, you can combine the items together. Just be careful of the order – you don’t want to try to copy your config files after you have deleted them!

Let me know if you have any other ideas!