Saturday, May 8, 2010

Git on Virtual ALT.NET Hispano

Last month I did a VAN on Git for the ALT.NET Hispano (Spanish-speaking) community.

Tired of always seeing the same tutorials using the command-line, I decided to mostly use git-gui and gitk instead, making it look less daunting and more visually appealing. I also focused mostly on Git-SVN, even including a demonstration with an actual Google Code SVN repository. Most people use SVN at their dayjobs so explaining git-svn serves a double purpose: it gives them something they can use immediately, and it acts as a gateway drug for the real DVCS.

Then we discussed workflows, which IMHO is the biggest benefit of DVCS and also the most different feature from centralized version control.

I know I never post any content in Spanish, well, this one's going to be an exception ;-)
Here's the recording of the session:

Thanks a lot to Jorge Gamba and the whole ALT.NET Hispano community!

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

    fsharp-webdev

    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
    
    [<EntryPoint>]
    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:

    fsharp-webdev2

    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.

Saturday, April 24, 2010

NHWebConsole 0.1 released

I just released the embeddable NHibernate console for web applications I wrote some months ago. You can get the binary here, it also includes a sample application so you can play with it and see how it's configured. The only requirements for NHWebConsole are .NET 3.5 and NHibernate 2.1.2.

It hasn't changed much since I first wrote about it. I fixed a couple of bugs and added HQL Intellisense thanks to Fatica Labs' wonderful HQL Editor. It's not overly pretty but it mostly works. Here's a screenshot:

Source code and documentation is on github.

Let me know if you use it and/or if you find any bugs!

Wednesday, March 24, 2010

Fiddler output for ELMAH

Fiddler is a great web debugger for web developers of any platform. ELMAH is great for error logging in ASP.NET apps. Both are practically must-have tools for any ASP.NET developer. So how about combining them to debug ASP.NET errors more easily?

Here's a module that sends a SAZ file attached to all ELMAH mails. If you're not familiar with Fiddler, SAZ stands for Session Archive Zip, it's basically a ZIP file containing raw HTTP request/responses. After installing this module, a sample ELMAH mail might look like this:

elmah-mail

See the last attachment? It's our SAZ file, click on it to open it with Fiddler:

fiddler-password

The SAZ is password-protected since the HTTP form might have sensitive information. Enter the password and you can see the request:

fiddler-1

Now you can edit the request from Fiddler, change the host to your local instance of the website and then replay the request to reproduce the error:

fiddler-2

Configuration is very easy: just register the ElmahMailSAZModule after ELMAH's ErrorMailModule. You can optionally supply a configuration, e.g.:

<configuration>
    <configSections>
        <sectionGroup name="elmah">
            <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
            <section name="errorMailSAZ" requirePermission="false" type="ElmahFiddler.ElmahMailSAZModule, ElmahFiddler"/>
        </sectionGroup>
    </configSections>
    <elmah>
    <errorMailSAZ>
      <password>bla</password>
      <exclude>
        <url>default</url>
        <url>blabla</url>
      </exclude>
    </errorMailSAZ>
    <errorMail
      from="pepe@gmail.com"
      to="pepe@gmail.com"
      subject="ERROR From Elmah:"
      async="false"
      smtpPort="587"
      useSsl="true"
      smtpServer="smtp.gmail.com"
      userName="pepe@gmail.com"
      password="pepe" />
  </elmah>
...

This will apply the password "bla" to the SAZ files, and NOT create any SAZ for any requests that match (regex) "default" or "blabla". The latter is useful to prevent potentially huge SAZ files coming from requests with file uploads.

Caveats:

  • Requires async="false" on the mail module, since it needs access to the current HttpContext.
  • Does not include the HTTP response. This could be implemented using Response.Filter, but I'm not sure it's worth it.

I'm also playing with the idea of keeping a trace of all the requests in a user session, in order to reproduce more complex scenarios (SAZ files can accomodate multiple requests). This would place a considerable load on the server though, and the resulting SAZ file could get quite big.

Source code is here. It's a VS2010 / .NET 4.0 solution.

Kudos to Eric Lawrence for recently implementing SAZ support in FiddlerCore, without it this wouldn't be possible.

Friday, March 19, 2010

Proxying and parallelizing processes

Some code just flat out refuses to run multi-threaded. Like GeckoFX. It's a great project, and I found it to be much more reliable than WebBrowser (aka IE), but it just won't run multi-threaded (or at least me and several other people haven't figured out how)

I had to write some CPU-intensive, non-interactive code involving GeckoFX, so parallelization was a must. Well, when multi-threading won't fly, multi-processing (as in launching code on a separate process instead of a separate thread) can be a viable alternative. This does complicate RPC a bit but we can tuck this under a proxy that serializes parameters and then gets the return value through a named pipe:

public class ProcessInterceptor : IInterceptor {
    ... 

    public void Intercept(IInvocation invocation) {
        var pipename = Guid.NewGuid().ToString();
        var procArgs = new List<string> {
            Quote(invocation.TargetType.AssemblyQualifiedName),
            Quote(invocation.MethodInvocationTarget.Name),
            pipename,
        };
        procArgs.AddRange(invocation.Arguments.Select(a => Serialize(a)));
        var proc = new Process {
            StartInfo = {
                FileName = "runner.exe",
                Arguments = String.Join(" ", procArgs.ToArray()),
                UseShellExecute = false,
                CreateNoWindow = true,
            }
        };
        using (var pipe = new NamedPipeServerStream(pipename, PipeDirection.In)) {
            proc.Start();
            pipe.WaitForConnection();
            var r = bf.Deserialize(pipe);
            r = r.GetType().GetProperty("Value").GetValue(r, null);
            proc.WaitForExit();
            if (proc.ExitCode == 0) {
                invocation.ReturnValue = r;
            } else {
                var ex = (Exception) r;
                throw new Exception("Error in external process", ex);
            }
        }
    }
}

And that "runner.exe" thing is the host, just a console app that is responsible for deserializing parameters, calling the method, managing exceptions and then send back the return value (if any):

public class Runner { 
    ... 
    public static int Main(string[] args) { 
        var pipename = args[2]; 
        using (var pipe = new NamedPipeClientStream(".", pipename, PipeDirection.Out)) { 
            pipe.Connect(); 
            try { 
                var type = Type.GetType(args[0]); 
                var method = type.GetMethod(args[1]); 
                var instance = Activator.CreateInstance(type); 
                var parameters = args.Skip(3).Select(p => lf.Deserialize(p)).ToArray(); 
                var returnValue = method.Invoke(instance, parameters); 
                bf.Serialize(pipe, new Result { Value = returnValue }); 
                return 0; 
            } catch (Exception e) { 
                bf.Serialize(pipe, new Result { Value = e }); 
                return 1; 
            } 
        } 
    } 
}

And now we can parallelize. Here's a silly example (can't post the actual GeckoFX process, it's proprietary stuff):

public class TargetCode { 
    public virtual int Add(int a, int b) { 
        return a + b; 
    } 
}

[Test] 
public void Parallel() { 
    var generator = new ProxyGenerator(); 
    var t = generator.CreateClassProxy<TargetCode>(new ProcessInterceptor()); 
    var r = Enumerable.Range(0, 100).AsParallel().Sum(i => t.Add(i, i)); 
    Assert.AreEqual(9900, r); 
}

This will launch a separate process for each iteration. On a dual-core CPU, the Task Parallel Library will launch by default at most two threads to run in parallel, so you would have at most two runner.exe instances running at the same time, thus achieving multi-process parallelism.

Now this is not a general solution, it worked for my specific usecase but it has several caveats:

  • Doesn't support generic or overloaded methods (it shouldn't be hard to implement)
  • Target code must be interceptable (virtual, non-sealed, etc)
  • Target code must have parameterless constructor (it shouldn't be hard to lift this restriction)
  • Method parameters are passed through command-line so they can't be very long. (it shouldn't be hard to lift this restriction)
  • Target code should be practically stand-alone since the host won't have the same app.config as it parent, nor any other previous initialization, etc.
  • The target code should be sufficiently long-running to justify the overhead of proxying, reflection, serialization and process launching.

Full code is here.