MSBuild Task for Embedded Resources.

Mar 11, 2009 at 11:32 PM
Edited Mar 12, 2009 at 3:05 AM
Greetings!  I have a need in a recent project to embed all JS and CSS resources, because of this the standard build task did not quite work.
I have written a new class that works great and thought I would share. it is very simple (using only the default options) but can be improved upon.

upon build it will find all embedded files with a .css or .js extension, call the correct compressor, and then re-embed the files.

below you will find the MSbuild task and corresponding C# code



<!-- Be  sure to specify a correct dll reference here-->
<UsingTask TaskName="EmbeddedResourcesTask" AssemblyFile="$(MSBuildProjectDirectory)\..\..\..\Resources\Yahoo.Yui.Compressor.dll" />


<Target Name="CleanResources" Condition=" '$(ConfigurationName)'=='Release' ">
    <Copy SourceFiles="@(EmbeddedResource)" DestinationFolder="$(IntermediateOutputPath)" Condition="'%(Extension)'=='.js' or '%(Extension)'=='.css' ">
      <Output TaskParameter="DestinationFiles" ItemName="Resources" />
    <EmbeddedResourcesTask SourceFiles="@(Resources)" DestinationFiles="@(Resources)" />
      <EmbeddedResource Remove="@(EmbeddedResource)" Condition="'%(Extension)'=='.js' or '%(Extension)'=='.css' " />      
      <EmbeddedResource Include="@(Resources)" />
      <FileWrites Include="@(Resources)" />

the C# code

#region Namespace References

using System;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;


namespace Yahoo.Yui.Compressor.MsBuild
    /// <summary>
    /// </summary>
    public class EmbeddedResourcesTask : Task
        /// <summary>
        /// Gets or sets the destination files.
        /// </summary>
        /// <value>The destination files.</value>
        public ITaskItem[] DestinationFiles { get; set; }

        /// <summary>
        /// Gets or sets the source files.
        /// </summary>
        /// <value>The source files.</value>
        public ITaskItem[] SourceFiles { get; set; }

        /// <summary>
        /// When overridden in a derived class, executes the task.
        /// </summary>
        /// <returns>
        /// true if the task successfully executed; otherwise, false.
        /// </returns>
        public override bool Execute()
                Console.WriteLine("Cleaning Resource files: ");
                if ((SourceFiles == null) || (SourceFiles.Length == 0))
                    Log.LogWarning("No SourceFiles provided", new object[0]);
                    return true;
                if ((DestinationFiles == null) || (DestinationFiles.Length != SourceFiles.Length))
                    Log.LogError("There must be as many DestinationFiles as SourceFiles", new object[0]);
                    return false;
                for (var i = 0; i < SourceFiles.Length; i++)
                    var item = SourceFiles[i];
                    var item2 = DestinationFiles[i];
                    if (!File.Exists(item.ItemSpec))
                        Log.LogError("Could not find source file '" + item.ItemSpec + "'", new object[0]);
                        return false;
                    string str;
                    using (var stream = File.OpenRead(item.ItemSpec))
                        using (var reader = new StreamReader(stream))
                            var ext = Path.GetExtension(item.ItemSpec);
                            str = reader.ReadToEnd();
                            switch (ext)
                                case ".css":
                                        str = CssCompressor.Compress(str, 120, CssCompressionType.StockYuiCompressor);
                                case ".js":
                                        str = JavaScriptCompressor.Compress(str, false, true, true, false, 120,
                                                                            Encoding.UTF8, CultureInfo.InvariantCulture);
                                        Console.WriteLine("invalid file type: {0}.  Must be a CSS or JavaSCript file.",
                            var color = Console.ForegroundColor;
                            Console.ForegroundColor = ConsoleColor.Yellow;
                            Console.WriteLine("\t " + item.ItemSpec.Replace("obj\\Release\\", string.Empty));
                            Console.ForegroundColor = color;
                    if (File.Exists(item2.ItemSpec))
                    using (var writer = File.CreateText(item2.ItemSpec))
                return true;
            catch (Exception exception)
                return false;
Mar 12, 2009 at 12:30 AM
Hi causas.

um ... i don't get it :( can u fill in the following blanks..

Files before:

File/Files after:

that way, i can see exactly what you're doing. cheers :)
Mar 12, 2009 at 2:48 AM
The developer will not have to actually specify any input files.
the task will search for any resource that is embedded into the DLL, so that upon compile there is no physical file deployed to the web server.

*note I forgot to mention above that this only kicks off during a compile in release mode to make debugging a bit easier.

I have put together a small sample that shows how everything works.

Mar 12, 2009 at 2:52 AM
so you're saying that the .js and .css files are saved INTO the result dll? and that it looks for js and css files that are embedded into a project (and not physically sitting in the file system)????

/me is really confused now.
Mar 12, 2009 at 3:00 AM

In the properties panel the build action needs to be changed form content to embedded resource  and a WebResource attribute needs added to the assemblyinfo.cs file.

Granted, this is not needed for every project:  but in my case (and I hope this helps out a few others as well) we are going to
be deploying to many clients and if we need to ship a patch it is easier to send one DLL rather than 500 individual files.
Mar 12, 2009 at 4:08 AM
Ok, so you're embedding your js and css. what about your aspx files? other image files?

lastly, if u deploy to clients, doesn't the combining part of the YUICompressor task put all the js into one single file and all the css into a single file, so that's 2 files (+ any dlls + any aspx files + web.config + any other files like images, etc (unless those are embedded)


i just don't understand the use of this application still.....

(please don't think i'm being negative .. far from it. i'm just trying to understand the scenario).
Mar 12, 2009 at 10:22 AM
We do also include images;  but not aspx files.

We don;t use to compressor to actually combine the files, only to compress them.

In our project we have many custom controls that each have their own script and styles.
By embedding the js/css we allow IIS to manage:

1) only if a control is loaded on a page (which we do not know until runtime)  will the js/css be served
2) if multiple of the same control are loaded the files only need served once.
3) IIS is better capable of controlling caching/expiration without developers having to think about it.

For our particular application: there is only one .aspx page.  from that everything is loaded dynamically from a database or web service, so in essence
when deployed to a client we only need to have ~4 files (plus dependent dlls):

1) default.aspx
2) the main DLL
3) web.config
4) services.asmx 
Mar 12, 2009 at 3:00 PM
Thank you for this.  it is exactly what I needed.

Did you build your class EmbeddedResourcesTask directly into the current YUI.Compressor DLL that you attached to your sample project?

How are you going to manage changes in the YUI.Compressor?  Re-download the source every time and re-compile with your class included?

Since I want to use it, I am trying to figure out what my workflow needs to be.

Thanks again & Regards,
Mar 12, 2009 at 5:19 PM
I am glad to hear this helped you.

For my code I actually added it to my own dll that has other build tasks,  and added a reference to the YUI compressor dll.

the code I posted above has been modified so that it can be patched in the project if the devs so choose.
Mar 12, 2009 at 11:35 PM
Edited Mar 12, 2009 at 11:36 PM
guys, i'll add this to the YUICompressor if u want. Causas, if u could compile some documentation and a complete sample Visual Studio project (such as the one posted above) .. making sure it's working perfectly, then we can add it to the documentation for this codeplex project.
Mar 13, 2009 at 2:17 AM
Edited Mar 13, 2009 at 2:19 AM
Sure. I have updated the sample project above and I will try to have some better documentation~2c and perhaps a small tutorial written by tomorrow.
Mar 23, 2009 at 8:46 PM
I have a very similar situation (using embedded .js files, only minifying, not combining).  However, I have no problem using the built-in task for this.  I just have it run before the compile phase so that the newly minified versions are what it embedded.

Having it run before compile looks like this:
where CompressJavaScript and CompressCss are two targets that I have defined.

I'm minifying each each file separately using MSBuild batching (as described by nameetpai in the thread Request: Output Directory).

That said, automatically minifying the files only in the embedded resource upon release mode is a pretty cool idea, and probably has a bit less friction than the solution I've come up with.

Anyway hope that helps for folks who don't want to have their own extra task in the mix.
Mar 24, 2009 at 4:32 AM
Guys, i've added this to the latest build of the project. I've not tested it (cause i'm not too sure how ... still waiting on that documentation).

So can some grab the latest code and tell me how it goes?
Mar 25, 2009 at 8:51 PM

 Thank you very much for this.

 I went tested this vs. my own version based on causas' but mine utilizes  1.2.2 Stable instead of 1.3 Beta.  The basic process of your version worked, but the resulting compressed JS application no longer functioned correctly (which I have not been able to debug, yet).
Original Javascript - 321 KB
YUI 1.2.2 Compressed - 207 KB
YUI 1.3 Compressed - 210 KB + does not function (JS not visible on launch)

My EmbeddedResourcesTask compression line using the older syntax:

str = JavaScriptCompressor.Compress(str, false, true, true, false);


Mar 26, 2009 at 4:33 AM
I'm wondering version 1.3 correctly obfuscates and minify's the Javascript, compared to v2.2.0.0  ... in general ....

i know the obfuscation is different (for jquery1.3) between the java version and this .NET version, when i try the java on my MAC OS (cause java is pre-installed on that).

BUT .. if i compare jquery 1.2.6 with the current code, both this and the java results are identical! so i'm a but confused.

I would love it, if some peeps could help me out and find why the latest version of jquery doesn't obfuscate exactly the same as my .NET version...
Mar 26, 2009 at 3:19 PM
After taking a closer look at this solution (the Embedded Resources task), I'm not sure why there's a need for a separate task.  It seems like the "magic" is all happening in the MSBuild script itself.  The task is just compressing the files, the same way that the built-in task is quite capable of doing.

I've altered my solution to minify the JavaScript on-the-fly when compiling in Release mode, just as the above solution.  My solution, however, just uses the standard CompressorTask in the YUI Compressor assembly. This allows verbose JavaScript to be checked into source control, viewed in Visual Studio, used on the page when compiled under Debug mode.  It then uses minified JavaScript when compiled in Release mode.  The JavaScript is compiled into the assembly, there are no external JavaScript files.  All JavaScript is accessed through the WebResource.axd proxy (a WebResource attribute must be added to the assembly for each file, and then accessed through Page.ClientScript.RegisterClientScriptResource).

Here's the .targets file:

<Project xmlns="">
  <UsingTask AssemblyFile="Yahoo.Yui.Compressor.NET20.dll" TaskName="CompressorTask"/>
  <Target Name="CompressJavaScript" Condition="'$(Configuration)' != 'Debug'">
    <!-- Create a copy of the embedded resources in the \obj folder, so that we aren't minifying the actual files in the project -->
    <Copy SourceFiles="@(EmbeddedResource)" DestinationFolder="$(IntermediateOutputPath)" Condition="'%(Extension)'=='.js'">
      <Output TaskParameter="DestinationFiles" ItemName="Resources"/>
    <!-- Compress each embedded resource file -->
      <!-- Replace the items in the EmbeddedResource collection with our minified versions -->
      <!-- Remove is only available in MSBuild 3.5, this won't work on 2.0 -->
      <EmbeddedResource Remove="@(EmbeddedResource)" Condition="'%(Extension)'=='.js'" />      
      <EmbeddedResource Include="@(Resources)" />
      <!-- FileWrites is the list of files to delete when performing a Clean -->
      <FileWrites Include="@(Resources)" />
Apr 16, 2009 at 1:18 PM
Thanks heaps Dukesb11, your solution is much more elegant. I might remove that new change and post your solution up into an FAQ.

very good work, 'ole chap!