Generating TypeScript proxy classes for Ajax Methods using Weld

1. June 2013 19:30 by martijn in .net, jQuery, MVC, typescript, Weld

In this post I want to talk about a little tool called Weld I wrote to make integrating client-side TypeScript and server side MVC C# easy. 

Imagine you have a server-side method that calculates some value you want to use client side. In this sample project I want to calculate the sum of two values I have in input fields.

image

image

Server-side I have this action-method in my home controller.

public int Sum(int x,int y)
{
return x + y;
}
Now to access this method client I just need to add a Weld attribute. For this I first install Weld using NuGet. 
Just “Install-Package Weld” in your NuGet package manager console. This will install Weld as well a TypeScript.Compile and JQuery typings into your project. 
TypeScript.Compile auto compiles all your typescript files post-build. This enables you to detect any problems caused by server side changes. 
The basic process :
  1. Normal compilation
  2. Weld generates TypeScript code for the classes your decorated with Weld attributes
  3. TypeScript compiles all files in your project with build actions 'TypeScriptCompile'
For this sample I add the AjaxMethod (Weld.Attributes.AjaxMethod) attribute to the ‘Sum’ action. 
Compile the project. Weld now has generated a proxy class for my home controller. I just need to include it and used it in the project. It is located in the /Scripts/Weld folder. 
I add the generated homecontroller.ts to my project. I my main TypeScript file I write the following to hook it all up:
/// <reference path="Weld/HomeController.ts" />/// <reference path="typings/jquery/jquery.d.ts" />
$(document).ready(function () {
$("#btnSum").click(function () {
HomeController.prototype.Sum($("#number1").val(), $("#number2").val(), (result) => {
$("#result").text(result);
});
});    
});
As you can see the details of the Ajax communication are handled by weld and you get a nice interface to your server side API :)
 

Creating a Yes/No toggle switch for a boolean property in ASP.MVC

11. May 2013 13:20 by admin in .net, jQuery, JQuery UI, MVC

When editing boolean values in MVC the default option is to use a checkbox. This results in the following Create page when using the default scaffolding

image

This is fine and works OK but sometimes you want something more..’fancy’. ASP.NET MVC ships with Jquery.UI so why no use the slider of JQuery.UI to create a nice toggle switch?

In this post I will use and modify a JQuery.UI plugin that will result in the following. The plugin is based on http://taitems.github.io/UX-Lab/ToggleSwitch/ which had some errors in my opinion.

image 

There are a two parts to this:

  1. Create a HTMLHelper extension method that renders a <select> element for the boolean property with <option> elements for true and false
  2. Change the rendered <select> element to a jquery slider using a JQuery.UI plugin

Creating the HTMLHelper

public static class HtmlHelperExtensions
{
public static MvcHtmlString ToggleSwitchFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression)
{
return htmlHelper.DropDownListFor(expression, new[]
{
new SelectListItem { Text = "No", Value = "false" },
new SelectListItem { Text = "Yes", Value = "true" } 
}, new { @class = "toggleswitch" });
}
}

This adds a HTMLHelper method that renders a dropdown list which comes down to a single select HTML element. There are two options in the select : one for true and one for false. To use it we just do 

@Html.ToggleSwitchFor(model => model.CommentsEnabled)

And we get the following html

<select class="toggleswitch" data-val="true" data-val-required="The CommentsEnabled field is required." id="CommentsEnabled" name="CommentsEnabled"><option value="false">No</option><option value="true">Yes</option></select>

This is all what we need to get our JQuery plugin going.

Creating the JQuery plugin

The plugin uses the JQuery.UI slider to create a toggle switch. This means that any styling or theme you might have for the slider will work for the toggle as well. The plugin transforms the select element into two labels with a slider in between. When you click on a label or move the slider the select control will be updated as well and when you do form post the values get updated. The http://taitems.github.io/UX-Lab/ToggleSwitch/ had two errors when I tried it

  1. The change event was just passing the change event of the slider. This meant even if the boolean property did not change (you moved the slider a little) you would still get a change event. In this version the change event is only fired when the value really changed.
  2. The plugin did not allow chaining.
  3. The normal ‘change’ event of JQuery was no longer fired. This is fixed by not having a change event on the control itself but just fire the existing change event.

To use it you need to add the CSS and JS of the plugin to your page. You also need the default JQuery UI js and css (which is not referenced by default!). Once you have done that you can activate the plugin for a control:

$(document).ready(function () {
$("#CommentsEnabled").toggleSwitch().change(function() {
alert("Changed!!");
});
});

I attached a sample MVC application as well as the JQuery.UI.ToggleSwitch plugin.

ToggleSampleMVC4.zip (771,1 KB)
jquery.ui.toggleswitch.zip (1,73 KB)

Adding and removing build targets from NuGet powershell

1. May 2013 15:56 by admin in .net, msbuild, nuget
In NuGet packages you can add powershell scripts to alter the .csproj file of the project in which your package is installed. Recently I had quote some trouble getting this exactly right but nailed it in the end. So here's a sample to share with google so I can find it later ;) This sample adds a target that runs after the AfterBuild target: The Get-Project and Get-MSBuildProject are from the nuget powertools by David Fowlder. (This script is part of the install.ps1 script from a open source project called Weld).

Adding a build target to a project

$project = Get-Project
$buildProject = Get-MSBuildProject
$target = $buildProject.Xml.AddTarget("WeldAfterbuild")
$target.AfterTargets = "AfterBuild"
$task = $target.AddTask("Exec")
$task.SetParameter("Command", "`".\..\packages\Weld.1.0.0\tools\Weld.console`" bin\`$(TargetFileName) `"`$(ProjectDir)\Scripts\Weld`"")
$project.Save() #persists the changes
Notice how the ` (backtick) is used to escape the " and $ characters. This result in the .csproj like this:
<Target Name="WeldAfterbuild" AfterTargets="AfterBuild">
<Exec Command="&quot;.\..\packages\Weld.1.0.0\tools\Weld.console&quot; bin\$(TargetFileName) &quot;$(ProjectDir)\Scripts\Weld&quot;" />
</Target>
This effectively calls the Weld.Console application passing it the current project .dll name and a target folder.

Removing a build target from a project

In uninstall.ps1 we can remove the same target like this:
$project = Get-Project
$buildProject = Get-MSBuildProject
$projectRoot = $buildProject.Xml;
Foreach ($target in $projectRoot.Targets)
{
If ($target.Name -eq "WeldAfterbuild")
{
$projectRoot.RemoveChild($target);
}
}
$project.Save() #persists the changes
Basically we search for target called "WeldAfterbuild" and then remove that from the project! Easy once you know how :)

Sample MVC 4 application

5. April 2013 10:15 by admin in
This is a sample Asp.NET MVC 4 application for the presentation I gave 5 april 2013 at Hanzehogeschool Groningen. It's created using Visual Studio 2012. To run you also need SqlExpress 2012 or change the connectionstring in web.config to a 'localdb' (for example) to make it work. 


Get it here (50Mb)

Setting up LinqToXSD in Visual Studio 2012

28. March 2013 20:29 by admin in .net, linqtoxsd, vs2012, linq

Intro


Linq To XSD is a code generation library for accessing xml in a strongly typed manner. Linq to XSD allows you to query and xml file like this:

Offcourse you need a XSD to define the structure of your xml but once you have that the API is much nicer than the general LinqToXML.

Setting it up in Visual Studio


Since it took me way too long and way too much googling to setup LinqToXSD in VS 2012 I thought I'd do 
a HOWTO setup for VS2012. So here's the step by step
  1. Download linqtoxsd from http://linqtoxsd.codeplex.com/ (I used 2.0.2.56002)
  2. Extract the zip to ($SolutionDir)\LinqToXSD
  3. Next unload the project in which you want use LinqToXSD (right click -> Unload Project) and edit the project file (right click->edit myproject.csproj)
  4. Now you have to reference the targets file in the LinqToXSD folder. At the top of the file insert the following line:

     <LinqToXsdBinDir Condition="'$(LinqToXsdBinDir)' == ''">$(SolutionDir)\LinqToXSD\</LinqToXsdBinDir>

    So it looks like this
    5. Finally at the bottom of the project file add   

        <Import Project="$(LinqToXsdBinDir)\LinqToXsd.targets" /> 

        below <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
    6. Reload the project
    7. If all is well you can now change the build action of xsd files in your project like this:

         
    8. Once this is done code is generated which you should be able use immediately. Your xsd/xml has to obey certain restrictions. I included two sample files which should work out of the box here :
 
books.xsd (,92 KB)
books.xml (,93 KB)

Hope this is of some use and happy easter!

Taking random elements out of a collection using LINQ

2. March 2013 10:05 by martijn in

An elegant solution is so simple that something seems to be missing. I had that feeling when I found this solution of taking a random subset of a set of items:

 

 
 public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> list, int count) 
{
var shuffledList = list.OrderBy(l => Guid.NewGuid());
return shuffledList.Take(count);
}



Programming is not plumbing

27. February 2013 12:26 by martijn in
As a C# developer I have a natural preference of C# over plSQL. Nevertheless i think there are many reasons why not to put business logic into the DB.
 
  1. plSQL does not contain the same high level building blocks (interfaces,classes,assemblies) as 3G languages. There might be equavalents sometimes for example packages but overall plSQL is a lot less expressive.
  2. Stored procedures quickly turn into large functions which are not nicely divided into small blocks. It might be possible with temp_tables etc but in practice these constructs are not used very often
  3. Introducing a SP layer usually introduces more people into the development process. Apart from changing the schema you also need people with plSQL knowledge.
  4. Adding a SP layer makes your code less DRY. Adding an extra field requires updating of all create and update SPs. This is boring, simple, repetetive and therefore very error prone. Moreover the repetitive nature of plSQL prevents changing your model and leads to suboptimal code.
I am not saying you should use EF directly in your business or view code but I do think that a DAL layer using EF instead of stored procedures strongly reduces the development time,maintenance and errors. Putting you data access code in a separate layer does allows you to switch to other techniques (for example from EF to SP) if needed.

Type safer databinding in legacy asp.net forms projects

22. February 2013 10:56 by admin in .net, vs2010 vs2012
I love compilers. I love ReSharper. And that's why i hate statements like this:

<%# DataBinder.Eval(Container.DataItem,"FirstName") %>
If I was to change the name of the FirstName property or would try to find all references to this property this would not work! In asp.net 4.5 there is a ItemType property on databinding controls which you can use so the dataitem gets a proper type. See http://brijbhushan.net/2012/07/01/strongly-typed-data-controls-and-modal-binding-in-asp-net-4-5-part-1/ for an explanation about this. Unfortunately not all code is asp.net 4.5 and we can not use this feature everywhere. 

In pre asp.net 4.5 projects we can use a different construct. On the container of the databinding control we expose a type-safe version on the current dataitem like this:
public Person CurrentDataItem
{
get { return (Person) Page.GetDataItem(); }
}
Now in our binding control we can use:
 <%# CurrentDataItem.FirstName %>
No strings involved :) See the attached vs2008 sample project for an example.

TypeSafeBindingExample.zip (27,75 KB)

Pinchzoom Image with Adobe Air

24. December 2012 10:06 by admin in actionscript, air, pinchzoom, image
For a recent project I needed to create an image view with pinchzoom similar to the gallery application on iOS. To my surprise this was not very trivial and online resources were limited. Hence this sample project. I use the touchbegin,touchmove,touchmove events to keep track of the users fingers. Because touchend is not always fired I also require a touch to be 'seen' every X milliseconds. Otherwise I remove it from my touchpoints administration. 

The code is based on the idea the location in 'image coordinates' below a users finger should remain the same while moving. If the user moves a finger we need to move/scale the image so that it snaps to the users finger. You can think of it like this

Ps=Pi*S + Poffset

This means that the screen position Ps is equal to image postion Pmultiplied by the scale S and added to an offset. We determine the new scale by looking at the distance between the two fingers. The new scale is newDistance/oldDistance. If the distance between the fingers increases the scale increases. Now that we know Ps,Pi and S we can calculate Poffset.

The core of the algorithm is in the touchmove event. This is the starting point for understanding the code. You can also just use the component like a normal image and set the MaxZoom to specifiy a maximal zoom level. Like this:
<components:PinchZoomImage width="100%" height="100%" source="@Embed('/assets/high.jpg')" MaxZoom="5" />

I added a sample project in zip file pinchzoom.zip (1,73 MB) as well.
The pinchzoomimage code:
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" clipAndEnableScrolling="true" 
creationComplete="creationCompleteHandler(event)" touchBegin="container_touchBeginHandler(event)"
 touchEnd="container_touchEndHandler(event)" touchMove="container_touchMoveHandler(event)" >
    
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import mx.events.FlexEvent;
            
            import components.TouchPoint;
            
            private var startImageX : Number;
            private var startImageY : Number;
            private var touchIsDown : Boolean;
            
            private var startX : Number;
            private var startY : Number;
            
            //dont' update on every move            
            private var SkipCount : Number = 0;            
            
            [Bindable]
            public var source: Object;
            
            private const Margin : Number =10; 
            private var lastX : Number;
            private var lastY : Number;
            private var ImageIsWide : Boolean;
            
            
            public var MaxZoom : Number = 1.5;
            private var maxScale : Number;
            
            private var TouchPoints : ArrayCollection = new ArrayCollection();
            
            protected function creationCompleteHandler(event:FlexEvent):void
            {
                flash.ui.Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;        
                
                var scale: Number =Math.min(width / image.width,height / image.height);
                image.scaleX = image.scaleY = scale;
                
                ImageIsWide = (image.width / image.height) > (width / height);
                
                if (ImageIsWide)
                {
                    image.y = (height -image.height*image.scaleY ) /2 ;
                }
                else
                {
                    image.x = (width -image.width*image.scaleX ) /2 ;
                }
                
                maxScale = scale*MaxZoom;
            }        
            
            protected function container_touchBeginHandler(event:TouchEvent):void
            {
                trace("down");
                // TODO Auto-generated method stub                
                TouchPoints.addItem(new TouchPoint(event.touchPointID,event.stageX,event.stageY));
            }    
            
            protected function container_touchEndHandler(event:TouchEvent):void
            {
                trace("up");
                // TODO Auto-generated method stub                
                var p = GetTouchPointById(event.touchPointID);
                if (p != null)
                {
                    var idx : int = TouchPoints.getItemIndex(p);
                    TouchPoints.removeItemAt(idx);    
                }
                
            }
            private function GetTouchPointById(id : int) : TouchPoint
            {
                for each (var p:TouchPoint in TouchPoints)
                {
                    if (p.Id == id)
                    {
                        return p;
                    }
                }
                return null;
            }
            
            private function SetImageWithinBounds(minX : Number,maxX : Number,minY : Number,maxY : Number)
            {
                image.x = Math.min(Math.max(image.x,minX),maxX);
                image.y = Math.min(Math.max(image.y,minY),maxY);
            }
            
            private function FitImageToScreen():void 
            {
                if (ImageIsWide)
                {
                    //make sure the image is not smaller  than te container
                    if (image.width*image.scaleX < width)
                    {
                        image.scaleX = image.scaleY =  width / image.width;
                        image.x = 0;
                    }
                    
                    var maxX : Number = 0;
                    var minX : Number = width - (image.width * image.scaleX);
                    var minY = (height -image.height*image.scaleY ) ;
                    var maxY = Math.max(0,(height -image.height*image.scaleY ) /2 );
                    
                    SetImageWithinBounds(minX,maxX,minY,maxY);
                    
                }
                else //high image
                {
                    //make sure the image is not smaller  than te container
                    if (image.height*image.scaleY < height)
                    {
                        image.scaleX = image.scaleY =  height/ image.height;
                        image.y = 0;
                    }
                    
                    var maxY : Number = 0;
                    var minY  : Number = height- (image.height* image.scaleY);
                    
                    var minX = (width -image.width*image.scaleX ) ;
                    var maxX = Math.max(0,(width-image.width*image.scaleX ) /2 );
                    
                    SetImageWithinBounds(minX,maxX,minY,maxY);
                }
                
            }
            
            private function UpdateTouchPoints(event:TouchEvent):void
            {
                //update point                
                var tp : TouchPoint = GetTouchPointById(event.touchPointID);
                if (tp == null)
                {
                    return; 
                }
                tp.CurrentX = event.stageX;
                tp.CurrentY = event.stageY;
                var now : Date = new Date();
                tp.LastTouchTime = now;
                
                //remove old points 
                for each (var tp : TouchPointin TouchPoints) 
                {
                    if ((now.getTime() - tp.LastTouchTime.getTime()) > 1000) 
                    {
                        var idx : int = TouchPoints.getItemIndex(tp);
                        TouchPoints.removeItemAt(idx);    
                    }
                }    
                
            }
            protected function container_touchMoveHandler(event:TouchEvent):void
            {
                SkipCount++;
                
                if (SkipCount < 3)
                {
                    return ;
                }
                SkipCount = 0;
                
                trace("move");
                
                UpdateTouchPoints(event);
                
                
                if (TouchPoints.length == 2) //zoom + move
                {
                    var p1  : TouchPoint = TouchPoints[0];
                    var p2  : TouchPoint = TouchPoints[1];
                    var scale :Number = p1.CurrentDistance(p2)  / p1.PreviousDistance(p2);
                    
                    //no more than max
                    scale = Math.min(maxScale/image.scaleX ,scale);
                    
                    //find image coordinate based on previous situation
var imageX : Number = (p1.PreviousX - image.x) / image.scaleX;
var imageY : Number = (p1.PreviousY - image.y) / image.scaleY;
//update scale
image.scaleX *= scale;
image.scaleY *= scale;
//update offset
var offsetX : Number = p1.CurrentX - image.scaleX*imageX;
var offsetY : Number = p1.CurrentY - image.scaleY*imageY;
image.x = offsetX;
image.y = offsetY;                   
                    
                    p1.UpdatePreviousPosition();
                    p2.UpdatePreviousPosition();
                }
                else if (TouchPoints.length == 1) //just move
                {
                    var p1  : TouchPoint = TouchPoints[0];
                    
                    var offsetX : Number = p1.CurrentX - p1.PreviousX;
                    var offsetY : Number = p1.CurrentY - p1.PreviousY;
                    image.x += offsetX;
                    image.y += offsetY;
                    
                    p1.UpdatePreviousPosition();
                }
                
                FitImageToScreen();                     
            }
            
        ]]>
    </fx:Script>
    
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
        <s:Rect width="100%" height="100%">
            <s:fill>
                <s:SolidColor color="#000000" />
            </s:fill>
        </s:Rect>
        <s:Image id="image" source="{source}" fillMode="clip" >
            
        </s:Image>    
    
    
</s:Group>


pinchzoom.zip (1,73 MB)

Mixing SEO urls with normal routing in Asp.Net using a custom RouteHandler

1. October 2012 20:24 by admin in
On a project I was working on we would like to have URLs like

www.thesite.com/greatproduct

Since this was a MVC 3 site the normal routing like this should work as well.

www.thesite.com/product/details/4

To make this work I was inspired bu the article of Lars-Erik Kindblad. In his article he defines a very greedy route that catches all: 

routes.MapRoute("Friendly", "{*FriendlyUrl}").RouteHandler = new FriendlyUrlRouteHandler();


This breaks the default MVC route defined like this

routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new {controller = "Product", action = "Index", id = UrlParameter.Optional},
                new[] {"thesite.Web.Controllers"} // Parameter defaults
                );

Since both routes are catch all mixing them was not an option. But what about override the functionality of the default route handler? This is very easy. Basically by subclassing MvcRouteHandler we can add custom routing as well :) My custom routehandler does a lookup in the product table to find a product of that name and then show the details for that product. If the product name is not found the normal functionality is 'restored'. Also I do a check to see if the name is not a controller name. So the url www.thesite.com/product still maps to the index method of my product controller even if some product's name is 'product'.
     public class FriendlyUrlRouteHandler : System.Web.Mvc.MvcRouteHandler
{
private static IList<string> controllerNames;private static IList<string> ControllerNames
{
get
{
if (controllerNames == null)
{
controllerNames = Assembly.GetExecutingAssembly().GetTypes().
Where(t => typeof(Controller).IsAssignableFrom(t)).
Select(c=>c.Name.Replace("Controller","")).
ToList();
}
return controllerNames;
}
}
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var friendlyUrl = (string)requestContext.RouteData.Values["controller"];
var firstPart = friendlyUrl.Contains("/")? 
friendlyUrl.Split('/')[0] : 
friendlyUrl;//it's a controller..handle normally
if (ControllerNames.Contains(firstPart))
{
return base.GetHttpHandler(requestContext);
}
if (!string.IsNullOrEmpty(friendlyUrl))
{
using (var ctx = new SiteDbContext())
{
                    var product = ctx.Events.FirstOrDefault(p => p.Title == friendlyUrl);
 if (product != null)
{
requestContext.RouteData.Values["controller"] = "product";
requestContext.RouteData.Values["action"] = "details";
requestContext.RouteData.Values["id"] = product.Id;
}
}
}
return base.GetHttpHandler(requestContext);
}
}