Archive

Archive for the ‘free software’ Category

Serving different views for mobile devices in ASP.NET MVC

May 3rd, 2010

As browsing becomes more commonplace on phones, sub-notebooks and (within the next year or so) Tablet PCs, there’s an increased appetite to tailor your user experience for people using these “non-desktop” devices.  You can leverage your existing application infrastructure without having to create costly or outsource applications for specific (*cough* iPhone) platforms that have lots of market share, to the exclusion of others (*cough* iPhone).

There are some excellent examples of this in the wild, Facebook and the BBC do a great job of this already.

So what about us ASP.NET MVC types?  Well thankfully, there’s a lot of stuff built in to the framework to allow us to do this with relative ease.  First you’re going to need a few things..

1) Go grab the latest Mobile Device Browser File from http://mdbf.codeplex.com.  If you’re familiar with the old “Browser Caps”, it’s the same sort of thing.  A regularly updated collection of device data.  It’s pretty interesting on it’s own, breaking down incoming devices by user agent / headers and offering you various stats (touch enabled, screen resolution etc).  You’ll probably want to keep this up to date periodically.

2) A new ViewEngine!  Thankfully, ASP.NET MVC has a pretty flexible pipeline when it comes to slotting in new ViewEngines.  The most transparent way to switch out which view you’re serving for any given request is to override some of the logic in the default ViewEngine to look elsewhere when a View is requested by the MVC framework.

3) A way to test this stuff.  I normally break out the “User Agent Switcher” FireFox plugin which you can grab here: https://addons.mozilla.org/en-US/firefox/addon/59

 

The View Engine

In order to keep this as close to vanilla MVC as possible, I’m going to extend the regular WebFormsViewEngine to add mobile device detection and view overriding.  What I’m going to do is check for a mobile browser when a request reaches the view engine, and if one is found, add some extra paths to look for the view files in at the top of the list of locations searched.  By doing this, we can prioritise the mobile edition of a website if the user is visiting from a phone, while degrading gracefully, allowing the regular version of the website to be served if a mobile version of a given page isn’t available.  This actually allows us to support mobile views piece by piece rather than forcing us to support the entire site out of the box.

Unfortunately, a few of the methods surrounding view resolution are marked as private in the WebFormsViewEngine and as such are inaccessible.  I’ve had to reflect in and copy a couple of methods to get around this.  Ideally the access modifier on these could be changed in later versions of the framework.

The key method we have to work with is

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{ … }

What we’re going to do is add a few extra locations based on the browser type.  With the mobile device browser file installed, this is really simple:

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");
    }
    if (String.IsNullOrEmpty(viewName))
    {
        throw new ArgumentException("viewName");
    }

    List<string> viewLocationsSearched;
    List<string> masterLocationsSearched;

    string[] viewLocationsToSearch = ViewLocationFormats;
    string[] masterLocationsToSearch = MasterLocationFormats;

    viewLocationsToSearch = AddMobileViewLocations(controllerContext, viewLocationsToSearch, MobileViewLocationFormats);
    masterLocationsToSearch = AddMobileViewLocations(controllerContext, masterLocationsToSearch, MobileMasterLocationFormats);

    string controllerName = controllerContext.RouteData.GetRequiredString("controller");
    string viewPath = GetPath(controllerContext, viewLocationsToSearch, viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
    string masterPath = GetPath(controllerContext, masterLocationsToSearch, masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);

    if (String.IsNullOrEmpty(viewPath)
        || (String.IsNullOrEmpty(masterPath)
        && !String.IsNullOrEmpty(masterName)))
    {
        return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
    }

    return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}

You’ll notice there’s a few methods that get called in there.  The most important of which is “AddMobileViewLocations”.  This really is where all the legwork is done, and looks like this

public class SwitchingViewEngine : WebFormViewEngine
{
    private const string CacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:";
    private const string CacheKeyPrefixMaster = "Master";
    private const string CacheKeyPrefixView = "View";
    private static readonly List<string> EmptyLocations = new List<string>();

    protected string[] MobileViewLocationFormats { get; private set; }
    protected string[] MobileMasterLocationFormats { get; private set; }

    public SwitchingViewEngine()
    {
        ViewLocationFormats = new[]
                                  {
                                      "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx",
                                      "~/Views/Shared/{0}.ascx"
                                  };

        MobileViewLocationFormats = new[]
                                        {
                                            "~/Views/{1}/{0}.mobile.aspx", "~/Views/{1}/{0}.mobile.ascx",
                                            "~/Views/Shared/{0}.mobile.aspx",
                                            "~/Views/Shared/{0}.mobile.ascx"
                                        };

        MasterLocationFormats = new[] {"~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master"};
        MobileMasterLocationFormats = new[] {"~/Views/{1}/{0}.mobile.master", "~/Views/Shared/{0}.mobile.master"};
    }

    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {   …    }

    private static string[] AddMobileViewLocations(ControllerContext controllerContext,
                                                   string[] viewLocationsToSearch,
                                                   IEnumerable<string> mobileViewLocations)
    {
        if (controllerContext == null
            || controllerContext.HttpContext == null
            || controllerContext.HttpContext.Request == null
            || controllerContext.HttpContext.Request.Browser == null
            || viewLocationsToSearch == null
            || viewLocationsToSearch.Length == 0
            || mobileViewLocations == null
            || mobileViewLocations.ToList().Count == 0
            || !controllerContext.HttpContext.Request.Browser.IsMobileDevice)
        {
            return viewLocationsToSearch;
        }

        var mobileViews = viewLocationsToSearch.ToList();
        foreach (var view in mobileViewLocations.Reverse())
        {
            mobileViews.Insert(0, view);
        }

        viewLocationsToSearch = mobileViews.ToArray();

        return viewLocationsToSearch;
    }

This method takes the current ViewLocations that are defined at the top of the class, does a bunch of guard checks (to prevent mobile switching crashing your request.. call me paranoid), then verifies that controllerContext.HttpContext.Request.Browser.IsMobileDevice is true.  This check makes use of the browser device file.  If all the checks pass, it inserts the new “mobile view paths” at the very top of the list of paths to be searched when resolving the location of a view file.  The calling method then subsequently calls the method “GetPath” (which is one of the private methods I’ve had to reflect out of the framework source code).  GetPath searches the supplied list of potential view locations, and returns as soon as it finds a match.  In our case, if the browser is mobile, and a view file with the extension mobile.aspx is found, this will be the first view resolved and returned.

The full code listing for the view engine (don’t worry, there’s a link at the bottom to grab all of this in one archive):

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;

namespace MultipleViewMvcExample.DemoCode
{
    public class SwitchingViewEngine : WebFormViewEngine
    {
        private const string CacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:";
        private const string CacheKeyPrefixMaster = "Master";
        private const string CacheKeyPrefixView = "View";
        private static readonly List<string> EmptyLocations = new List<string>();

        protected string[] MobileViewLocationFormats { get; private set; }
        protected string[] MobileMasterLocationFormats { get; private set; }

        public SwitchingViewEngine()
        {
            ViewLocationFormats = new[]
                                      {
                                          "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx",
                                          "~/Views/Shared/{0}.ascx"
                                      };

            MobileViewLocationFormats = new[]
                                            {
                                                "~/Views/{1}/{0}.mobile.aspx", "~/Views/{1}/{0}.mobile.ascx",
                                                "~/Views/Shared/{0}.mobile.aspx",
                                                "~/Views/Shared/{0}.mobile.ascx"
                                            };

            MasterLocationFormats = new[] {"~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master"};
            MobileMasterLocationFormats = new[] {"~/Views/{1}/{0}.mobile.master", "~/Views/Shared/{0}.mobile.master"};
        }

        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (String.IsNullOrEmpty(viewName))
            {
                throw new ArgumentException("viewName");
            }

            List<string> viewLocationsSearched;
            List<string> masterLocationsSearched;

            string[] viewLocationsToSearch = ViewLocationFormats;
            string[] masterLocationsToSearch = MasterLocationFormats;

            viewLocationsToSearch = AddMobileViewLocations(controllerContext, viewLocationsToSearch, MobileViewLocationFormats);
            masterLocationsToSearch = AddMobileViewLocations(controllerContext, masterLocationsToSearch, MobileMasterLocationFormats);

            string controllerName = controllerContext.RouteData.GetRequiredString("controller");
            string viewPath = GetPath(controllerContext, viewLocationsToSearch, viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
            string masterPath = GetPath(controllerContext, masterLocationsToSearch, masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);

            if (String.IsNullOrEmpty(viewPath)
                || (String.IsNullOrEmpty(masterPath)
                && !String.IsNullOrEmpty(masterName)))
            {
                return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
            }

            return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
        }

        private static string[] AddMobileViewLocations(ControllerContext controllerContext,
                                                       string[] viewLocationsToSearch,
                                                       IEnumerable<string> mobileViewLocations)
        {
            if (controllerContext == null
                || controllerContext.HttpContext == null
                || controllerContext.HttpContext.Request == null
                || controllerContext.HttpContext.Request.Browser == null
                || viewLocationsToSearch == null
                || viewLocationsToSearch.Length == 0
                || mobileViewLocations == null
                || mobileViewLocations.ToList().Count == 0
                || !controllerContext.HttpContext.Request.Browser.IsMobileDevice)
            {
                return viewLocationsToSearch;
            }

            var mobileViews = viewLocationsToSearch.ToList();
            foreach (var view in mobileViewLocations.Reverse())
            {
                mobileViews.Insert(0, view);
            }

            viewLocationsToSearch = mobileViews.ToArray();

            return viewLocationsToSearch;
        }

        private string GetPath(ControllerContext controllerContext, string[] locations, string name,
                               string controllerName, string cacheKeyPrefix, bool useCache,
                               out List<string> searchedLocations)
        {
            searchedLocations = EmptyLocations;
            if (string.IsNullOrEmpty(name))
            {
                return string.Empty;
            }
            if ((locations == null) || (locations.Length == 0))
            {
                throw new InvalidOperationException("Property cannot be null or empty.");
            }
            bool flag = IsSpecificPath(name);
            string key = CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName);
            if (useCache)
            {
                string viewLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
                if (viewLocation != null)
                {
                    return viewLocation;
                }
            }
            if (!flag)
            {
                return GetPathFromGeneralName(controllerContext, locations, name, controllerName, key,ref searchedLocations);
            }
            return GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
        }
        private static bool IsSpecificPath(string name)
        {
            char ch = name[0];
            if (ch != ‘~’)
            {
                return (ch == ‘/’);
            }
            return true;
        }

        private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey,
                                               ref List<string> searchedLocations)
        {
            string virtualPath = name;
            if (!FileExists(controllerContext, name))
            {
                virtualPath = string.Empty;
                searchedLocations = new List<string> {name};
            }
            ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
            return virtualPath;
        }

        private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name,
                                              string controllerName, string cacheKey, ref List<string> searchedLocations)
        {
            string virtualPath = string.Empty;
            searchedLocations = new List<string>();
            for (int i = 0; i < locations.Length; i++)
            {
                string str2 = string.Format(CultureInfo.InvariantCulture, locations[i],
                                            new object[] {name, controllerName});
                if (FileExists(controllerContext, str2))
                {
                    searchedLocations = EmptyLocations;
                    virtualPath = str2;
                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                    return virtualPath;
                }
                searchedLocations[i] = str2;
            }
            return virtualPath;
        }

        private string CreateCacheKey(string prefix, string name, string controllerName)
        {
            return String.Format(CultureInfo.InvariantCulture, CacheKeyFormat,
                                 GetType().AssemblyQualifiedName, prefix, name, controllerName);
        }
    }
}

 

The Wiring

Now you have your view engine, you need to register it as the default view engine in your MVC application.  Easy!  Open up your Global.aspx.cs file and add the following to ApplicationStart

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new SwitchingViewEngine());

Done!

Now you need to actually add the browser detection file to your application.  Presuming you downloaded the latest archive from the codeplex url at the top of this article, all you need to do is copy the supplied mobile.browser file to App_Browsers/Device/* in your MVC application.

That’s all the wiring you need to get everything up and running.

If you’ve done it right, a default “New MVC Template” project with these additions might look something like this:

image

For the sake of this demo, I put the view engine in a “DemoCode” sub-namespace. You don’t want to do that, put it somewhere sensible!

The more astute reader might now notice that my Views/Home directory in the above screenshot has an extra file, not supplied by the template, called “Index.mobile.aspx”.  Likewise, my /Views/Shared directory has Site.mobile.Master.  These are files I want the view engine to resolve if a user hits the http://localhost/Home default route from a mobile device.

Index.mobile.aspx looks like this:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.mobile.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Mobile Home Page
</asp:Content>

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%= Html.Encode(ViewData["Message"]) %></h2>
    This is my mobile index page.
</asp:Content>

and Site.mobile.Master looks like this:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
</head>

<body>
    <div class="page">

        <div id="header">
            <div id="title">
                <h1>My MVC Application - mobile master page</h1>
            </div>
            <div id="logindisplay">
                <% Html.RenderPartial("LogOnUserControl"); %>
            </div>
            <div id="menucontainer">
                <ul id="menu">             
                    <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
                    <li><%= Html.ActionLink("About", "About", "Home")%></li>
                </ul>
            </div>
        </div>

        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />

            <div id="footer">
            </div>
        </div>
    </div>
</body>
</html>

Nothing especially revolutionary.

 

Testing

First, install that FireFox plugin I mentioned at the top of the article, create a new MVC project (just use the template), and wire up the view engine and browser file.  Add some extra views with the .mobile.aspx prefix.  Or download the sample attached to the bottom of this post!

Start the site up in Cassini (Visual Studios default web server) and hit /Home.  You should see this:

image

Now, lets use the new FireFox plugin…

image

Select iPhone 3.0 from that menu and refresh the page…

image

Bang!  The more eagle eye reader might have notice that all I did in my “mobile” master page, was delete the CSS reference from the default MVC template, thus the above screenshot.

And that’s it.

 

Further Thoughts

This solution goes quite a way, but here are a few other ideas:

  • Don’t just do browser type detection, detect and switch on subdomain, so any visitors hitting http://m.mysite.com get a different view.
  • Allow the user to opt-out of the reduced view with a session cookie.
  • Switch views to a low-fi version to victimise IE6 users!
  • Target tablet PCs based on resolution to build a “touch UI”

It’s all pretty simple.  The really nice thing about this solution is that your designers can just add these mobile views as and when they see fit, as the same action methods that execute for the “full fat” website are run, and the same strongly typed view models (which you’re using, right? get out of here with your ViewData..) are delivered.  Designers can implement portable websites, piece by piece, just with a little view engine change.

As with all internet code, your mileage may vary, but this technique works for me.

You can download a working VS2008 solution (so long as you have ASP.NET MVC1 installed on your system) containing all the code used above from: http://github.com/davidwhitney/MultipleViewMvcExample

MobileTFL 1.1.0.0

April 17th, 2010

A really quick note, MobileTFL, the London Tube status application for Windows Mobile 6+, has just been updated to version 1.1.0.0.

Changes: Supported new TFL data feed format, fixed breaking bug.

Get it here: http://www.davidwhitney.co.uk/content/blog/index.php/software/

I’m currently an Android user while awaiting Windows Phone 7, so I’ve only been able to test this against unit tests and the device emulator, but it looks fine. That said, feedback would be appreciated.

Thanks to all the people that emailed me to let me know the app had broken.

Reusable Editable Fields for ASP.net MVC Using jQuery

October 8th, 2009

A friend recently asked me about editing items inline using ASP.net MVC, the kind of thing that was auto magically wired up with post backs in “old fashioned” asp.net so I’ve whipped up a small example showing how you can use jQuery to declaratively set up interactive field editing with a sprinkling of Ajax and JSON.

I’m basing this example on the default ASP.net MVC starter project for brevity (download attached) but here’s an overview:

First you need to set up an Action method (or multiple action methods) on your controller to accept the modification of data.  In my example I’ve added an unimaginative method called “SetField” to the HomeController that looks like this:

public ActionResult SetField(string fieldName, string fieldValue)
{
    var response = Json(fieldValue);
    return response;
}

As you can see, it doesn’t do very much (useful implementation left to the reader) but it accepts the parameters of a field name, and a field value.  You’ll need to roll your own validation and sanity checking here.  It then returns the fieldValue using the MVC Json helper object, as a Json object.  In a real world example, you’d want to call and update in this method.

Now, in the view, jQuery does most of the hard work.

First I added a few CSS classes to the header on the master page (for the sake of example):

<style type="text/css">
    .editableItem { display: block; }
    .fieldViewer { display: block; }
    .fieldEditor { display: none; }
    .editableItemCancel { display: block; }
    .editableItemBox { display: block; }
</style>

I then added an example to the view that looked like this:

<div class="editableItem" id="editable_FieldName">
    <div class="fieldViewer">Click me to edit me!</div>
    <div class="fieldEditor"><input class="editableItemBox" type="text"/><span class="editableItemCancel">cancel</span></div>
</div>

With this HTML I set up some conventions that I’ll rely on when using jQuery.  Firstly, every editable item should use the class “editableItem” and have the id “editable_FieldName”.  I use the class in a jQuery selector and the Id to establish which field is being edited.  Inside the editableItem should be a fieldViewer, containing the current data, and a fieldEditor, which is hidden by default, and contains some kind of editable controller and a cancel button.  You could insert these elements at runtime if you wished, but in order to keep the example simple I’ve declared them in the HTML.

Next I added some jQuery… The jQuery defines some Javascript behaviour associated with the classes used in the HTML, this way, the mark-up can be reused to edit multiple fields rather than being keyed to the Id of a specific field.

<script src="/Scripts/jquery-1.3.2.js" type="text/javascript"></script>
<script type="text/javascript">
    jQuery(document).ready(function() {

        $(".editableItem .fieldViewer").click(function() {
            var parentId = $(this).parent().attr("id");
            $(’#’ + parentId + " .fieldEditor .editableItemBox").val($(’#’ + parentId + " .fieldViewer").text());
            $(’#’ + parentId + " .fieldViewer").toggle();
            $(’#’ + parentId + " .fieldEditor").toggle();
        });

        $(".editableItem .fieldEditor .editableItemCancel").click(function() {
            var parentId = $(this).parent().parent().attr("id");
            $(’#’ + parentId + " .fieldViewer").toggle();
            $(’#’ + parentId + " .fieldEditor").toggle();
        });

        $(’.editableItem .editableItemBox’).keypress(function(e) {
            if (e.which == 13) {
                var parentId = $(this).parent().parent().attr("id");
                var fieldName = parentId.replace(/editable_/, "");

                $.post(’/Home/SetField’,
                {
                    fieldName: fieldName, fieldValue: $(’#’ + parentId + " .editableItemBox").val()
                },
                    function(data) {
                        $(’#’ + parentId + " .fieldViewer").text(eval(’(’ + data + ‘)’));
                        $(’#’ + parentId + " .fieldViewer").toggle();
                        $(’#’ + parentId + " .fieldEditor").toggle();
                    })
            }
        });

    });
</script>

Quite simply, if you click the editable field, it toggles into a textbox.  If you hit enter on the textbox, the value is posted to the previously defined Action on the Controller.  If you hit cancel, the display is toggled back.

I’d not recommend copy and pasting this exact example into a production system, but hopefully it’ll guide you through a simple scenario.  You can use a similar technique to add all sorts of little Ajax tricks (auto-suggest, lookups, dynamic menus) to your ASP.net MVC site using jQuery and Json (both of which are included in the core asp.net MVC framework).

Download the example solution here

Creating a WCF Proxy to talk to Magento

October 5th, 2009

I got a message from a friend who was struggling to do an integration piece with the Magento eCommerce Platform using the SOAP endpoint available at http://yourserver.co.uk/api/v2_soap?wsdl.

He brought an interesting problem to me, namely that the WCF svcutil executable (and built in Visual Studio 2008) was failing to generate any proxy code when supplied with a seemingly valid wsdl.

I did a quick test and managed to instantly reproduce the error.

Weirder still, when using the “old” .Net 2.0 add web reference method rather than the .Net 3.0+ “Add Service Reference”, the framework managed to create a non-WCF reference just fine.

Dropping down to the command line I saw some unusual messages being displayed by svcutil.exe:

c:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin>SvcUtil.exe http://yourserver.co.uk/api/v2_soap?wsdl
Attempting to download metadata from ‘http://yourserver.co.uk/api/v2_soap?wsdl’ using WS-Metadata Exchange or DISCO.

(Lots of error messages here…)

Error: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.Se
rviceModel.Description.XmlSerializerMessageContractImporter
Error: The ‘ ‘ character, hexadecimal value 0×20, cannot be included in a name.
Parameter name: name
XPath to Error Source: //wsdl:definitions[@targetNamespace='urn:Magento']/wsdl:p
ortType[@name='Mage_Api_Model_Server_V2_HandlerPortType']

Error: Cannot import wsdl:binding
Detail: There was an error importing a wsdl:portType that the wsdl:binding is de
pendent on.
XPath to wsdl:portType: //wsdl:definitions[@targetNamespace='urn:Magento']/wsdl:
portType[@name='Mage_Api_Model_Server_V2_HandlerPortType']
XPath to Error Source: //wsdl:definitions[@targetNamespace='urn:Magento']/wsdl:b
inding[@name='Mage_Api_Model_Server_V2_HandlerBinding']

Error: Cannot import wsdl:port
Detail: There was an error importing a wsdl:binding that the wsdl:port is depend
ent on.
XPath to wsdl:binding: //wsdl:definitions[@targetNamespace='urn:Magento']/wsdl:b
inding[@name='Mage_Api_Model_Server_V2_HandlerBinding']
XPath to Error Source: //wsdl:definitions[@targetNamespace='urn:Magento']/wsdl:s
ervice[@name='MagentoService']/wsdl:port[@name='Mage_Api_Model_Server_V2_Handler
Port']

Generating files…
Warning: No code was generated.
If you were trying to generate a client, this could be because the metadata docu
ments did not contain any valid contracts or services
or because all contracts/services were discovered to exist in /reference assembl
ies. Verify that you passed all the metadata documents to the tool.

Warning: If you would like to generate data contracts from schemas make sure to
use the /dataContractOnly option.

c:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin>

So I did a little digging through the WSDL and found an undocumented bug in Magneto’s schema.

First I saved a local copy of the WSDL and used visual studio to reformat the document into some sort of readable state, then I had to make a few corrections to the WSDL to allow SvcUtil to correctly parse the malformed document.

Change 1:  Replace a badly encoded apostrophe – I removed the “’s” from the following operation definition…

<operation name="customerGroupList">
<documentation>Retrieve customer’s groups</documentation>
<input message="typens:customerGroupListRequest"/>
<output message="typens:customerGroupListResponse"/>
</operation>

Change 2: Replace a trailing space in an operation name

<message name="catalogProductGetSpecialPriceRequest">
  <part name="sessionId" type="xsd:string"></part>
  <part name="product" type="xsd:string"></part>
  <part name="storeView " type="xsd:string"></part>
</message>

If you look carefully at the above message definition, you’ll notice that name=”storeView “ contains a space, making the wsdl invalid.  Remove the space so it reads “storeView”.

With these two errors corrected, SvcUtil had no problem generating an appropriate WCF proxy from the corrected wsdl file.

Magneto will hopefully fix this error in the WSDL, but until this time, it’s probably quite safe to follow these steps to generate your own proxy.

To reproduce:

  • Go to http://yourserver.co.uk/api/v2_soap?wsdl and save the contents of your file to the local disk (c:\test\main.wsdl)
  • Open the file in visual studio, and reformat the document for readability (CTRL+K, CTRL+D).
  • Remove the apostrophe from the documentation tag for the customerGroupList operation.
  • Remove the space after the name=”storeView “ in the catalogProductGetSpecialPriceRequest message definition.
  • Open a command prompt and enter
  • c:\test>c:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe main.wsdl
  • SvcUtil will produce two files, Magento.cs (your WCF proxy) and output.config, your endpoint configuration.

Localizing ASP.Net MVC Pages without the need to RunAt=”server”

August 20th, 2009

A common complaint when faced with localizing ASP.net MVC pages is that littering your code with tonnes of runat=”server” tags breaks the “purity” of the MVC model.  Regardless of how meaningful that debate is, there is a way to achieve globalisation without server controls.

I’m going to walk you through a sample implementation which should hopefully make this clearer.  As a standard disclaimer, none of this code has been tested in a production environment and I wouldn’t advise implementing it blindly.

I’m going to attempt to play this out mostly in screenshots…

The Idea

  • The required language is stored in a database / session / extrapolated from the Url route information
  • Your view pages derive from the type TranslatableViewPage
  • This page adds support for a LanguageCode property that you can make use of inside the view along with adding a class implementing ITranslator, a class that provides hooks into a translation database.
  • Your controller derives from the type TranslatableController.
    • This controller add a method called ViewInLanguage(string languageCode) that you use instead of View() or View(model) to return your Asp.net MVC view.
  • At application start up in the Global.asmx file, you register a default language code for failure conditions, and specify the implementation of ITranslator you wish to use to acquire localized strings.
  • If your TranslatableViewPage when rendering your output, you simple need to use the embedded <%=Translator.Translate(propertyName) %> method call to load a translated string while the page output is rendering.
  • Class Layout

    The following is built upon the standard Asp.net MVC starter project for the sake of illustration.

    image

    Implementation

    First, configure the translation settings and register your translator…

    image

    You need to configure a fail-safe default language code and a type that implements ITranslator.  This translator will be responsible for doing the heavy lifting.  Ideally we’d add some inversion of control here to allow you to define the translator implementation at configuration time, but that’s outside of the scope of this example.  I’ve implemented a very crude resource .resx translator for the example.

    Next, make your controllers inherit from TranslatableController

    image

    Once inherited, switch from using the View(); method to ViewInLanguage() passing in your desired language code (gathered from the user session / database / Url route).

    Then set your view type (or derive your view from) TranslatableViewPage

    image

    Once your view is inheriting from TranslatableViewPage, you’ll have access to an instance of your specified Translator and the LanguageCode inside the view which you can use in a manner similar to the built in HtmlHelper class to access your translated strings.

    How It Works

    The TranslatableController provides you with ViewInLanguage and when called generates the standard MVC ActionResult.  This ActionResult will be a ViewResult, which is then wrapped in a LocalizedViewResult wrapper class adding a LanguageCode.

    When ExecuteResult is called on the LocalizedViewResult to render the view, the LanguageCode is placed in the TempData array in the TranslatableViewPage.  Then, when the OnInit(EventArgs e) method is called on the TranslatableViewPage, this LanguageCode is extracted and placed in the LanguageCode property on the page.  In addition to this the page provides a constructed instance of ITranslator which you can use in your views to source translated data as the page is rendered.

    Source Code

    Download Here

    Installing certificates using WiX / Voltive (A Code Sample)

    April 22nd, 2009

    I’ve previously provided a code snippet illustrating how to use WiX (the “new” Windows Installer framework) to install certificates onto the target machine.

    I’ve had some good feedback about the post, but also had a request for a working example (in order to illustrate how the code fragment is used in an Installer.wxs file).

    I’ve cooked up a sample visual studio solution that installs a test certificate (generated using the MSDN makecert certificate example “makecert -sk XYZ -n “CN=XYZ Company” testXYZ.cer “).

    You’ll probably need to re-path references to the WixIISExtension and WixUIExtension in relation to your development environment but hopefully this should help you.

    The previous post is the third most accessed post in my blog (which probably speaks as much for the number of people that read this as it does it’s utility) so this should be useful to someone.

    It’s worth noting that this example installs the junk certificate as a Root CA so you might want to change that or ensure you remove it once you’re done.

    Download the Sample Solution

    Mobile TFL 1.0.0.4 – London Tube Status Updates On Your Windows Mobile

    March 17th, 2009

    [NOTE: This post relates to an old version of MobileTFL - Please see http://www.davidwhitney.co.uk/software for the latest version]

    I’ve just compiled what I hope is the final version of this application barring London growing extra underground lines.

    I’ve written a small Windows Mobile application for phones running WinMo 5.0+ with Compact Framework 2.0+.  It syndicates the Transport for London live data in similar way to the pre-existing iPhone and Android applications allowing you to view the current tube network status, planned engineering outages and local station notifications at the push of a finger on your Windows Mobile.

    It’s currently living at a holding page at http://www.davidwhitney.co.uk/software (soon to become a larger site) and it’s a free download for anyone that uses Windows Mobile.

    The 1.0.0.4 update is a reaction to some feedback I’ve received over at http://www.xda-developers.com and comes complete with coloured glyphs representing each tube line and enhanced resolution support for devices with “unusual” resolutions such as the Sony X1.  I’ve also had a stab at some font and UI element scaling so hopefully the interface will look quite natural on all your devices.

    I’ve only managed to test it in the wild on a Touch Diamond, Touch Cruise and the Windows Mobile 5.0 Virtual Machine (along with in Windows).  It seems to run admirably in those conditions.

    Obviously it requires a data plan so beware of any provider costs.

    You can download the .cab directly at this link: http://www.davidwhitney.co.uk/software/repository/MobileTFL/1.0.0.4/MobileTFL.Setup.CAB

    ss1 ss4
    ss5 ss6

    Mobile TFL – London Tube Updates On Your Windows Mobile Phone

    March 16th, 2009

    A very quick late night post.

    I’m a big fan of HTC’s Touch Diamond once you remove all the cruft that the major phone networks like to cram onto their devices.  It’s a pocket sized, powerful smart phone.  I picked one up only a few months ago as a replacement for my HTC Touch, which was starting to feel very very slow compared to some of the handsets on the market, and the addition of HDSPA on the Diamond was the clincher.

    Anyway, as a result the good lady has been quite impressed with the functionality of the Diamond.  She works in the media and the ability to access the internet with an almost desktop like experience (and without an iPhone) was very appealing.  While we were in town this weekend sorting out a new contract for her, I noticed that Google’s G1 ships with a London Tube service status application.  Seeing as Eleanor lives in London it seemed like an instantly cool thing to have on hand, but to my surprise, there isn’t anything comparable available for Windows Mobile (or there is and it’s too difficult to track down).

    So I wrote one.

    I’ve put up a really REALLY retro holding page at http://www.davidwhitney.co.uk/software for the purpose of releasing this application, hopefully over the coming months I’ll use it as a gather place for all the applications I’ve written, both free and pay-for, but for the moment, feel free to go and pick up a copy of the ingeniously named MobileTFL (after the transport-for-London website, where the application sources its data).

    If you’re too lazy to click through one link:

    Download Mobile TFL for Windows Mobile Here (Cab file)

    You’ll have to forgive the exceptionally low-fi website and hilarious low res Visual Studio 2008 virtual machine screenshot.  I’ve only really tested the app on the Diamond I have sitting on my desk right now and it works a treat.

    Obviously it requires you have a data plan that’s from a company that believes in Mobile internet rather than customer robbery but it only uses a tiny amount of data (it’s just a Http request).

    It also requires the Compact Framework.  I’ve built it under CF3.5 but I suspect it’ll run just fine under CF2.0 (they’re binary compatible after all).

    Feedback is more than welcome, just send me an email about it.  I’m especially interested on how the rendering looks on your devices, as the rendering code was written quite quickly and as soon as it looked ok in the VM and on my device I pretty much packaged it up.

    I doubt it’ll brick your device, but if it does, you know the drill, it’s your problem.

    Time for sleep now.

    [Update: Microupdate to version 1.0.0.3 to fix a few bugs and aestetics, the above link has changed, revisit it for the new version]

    Simple C# HTTP Server for Windows Mobile

    February 17th, 2009

    Recently I’ve been trying to pay a debt of sorts.  I use the software provided on the fantastic XDA-Developers frequently on my Windows Mobile devices and have been doing for almost three years now.

    So currently, if I have a little bit of free time, I’ll nip past their development section and try and fulfil a random request.

    Today’s request might come in useful to a little bit of a wider audience so I’ll post it here.  The initial problem is outlined in this thread and the upshot was that someone needed a very simple Http server that could run on Windows Mobile, written in C#’, and was programmable.

    What the guy really seemed to be looking for was something that used the Http protocol to return random computed data, so after a tiny bit of googling and a simple MSDN example, I’ve built a really simple web server for Windows Mobile.

    Amusingly, my sample doesn’t behave much like a web server at all.  It just tells you what you requested, but it should be enough to get you going in the right direction.  It’s derived from the MSDN example with some additional sugar, and I certainly wouldn’t suggest that it’s got a threading model that’d stand up in a production environment (at a glance it looks like it’d process requests in sequence…) but hopefully it’s useful to somebody looking to produce a simple server, or who is just interested in how Http works.

    I’m not really sure if I can accurately call it a webserver, seeing as it doesn’t even support a full set of Http Verbs, but you get the idea.

    I’ve not actually bothered compiling this on a mobile device yet (lack of inclination) however seeing as it was explicitly based on a socket programming for Windows Mobile MSDN example, I suspect it’ll work just fine.  No warranty, do what you will with it.

    Download Simple C# Http Server for Windows Mobile (Source Code Only) (7kb)

    C# Subversion ChangeLog Generator

    January 19th, 2009

    Ever wanted to generate a change log from all your (dubious) Subversion repository comments?  For some reason want to do it in C# or just by calling an exe?  Now you can!  You can even tie it in to continuous integration!

    About 6 months ago I spent an evening porting the Subversion Change Log generator to C# in order to integrate Change Log generation it into our Cruise Control.Net nightly builds at work.

    It’s not a direct port, more a C# reimplementation, but it produces the same output as svn2cl (which in itself was inspired by cvs2cl).  The gist is, that it calls svn.exe on the command line and retrieves the full history of comments for whichever svn path you request.

    It’s not the most elegant thing I’ve ever written (it pretty much just spawns the svn.exe and pipes the output through an XSL transform) and requires you having a command line svn.exe on your system (which if you use subversion on windows is pretty likely), but if you do you should be able to generate change logs from your repositories by calling the compiled executable like so:

    SubversionReportProducer.exe –style=ChangeLog.xsl –outputLocation=out.txt –repositoryPath=svn://repo/trunk

    If you don’t want to pass the subversion exe path and repository path through on each execution you can specify them by modifying the values in SubversionReportProducer.exe.config.  Those values will always attempt to load from configuration, and then attempt to load from a command line parameter (overwriting any app.config settings) allowing for maximum flexibility in batch scripts and automation.

    I’ve bundled it with a couple of XSL transform files to produce some output, mostly taken from svn2cl and one subtle adaption that we use in our build process (ingeniously named changelog.xsl).

    Your mileage by vary, but this could well be of use to someone.

    Source code provided, feel free to contact me about any bugs but I’m pretty much just kicking this out into the wild..

    Download Source
    Download Compiled Executable
    Requires .Net 2.0+