Tuesday, August 23, 2011

Integrating JavaScript and C# with Script#

Have you ever had an enum in your server code that you wanted to access in client side code? Or wanted the safety of compile time errors when there are discrepancies between your server-side and client-side code? Or had a C# Data Transfer Object (DTO) that you wanted to enable Intellisense for in JavaScript? I hadn't found a good solution either ... until now.
Compiling C# to What!?
Compiling C# code into JavaScript may seem foreign, but Script# is a mature technology that is absolutely worth a look. Our team has been using it very happily for about three months. We've found a number of benefits including:
  • Consistently working Intellisense
  • Better encapsulation
  • Simpler Object Orientation
  • Easier deployment (Script# compiles multiple files to a single, optionally minified file)
  • Safer refactoring
  • Simpler unit testing
  • Type safety; and
  • Design time syntax checking (no more mistyping a variable and accidentally declaring it to the global scope)
Scott Hanselman covered most of these topics in last month's Hanselminutes episode: Script# Compiles to JavaScript. If you have a spare car ride I definitely recommend the listen. But what wasn't covered were some additional benefits provided by an off the beaten path approach to this great technology. The main benefit is tighter server side C# to client side JavaScript integration. A secondary benefit is less of a dependency on Script#.
To the Command Line
Typically to get going with Script# you:
  1. Install the Script# Visual Studio plug-in
  2. File -> New project
  3. Select "Script Library" and
  4. Compile to generate JavaScript
Nice and easy. The down side to this approach is you can't easily include files outside of your project. Specifically, you can't include data transfer objects or enum's from your server side code. Furthermore, all of your team members must install Script#. If instead you compile with Script#'s "ssc.exe" command you obtain more control and get less dependency.
How-To
The how-to looks something like this:
  1. Download and install Script#
  2. Add a Class Library (not a Script# project)
  3. Project properties -> Build -> Advanced -> "Do not reference mscorlib"
  4. Ideally move the Script# files (C:\Program Files (x86)\ScriptSharp) locally to the solution and check them into source control to a Libs or something similar
  5. Remove all references, but add: ScriptSharp.dll, ScriptSharp.Web.dll, Script.Jquery
  6. Edit your .csproj to manually reference Script#'s mscorlib (right click, Unload project, Edit MyProject.csproj)

    <Reference Include="mscorlib, Version=0.7.0.0, Culture=neutral, PublicKeyToken=8fc0e3af5abcb6c4, processorArchitecture=MSIL">
      <
    SpecificVersion>True</SpecificVersion>
      <
    HintPath>..\Libs\ScriptSharp\v1.0\mscorlib.dll</HintPath>
    </
    Reference>

  7. Modify AssemblyInfo.cs and remove the following lines:

    [assembly: ComVisible(false)]
    [assembly: Guid("b5e2449f-193c-46d1-9023-9143618d8491")]

  8. Modify AssemblyInfo.cs and add the following:

    [assembly: ScriptAssembly("ScriptSharpDemoAssembly")]

  9. Ensure it compiles in Visual Studio
  10. Create a batch script or PowerShell script that compiles using ssc.exe like this:

    ..\Libs\ScriptSharp\v1.0\ssc.exe ^
        /debug ^
        /out:MyScriptSharp.js ^
        /ref:"..\Libs\ScriptSharp\v1.0\Framework\mscorlib.dll" ^
        .\Properties\AssemblyInfo.cs ^
        .\Class1.cs

Notice the last two lines in the script are a list of the files that you want to include. The point of this exercise is that you can now include files outside of your main Script# project in that list.
Now for completeness if you put a simple static method in Class1.cs, something like this:
namespace MyScriptSharp
{
    public class Class1
    {
        public static string HelloWorld()
        {
            return "Hello World!";
        }
    }
}

Then run the batch file you should get something like this:
Type.registerNamespace('MyScriptSharp');

////////////////////////////////////////////////////////////////////////////////
// MyScriptSharp.Class1

MyScriptSharp.Class1 = function MyScriptSharp_Class1() {
}
MyScriptSharp.Class1.helloWorld = function MyScriptSharp_Class1$helloWorld() {
    return 'Hello World!';
}

MyScriptSharp.Class1.registerClass('MyScriptSharp.Class1');

Obviously you could get these results faster with approach #1. But now you have a lot more control.
Summary
The main downside to this approach is maintaining the batch file is a bit of a hassle. But the upsides are that you can include any file from your server-side C# code. And any changes in that server-side code are automatically reflected in your JavaScript. And any breaking changes in your server-side code generate compile time errors in your client side code. And furthermore none of your team members need to install Script#. For our team it's an easy tradeoff. What about for yours? Please share your thoughts in the comments or on twitter.

3 comments:

andry said...

very good tutorial and can hopefully help me in building json in the application that I created for this lecture. thank you

Anonymous said...

You're missing the end bracket on the line: [assembly: ScriptAssembly("ScriptSharpDemoAssembly")

Lee Richardson said...

Fixed the closing bracket, thanks.