Create a new project by opening Visual Studio and create a new "SharpKit 5 Web Application" project.
SharpKit can now run as a local HTTP service, inside a windows service container, or in console mode. You can find the windows service by running "services.msc" and finding a service named SharpKit. If this service is stopped, SharpKit will revert to command line execution mode.
To start and stop the SharpKit Windows Service:
Run: services.msc
Find the service, click start / stop.
It is also possible to run the service manually in console mode using the following command:
skc5.exe /service:console
SharpKit is hosted on port 7667 by default, it is possible to execute GET command on it, with the same command line arguments format:
http://localhost:7667/Compile?CommandLineArgs=/why
Any command line argument can be set in the CommandLineArgs query string parameter, this specific command may help you debug license validation issues.
Debugging of C# code from within the browser is currently supported in Google Chrome only.
A demo of debugging is available in our open-source SVN repository, named: SharpKitSourceMappingSample
http://code.google.com/p/sharpkit/source/checkout
To enable debugging of C# code, please follow these steps:
[JsExport(GenerateSourceMaps = true)]
<add name="SourceMapsHandler" type="SharpKit.Web.Server.Handlers.SourceMapsHandler, SharpKit.Web" verb="*" path="SourceMaps.ashx" />
Upgrading from SharpKit v4 to v5 is very simple, and can be easily reverted back.
Before upgrading, check-in / commit your code, after upgrade, build your project and compare your newly generated js files with your previous ones.
Edit your .csproj file, replace the SharpKit v4 import line, with the SharpKit v5 import line:
<!--Replace this line:--> <Import Project="$(MSBuildBinPath)\SharpKit\4\SharpKit.Build.targets" /> <!--With this line:--> <Import Project="$(MSBuildBinPath)\SharpKit\5\SharpKit.Build.targets" />
Parallel execution can be disabled in skc5.exe.config file, set appSettings "Parallel" to "false".
If you're running an activated version of SharpKit, but still getting license errors, run the following:
skc5.exe /why
Since SharpKit runs as a different user when in windows service, it's best to run it as an HTTP request while the service is running:
http://localhost:7667/Compile?CommandLineArgs=/why
Check the filename of the license, if it doesn't exist, run the /why in command line, and copy the file there.
If you run into any issues using SharpKit 5, please email us or post in our forum.
This chapter will explain how to start using SharpKit in new or existing projects, SharpKit easily integrates into any project type, whether it's a web application, class library or even console application.
SharpKit enabled ASP.NET web application will be created with an aspx page, and a C# code-behind with a class that is converted to JavaScript during compilation.
Add the following line on your .csproj file: (add it right after all 'Import' sections in your project file)
<!--This line is in any .csproj file: --> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <!--Add this line after all Import sections: --> <Import Project="$(MSBuildBinPath)\SharpKit\5\SharpKit.Build.targets" />
Add the following references into your project:
SharpKit supports any C# type project, including web, console (.exe) and class library (.dll) project types.
Edit your .csproj file, replace the SharpKit v2 import line, with the SharpKit v4 import line:
<!--Replace this line:--> <Import Project="$(MSBuildBinPath)\SharpKit\SharpKit.Build.targets" /> <!--With this line:--> <Import Project="$(MSBuildBinPath)\SharpKit\5\SharpKit.Build.targets" />
Update assembly references, make sure to update SharpKit.JavaScript.dll, never reference both assemblies. SharpKit v4 assemblies are located in your Program Files\SharpKit\4\ folder.
Edit your .csproj file, replace the SharpKit v4 import line, with the SharpKit v5 import line:
<!--Replace this line:--> <Import Project="$(MSBuildBinPath)\SharpKit\4\SharpKit.Build.targets" /> <!--With this line:--> <Import Project="$(MSBuildBinPath)\SharpKit\5\SharpKit.Build.targets" />
In this chapter you will learn about the different SharpKit C# to JavaScript conversion modes. Each mode has a unique set of rules designed to help you achieve any type of JavaScript code, while remaining in a fully typed, valid C#.
To convert a class into JavaScript, all you have to do is to decorate your class with a JsType attribute. There are several modes in which SharpKit can convert C# to JavaScript, by setting the JsMode parameter on the JsType attribute.
Create a new class and add the following code:
[JsType(JsMode.Global, Filename="Site.js")] class Site : HtmlContext { public static void btnHello_click(HtmlDomEventArgs e) { document.body.appendChild(document.createTextNode("Hello SharpKit!")); } }
function btnHello_click(e) { document.body.appendChild(document.createTextNode("Hello SharpKit!")); }
<html> <head> <script src="Site.js"></script> </head> <body> <button onclick="btnHello_click(event);">Click me</button> </body> </html>
The JsType attribute will cause SharpKit to convert the class Site into the file Site.js, you can see the js file included in the htm file.
A class decorated with a JsType(JsMode.Global) attribute will be converted in the following rules:
Static methods are converted to global functions (named functions)Static fields are converted to global variablesStatic constructor is converted to global code
[JsType(JsMode.Global, Filename="Global.js")] class Global { static JsNumber MyNumber = 0; static Global() { HelloWorld(); } public static void HelloWorld() { document.body.appendChild(document.createTextNode("Hello SharpKit!")); } }
var MyNumber = 0; HelloWorld(); function HelloWorld() { document.body.appendChild(document.createTextNode("Hello SharpKit!")); }
Although it seems that the C# and JavaScript code is similiar, notice that it's fully typed. Write this code in a visual studio project, and hover with your mouse over the token 'document' or 'createTextNode' and see the documentation. Any attempt to modify the code to an unexisting method will result in compilation error.
A class decorated with a JsType(JsMode.Prototype) attribute will be converted in the following rules:
Constructor is converted into a JavaScript constructor functionInstance methods are converted into prototype functionsStatic methods are converted into functions on the constructor function
[JsType(JsMode.Prototype, Filename="Contact.js")] class Contact { public void Call() { } public static Contact Load() { return null; } }
Contact = function() { } Contact.prototype.Call = function() { } Contact.Load = function() { return null; }
A class decorated with a JsType(JsMode.Json) attribute will not be exported at all, it will give you the ability to use JSON notation on typed classes.
[JsType(JsMode.Json)] class MyOptions { public JsString Name { get; set; } public bool IsCool { get; set; } }
[JsType(JsMode.Global, Filename="MyPage.js")] class MyPage { public static void Main() { var options = new MyOptions { Name="MyName", IsCool=true }; } }
function Main() { var options = {Name:"MyName", IsCool:true}; }
This technique is very useful on constructor Options class, for example, jQuery.ajax(options) function. It also gives you the ability to share web service data contracts between client and server. Simply mark your data contract classes with the JsType(JsMode.Json) and you'll be able to use them in a C# to JavaScript context.
SharpKit supports any JavaScript based library, in order to use a library with SharpKit, you must obtain a C# 'header' file of the library. A C# header file contains all the classes and methods that a certain library offers. The methods in this file have no implementation, and are not converted into JavaScript, they simply give you the ability use them from C#. You can use these files by adding them to your project, or by referencing compiled assembly version of them.
To make things easy, we have created a googlecode project called SharpKit SDK, which contains open-source C# header files for all popular web libraries such as: jQuery, ASP.NET Ajax, ExtJS, jQTouch and more... These libraries are maintained by us and by members of the SharpKit community, if you'd like to contribute, let us know. You should know that all of these libraries are also included in the SharpKit installer, and are available on your SharpKit Program Files folder.
In order to use jQuery, you'll need to add a reference to SharpKit.jQuery assembly, or include the SharpKit.jQuery cs file in your project. There's a header file and an assembly for each version of jQuery. You'll also have to add jQuery js file to pages that use it.
<html> <head> <title>Hello World</title> <script src="res/jquery-1.4.4.min.js" type="text/javascript"></script> <script src="res/HelloWorld.js" type="text/javascript"></script> <script> $(HelloWorldClient_Load);</script> </head> <body> <button>Click me</button> </body> </html>
using SharpKit.JavaScript; using SharpKit.Html; using SharpKit.jQuery; namespace SharpKitWebApp { [JsType(JsMode.Global, Filename = "~/res/HelloWorld.js")] public class HelloWorldClient : jQueryContext { static void HelloWorldClient_Load() { //J() is instead of $() which is not allowed in C# J("button").click(e => alert("Hello world")); } } }
function HelloWorld_Load() { $("button").click(function(e){ return alert("Hello world")}); }
There are situations in which you will have to write your own header files, this happens in two common scenarios:
In order to create a header file, all you have to do is to create empty implementation of the needed classes and members, decorate them with a JsTypeAttribute in the proper mode, and prevent these classes of being exported.
To prevent C# class with JsTypeAttribute from being exported into JavaScript, use the JsTypeAttribute.Export property.
[JsType(JsMode.Prototype, Name="MyExternalLibrary", Export=false)] class MyExternalLibrary { public static void DoSomething(){} } [JsType(JsMode.Global, Export=false)] class MyExternalFunctions { public static void DoSomethingElse(){} }
[JsType(JsMode.Global, Filename="MyPage.js")] class MyPage { public static void Main() { MyExternalLibrary.DoSomething(); MyExternalFunctions.DoSomethingElse(); } }
function Main()
{
MyExternalLibrary.DoSomething();
DoSomethingElse();
}
Please note that the JsTypeAttribute.Mode and Name properties are very important, even if you're not exporting this code. These attributes are used by SharpKit compiler to generate the proper code when you refer to these classes. The JsType(Name="MyExternalLibrary") is very important, since it tells SharpKit to ignore this type's namespace and name, and use the specified name instead.
SharpKit uses Custom Attributes to control and customize JavaScript code generation behavior. These attributes are located in the SharpKit.JavaScript namespace, every type and member in C# has its own attribute, like: JsTypeAttribute, JsMethodAttribute, JsPropertyAttribute and JsFieldAttribute. Attributes can be applied directly on a type / member like this:
[JsType(JsMode.Prototype)] public class Grid { [JsMethod(Name="render")] public void Render() { } [JsProperty(Name="element")] public HtmlElement Element { get;set; } }
Grid = function() { } Grid.prototype.render = function() { }
Attributes can also be applied externally, on types / members within a project, or even on external referenced types:
[assembly: JsType (TargetType = typeof(Grid), Mode=JsMode.Prototype)] [assembly: JsMethod(TargetType = typeof(Grid), TargetMethod="Render", Name = "render")] [assembly: JsMethod(TargetType = typeof(string), TargetMethod = "Substring", Name = "substr", NativeOverloads = true)]
In the example above we can see that it is possible to mark external types such as System.String, and allow SharpKit to generate proper code when it is used. In this case, if anyone uses the Substring() method, it will be generated as substr() in JavaScript.
JsTypeAttribute is the most basic and important attribute in SharpKit, it marks any type that should be exported to JavaScript in an "opt-in" style, just like DataMemberAttribute in WCF. JsTypeAttribute has many options that control code generation behavior, but in order to make things more simple, we have created 4 templates, or, modes, in which all options are set to a default value according to popular usage types. So, setting a JsType to Prototype mode:
[JsType(JsMode.Prototype)] public class Tree { }
Is the equivalent of setting all of these flags:
[JsType(Native = true, NativeOverloads = true, NativeDelegates = true, AutomaticPropertiesAsFields = true, NativeConstructors = true, NativeEnumerator = true, NativeFunctions = true, NativeJsons = true, IgnoreGenericTypeArguments = true, IgnoreGenericMethodArguments = true, InlineFields = true)] public class Tree { }
Available modes are: Global, Prototype, Json and Clr. You can read more about these modes here.
There are two very popular techniques for optimizing JavaScript and CSS files - consolidation and minification. The JsMergedFileAttribute allows you to do both:
[assembly: JsMergedFile(Filename="MySite.js", Sources = new string[] { "jQuery.js", "MyPage1.js", "MyPage2.js" })] [assembly: JsMergedFile(Filename="MySite.min.js", Sources = new string[] { "MySite.js" }, Minify = true)]
In this code example, a consolidated file named MySite.js will be created, containing all code from jQuery.js, MyPage1.js and MyPage2.js files. The second attribute, takes the previously consolidated file MySite.js, and generates a minified version of it in MySite.min.js. The same rules apply to css files, if the file extension is '.css' the file will be minified in CSS rules minification algorithm.
SharpKit allows you to write native JavaScript code, just as if you've written it yourself. Although C# and JavaScript share many similarities, there are differences as well. This section will explain you how to bridge the gap between the two worlds, and allow you to enjoy the freedom of JavaScript, while retaining the type-checking and validity of C#.
Every JavaScript primitive type has an equivalent C# type. In order to avoid ambiguity with native C# types, a "Js" prefix is added to each JavaScript primitive type. For example the String object is named JsString in C#, Number is named JsNumber and so on...
Although a class is named JsString in C#, when it is converted to JavaScript, it will be written as String. This is achieved by using the JsTypeAttribute.Name property:
[JsType(JsMode.Prototype, Name="String", Export=false)] public class JsString { }
All of JavaScript keywords are located in a single class called JsContext, you can either call the methods on it by qualifying them with the class name, e.g.: JsContext.@typeof(obj); Or, you can simply inherit from the JsContext class and use the methods directly without any prefix.
The verbatim (@) literal is used in C# to disambiguate between reserved C# keywords and other member / variable names. The 'typeof' method for example is a keyword in C#, so in order to disamibuate it with the JavaScript 'typeof' function, the verbatim (@) literal is needed.
The As<T>() extension method, located in the SharpKit.JavaScript namespace, helps you to cast from one type to another without affecting the generated JavaScript code. for example:
using SharpKit.JavaScript; using SharpKit.Html; [JsType(JsMode.Global, Filename="MyPage.js")] class MyPage : HtmlContext { public static void Main() { var input = document.getElementById("input1").As<HtmlInputElement>(); input.value = "MyValue"; } }
function Main() { var input = document.getElementById("input1"); input.value = "MyValue"; }
This sample code will show you how to write a client-side grid control using SharpKit. SharpKit is a tool that allows you to write C# code and convert it JavaScript during compilation. This process helps you write code much faster, and with less errors, it also helps you to document your code so other developers can use it more easily. So, to avoid confusion, all the code here will be shown in C#, but in fact, it is standard JavaScript code, and can be used with / without SharpKit afterwards.
We'll start by learning how to implement the Grid, then move on to using the Grid, and finally, learn how to optimize DOM manipulation to support legacy browsers.
[JsType(JsMode.Prototype, Filename = "Grid.js")] public class Grid : HtmlContext { public Grid() { Rows = new JsArray<GridRow>(); } public HtmlElement Element { get; set; } public HtmlElement GridBody { get; set; } public JsArray<GridRow> Rows { get; set; } public void Render() { if (Element == null) return; Element["_Grid"] = this; if (GridBody == null || GridBody.nodeName != "TBODY") { GridBody = document.createElement("TBODY"); Element.appendChild(GridBody); } } }
This Grid class is designed to be used in the following pattern:
var grid = new Grid { Element = document.getElementById("MyGrid") }; grid.Render();
The grid class has a collection of Rows where each row is of type GridRow. GridRow is a json class, which means that it's used only to contain data about the row, in our case we will contain a reference to the table row element (a TR element), and a Data property that associates the row with some data object.
[JsType(JsMode.Json)] public class GridRow { public HtmlElement Element { get; set; } public object Data { get; set; } }
The GridRow class is designed to be used in the following pattern:
var row = new GridRow { Element = grid.CreateRow(document.getElementById("MyGridRowTemplate")) }; grid.AddRow(row);
Now it's time to implement an AddRow method, that will add a GridRow to our Rows collection, and append it to the DOM.
public void AddRow(GridRow gr) { var body = GridBody; body.appendChild(gr.Element); gr.Element["_GridRow"] = gr; Rows.push(gr); }
public HtmlElement CreateRowElement(HtmlElement template) { return template.cloneNode(true); }
This method simply clones an element, in our case this will be TR element to be cloned for each row in the grid.
Now, we're ready to use the grid:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Grid Demo - SharpKitSamples</title> <link href="Grid.css" rel="stylesheet" type="text/css" /> <script src="res/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="Grid.js" type="text/javascript"></script> <script src="GridDemo.js"></script> <script>$(Load);</script> </head> <body> <h1>Grid Demo</h1> <table id="MyGrid" class="Grid"> <thead> <tr> <th>Name</th> <th>Age</th> <th>Phone Number</th> <th>Description</th> </tr> </thead> <tbody style="display: none"> <tr id="MyGridRowTemplate"> <td class="CellName"></td> <td class="CellAge"></td> <td class="CellPhoneNumber"></td> <td class="CellDescription"></td> </tr> </tbody> </table> </body> </html>
This html file contains a TABLE element, for the grid, with some headers and a row template. We can clone this row to create new rows in the grid. You can also notice the $(Load) code which is in fact the jQuery ready event, this means that when the DOM is ready the Load() function will be invoked.
[JsType(JsMode.Global, Filename = "GridDemo.js")] public class GridDemoClient : jQueryContextBase { public static void Load() { var list = new JsArray<Contact>(); for (var i = 0; i < 30; i++) { var c = new Contact { Name = "MyContact" + i, Age = i, PhoneNumber = "44557799" + i, Description="This is a contact "+i }; list.push(c); } var grid = new Grid { Element = document.getElementById("MyGrid") }; grid.Render(); foreach (var c in list) { var row = new GridRow { Element = grid.CreateRow(document.getElementById("MyGridRowTemplate")), Data = c }; var tr = J(row.Element); tr.find(".CellName").text(c.Name); tr.find(".CellPhoneNumber").text(c.PhoneNumber); tr.find(".CellAge").text(c.Age.ToString()); tr.find(".CellDescription").text(c.Description); grid.AddRow(row); } } }
In this code, we're generating 30 sample contacts, then we create GridRow objects for them, we also create TR elements cloned from our template and bind the data from the contact instance to each row.
With a solid grid API, it's easy to implement a feature like sorting, all we want to do is to clear the rows in the grid, sort them, and render them back to the grid.
public static void SortByName() { var rows = Grid.Rows.OrderBy(t => t.Data.As<Contact>().Name); Grid.DeleteAllRows(); foreach (var row in rows) Grid.AddRow(row); }
OrderBy method is a custom extension method implemented in a small utility class:
[JsType(JsMode.Prototype, Filename = "GridDemo.js")] static class JsArrayExtensions { public static JsArray<T> OrderBy<T>(this JsArray<T> array, JsFunc<T, object> selector, bool desc) { var array2 = array.slice(0); if (!desc) array2.sort((x, y) => Compare(selector(x), selector(y))); else array2.sort((x, y) => CompareDesc(selector(x), selector(y))); return array2; } static JsNumber Compare(object x, object y) { var xx = x.As<int>(); var yy = y.As<int>(); if (xx > yy) return 1; if (xx < yy) return -1; return 0; } }
This is a simplified LINQ sorting implementation on JavaScript arrays, it is implemented using extension methods, to make code usage easier and more readable. It is still converted to plain JavaScript by SharpKit.
The next step is to support sorting by any property and remember the last sorting we've done, to toggle between Ascending and Descending sorting. We should also change the look of the currently sorted column, so the user will understand that the grid is sorted.
static Grid Grid; static HtmlTableCell LastSortHeader; static JsString LastSort; static bool IsLastSortDescending; public static void SortBy(HtmlTableCell header, JsString pe) { J(LastSortHeader).removeClass("Sorted").removeClass("Descending"); IsLastSortDescending = LastSort == pe && !IsLastSortDescending; LastSort = pe; LastSortHeader = header; J(LastSortHeader).addClass("Sorted"); J(LastSortHeader).toggleClass("Descending", IsLastSortDescending); var rows = Grid.Rows.OrderBy(t => t.Data.As<JsObject>()[pe], IsLastSortDescending); Grid.DeleteAllRows(); foreach (var row in rows) Grid.AddRow(row); }
Now all that's missing is to call the SortBy method when clicking the grid headers:
<table id="MyGrid" class="Grid"> <thead> <tr> <th onclick="SortBy(this, 'Name');">Name</th> <th onclick="SortBy(this, 'Age');">Age</th> <th onclick="SortBy(this, 'PhoneNumber');">Phone Number</th> <th onclick="SortBy(this, 'Description');">Description</th> </tr> </thead> <tbody style="display: none"> <tr id="MyGridRowTemplate"> <td class="CellName"></td> <td class="CellAge"></td> <td class="CellPhoneNumber"></td> <td class="CellDescription"></td> </tr> </tbody> </table>
That's it, simple, straightforward, fast and highly customizable.
Legacy browsers can slow, sometimes a simple jQuery usage can result in hard performance penalties in browsers like IE7, sometimes you have to get your hands dirty and implement optimized DOM APIs of your own. SharpKit allows me to do this easily by implementing extension methods. It makes the code easy to read and easy to maintain.
[JsType(JsMode.Prototype, Filename = "Grid.js")] static class Extensions { public static void AppendChildFast(this HtmlElement el, HtmlElement newElement, HtmlElement lastChild) { if (lastChild != null && SupportsInsertAdjacentElement) lastChild.insertAdjacentElement("afterEnd", newElement); else el.appendChild(newElement); } }
Now, I can use el.AppendChildFast(), as if it was a method on HtmlElement, when in fact, it's a static extension method. About this method, in old browsers like IE7, appendChild() can be very slow, since the browser iterates over all the siblings until it reaches the end in order to add the element. This method gets a pointer to the lastChild and uses insertAdjacentElement method to add the element without this overhead.
In conclusion, writing JavaScript code can sometimes be complicated, using SharpKit allows us to focus on writing the code, and perform refactorings and cleanups afterwards. It's also possible to add xml documentation and generate a help file to allow other developers to easily integrate your component.
SharpKit is an extensible compiler, with plugin support, this means that it is possible to implement a plugin and add new features.
Compiler plugins can add and customize C# and JavaScript code, or even support conversion to a different target language.
In order to write a plugin, follow these steps:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using SharpKit.Compiler; namespace MySharpKitPlugin { public class MyPlugin : ICompilerPlugin { public ICompiler Compiler { get; set; } public void Init(ICompiler compiler) { Compiler = compiler; Compiler.AfterParseCs += new Action(Compiler_AfterParseCs); Compiler.BeforeSaveJsFiles += new Action(Compiler_BeforeSaveJsFiles); } void Compiler_AfterParseCs() { Console.WriteLine("MyPlugin: AfterParseCs: Hello"); } void Compiler_BeforeSaveJsFiles() { Console.WriteLine("MyPlugin: BeforeSaveJsFiles: Hello again"); } } }
Create a test project for your plugin:
<ItemGroup> <SkcPlugin Include="MySharpKitPlugin.MyPlugin, MySharpKitPlugin"><InProject>false</InProject></SkcPlugin> </ItemGroup>
You can use the 'Compiler' object to read and modify the C# and JavaScript in-memory model. In order to use the C# model, you'll need to add a reference to NRefactory assemblies as well.