Mount SkyDrive Natively in Windows

Microsoft SkyDrive is a free 25GB online storage locker available to anyone with a Live ID. Microsoft just gave it a fresh new web interface but has never provided a way to access it natively from the Windows Shell.

On the bright side, SkyDrive is implemented on WebDav over SSL which is an open standard and one that is incorporated into Windows Vista and later. (You could map a drive to a WebDAV location in XP too but not over HTTPS.)

The trick is that the SkyDrive DAV URLs are obscure and not easily discoverable but I found a cool little command-line utility on codeplex which spits out the URLs to each of your root-level DAV shares in SkyDrive.

utility

 

Use these URLs with net use or “map a network drive” in Windows Explorer and you have access to skydrive contents via the Windows Shell or a command-line.

webdav-skydrive

It seems like this should also work in OS X but the Finder keeps giving me an error bonk that it can’t contact the server. I’m not sure what the problem is there.

Advertisements

Cool PowerShell Script Replicates Telnet

Lee Holmes has a cool script to reproduce telnet-like functionality via the TcpClient object in PowerShell.

## Connect-Computer.ps1 
## Interact with a service on a remote TCP port 
param( 
    [string] $remoteHost = "localhost", 
    [int] $port = 23
     ) 

try
{
    ## Open the socket, and connect to the computer on the specified port 
    write-host "Connecting to $remoteHost on port $port" 
    $socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port) 
    if($socket -eq $null) { return; } 

    $stream = $socket.GetStream() 
    $writer = new-object System.IO.StreamWriter($stream) 

    $buffer = new-object System.Byte[] 1024 
    $encoding = new-object System.Text.AsciiEncoding 

    while($true) 
    { 
       ## Allow data to buffer for a bit 
       start-sleep -m 500 

       ## Read all the data available from the stream, writing it to the 
       ## screen when done. 
       while($stream.DataAvailable)  
       {  
          $read = $stream.Read($buffer, 0, 1024)    
          write-host -n ($encoding.GetString($buffer, 0, $read))  
       } 

       ## Read the user's command, quitting if they hit ^D 
       $command = read-host 
      
       ## Write their command to the remote host      
       $writer.WriteLine($command) 
       $writer.Flush() 
    } 
}
finally
{
    ## Close the streams 
    $writer.Close() 
    $stream.Close()
}

This solves the problem of telnet.exe crashing conhost.exe when using Console2.

Update

I tweaked Lee’s code to use try/finally which obviates the need for a special escape sequence to clean up the TCP resources.

Adapting the Microsoft Chart WebControl to MVC

The Chart control for ASP.Net that comes baked into .NET 4 or as an add-on to .NET 3 is useful but it is built around the WebForms rendering model which is uncomfortable in MVC applications. There is no pretty way to embed the WebChart control out-of-the-box. Solutions I have seen revolve around either giving up on the MVC separation and embedding the control into a View or creating custom web user controls for each chart and treating these as partial views. The former solution will only work with the WebForm view engine and not Razor or other view engines and the latter is just messy. Either way it feels like walking around with toilet paper stuck to your shoe. A third way is to return the image bytes of from a Chart object created in the controller as a FileResult. This works but you lose the image map tooltips.

The crux of the problem is that Chart is a DataBoundControl which is designed to magically generate its own HTML and to serve an image resource to itself when the rendered HTML is interpreted. It isn’t really meant to have an external controller tell it what to do. However, the Chart control has some nice features:

  1. It generates cross-browser friendly output
  2. It generates mouseover tooltips
  3. It doesn’t require Flash, Silverlight or JavaScipt because it uses MAP and AREA elements to create the tooltips
  4. With a little coercion, it will generate its image resource for you as a byte array which means you can alternatively embed the chart in an alternate resource like a PDF or other document envelope

Chart the MVC Way

Having decided that the Chart control does something we want, can we bend it to the MVC rendering model while preserving the nice tooltip image maps? The answer is yes we can. I have created a handful of extension methods that turn Chart on its head and allow you to render it out of a Controller class as an ActionResult. I’ll show you how things look in the View and Controller and then walk you through how it works.

View

<% Html.RenderAction( "PerformanceHistoryChart", new { id = Model.Id, shareClass = Model.ShareClass } ); %>

Controller

[OutputCache( Duration = 10, VaryByParam = "id;shareClass" )]
public ActionResult PerformanceHistoryChart( Guid id, string shareClass )
{
    return this.RenderChartMap( "PerformanceHistoryImage", id, shareClass, NewPerformanceHistoryChart );
}

[OutputCache( Duration = 10, VaryByParam = "id;shareClass" )]
public ActionResult PerformanceHistoryImage( Guid id, string shareClass )
{
    return this.RenderChartImage( id, shareClass, NewPerformanceHistoryChart );
}

private static Chart NewPerformanceHistoryChart( Guid id, string shareClass )
{
    Chart chart = new Chart();
    var series = perfChart.Series.Add( "Default" );
    var area   = perfChart.ChartAreas.Add( "ChartArea" );
    var values = ReportUtility.GetNetReturnOnValuePercentageHistory( id, shareClass );
    series.Points.DataBindXY( values.Item1, values.Item2 );
    series.ToolTip = "Fiscal Year: #VALX, Growth: #VALY{P}";
    area.AxisY.Title = "%";
    area.AxisY.LabelStyle.Format = "{P2}";
    return chart;
}

What’s going on in the View is that we are rendering the result of calling PerformanceHistoryChart on the controller. PerformanceHistoryChart renders HTML which includes an IMG tag reference to the PerformanceHistoryImage action which streams back the chart image as a PNG.PerformanceHistoryChart and PerformanceHistoryImage make use of my Chart to MVC adapter extension methods to do their work. There are two sets of extension method overloads:

  • this.RenderChartMap
  • this.RenderChartImage

In order to render any chart to a browser you need to provide two action methods. One of them calls RenderChartMap which generates HTML and the other calls RenderChartImage which returns the image as a FileResult for the IMG tag generated by RenderChartMap. Both extension methods accept a factory Func delegate which they use to create the Chart object that they manipulate internally. I’ve created overloads which accept 1 to 5 arguments.

MvcChart Extension Method Adapter Class

I’m sure this could be made more elegant but my first cut is working well. One limitation of my implementation is that you need a pair of extension method overloads that has enough arguments for your Chart factory Func  to work. Another issue is that you need to have an explicit route defined for the Actions which return the chart image FileContentResult. The reason is that the Chart control always appends a ?<some-guid> which is part of the caching mechanism it uses for storing images when it is rendering itself as a WebForms control. Therefore, I need to have an explicit route without any query string and allow the query string the Chart control generates to dangle off the end. My assumption is that the arguments will be passed in order from left to right in the URL as they appear in the method call. Also, since we are taking over rendering the Chart control via our Controller we give up the baked-in image caching. This is probably not much of an issue but it makes sense to use OutputCaching attributes to reduce the hits on the Chart factory Funcs and therefore your database.

Here’s my MvcChart class which contains the adapter extension methods. Happy coding.

using System.IO;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;

namespace System.Web.Mvc
{
    /// <summary>
    /// <para>This class adapts the System.Web.UI.DataVisualization.Charting.Chart API to something
    /// more friendly to ASP.Net MVC by attaching extension methods to the Controller type.Requires
    /// an explicit route mapping for all arguments. The arguments will be inserted into the Image 
    /// generating URL in order.</para>
    /// <para>In each controller we need a pair of ActionMethods. One generates the IMG, MAP and AREA tags.
    /// The other streams the chart PNG. The URL to the chart PNG method is referenced by the imageActionName
    /// parameter in the RenderChartMap() method call. We also need a factory method which generates the Chart
    /// object instance. The chart factory is passed as a delegate to these adapter extension methods.</para>
    /// <remarks>In order to render each chart in a browser, the factory method must be executed twice.
    /// Consider using the OutputCache attribute to reduce hits on the data backend.</remarks>
    /// </summary>
    public static class MvcChart
    {
        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T">Type of the factory argument.</typeparam>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T>( this Controller controller, string imageActionName,
            T arg, Func<T, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg );
            //FWIW: ImageLocation URL will have ?{some-guid} appended which is part of its classic WebForms ASP.Net
            //web control rendering model
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/", 
                GetApplicationPath( controller ), 
                GetControllerName( controller ), 
                imageActionName, 
                arg );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1,T2>( this Controller controller, string imageActionName, 
            T1 arg1, T2 arg2, Func<T1, T2, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/", 
                GetApplicationPath( controller ), 
                GetControllerName( controller ), 
                imageActionName, 
                arg1, 
                arg2 );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1, T2, T3>( this Controller controller, string imageActionName,
            T1 arg1, T2 arg2, T3 arg3, Func<T1, T2, T3, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/{5}/",
                GetApplicationPath( controller ),
                GetControllerName( controller ),
                imageActionName,
                arg1,
                arg2,
                arg3 );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1, T2, T3, T4>( this Controller controller, string imageActionName,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func<T1, T2, T3, T4, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/{5}/{6}/",
                GetApplicationPath( controller ),
                GetControllerName( controller ),
                imageActionName,
                arg1,
                arg2,
                arg3,
                arg4);
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render img tag and associated image map for a chart.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory argument.</typeparam>
        /// <typeparam name="T2">Type of the second factory argument.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <typeparam name="T5">Type of the fifth factory argument.</typeparam>
        /// <param name="imageActionName">Method name in this controller that streams the chart image.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="arg5">Fifth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns>ContentResult that generates HTML for the chart.</returns>
        public static ContentResult RenderChartMap<T1, T2, T3, T4, T5>( this Controller controller, string imageActionName,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func<T1, T2, T3, T4, T5, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4, arg5 );
            chart.ImageLocation = string.Format( "{0}{1}/{2}/{3}/{4}/{5}/{6}/{7}/",
                GetApplicationPath( controller ),
                GetControllerName( controller ),
                imageActionName,
                arg1,
                arg2,
                arg3,
                arg4,
                arg5 );
            return RenderChartMap( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T">Type of the id.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T>( this Controller controller, T arg, Func<T, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2>( this Controller controller, 
            T1 arg1, T2 arg2, Func<T1, T2, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2 );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2, T3>( this Controller controller, 
            T1 arg1, T2 arg2, T3 arg3, Func<T1, T2, T3, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3 );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2, T3, T4>( this Controller controller,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, Func<T1, T2, T3, T4,  Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4 );
            return RenderChartImage( chart );
        }

        /// <summary>
        /// Render chart image byte stream as a PNG file.
        /// </summary>
        /// <typeparam name="T1">Type of the first factory parameter.</typeparam>
        /// <typeparam name="T2">Type of the second factory parameter.</typeparam>
        /// <typeparam name="T3">Type of the third factory argument.</typeparam>
        /// <typeparam name="T4">Type of the fourth factory argument.</typeparam>
        /// <typeparam name="T5">Type of the fifth factory argument.</typeparam>
        /// <param name="controller">Controller instance.</param>
        /// <param name="arg1">Id used by the chart factory to find the data to generate the chart.</param>
        /// <param name="arg2">Second parameter used by the chart factory.</param>
        /// <param name="arg3">Third parameter used by the chart factory.</param>
        /// <param name="arg4">Fourth parameter used by the chart factory.</param>
        /// <param name="arg5">Fourth parameter used by the chart factory.</param>
        /// <param name="chartFactory">Delegate capable of generating a new Chart instance.</param>
        /// <returns></returns>
        public static FileContentResult RenderChartImage<T1, T2, T3, T4, T5>( this Controller controller,
            T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, Func<T1, T2, T3, T4, T5, Chart> chartFactory )
        {
            Chart chart = chartFactory.Invoke( arg1, arg2, arg3, arg4, arg5 );
            return RenderChartImage( chart );
        }
        
        private static ContentResult RenderChartMap( Chart chart )
        {
            chart.RenderType = RenderType.ImageMap;
            //Make sure that each chart rendered into the page has a unique ID in HTML by
            //assigning a Guid to the ID property. Otherwise, they will all have the same
            //ID and the image map will only work for the first chart at best.
            chart.ID = Guid.NewGuid().ToString( "N" );
            using( var writer = new StringWriter() )
            using( var htmlTextWriter = new HtmlTextWriter( writer ) )
            {
                chart.RenderControl( htmlTextWriter );
                return new ContentResult() { Content = writer.ToString() };
            }
        }

        private static FileContentResult RenderChartImage( Chart chart )
        {
            using( MemoryStream chartStream = new MemoryStream() )
            {
                chart.SaveImage( chartStream, ChartImageFormat.Png );
                return new FileContentResult( chartStream.ToArray(), "image/png" ) { FileDownloadName = "chart.png" };
            }
        }

        private static string GetApplicationPath( Controller controller )
        {
            var applicationPath = controller.HttpContext.Request.ApplicationPath;
            applicationPath = applicationPath.EndsWith( "/" ) ? applicationPath : applicationPath + "/";
            return applicationPath;
        }

        private static string GetControllerName( Controller controller )
        {
            var controllerName = controller.GetType().Name;
            if( !controllerName.EndsWith( "Controller" ) )
                throw new InvalidOperationException( "Controller names must end with \"Controller\"" );

            controllerName = controllerName.Substring( 0, controllerName.Length - 10 ); ;
            return controllerName;
        }
    }
}

%d bloggers like this: