Monday, April 26, 2010

VS launcher for F# web apps

Sadly, Visual Studio 2010 does not include any F# web application project type.

In practice, this means that if you want to use F# in a web project (be it MVC or WebForms), you have to start with a basic "F# library" project, then manually create a web.config or copy it from somewhere else, manually create a global.asax, global.asax.fs, etc. Or you can let the main web project (i.e. the one with the Global.asax) be a normal C# web app project and reference a F# library where the controllers/webforms are defined.

Another minor annoyance is that you lose the F5 functionality to launch the application, since Visual Studio doesn't know it's a web application. A couple of workarounds for this:

  1. Reference the VS built-in dev web server as the starting external program. This is usually in C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE


    The downside of this is that it doesn't automatically launch your default web browser to your app. It might sound somewhat silly, but when you're used to getting a browser immediately, automatically, it's kind of annoying not having it.

  2. Write a little wrapper around WebServer40.exe that launches a browser. Here's the code:
    open System
    open System.IO
    open System.Diagnostics
    open System.Reflection
    let main args =
        // code adapted from FSharp.PowerPack's AspNetTester
        let progfile = 
            let prg = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
            if Environment.Is64BitProcess
                then prg + " (x86)"
                else prg
        let webserver = Path.Combine(progfile, @"Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE")
        if not (File.Exists webserver)
            then failwith "No ASP.NET dev web server found."
        let getArg arg = args |> Seq.tryFind (fun a -> a.ToUpperInvariant().StartsWith arg)
        let webSitePath = 
            match getArg "PATH:" with
            | None -> Directory.GetParent(Directory.GetCurrentDirectory()).FullName
            | Some a -> a.Substring 5
        let port = 
            match getArg "PORT:" with
            | None -> Random().Next(10000, 65535)
            | Some a -> Convert.ToInt32 (a.Substring 5)
        let vpath =
            match getArg "VPATH:" with
            | None -> ""
            | Some a -> a.Substring 6
        let pathArg = sprintf "/path:%s" webSitePath
        let portArg = sprintf "/port:%d" port
        let asm = Assembly.LoadFile webserver
        let run (args: string[]) = asm.EntryPoint.Invoke(null, [| args |]) :?> int
        Process.Start (sprintf "http://localhost:%d%s" port vpath) |> ignore
        run [| pathArg; portArg |]
    Place the exe (I called it WebStarter.exe) in your web app root, then put it as starting external program:


    You can optionally define a fixed port (by default a random port is used), a different root path or a virtual path to start the browser. Set your F# web app project as "Startup Project", hit F5 and voilĂ , browser launches with the debugger hooked up :)

  3. UPDATE: Steve Gilham has another solution, you can just add a couple of elements to the fsproj to turn it into a web app project.
  4. UPDATE: Tomas Petricek created a MVC project template.


Matthew Sullivan said...

You should just create an F# web app template and share it via the gallery or zip it here?

Not sure if what you did with the exe would exactly simply save to a template but I'm sure it could be worked around.

I found the f# templates pretty sparse. But it's pleasantly simple to just save various initial project settings as new templates.

mausch said...

@Matthew: I agree that a project template would be the right way to do this, but honestly I have no idea about project templates and I'm not interested in learning about them right now. Please let me know if you do create such a template though :)

Steve said...

Having opened up a C# web project file, I spotted this line in the first <PropertyGroup>


Having managed in the past to turn vanilla C# libraries into workflow projects by transplanting similar Guid tags like this, I suspect putting this in your .fsproj file might make Visual Studio treat that as a proper Web project.

Steve said...

...continued: There's also a large <ProjectExtensions> (probably a bit big to transcribe here) at the end of the C# file controlling web server settings, which you'd probably also need to transplant.

mausch said...

@Steve: Tried that, didn't work.

Steve said...

My bad for shooting from the hip -- the recipe as given doesn't quite work, because I misunderstood the meaning of the project guids. Now I've had a few minutes try try it, I have it working for real.

In the guids clause, GUID fae04ec0-301f-11d3-bf4b-00c04f79efbc means a C# project -- so setting that means your F# project doesn't load -- fails with a "project of the same name already loaded".

Using F2A71F9B-5D33-465A-A702-920D77279786 (which means an F# project as you can see in the .sln file) and the web project GUID 349c5851-65df-11da-9384-00065b846f21 in a ProjectTypeGuid element; adding all the extra references, and copying in the extra <ProjectExtensions> clause from a C# web project gives me an F# project which launches an entirely trivial default.aspx for me in VS2010.

mausch said...

@Steve: Nice!! I'll give it a try, thanks!