Archive for the ‘geek’ Category

iTunes Persistent Id Cloner – Sync one iPhone / iPod with many computers

Wednesday, January 25th, 2012

I frequently have trouble trying to copy music onto my iPod due to iTunes’s draconian binding and synchronising rules. These rules exist to ensure that only “authorised” files can be copied onto any given device. Apple implement this by binding your device to a specific instance of iTunes using a randomly generated Persistent Id that’s registered on your computer and on your device.

I have 3 computers that I use regularly, and I regularly rip CDs in different places and want to put that music on my iPod for travelling imminently. By default, iTunes restricts me from being able to do this.

Tonight I hacked together a little command line app to clone your persistent Id’s across multiple machines.

In order to use it, you need a copy of iTunes bound to your device installed on a machine. Once you have that, you run the program in –extract mode, which will show you the persistent Id for that machine. You can then use the program on other computers to patch any installed iTunes libraries to believe that they have the same persistent Id as the “master” machine. It’s a pretty trivial process.

If it breaks anything, don’t come crying, but “works on my machines”. Happy listening.

Source code / detailed instructions: https://github.com/davidwhitney/itunes-persistent-id-cloner
Executable: https://github.com/downloads/davidwhitney/itunes-persistent-id-cloner/4f7dd551998d515108d937d5b39f28a7b2a058ff.zip

The iTunes Persistent Id Cloner!

Small command line app to:

1) Extract the persistent Id from an iTunes library (this is the thing that your device is bound to)
2) Patch up an iTunes library with a persistent Id you supply (I bet you can see where this is going).

Usage:

Compile.

1) Quit iTunes on machine that you want to be considered your "master" persistent Id.
    I'd suggest the one you've already got your iPod / iPhone registered on.

2) Run itunes-persistent-id-cloner.exe -extract
    Write down the Persistent Id it gives you.

3) Install a fresh copy of iTunes on another machine
    Run it once, and quit iTunes.

3) Run itunes-persistent-id-cloner.exe -patch on another machine
    When prompted, enter the persistent Id supplied by the other Id.
    Ensure iTunes isn't running.
    Hit enter.

No friendly error messages or error handling at the moment.
Don't make typos, things might crash. Worse case, you can run / patch a second time.

CouchDb using C# in 15 minutes

Monday, March 14th, 2011

I’ve spent quite a lot of time experimenting with document databases at the moment, almost exclusively RavenDb of late.  We’re currently looking at sweeping architecture changes at work, and unfortunately it doesn’t look like Raven is going to be suitable (nothing to do with the product, which is excellent, just a target audience with a specific ambivalence to Windows server exclusive products).  Knowing that a lot of Ravens design decisions were based around using CouchDb, and knowing that CouchDb might be a better fit for the scenario I have in mind, I set out to see how easy Couch is to use on Windows.

Short answer? Very very easy, so here’s a few quick steps for someone that wants to get up and running quickly.

First, go grab a copy of your platform-specific installer from CouchBase and install it. This is a 2-3 click process, and as the installer exits your browser pops up into “Futon” the Couch web UI.

Then go to https://github.com/foretagsplatsen/Divan , clone / download the Divan source code and build it. Divan is a handy open source project aimed at wrapping the Couch API (which is RESTful, so if you want to roll your own client library feel free). Divan is quite bare to the metal and relies on Json.Net for de/serializing documents.

Launch Visual Studio and new up a project of your choice. I’ve chucked together a few utility classes while experimenting to deal with persistence in order to get a syntax that looks like this:

   1: const string host = "localhost";

   2: const int port = 5984;

   3: var server = new CouchServer(host, port);

   4: var db = server.GetDatabase("myfirstdatabase");

   5:  

   6: var document = new SomeRandomAggregateRoot

   7:                    {This = "Is cool!", Subclass = new SomeRandomClass {IsAwesome = "hell yeah!"}};

   8:  

   9: using (var couchRepo = new CouchRepository<SomeRandomAggregateRoot>(db))

  10: {

  11:     var id = couchRepo.Save(document);

  12:     var returnedCouchDoc = couchRepo.Retrieve(id);

  13:     ViewData["Message"] = returnedCouchDoc._id;

  14: }

  15:  

  16: return View();

Which I’m pretty happy with as it seems very usable and maps pretty simply from the underlying Json documents.

I have a base class and an interface at the bottom of the object graph that look like this

   1:  

   2:     public class CouchAggregateRoot : ICouchAggregateRoot

   3:     {

   4:         public string _id { get; set; }

   5:         public string _rev { get; set; }

   6:     }

   7:  

   8:     public interface ICouchAggregateRoot

   9:     {

  10:         string _id { get; set; }

  11:         string _rev { get; set; }

  12:     }

and two little repository classes that look like this:

   1: public class CouchRepository<T> : CouchRepository where T : CouchAggregateRoot

   2: {

   3:     public CouchRepository(ICouchDatabase couchDatabase):base(couchDatabase)

   4:     {

   5:     }

   6:  

   7:     public T Retrieve(string id)

   8:     {

   9:         return Retrieve<T>(id);

  10:     }

  11:  

  12:     public string Save(T @object)

  13:     {

  14:         return Save<T>(@object);

  15:     }

  16: }

  17:  

  18: public class CouchRepository: IDisposable

  19: {

  20:     protected readonly ICouchDatabase CouchDatabase;

  21:  

  22:     public CouchRepository(ICouchDatabase couchDatabase)

  23:     {

  24:         CouchDatabase = couchDatabase;

  25:     }

  26:  

  27:     public string Save<T>(T @object) where T : CouchAggregateRoot

  28:     {

  29:         var couchDocument = new CouchDocumentWrapper<T>(@object);

  30:         CouchDatabase.SaveDocument(couchDocument);

  31:         return couchDocument.Id;

  32:     }

  33:  

  34:     public T Retrieve<T>(string id) where T : CouchAggregateRoot

  35:     {

  36:         var rawDocument = CouchDatabase.GetDocument(id);

  37:         var @object = JsonConvert.DeserializeObject<T>(rawDocument.ToString());

  38:         @object._id = rawDocument.Id;

  39:         @object._rev = rawDocument.Rev;

  40:         return @object;

  41:     }

  42:  

  43:     public void Dispose()

  44:     {

  45:         // Should probably get round to implementing this...

  46:     }

  47: }

They’re obviously not entirely fleshed out, and the Divan project obviously encapsulates more of Couches functionality than those two methods alone.

When you execute the code snippet above (which for the sake of this example was just in a random ASP.NET MVC Controller’s Get method) you’ll see a new database called “myfirstdatabase” created in Futon with your newly created document inside it.

This is obviously just scratching at the surface, but I’ve been exceptionally pleased at how easy it is to get a File –> New Project up and running using readily available tools.

Code in full on GitHub for posterity

The perils of outsourcing software development

Monday, November 29th, 2010

I’m frequently asked for my opinion on outsourcing software projects and components and in the vast majority of cases I always end up explaining my distaste for outsourcing when you work in the software industry. It just doesn’t work very well at all.

People often decide that outsourcing could be a great idea for a specific project because they see an opportunity to bring something to market very quickly and don’t want to disrupt their existing development resource or interrupt on-going project work. Unfortunately, this decision is often based around the fundamental mistruth that “outsourcing, on a sufficiently small scale, is both cheap and quick and as such harmless.”

The train of thought that leads people to outsourcing is often derailed by a quick experiment in discovering what your company is really about and what your company really does. I have one rule:

“Never outsource your core competences or anything that leads to your competitive advantage”

It’s that simple. If you outsource something that gives your business a real competitive advantage, there is absolutely nothing to stop any or all of your competitors doing exactly the same thing. In this scenario, the only people that win are the agencies that produce the outsourced software.

I’ve mentioned this to people in the past who have been surprised by what they were about to do, and just hadn’t identified the product they were about to outsource as a competitive advantage. What this really highlighted to me was how people often struggle to identify what their company is really about and what their core competencies and products really are. Effectively what the “essence” of the company is and what the product really is.

A little experiment that I encourage people to do, is to take what you perceive your “core” product to be and then mentally remove everything else from your business in a “virtual outsourcing” thought experiment. Take what’s left, the nucleus of your product and look at it’s characteristics. Those characteristics are what your company is really about, and like it or not, if you attempt to outsource any of those core characteristics, you are deciding to side-line your own creative advantage.

This experiment is especially good at highlighting when a company’s’ core asset is technology or a technology product, while they perceive themselves to be more of a media company or non-tech company. If your core product is technology, you are a technology company regardless of whether that’s what you set out to be. This often applies to online business’ who fail to grasp that if their product is a website, you can never take the technology away, and at it’s very core, that is the value proposition of their business.

Understanding your core competency is not the only place that outsourcing and software development fall apart, it just illustrates how businesses reach these conclusions in the first place. The original software outsourcing model was a natural evolution of the idea that software development process could be modelled after manufacturing in some sort of glorified “software assets” process where you built a bunch of components that just plugged together to make a system. This idea rode on the back of the general good practice of reuse in software development and the boom in “4GL” programmer-less languages in the 90s. Well, that fad never really worked out, but it took people awhile to realise that the core assumption that software is like manufacturing was fundamentally flawed.

“Software isn’t a manufacturing process, it’s a design process, and as such, should not be outsourced as if it were mass production or duplication”

Outsourcing the manufacture of plastic cups works really well. We all know what a cup looks like, and even if you make a particularly fancy cup, you’ll get your designers to come up with the patterns, you’ll ship your pattern to a manufacturing plant who specialise in duplicating plastics and you’ll ask them to make a few thousand. Which part of producing a few thousand of your plastic fancy cups was the important part? The design that you handed over that made your cup different to the others was the important part, not the duplication. Software works in exactly the same way.

When software was first offshored, there was this fundamental misunderstanding that the development of the software was akin to pressing CDs in a plant, or manufacturing a thousand cups, when actually it was more like when Apple make products “Designed in California”. They’re produced en mass in China, but that’s not the important part, the design is. Treating the design of your software as if it were replication will only ensure low quality of the resulting product.

If you’re attempting to outsource components of a bigger system, you’re also going to face a whole second set of problems all related to trying to make your outsourced components consistent and maintainable in the context of a greater software product. You’re likely to receive software that’s outside of any “house-style” in your code (with different conventions), the quality will be unmonitored and variable and you’ll find it very difficult to prescribe patterns, practices and QA processes. Once you’ve dealt with that collection of known unknowns, you’ll then have to integrate the outsource components into the system with nothing more than a contract in place to ensure that they work. Don’t let that contract fool you though, it’s not security, it just means you’re allowed to get mad when the software doesn’t work. You’re not going to get your wasted time back. Despite the best quotes and claims of third parties, never underestimate the effort of integration. I worked on a particular project which touted a “4 hour” integration process. All things considered, with 3 days of meetings and the trailing two weeks of implementation, it took roughly 300x the quoted length of time.

People are often driven to outsourcing as a way to circumvent resource restriction but all that happens when this is attempted is you fall foul of the traps highlighted by Fred Brooks in The Mythical Man-Month – namely, the communication overhead of coordinating outsourcing and your internal development and the effort that it requires vastly outweighs any perceived benefits of the outsourcing exercise to start with. This is all compounded by the fact that humans are notoriously bad at writing software specifications, and your best attempt at outsourcing is only as good as your best attempt at communicating your ideas in way that the other party thoroughly understands. But as soon as you specify succinctly, you loose any kind of adaptability and end up, in the best case, with the product you thought you needed at the start of the process, rather than the product you know you need by the end because of changing business requirements.

Rework in conjunction with outsourcing is difficult and costly and if you end up down that route, you’re going to want to avoid it at all costs, though, ironically, if the outsourcing venture is successful, you’re likely to want to bring the project in house anyway to curb high maintenance costs and loosing your competitive advantage. Maintaining outsourced software is expensive for a very obvious reason: agency culture dictates that they must charge an appropriate amount to offset their risk of having no work. Bringing outsourced or offshored projects back in house is going to cause you yet more problems, especially if your technology stack doesn’t exactly match that of the outsourcing partner. That great PHP system they wrote? Doesn’t work so well with your ASP.NET software or your Ruby on Rails app. You’re then faced with the horrible position of having to re-write to bring the software back in house, or maintain a whole separate set of systems to support this product, raising it’s total cost of ownership significantly. This might not be a problem if you make hybrid technology choices deliberately, but at this point they’re effectively forced upon you.

So is outsourcing ever a good idea? Well, probably not, all things considered. You’re more likely to get more consistent results by spending the money you’d allocated for that killer new outsourced project training your staff. In the long run it’s better value. It’s also sometimes more prudent to skip what you perceive to be that “killer opportunity” when you don’t have time or resource and revisit it later with a quality product that maintains your own creative vision and competitive advantage. There are a few well established large agencies with a known track record for delivery and professionalism. You’ll find these agencies are more likely to work alongside you and your staff, rather than dropping a boxed product on your lap, and this co-working gives them a distinct advantage over any other form of outsourcing as they are more likely to work well inside of your internal processes.

If you’re considering outsourcing a software project, you should always ensure that you factor the effort required to bring that project back in house as part of the estimate of cost. If you’re considering outsourcing part or all of your core product, you’ve probably already lost.

Pluralization (Pluralisation!) in C#

Monday, November 15th, 2010

So today at work we were spitting some league table-ish (shh! unreleased feature!) data onto a screen and I realised I was going to have to manually pluralise “0 donations”, “1 donation”, “2 donations” depending on the number of donations having been made. I felt a little bit of the crazy eye coming on, because I’ve solved this problem a hundred times before.

By “solved” I mean, “I’ve written a shitty if(..) statement to solve this problem a hundred times before”, which totally sucks.  And I exclaimed “but surely this is a problem everybody that has ever displayed text has faced, it can’t possibly be that we all solve this every day”.

Turns out, that this has pretty much been the case until recently. Rails has Pluralize methods, and most ORMs clearly have this functionality to some extent but I’d never happened across a general purpose .NET library to do the job.  After sitting and expressing my utter disbelieve that this isn’t (at least in it’s simplest form) a solved problem, I actually used some GoogleFu to discover that it was.

So, in .NET4, it looks like Microsoft actually got round to implementing this in the framework, I’d not happened across it before, but it’s pretty freaking cool.

So let me introduce, if I may, the System.Data.Entity.Design.PluralizationServices namespace!

The more observant of you might notice that it appears to be in a namespace with “Entity” in the title. I suspect it was cooked up as part of EF (boo! hiss!) but luckily, you don’t need to actually reference the main System.Data.Entity assembly as it’s been separated out into a satellite assembly.  It also looks like localization is hiding behind one giant NotImplementedException(); currently, but the usage is pretty simple and calmed me down in no time.

var _pluralizationService = PluralizationService.CreateService(Thread.CurrentThread.CurrentCulture);
var plural = _pluralizationService.Pluralize(singular);

and that’s it!

Almost too good to be true, but it actually works, managing “donation”=>”donations”, “sheep”=>”sheep” and as esoterically pointed out on twitter, even “cardex”=>”cardecies”. Which is really pretty cool.

We took it a little further and added support to it in our suite of extension methods for the ASP.NET MVC HtmlHelper class, so in our MVC views we can do this:

<%@ Import Namespace="Extensions" %>
<%=Html.Language().CorrectTenseForQuantity(Model.Quantity, "word") %>

Which I thought was pretty tasty (and at the very least, gives us the same thing the Rails guys have, just with slightly more explicit syntax (my personal preference).

I’ve put our extension implementation up as a Gist on GitHub here if anyone wants to see the full implementation.

Pretty cool.

Serving different views for mobile devices in ASP.NET MVC

Monday, 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[]
               &
#160;                        {
                                            "~/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"
              
60;                       };

            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)
        &#
160;   {
                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

Saturday, 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.

Automatic Html Encoding in ASP.NET 4.0

Tuesday, April 13th, 2010

I spent today at Microsofts Techdays Visual Studio 2010 launch event in London.  Lots of interesting stuff, covered in depth all over the internet (just Google, or bing, if you’re a sadist “changes from .NET 3.5 to .NET 4.0″).  A tiny thing that caught my attention for it’s pure utility to the masses is a new operator in ASP.NET 4.0 that deals with Html Encoding of data implicity.

You’ll likely be familiar (especially if you’re working with ASP.NET MVC) with the <%= notation for referencing properties in the context of the ASP page.  In ASP.NET 4.0 this has been joined by <%: This addition automagically HtmlEncodes any content between the opening and closing tags to prevent repetitive tag soup of <%=HttpUtility.HtmlEncode(myProperty)%> all over your views, and removes the temptation to push HtmlEncoding into your Controller or Model, two places where encoding really shouldn’t be a concern.

Just struck me as a nice little change that’ll make everyday life that little bit easier for a majority of web developers. Sure it’s not quite the new Xaml designer (which is a work of beauty) or Intellitrace (which looked like a total game changer for retrospective debugging), but you know, it’s the little things that count.

What Are APIs Anyway?

Friday, February 19th, 2010

What are APIs anyway?

Everyone’s heard of APIs these days.  Facebook has them, Twitter has them, Hotmail has them, Microsoft Office has them, Windows has them, Mac OS has them, pretty much everything has them.  I’m going to try and explain in simple terms, but also, almost by contradiction, in detail, what an API really is.  I’ll try and explain why SOAP isn’t how you clean yourself, and how being restful isn’t the same as being lazy.

APIs aren’t new, in fact, APIs are really really old.

Let’s start with a really simple definition.  API is an acronym and it stands for “Application Programming Interface”

To steal the current definition from Wikipedia “An application programming interface (API) is an interface implemented by a software program to enable interaction with other software, much in the same way that a user interface facilitates interaction between humans and computers. APIs are implemented by applicationslibraries and operating systems to determine the vocabulary and calling conventions the programmer should employ to use their services. It may include specifications for routinesdata structuresobject classes and protocols used to communicate between the consumer and implementer of the API.”

That’s a mouthful.  So to translate that into English, an API is a pre-defined set of “stuff” that allows one program to “talk” to another program.  This “stuff” is normally a set of “functions” that another program can call which makes the program being called do something.  Sometimes that other program is really just another part of the same program, and sometimes it’s a different system on a different machine in a different country.  That something is normally described in some kind of documentation, written somewhere, by someone.

If this sounds really vague it’s because in the real world, it really is.  Some of the first “APIs” involved dropped text files into directories on a computer that another program would watch for a read from, and subsequently do “something”.  Arguably the most widely used API is the one that we use to write software for Windows (the Win32 API) and by contrast, that’s a C++ library of code that you can only call if you’re a programmer.  In the real world, we’ve tried to solve some of this ambiguity by standardizing the way APIs work around a few common bits of technology; both for our own sanity and to hopefully help software all just kind of work together.

A lot of APIs are code libraries called by other code libraries to do a specific task.  Microsoft released DirectX to deal with 3d graphics, OpenGL offered an open alternative.  Microsoft released the Windows API for Windows development; Apple released Carbon and Cocoa to program Mac OS.  That said when you hear about or discuss APIs casually, what you’re probably thinking about are actually “Web APIs”.

Web APIs

A Web API is an API like anything else, except it’s designed to work over the web.  As a result of this, since about 1994, there have been a number of efforts by a number of people (yes, that vague again!) to standardize the way systems communicate over the internet.

At first, everyone kind of invented their own way of communicating over the internet, people would connect to a server on some port (a port is just a pre-defined “way in”) and send some random pre-defined data in there, and that would make the computer at the other end do something.  Every API was different, and every time that you needed to talk to a different system, you had a really steep learning curve to work out how to talk to every application you wanted to integrate with.  It was a bit rubbish really.

So in 1998 a bunch of really smart people (Dave Winer, Don Box, Bob Atkinson, and Mohsen Al-Ghosein) got together and came up with SOAP (“Simple Object Access Protocol”).  SOAP is a big bunch of XML that was designed (and ratified) as a standard language that every different system could understand. SOAP is often also referred to as “web services”.

Basically it was designed to reduce the learning curve of learning the details of each system.  So now, if Timmy wanted to talk to Peters shiny new web application, he could point his developer tools at a standard location (something called a WSDL (web service description) document) and he could just ask Peters web application what it did, and how he could use it.

SOAP was actually pretty good and solved a lot of problems and is still widely used today.  It really made waves by helping Microsoft software work pretty well with Java software and everyone was happy.

Almost.

See, because SOAP was solving a lot of big problems in freaking huge enterprise systems, it had a lot to be concerned about; lots of security, lots of encryption, lots of authentication.   That meant that the SOAP “language” was pretty big.  As an example, here (stolen from Wikipedia again!) is the SOAP message that would be sent across the internet to get the current stock price of IBM, from some cool stock price giving web service.

POST /InStock HTTP/1.1

Host: www.example.org

Content-Type: application/soap+xml; charset=utf-8

<?xml version=”1.0″?>

<soap:Envelope

xmlns:soap=”http://www.w3.org/2001/12/soap-envelope”

soap:encodingStyle=”http://www.w3.org/2001/12/soap-encoding”>

<soap:Body xmlns:m=”http://www.example.org/stock”>

<m:GetStockPrice>

<m:StockName>IBM</m:StockName>

</m:GetStockPrice>

</soap:Body>

</soap:Envelope>

People soon started thinking “Look at all that crap! What’s it all for! Why do I need it?! There’s lots of overhead here!  It slows me down!” and everyone thought “oh actually, well said” and RESTful web services were born.

REST is yet another stupid acronym that actually doesn’t count as an acronym because it uses random letters but developers think is either cool clever or funny.  What it claims to mean is “REpresentational State Transfer” and was largely both a reaction to the complexity and overheads of SOAP, but also being designed by Roy Fielding (one of the authors of the “Hypertext transfer protocol”, the silly http:// bit you type in a browser) it was conceived as an API model that was “closer to the nature of the web”.

What this means to us lay folk, is that while SOAP can technically be used over other protocols (it’s not bound to http), and as such has to bake in security and authentication models into its protocol, REST is designed specifically for the web, it doesn’t work without the web, and it wouldn’t make any sense without the web.  Fielding basically decided that “the web does all of that stuff anyway”, we have security via https / SSL certificates, we have semantics that describe getting and pushing data in the HTTP headers (HTTP headers explain what you’re trying to do to the server you’re connecting to, for example, you use GET for getting stuff, POST and PUT for doing stuff), so let’s just use that and be done with it.

As a result of this way of thinking, REST is a simplified and less general Web API pattern, not concerned with infrastructure like SOAP is.  I’ll convert the above stock price example into a REST example below.

GET /Stocks/Price/IBM HTTP/1.1

Host: www.example.org

Content-Type: application/xml; charset=utf-8

Done.

Basically, REST takes out all the fluff, an decides that if you want to get a stock price for IBM, just make a GET request to the URL http://www.example.org/Stocks/Price/IBM and let the web server at the other end work out what to do from the URL.

The above example would probably return an XML document that looks like this.

<stocks><stock name=”IBM” price=”3.45”/></stocks>

People have started to gravitate towards REST due to its simplicity.  There’s lots of other stuff a RESTful web service should do, it should provided you with all the information that a computer would need to go through a process, just like a user interface gives the user all the information they need to click through a process, but fundamentally it’s simple and leverages the existing semantics of the web to its advantage.

Ok, Give Me One Of Those!

So now we know what an API is there to do (let two systems talk to each other) and how they do it (as a general rule, either via SOAP or RESTful services) and we know that everyone else has one, let’s get one of our own.

There are plenty of tools available in pretty much any programming language to make making creating APIs pretty easy.  There are plenty of design concerns to take into account when building an API but in my opinion, the way to approach the problem is to work out what your users really want to do with your application, website or platform, and let them do it.

A good API lets another programmer talk to your system using terms that he understands, so take the time building up a glossary of your business terms vs. what the public understands those concepts to be.  Don’t build API methods that look like:

/PaymentResolutionProcess/PaymentResolveTable3/Resolve/EntityId/123

because it means nothing to anyone, instead, build a bunch of methods that make sense for people to use, the above example could look like this instead:

/Payment/MakePayment/123

Watch your language, and build sensible interactions with your system.  Don’t make APIs for stuff that isn’t going to be used, and where possible, just have the APIs call the same code that your website does.

Get this right, and you’re on a gradual but successful road to calling your “website” a “platform”.

C# Access Modifiers Are Type Specific, NOT Instance Specific

Wednesday, February 17th, 2010

Here’s an interesting example from a brief discussion I was having on twitter yesterday with @DotNetWill.

Did you realize that access modifiers in .NET are type specific rather than instance specific. It’s not a weird edge case, it is exactly how the language spec lays it out, but it’s not how most people think access modifiers work. This is because it’s not very often that any given type will have a reference to ANOTHER instance of that type, it just tends to not come up.

Either way, could make for some hilariously difficult debugging if you weren’t aware of it and something was “playing with your privates” by reference.

You might have seen this kind of usage in a singlet*on, constructor or builder class (or as @jagregory pointed out, cloning methods), but generally it’s just not a very common usage example. However, it WILL both compile and execute.

// Example of access modifiers being specific to a type not an instance
public class MyType
{
     private MyType _innerMyType;

     public MyType()
     {
     }

     public void MakeInnerMyType()
     {
          _innerMyType = new MyType();
          _innerMyType._innerMyType = new MyType();
     }
}

Nothing ground breaking, but a fun and interesting little example illustrating a common misconception.

ASP.NET MVC View Engine That Supports View Path Inheritance

Tuesday, January 19th, 2010

I was working on a small MVC project where we were dealing with Inherited controllers (SomeController was inherited by SomeMoreSpecificController) and we decided that it’d be nice to have a similar hierarchy of sharing and inheritance at the View level.

Unfortunately, out of the box, ASP.net MVC looks in two default locations for your views and partials by convention.  The first is /Views/ControllerName/ViewName.aspx, the second is /Views/Shared/ViewName.aspx.  We wanted to allow SomeController to have it’s own set of more generic views, that could later be overridden in special cases by the views provided by SomeMoreSpecificController.

In order to do this in ASP.net MVC, you need to override the default view engine to change the location that the runtime looks for your views.

“Here’s one I made earlier”.

Using this ViewEngine, if you call an action method on SomeMoreSpecificController it’ll first check /Views/SomeMoreSpecificController/…, then /Views/SomeController/… (the base class), then finally /Views/Shared, allowing you a little more control over the organisation of your views.

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

namespace MyMvc.Mvc
{

    public class InheranceViewEngine : 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>();

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

        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 controllerName = controllerContext.RouteData.GetRequiredString("controller");
            string viewPath = GetPath(controllerContext, ViewLocationFormats, viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
            string masterPath = GetPath(controllerContext, MasterLocationFormats, 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);
        }

        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            List<string> strArray;
            if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); }
            if (string.IsNullOrEmpty(partialViewName)) { throw new ArgumentException("Partial View Name is null or empty.", "partialViewName"); }
            string requiredString = controllerContext.RouteData.GetRequiredString("controller");
            string str2 = GetPath(controllerContext, PartialViewLocationFormats, partialViewName, requiredString, "Partial", useCache, out strArray);
            if (string.IsNullOrEmpty(str2))
            {
                return new ViewEngineResult(strArray);
            }

            return new ViewEngineResult(CreatePartialView(controllerContext, str2), this);
        }

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

            bool nameRepresentsPath = IsSpecificPath(name);
            string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName);

            if (useCache)
            {
                string result = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);

                if (result != null)
                {
                    return result;
                }
            }

            if (nameRepresentsPath)
            {
                return GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations);
            }
            return GetPathFromGeneralName(controllerContext, locations, name, controllerName, cacheKey, ref searchedLocations);
        }

        private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string cacheKey, ref List<string> searchedLocations)
        {
            string result = String.Empty;
            searchedLocations = new List<string>();

            for (int i = 0; i < locations.Length; i++)
            {
                string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName);
                if (FileExists(controllerContext, virtualPath))
                {
                    searchedLocations = EmptyLocations;
                    result = virtualPath;
                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
                    return result;
                }
                searchedLocations.Add(virtualPath);
            }

            return GetPathFromGeneralNameOfBaseTypes(controllerContext.Controller.GetType(), locations, name, controllerContext, cacheKey, result, ref searchedLocations);
        }

        private string GetPathFromGeneralNameOfBaseTypes(Type descendantType, string[] locations, string name, ControllerContext controllerContext, string cacheKey, string result, ref List<string> searchedLocations)
        {
            Type baseControllerType = descendantType;
            if (baseControllerType == null
                || !baseControllerType.Name.Contains("Controller")
                || baseControllerType.Name == "Controller")
            {
                return result;
            }

            for (int i = 0;i < locations.Length;i++)
            {
                string baseControllerName = baseControllerType.Name.Replace("Controller", "");
                string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, baseControllerName);

                if (!string.IsNullOrEmpty(virtualPath) &&
                    FileExists(controllerContext, virtualPath))
                {
                    searchedLocations = EmptyLocations;
                    result = virtualPath;
                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
                    return result;
                }

                searchedLocations.Add(virtualPath);
            }

            return GetPathFromGeneralNameOfBaseTypes(baseControllerType.BaseType, locations, name, controllerContext,
                                                     cacheKey, result, ref searchedLocations);
        }

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

        private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref List<string> searchedLocations)
        {
            string result = name;

            if (!FileExists(controllerContext, name))
            {
                result = String.Empty;
                searchedLocations = new List<string>{ name };
            }

            ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
            return result;
        }

        private static bool IsSpecificPath(string name)
        {
            char c = name[0];
            return (c == ‘~’ || c == ‘/’);
        }

    }
}