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);
}
}

Closing all sql connections from Solution Explorer.

27. September 2012 17:21 by admin in .net, vs2010 vs2012
In my development work I get the following message quite a lot : Cannot drop database "XXX" because it is currently in use. This usually means I have an existing website or tool with a connection to the database I want to drop. Especially with Entity Framework Code-First I tend to rewrite the whole DB quite often. Instead of killing the 'guilty' process I decided to create a VS shortcut to deal with it. So here's what I have now:


What does this do?

  1. Look for a App.Config or Web.Config of the selected project
  2. Look for connection strings in the configuration file
  3. Use the first connection string and try to connect to the corresponding master db
  4. Close existing connections to the database specified in 3

So HOW does it work?

For this purpose i created a small console application and integrated it into my Visual Studio as an external command. 

The application (source code) is included as a zip file here: connection-killer.zip (211,17 KB)
This consists of a VS2012 solution but it should be easy to open it in VS2010 as nothing special is done. (For VS2010 just import the .cs files into a 'fresh' project) The heart of the application is the following:

Basically switching between SINGLE_USER and MULTI_USER closes all active connections. 

Next (and final) step is to integrate the program into vs2012. The steps are the same on VS2010 and afaik VS2008. Here's how:
1 Add the program as external tool
Via Tools->External tools click add. Point to the point where you have connection-killer exe file and change the initial directory of the application. Move connection killer up so it is the first external tool (you'll see why later). It should look like this:


2 Create a short in the Solution Explorer context menu
  1. Go Tools->Customize and click the Commands tab. 
  2. Click the context menu 'Project and Solution Context Menus | Project'
  3. Add command select category 'tools' and select command 'External Command 1'
  4. Click 'Modify Selection' and rename the menu item to kill connection or something
  5. Close the dialog and you're done

Packages needed for mvc 4 and scaffolding.

27. September 2012 14:56 by admin in
Here's my packages folder after I added a MVC site and installed mvc-scaffolding. 

I agree with the lego strategy of Asp.Net but isn't this overkill? Added the version specific dependencies and the fact that packages don't get uninstalled if no longer needed and things get really messy quickly.