Why I won't be using Microsoft.Bcl.Immutable package (despite much anticipation)

Looking at the newly released ImmutableCollection package, I see some confusing restrictions in it's licensing agreement (especially for something that must be redistributed with your application)

You may not ... work around any technical limitations in the software;

Does this mean it's illegal to use reflection to get at private bits of the library?

You may not ... use the software for commercial software hosting services.

This sounds quite scary. Does this mean I couldn't make a commercial website or SAS product using this library?

You may not ... modify or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. An Excluded License is one that requires, as a condition of use, modification or distribution, that the code be disclosed or distributed in source code form; or others have the right to modify it.

Does this mean I can't use GPL licensed code with this library?

You may not ... distribute Distributable Code to run on a platform other than the Windows platform;

This looks rather obvious, but does this mean I couldn't make a website that ran on Mono and used this library, or does it only mean that I couldn't make something using Mono and then package the program and this library in a Linux debian package or something else? (ie, distribute, not just run)

This license worries me greatly about my ability to use this (much anticipated) library. These questions probably require a lawyer to fully resolve, and I'm not going to buy the time to ask one.. so, I just won't be using this library, as cool as it looks and as much as this has been anticipated for me.

Unless you have a legal team you can ask these questions, I wouldn't use this library either until Microsoft changes the license to something less hostile to developers

Also, if you want a version of this with a sane license, see the up and coming MIT-Licensed Project

Posted: 9/26/2013 4:18:27 PM

CacheGen Proof Of Concept

So, I finally have CacheGen to where I can probably integrate it into this website. I did some rough concurrency testing (spawning 60 threads accessing the cache with random clearing). It's a rough test, but it does show that there isn't anything obviously wrong with it at least.

So, the code it generates is brilliantly simple as well. Some good use cases for this:

  • Keep all your cache settings in one place
  • Statically typed and named! No more remembering manual casts or magic strings
  • Make your caching logic testable! It generates code against an easily mockable interface
  • Switch out your caching layer with ease.

Now, I'm only going to elaborate on the last point. "Why would I ever want to change out my caching layer!?"

Here's why. You built Bookface 1.0 and a few dozen users are on it. People start talking though and suddenly you have a few thousand(or more). You page response times have crept up into the seconds range. Something must be done. After upgrading servers, and expanding some of the hardware, you find the bottleneck. Your web server's caches are being cleared too often. There isn't anything you can do though, the memory is maxed out as it is. So, obvious choice: Use something like memcached for distributed caching on a dedicated server or two.

What's makes using memcached or something so hard? It requires code changes! Luckily for you, you used CacheGen though. Why? All of your caching is in one place, and your interface to the caching method(CacheMechanism) is in one single simple class. It's trivial to implement a two-level cache between ASP.Net and memcached at this point and all of your code relying on your cache will just magically work without being changed.

This is what I think makes CacheGen especially awesome. It manages your caching settings, makes everything statically typed, AND lets you have an almost unreal amount of flexibility.

It's not quite ready for primetime yet. I've proved that it should work, the thing now to do is clean up the API some and add some more unit testing to see if I can catch more bugs.

Anyway, I don't expect this process to take too long. I plan to tag an alpha release for this relatively soon (within the month)

Posted: 12/8/2012 7:21:17 AM

Making Caching Awesome Again

So, I've recently began a new project. It's no where near stable, or even usable. But I think it's a good enough time to introduce it.

It's called CacheGen and it's hosted at bitbucket and BSD licensed.

Basically, it's a T4 template that generates really awesome helpers for caching. It's geared at ASP.Net, but my goal is to make it so it can be used in other ways as well. So, how does it work? You give it a list of specifications for something you want to cache. Let's say you have a string that's really expensive to generate, but doesn't belong in a database. To go on my theme of markdown being slow, let's call it MarkdownTranslation

You tell the T4 template to create a cache item for it like this:

var tmp=new CacheObject
{
  Name="MarkdownTransform",
  ValueType="string";
}
var cache=new CacheGen("Earlz.Example.MyCache");
cache.Items.Add(tmp);
Write(cache.ToString();

And then, you can use the MyCache class like this:

var text=MyCache.MarkdownTransform ?? (MyCache.MarkdownTransform=Markdown.Translate(foobar));

Much much easier. But, there is a flaw in this. It's possible that we could consume double the server resources required for this. This wouldn't be a problem in this case, but imagine a very heavy SQL query or something. This isn't quite what we want when things are really expensive. So, let's use some other fancy syntax

var text=MyCache.MarkdownTransformCache.LockGetOrLoad(()=>Markdown.Translate(foobar));

What this will do instead is raise a flag so that requests for MarkdownTransform will block and during this time, it will execute the expensive code and other threads will sleep. This way, it only gets executed once. And, when it finally gets the results back, the cache will be loaded with it and the other threads will be able to access it. So, instead of computing the expensive thing multiple times, instead we just hold out other requests for a little while so the expensive thing is done only once.

Sure, you can do this same thing with ASP.Net's cache, but how much code would it require for each time you did this? Hence why it's T4. Also, it's completely statically typed. No casting!

Now, what if you wanted to cache the markdown for multiple posts? Or just want to cache entire post objects? Well, I thought of this too:

You'd make the CacheObject like this in T4:

Name="Transforms";
KeyType="ObjectId";
ValueType="BlogPostData";

Then, you could apply the same lock-first type behavior like so:

var post=MyCache.TransformsCache.LockGetOrLoad(objectid, ()=>LoadPost());

Or, if you prefer the (probably safer) possibly execute twice behavior:

var post=MyCache.Transforms[objectid] ?? (MyCache.Transforms[objectid] = LoadPost());

These are the ideas anyway. None of it is actually implemented yet, and a lot more research must be done to ensure that this is a sane way to go about it. But, you can expect to get something similar to this.

Posted: 12/6/2012 5:17:43 AM

MarkdownSharp is freaking slow!

So, I use MarkdownSharp on this website for translating my posts from markdown to HTML. During a profiling session, I found that MarkdownSharp is horribly HORRIBLY slow. It was the bottle neck of my entire website. On my personal machine, requests per second went from 325 (or ~22ms per request) to 720 (or ~9ms per request) when I cached the markdown translated text.

Personally, I find it pitiful that Markdown was more of a bottleneck than my database. Getting of my post data from my database: ~5 ms. Translating all of the text in the posts to HTML: ~15ms. What the hell is wrong with this!?

I'm sure Markdown isn't trivial to translate of course, and mono generally sucks at string operations anyway. But still, I find this absolutely ridiculous that the only thing so far I've had to cache is markdown translations.

Anyway, this website is updated now and should be capable of handling about 100 requests per second. I suspect the primary reason I can't achieve anything higher than that is a combination of Apache having a lot of overhead and my VPS being rather weak anyway. But, this should be plenty for this low traffic blog.

Posted: 12/3/2012 4:36:54 AM

How to unit test T4 code generators

So, you're like me and have a greater than 500 line T4 template that is a steaming pile of... code. And of course, no syntax highlighting without addons, no intellisense, generally horrible Visual Studio support, and near impossible to unit test.

Well, my friends, I have just the thing for you! After beating my head against a wall for several days, I've found salvation!

To use my method requires some "neat" code, and it requires a few assumptions:

  • You're using T4 to generate C# code
  • You're then taking this C# code and including it in your project
  • You're ready to do some major refactoring for an awesome experience in the end (and your T4 template will be so much cleaner afterwards!)
  • You can have at least 2 tiles tied to T4 (one T4 template and one include file)

The corner stone of making T4 testable is to separate out the "logic" and the "content"; where the content is the generated C# code, and the logic is what you do to generate it.

To do this and enforce this separation cleanly, you must have two files. One of these files is the T4 "view", and another file is the logic, which is capable of being normally compiled outside of T4.

Example Untestable T4 Code

Lets start with a simple example. You have a simple T4 template which takes a file like so:

foo=bar
biz=baz

and turns it into

public class GeneratedClass
{
  public foo="bar";
  public biz="baz";
}

Here is a simple(and untestable) T4 template for it.:

<#@ template language="C#v3.5" hostspecific="true"#>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>

using System;
namespace Earlz.SampleT4
{
    public class GeneratedClass
    {
<#
    string filename="fields.txt";

    string path=Path.GetDirectoryName(Host.TemplateFile);
    var f=File.OpenText(Path.Combine(path, filename));
    string text=f.ReadToEnd();
    text=text.Replace("\r", ""); //strip extra line endings (if needed)
    var lines=text.Split('\n');
    foreach(var line in lines)
    {
        var parts=line.Split('='); //split for each element
#>
        public string <#= parts[0] #> = @"<#= parts[1].Replace("\"", "\"\"") #>";
<#
    }
#>
    }
}

There are a few rumored methods of testing this T4 file:

  • Run an integration test manually comparing the code (very very brittle)
  • I can't think of anything else worth mentioning

The Great Refactor

Now, What I suggest:

Let's refactor this and make it so we can eliminate some logic out of our "view". In this case, the view should worry about getting the code into the generated file and that's it. The logic on the other hand should work at a level of abstraction.

Here's what we're going to do:

  • Eliminate most of this code from the view
  • Create a new file for the logic
  • Use a very clever trick so that our logic file will compile outside of T4 and work when included in T4
  • Make it so that instead of just outputting text, we're building an object model that happens to be easily translatable to text

Because I came prepared, I already have the object model abstraction built. you can catch it at the bottom of the last code snippet. (It's easy to rip out and put elsewhere)

Compiling Code In and Out of T4

So, we create a new T4 logic file and name it something like "GenerateClassLogic.tt.cs". Now, you may be thinking "but you can't use the same code in T4 and a regular project!" *WRONG!

I came across a very nifty trick. Behold:

//<#+
/*That line above is very carefully constructed to be awesome and make it so this works!*/
#if NOT_IN_T4
//Apparently T4 places classes into another class, making namespaces impossible
namespace MyNamespace.Foo.Bar
{
    using System;
    using System.Linq;
    using System.Text;
    using System.Collections.Generic;
    using System.IO;
#endif
//regular ol' C# classes and code...

#if NOT_IN_T4
} //end the namespace
#endif
//#>

The key parts are the first and last lines. These begin with a comment so that the C# compiler will ignore them outside of T4, but inside of T4 these instruct the transformer to include this as a "class feature" (which ends up being a nested class)

Note here also that you must define a compile symbol. This is super easy to do. If you add it to your project, then it won't carry over to the T4 template though, making this an easy way to add in a few key things that can't be done without knowing if we're executing within T4 or not.

So, now we have a T4 logic file that will compile inside and outside of T4. Perfect for unit testing! All we need now is some logic!

The Result

Here is what I came up with:

//<#+
/*That line above is very carefully constructed to be awesome and make it so this works!*/
#if NOT_IN_T4
//Apparently T4 places classes into another class, making namespaces impossible
namespace Earlz.SampleT4.Internal
{
    using System;
    using System.Linq;
    using System.Text;
    using System.Collections.Generic;
    using System.IO;
#endif
    //regular ol' C# classes and code...

    public class GenerateClassFromText : ClassGenerator
    {
        public GenerateClassFromText(string text)
        {
            Init(text);
        }
        public GenerateClassFromText(string templatefile, string filename)
        {
            string path=Path.GetDirectoryName(templatefile);
            var f=File.OpenText(Path.Combine(path, filename));
            Init(f.ReadToEnd());
        }
        public void Init(string text)
        {
            text=text.Replace("\r", ""); //strip extra line endings (if needed)
            var lines=text.Split('\n');
            foreach(var line in lines)
            {
                var parts=line.Split('='); //split for each element
                var field=new Field
                {
                    Accessibility="public",
                    Name=parts[0],
                    Type="string",
                    InitialValue=string.Format("@\"{0}\"",parts[1].Replace("\"", "\"\""))
                };
                Fields.Add(field);
            }
        }

    }

    //shove this all into one file so we don't force implementers to hand combine this or copy over more than 2 files
    public class ClassGenerator : CodeElement
    {
        virtual public List<Property> Properties
        {
            get;
            private set;
        }
        virtual public List<Method> Methods
        {
            get;
            private set;
        }
        virtual public List<Field> Fields
        {
            get;
            private set;
        }
        virtual public string Namespace
        {
            get;set;
        }
        virtual public string OtherCode
        {
            get;set;
        }
        public virtual string BaseClass
        {
            get;set;
        }
        public ClassGenerator()
        {
            Properties=new List<Property>();
            Methods=new List<Method>();
            Fields=new List<Field>();
            Accessibility="";
        }
        public override string ToString ()
        {
            StringBuilder sb=new StringBuilder();
            sb.Append("namespace "+Namespace);
            sb.AppendLine("{");
            sb.AppendLine(PrefixDocs);
            sb.Append(GetTab(1)+Accessibility+" class "+Name);
            if(string.IsNullOrEmpty(BaseClass))
            {
                sb.AppendLine();
            }
            else
            {
                sb.AppendLine(": "+BaseClass);
            }
            sb.AppendLine(GetTab(1)+"{");
            foreach(var p in Properties)
            {
                sb.AppendLine(p.ToString());
            }
            foreach(var m in Methods)
            {
                sb.AppendLine(m.ToString());
            }
            foreach(var f in Fields)
            {
                sb.AppendLine(f.ToString());
            }
            sb.AppendLine(OtherCode);
            sb.AppendLine(GetTab(1)+"}");
            sb.AppendLine("}");
            return sb.ToString();
        }

    }
    abstract public class CodeElement
    {
        public const string Tab="    ";
        public string Name
        {
            get;
            set;
        }
        public string Accessibility
        {
            get;
            set;
        }
        string prefixdocs;
        virtual public string PrefixDocs
        {
            get
            {
                return prefixdocs;
            }
            set
            {
                prefixdocs=GetTab(2)+"///<summary>\n"+GetTab(2)+"///"+value+"\n"+GetTab(2)+"///</summary>";
            }
        }
        public override string ToString ()
        {
            throw new NotImplementedException();
        }
        public static string GetTab(int nest)
        {
            string tmp="";
            for(int i=0;i<nest;i++)
            {
                tmp+=Tab;
            }
            return tmp;
        }
        protected CodeElement()
        {
            Accessibility="";
            PrefixDocs="";
        }
    }
    public class Property : CodeElement
    {
        public string Type
        {
            get;set;
        }
        public string GetMethod
        {
            get;
            set;
        }
        public string SetMethod
        {
            get;
            set;
        }
        public override string ToString ()
        {
            string tmp=GetTab(2)+PrefixDocs+"\n";
            tmp+=GetTab(2)+CodeElement.Tab+Accessibility+" "+Type+" "+Name+"{\n";
            if(GetMethod!=null)
            {
                tmp+=GetTab(2)+GetMethod+"\n";
            }
            if(SetMethod!=null)
            {
                tmp+=GetTab(2)+SetMethod+"\n";
            }
            tmp+=GetTab(2)+"}\n";
            return tmp;
        }
        public Property()
        {
            GetMethod="get;";
            SetMethod="set;";
        }
    }
    public class Field : CodeElement
    {
        public string Type
        {
            get;
            set;
        }
        public string InitialValue
        {
            get;
            set;
        }
        public override string ToString ()
        {
            string tmp=GetTab(2)+PrefixDocs+"\n";
            tmp+=GetTab(2)+Accessibility+" " +Type+" " +Name;
            if(InitialValue!=null)
            {
                tmp+="="+InitialValue+";";
            }else{
                tmp+=";";
            }
            return tmp;
        }
    }
    public class Method : CodeElement
    {
        public string ReturnType
        {
            get;
            set;
        }
        public List<MethodParam> Params
        {
            get;set;
        }
        public string Body
        {
            get;set;
        }
        public Method()
        {
            Params=new List<MethodParam>();
            Body="";
            ReturnType="void";
        }
        public override string ToString ()
        {
            string tmp=GetTab(2)+PrefixDocs+"\n";
            tmp=GetTab(2)+Accessibility+" "+ReturnType+" "+Name+"(";
            for(int i=0;i<Params.Count;i++)
            {
                tmp+=Params[i].ToString();
                if(i==Params.Count-1)
                {
                    tmp+=")";
                }
                else
                {
                    tmp+=", ";
                }
            }
            if(Params.Count==0)
            {
                tmp+=")";
            }
            tmp+="\n"+GetTab(2)+"{\n";
            tmp+=Body;
            tmp+="\n"+GetTab(2)+"}";
            return tmp;
        }
    }
    public class MethodParam
    {
        public string Name{get;set;}
        public string Type{get;set;}
        public override string ToString ()
        {
            return Type+" "+Name;
        }
    }
#if NOT_IN_T4
} //end the namespace
#endif
//#>

Wow, so much cleaner! Plus, go to modify it in Visual Studio. What's that? Theirs actually intellisense!? Yes! There is! It will also throw compiler errors when you screw stuff up.

Also, I trimmed the T4 view down significantly as well and of course put in an include statement for our logic file:

<#@ template language="C#v3.5" hostspecific="true"#>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>

<#
    string filename="fields.txt";
    var gen=new GenerateClassFromText(Host.TemplateFile, filename);
    gen.Namespace="Earlz.SampleT4";
    gen.Name="GeneratedClass";
    gen.Accessibility="public";
#>

<#= gen.ToString() #>

<#@ include file="GenerateClassLogic.tt.cs" #>

Wow that's simple!

Now note: None of the stuff in your "view" is testable. You should always keep that in mind and keep it as simple as humanely possible.

If you'll notice, other than a bit of boilerplate XML documentation, the generated code is exactly the same. This is intentional :)

Unit Testing T4

What were we trying to do again? Ah yes. Tests. Let's add some NUnit tests for this!

And so, by simply adding a reference to our project in the NUnit test project, we instantly get access to the logic of the code generator. Here is the quick testing I did:

[TestFixture]
public class CodeGeneratorTests
{
    string TestText=
@"Foo=Bar
Biz=Baz
TestQuotes=""foo bar""";

    [Test]
    public void EnsureFieldWritten()
    {

        var gen=new GenerateClassFromText(TestText);
        Assert.IsTrue(gen.Fields.Any(x=>x.Name=="Foo"));
        Assert.IsTrue(gen.Fields.Any(x=>x.Name=="Biz"));
    }
    [Test]
    public void EnsureFieldsPublic()
    {
        var gen=new GenerateClassFromText(TestText);
        var tmp=gen.Fields.Single(x=>x.Name=="Foo");
        Assert.AreEqual(tmp.Accessibility, "public");
    }
    [Test]
    public void EnsureFieldsQuoted()
    {
        var gen=new GenerateClassFromText(TestText);
        var tmp=gen.Fields.Single(x=>x.Name=="TestQuotes");
        Assert.AreEqual(tmp.InitialValue, @"@""""""foo bar""""""");
    }
}

Conclusion and Remarks

So, in conclusion, we've learned that T4 is actually capable of taming. (when I first learned T4, I wouldn't have thought it possible either) The main thing to do is maintain a separation of "content" and logic. Now of course, there are some gotchas to watch for:

  • This only works when your T4 generator's target is to generate C# code
  • When you add a reference to something, you must add it to both the logic file and the view
  • It's still very difficult to reference external assemblies(particularly project assemblies). This doesn't solve that problem at all
  • You must define a compiler symbol for your project if you wish to run unit tests against it.
  • When other people use your T4 template outside of your project(and thus don't need to test it), they must ensure that the logic file is not compiled outside of the T4
  • Unit testing that a piece of code is generated is very difficult and brittle(hence the need for abstractions where possible to make this easier)
  • My abstraction mechanisms for building objects aren't really that good. They're good enough for me, but please someone improve them! (if you do it I'll link to you from here)

Happy code generation!

Posted: 11/21/2012 3:46:10 AM

Get REAL memory usage for a type in C#

So far, the using direct IL is very seldom required with how powerful C# is. However, I found a good need: C#'s sizeof operator sucks!

So, I wrote a tiny library to use the sizeof IL instruction.

Differences:

  • Doesn't throw an error on reference types(returns size of pointer for platform)
  • Accurately gets the size of an object IN MEMORY, not just the used size of the type.
  • Has no problems dealing with mixed structs containing references and fields
  • Works exactly how you would expect it to and will work on any type

Also, the IL is extremely simple, boiling down to 2 actual instructions sizeof and ret. It also is verifiable and runs on Mono and .Net, and of course works on any platform (probably including Mono For Android and friends and compact framework?)

It's BSD licensed and here is some example code showing how to use it:

using System;

using Earlz.BareMetal;
namespace Testfoo
{
  class MainClass
  {
    public static void Main (string[] args)
    {
      Console.WriteLine(BareMetal.SizeOf<int>());
      Console.WriteLine(BareMetal.SizeOf<string>());
      Console.WriteLine(BareMetal.SizeOf<Foo>());
      Console.WriteLine(BareMetal.SizeOf<Bar>());
      Console.ReadKey();
    }
  }
  struct Foo
  {
    int a, b;
    byte c;
    object foo;
  }
  class Bar
  {
    string foo;
    string bar;
    AssemblyLoadEventArgs meh;
    DateTime d;
  }
}

You can get the precompiled library (and see the source) at bitbucket

Posted: 11/14/2012 3:17:15 AM

Next project: TranslationMatrix

Yes, I completely stole that name from Dr. Who. But, it's very fitting.

Basically, my next project will be a way to magically modify any .Net program so that, given translation strings, it should be capable of being translated to another language, requiring no changes to the source code at all!

Pretty exciting stuff! I'll have more news about it as I implement it. First off, it won't be open source, at least not initially. Other details will surface later.

Posted: 11/9/2012 5:11:47 AM

Nifty snipplet to fill data into a dynamic type

So I'm trying to put together a fairly major update to BarelyMVC so that I no longer have to resolve strings to their actual types.

I realized that this is an extremely common thing that must be done. Any ORM worth it's weight in salt does this, as well as many other things. So, I'll go ahead and give everyone who hasn't done this a nice starting point. As you can tel, my code is geared towards my router, but easily adjusted. The only big thing missing is a way to specify a custom converter. I'll probably update this code snipplet when I get that figured out.

I decided against containing the code here as I have no syntax highlighting, so check it out as a gist

Anyway, what this will eventually allow is instead of making code like this:

//in an HttpHandler
int tmp=0;
if(!int.TryParse(RouteParams["id"], out tmp))
{
  throw new HttpException(404);
}
//tmp now contains a valid integer

instead, you can just create a route parameter model like so:

class BlogRouteModel
{
  public int ID {get; set; }
}

and then be able to do something like

Lookup(RouteParams.ID); //contains a valid ID

I haven't figured out the exact way this should all tie together. Apparently generics everywhere isn't the solution as I quit after I would have to somehow contain a list of generic lists with the lists being of a generic type and so on.. It was so fugly.

Also, this same type of convention will be used for HTTP Form variables, so no more parsing of integers and making sure things exist and such there.

Posted: 10/14/2012 12:55:57 AM

ILDump -- New mini project

I've created a new little project now which hopefully fill a small niche.

Basically, at my work I'm currently debugging an issue where subtle IL changes(such as adding a nop or slightly reordering a switch) exposes a bug in Microsoft's JIT compiler for .Net 4.5. I have two assemblies. One that works, and one that crashes. The only difference between them is subtle IL changes. They both do the same thing, have the same methods, etc.. and they work fine on a JIT compiler that isn't 4.5.

So far, my attempts at figuring out what's going on has been going very poorly. The main reason is that there aren't a ton of tools out there for working with IL. I know, I could dump the IL as C# or something with ILSpy, but the IL is so similar that it turns into the same C# code. Attempts to reproduce it in a smaller test case have went poorly so far as well.

My options were as such:

  1. Use ILDasm and then a diff program to figure out the difference between the programs
  2. Use Reflector to dump out IL like ILDasm, but also put it in separate files.
  3. Pound my head against the wall

So far, option 3 has been showing the most promise. The problem with dumping IL and then trying to diff it is that current disassemblers will prefix a label for the address of every line... this is fine except for when you prefer that every line doesn't have a difference when a nop is added that changes the address.

So, how does ILDump solve this problem? Well, first off it won't print out labels on each line unless another instruction "points" to that label. Also, because addresses don't stay constant, it can rename labels to something like Label1 instead of IL_0012, to make less false differences. Also, it includes a small option for trimming out NOPs, sorting by method name, and constraining it's output to a class/namespace.

Anyway, ILDump is BSD licensed over at BitBucket. It uses Mono.Cecil for it's IL parsing, and compiles/works on Mono and Microsoft .Net. Also, if you want to download a precompiled version, try out ildump.zip

It's a very simple tool, so there hasn't been a whole lot to do on it, but it's still improving by the day(hour).

Posted: 9/2/2012 6:57:05 PM

BarelyMVC Doesn't Quite Work on Microsoft ASP.Net

Well, there are quite a few small bugs in BarelyMVC that I had to fix when running it under Microsoft's ASP.Net... and one problem that I can't seem to figure out.

For some reason, when trying to use the route /, Microsoft's xsp ignores BeginRequest in the Global.asax and instead just shows a list of directories.

So, BarelyMVC is ready to test out on Mono, but sadly, not Microsoft's .Net.

Posted: 7/12/2012 6:56:04 AM