But how does it work?!

I was going to put a rant in here about PHP sucking, but instead I'm going to do something useful and informative. Describe how this blog works.

When I started this blog, I had only used ASP.Net with WebForms. Even though I didn't know anyway to do it better, I could see the shortcomings of pretending HTTP is a stateful protocol. This is because before ASP.Net, I used PHP, which actually taught me quite a bit about the HTTP protocol. I've since abandoned the PHP ship due to stuff like this, but that's another story.

I believe at the time ASP.Net MVC was still pre-release or still filled with bugs, especially for Mono. I was set on using Linode for my hosting because I've never had any problems with them, and I prefer managing Linux servers to Windows... So I had a choice, Use WebForms, or use a different framework. I really like C# though, so I instead created a very minimalistic framework.

This framework eventually got broken up into 3 different pieces of software.

  1. EView -- a powerful, yet simple, static view generator written completely in T4
  2. EFramework -- a routing engine and basic replacement for using ASP.Net MVC or WebForms
  3. FSCAuth --a very easy to use Authentication library

At first, these were all one framework named EFramework, but I since broke them up since they are all useful by themselves as well.

So first up, I'll explain how EView generates everything you see here.

EView is just a single T4 file. I would've broken it up into separate files but at the time Mono had a bug that prevented me from doing it easily. How it works basically is you place EView into a directory, and when you run the T4 file template, it'll read all the files from the current directory and generate a C# file.

So, let's do a simple example. You have a folder named Templates and 3 files, EView.tt (the T4 file), IndexView.html, EntryView.html

So, you run EView.tt. Visual Studio (or MonoDevelop or whatever) runs the T4 template during design time. EView will scan the folder it's placed in. It finds IndexView.html and EntryView.html

Let's say IndexView.html contains this:

<!DOCTYPE HTML>
{@
Entry as IEView;
@}
<html>
<head>
    <title>Test Index</title>
</head>
<body>
  <div class="entry">
    {=Entry=}
  </div>
</body>
</html>

Now let's explain. What's that weird {@ thing at line 2? This is an EView directive. EView has blocks like { } The symbol after the { and before the } indicates what the directive is. The @ directive is for variable declarations. So basically, in the view class it generates, it'll add the public variable Entry of type IEView to it. IEView is the interface that all EView views implement. After that it looks like plain HTML until {=Entry=}. The {= directive tells EView "put this variable's value here. So it wants to put theEntryvariable there. EView is smart enough and knows that Entry is a View, so it callsEViewRender at that point.

So basically, this all generates a view class like this: (more complicated than this, but to be easy to understand, this example is simpler)

  public class IndexView : IEview
  {
    public IEView Entry;
    public string EViewRender(){
      StringBuilder sb=new StringBuilder();
      sb.Append(@"""<!DOCTYPE HTML>
      <html>
    <head>
        <title>Test Index</title>
    </head>
    <body>
      <div class=""entry"">
     """);
     //insert {=Entry=}
     sb.Append(Entry.EViewRender); //a lot more logic goes into this, but just for example sake
     sb.Append(@"""
      </div>
    </body>
      </html>
      ");
      return sb.ToString();
    }
  }

And that's basically how it works. So when you want to use IndexView, you just do this:

var index=new IndexView();
index.Entry=MyEntryView; 
string html=index.EViewRender();

So as you can see, it's very powerful really, while views are really simple to create. No weird XML tags to use, or other crao. You just write your HTML and use a { } blocks to describe how the view should be dynamic. It supports many more operations than shown here such as if statements, foreach statements, and a special Layout directive that works similar to ASP.Net's MasterPages.

So now you understand a bit of what my views look like and how they're rendered, we can move on to EFramework.

EFramework was really an experiment to see if what I was trying to do was even possible.. and then I thought it worked really well, so I kept at it.

What exactly does EFramework provides? It provides 2 things:

  1. A powerful routing engine
  2. An HttpHandler class that functions similar to ASP.Net's .ashx Handlers.

So let's start with the routing engine. The routing engine has 3 pattern matching engines

  1. Regex for regular expressions
  2. Plain for exact plain text matches
  3. Simple, used for my own pattern matching that resembles ASP.Net MVC's routing URLs

I'll only cover the Simple type of pattern matching.

Here is the Routing record for the pages of my Blog:

Routing.AddRoute("paged_index",PatternTypes.Simple,"/blog/{page}",()=>{return new BlogHandler();});
  1. "paged_index" is the name of the route, which is passed to the HttpHandler that handles the route.
  2. PatternTypes.Simple is which type of pattern matching
  3. "/blog/{page}" is the pattern to match. It basically says match anything that looks like /blog/* where * is anything, and put the wildcard value into the page route variable
  4. This is a lambda for creating a new HttpHandler. So basically, when a route matches, it will call on this Lambda and expects for the lambda to return an HttpHandler of some sort

The Simple pattern matching is also quite powerful and can do more than simple wildcards. It's aware of how / affects URLs and can do the following:

  1. "/blog/{entryid}/comment/add" This will match anything like /blog/*/comment/add where * can be basically anything but / and when it reaches a / it knows the wildcard is over
  2. "/test3/{id}/{action=[view,edit]}/{*}" This one is a bit more complex. The first {id} of course matches anything up til the next /. {action=[view,edit]} indicates that this wildcard can only be view or edit. If it is not one of those two, the route doesn't match to the URL. And finally, the {*} adds support for "descriptive" URLs, such as from StackOverflow: http://stackoverflow.com/questions/2185781/why-cant-c-sharp-compiler-follow-all-code-paths-through-a-switch-statement

That's about as complex as it gets right now. I'm sure there are some URLs it can't accept, but it works for most URLs I've seen.

Now then, how does an HttpHandler work exactly?

public class BlogHandler : HttpHandler
{
  public override void Get ()
  {
    if(RouteID=="page"){
      var v=new IndexView();
      v.Entry=GetEntry(RouteParams["id"]);
      Write(v.EViewRender());
      return;   
  }
}

Pretty simple. The Get function is called when the HTTP method is GET, Post for POST and etc. RouteParams is a dictionary with all of the route variables in it. So in this case, the route probably looks like

Routing.AddRoute("page",PatternTypes.Simple,"/blog/{id}",()=>{return new BlogHandler();});

And that's for the most part, what makes EFramework good enough for me.

And then there is FSCAuth, what prevents all of you from posting articles on my blog.

FSCAuth was made because I think ASP.Net's authentication mechanisms are a bit too complex for simple authentication scenarios, such as this blog. And FSCAuth is so easy to use and so secure(by default) that I decided to try to sell it. So how does FSCAuth tie into this site?

Well, I use MongoDB for my database (also mainly for learning something new :) ). FSCAuth works by having a UserStore class and a few configuration options set in Global.asax

First the easy part. My configuration looks like this:

        Authentication.UserStore=new MongoUserStore(); //set the UserStore to be used
        Authentication.SiteName="Last Year's Wishes"; //put into login cookies
        Authentication.LoginPage="/login"; //where to redirect to when authentication is required
        Authentication.UniqueHash=Config.UniqueHash; //our secret hash
        Authentication.CookieHttpOnly=true; //Set HttpOnly property on cookies
        Authentication.CookieSecure=false; //set to choose to only transport cookies over HTTPS 
        Authentication.CookieUseBase=true; //hash the physical path of the application into cookies. 
        Authentication.CookieUseBrowserInfo=false; //hash in client's browser information to the cookies. 
        Authentication.CookieUseIP=true; //Hash in the client's IP address to the cookie. Ties the cookie to the client's IP 

Now the last configuration thing to do is create our UserStore. This is a direct rip of mine (to show how easy it is to create one)

public class MongoUserData : UserData{
    [BsonId]
    public ObjectId ID{get;set;}
}

public class MongoUserStore : IUserStore
{
    public MongoUserStore ()
    {
    }
    public UserData GetUserByName (string username)
    {
        var db=Config.GetDB(); //gets our MongoDB database instance
        var u=db.GetCollection<MongoUserData>("users").FindOneAs<MongoUserData>(Query.EQ("Username",username));
        return u;
    }

    public bool UpdateUserByID(UserData user)
    {
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        if(u.Save<MongoUserData>(userdata)==null){
            return false;
        }else{
            return true;
        }
    }

    public bool AddUser (UserData user)
    {
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        if(u.Find(Query.EQ("Username",userdata.Username)).Count()!=0){
            return false;
        }
        u.Insert<MongoUserData>(userdata);
        userdata.UniqueID=userdata.ID.ToString();
        u.Save<MongoUserData>(userdata);
        return userdata!=null;
    }
    public bool DeleteUserByID(UserData user){
        var userdata=(MongoUserData)user;
        var db=Config.GetDB();
        var u=db.GetCollection<MongoUserData>("users");
        u.Remove(Query.EQ("_id",userdata.ID));
        return true;
    }
}

As you can see, it's not too complex to create... unlike a similar thing for tying ASP.Net's Forms Authentication to MongoDB.. I know it can't be done in only 58 lines of code anyway.

So then, how to actually make use of FSCAuth, all I do is call Authentication.RequiresLogin() or one of the other Authentication functions.

This is my actual Post method for my BlogHandler:

    public override void Post(){
        if(RouteID=="new"){
            Authentication.RequiresLogin();
            var c=Config.GetDB().GetCollection<BlogEntryData>("entries");
            var entry=new BlogEntryData{
                Title=Form["Title"],
                Text=Form["Text"],
                Publish=Form["Publish"]!=null,
                Posted=DateTime.Now,
                Tags=new List<string>(Form["Tags"].Split(new char[]{' '},StringSplitOptions.RemoveEmptyEntries))
            };
            c.Save<BlogEntryData>(entry);
            Response.Redirect("/blog/view/"+entry.ID.ToString());
        }
        if(RouteID=="edit"){
            Authentication.RequiresLogin();
            var c=Config.GetDB().GetCollection<BlogEntryData>("entries");
            var entry=c.FindOneByIdAs<BlogEntryData>(new ObjectId(RouteParams["id"]));
            entry.Title=Form["Title"];
            entry.Text=Form["Text"];
            bool p=Form["Publish"]!=null;
            if(!entry.Publish && p){ //if not previously published, update the time so it's "bumped"
                entry.Posted=DateTime.Now;
            }
            entry.Publish=p;
            entry.Edited=DateTime.Now;
            entry.Tags=new List<string>(Form["Tags"].Split(new char[]{' '},StringSplitOptions.RemoveEmptyEntries));
            c.Save<BlogEntryData>(entry);
            Response.Redirect("/blog/view/"+entry.ID.ToString());
        }
    }

And that's all I have to do. Super simple.

Even though I created this framework as a workaround because MVC at the time was very experimental with Mono, I found that I rather like my framework. I can see quite a few limitations, but it works wonderfully for small scale websites. I also have not measured the performance of any of this except for FSCAuth, so I'm sure it's not enterprise ready, but for this low-traffic blog, it works really well. And I learned a lot about ASP.Net in building it.

Anyway, hope you enjoyed reading how this site works and maybe are curious about one of my 3 projects. In case you are, here are the links to the projects again:

  1. EView -- a powerful, yet simple, static view generator written completely in T4
  2. EFramework -- a routing engine and basic replacement for using ASP.Net MVC or WebForms
  3. FSCAuth --a very easy to use Authentication library
Posted: 5/3/2012 8:16:16 PM

Comments

Posting comments is currently disabled(probably due to spam)