Saturday, May 30, 2009

Free web development tools for Microsoft platform

This article is about building websites using ASP.NET. "ASP.NET???" - you would exclaim in disbelief - "But this is 2009! Ruby! Rails! Python and PHP! Why would anyone even look at this boring last-century technology for building HR applications???"

There are several reasons why ASP.NET is worth more than a shrug in today's web development.

First, the tools are REALLY nice. Visual Studio absolutely rocks as a development environment.

Because of the Intellisense magic, most lines of code take only a few keystrokes to enter. Here is an example:

(1) 'c':



(2) '.':



(3) '.':



(4) '.(DownArrow':



(5) ');'

...and a line of code has been entered in roughly 8 keystrokes and under 5 seconds. When you type say a thousand lines of code per day, believe it or not, this makes a huge difference.

The debugger is fantastic - you can set breakpoint on the client and on the server, in C# and in JavaScript, and it works the way you'd expect it to work. In my days in Gmail the debugging experience was Firebug and even that involved an inordinate amount of prayer to my atheistic gods. Debugging in IE required making a pact with the devil. And as you know, Python has no debugger at all, because Pythonic code does not require debugging by definition :-).

Second, there is a lot to be said for a type-safe language.

In the world where most development is confined to 500-lines-of-Javascript-and-a-lot-of-JPEGs Facebook apps, the idea that every object is a hash map, and every squence of keystrokes is a variable makes perfect sense. Why spend time thinking about class hierarchies when we could be delighting the customers instead?

But suppose your app is 100000 lines of code. Standard software development principles - code isolation, compiler-time type checking, interfaces - become incredibly important.

How many huge applications have you seen that are written in dynamic language? I've worked on one - Gmail - which was around 250Kloc of JavaScript at the time. The only way doing anything there was possible was because of Google's internal JavaScript tools that did a lot of static code-checking at compile time, and because the v2 code base was built on object-oriented concepts - class libraries, inheritance, etc. It was very unusual - and very restricted - JavaScript for sure!

But even with all of these great tools, the life was very hard - I don't think Gmail team will be able to increase their code base very much further...

One huge advantage of ASP.NET is its ability to model an HTML page in C# - every HTML element is created as a .NET object on the server, and is then rendered into HTML. This allows your compiler to be very upfront about things that you would otherwise be finding through testing or user feedback!

For example, this is a code snippet that creates a very simple web page:

Label l = new Label();
l.Text = "<h1>Welcome!</h1>";
ActivePage.Controls.Add(l);
TextBox t = new TextBox();
t.ID = "textbox";
ActivePage.Controls.Add(t);
Button b = new Button();
b.Text = "Submit";
b.ID = "submit";
b.Click += new EventHandler(clickProcessor);
ActivePage.Controls.Add(b);


I think it was primarily because of the tools and the language that I was able to clone Google Mondrian in C# in less than a quarter of time it took Guido to write it in Python. (http://malevich.codeplex.com)

Alright, alright, you would say, but don't I have to pay Micro$oft thousands of dollars for the privilege? Ruby, PHP, Python, and MySQL are free, and have you checked the going rate for SQL Server recently?

This used to be true, and it was my constant mantra at Microsoft that we should ship at least the basic dev tools for free. It was always my opinion that if we did ship free C compiler with DOS and Windows, GNU tool chain - and, consequently, Linux - would not have existed.

Maybe I was not alone in thinking this, and better late than never, so starting around 2005 Microsoft began shipping free Express version of Visual Studio Suite, as well as SQL Server Express Edition. This articles is an introduction to these tools.

Without much ado, let's dig into free development with Microsoft tools.

First, you need a free OS. Windows 7 RC is free (for a year :-)) and is available here:
http://www.microsoft.com/Windows/Windows-7/download.aspx


On a modern DSL connection it takes about an hour to download, and another 30 minutes to install (including the updates) - at least on a virtual machine I used.


After the OS is installed, go here: http://www.microsoft.com/web/downloads/platform.aspx and download Microsoft Web Platform using "Installer 2.0 Beta". Make sure you choose everything under the Database tab, and "Visual Web Developer 2008 Express with SP1" under Tools.

Before the installation, it will ask you what authentication scheme to use for SQL Server. Select Windows Authentication.



During the installation, it will complain multiple times about "Known compatibility issues" between Win7 and various SQL Server components. Click "Continue" every time - but you will need to install SQL Server SP1 (later).

Once the installation is complete, go to Windows Updates, and click "Get updates for other Microsoft products". Install all default and optional updates (that are not language packs). Pay attention to the optional updates - the list should include SQL Server SP1. Make sure this gets in.

Your setup is now complete! From start to finish, on a decent DSL connection, it should take no more than 2.5 hours do download and install everything.

You can use free version of Visual Studio for building web applications, and a free version of SQL Server as a backend for them.

As an example of using these tools, we'll build a simple guest book app.

Our app will consists of a database that will store comment records in our guest book, and a web front-end that would allow visitors to enter comments (as well as their names and email addresses).

Let's create the database!

Our database will be really simple. It will contain records which will consists of the record key (an integer), a 50-character name, a 50-character email alias, and an unlimited comment string.

Run Microsoft SQL Server Management Studio (from Programs->Microsoft SQL Server 2008). Right-click on the list of the databases, and select "New Database...". Enter the name of the database (GuestBook), and click OK.


This creates an empty database. Now expand its tree in the left pane, and click on the table. Select "New table...". A windows that allows you to edit the database structure will open. DO NOT save the table until all the editing is done!

First, let's create an identity field - the name would be "Id", the type "int". Right-click on the field and chose "Set Primary Key".


Then, in the list of options below, make this an identity record (a unique key that identifies the record in the database).


Then create the other fields: Author and Email (both nvarchar(50)), and Comment ((nvarchar(MAX)) - all three of them NOT NULL.


Save the table (Control-S) and name it CommentRecord:


Now that we have the place to store data, we should create a security model for it. SQL Server has two security primitives that are of interest here - database users, and SQL Server logins. "A login" is a SQL equivalent of a Windows user. When you allow a user to connect to the database engine, you create a login for this user. In our case, we want to allow IIS user to connect to SQL database, so we create a login for "IIS APPPOOL\DefaultAppPool" by right-clicking on Security/Logins and selecting New... from the menu:


Logins are global to the database engines, and are a way to identify external users to SQL. The databases also have a concept of users, which are projections of logins on the individual databases (why SQL developers decided to have two entities instead of one, beats me). So we now have to create a user for the GuestBook database, and associate it with the login we just created. We will name the user "GuestBookUser". Expand the GuestBook database tree, Security, and right click on Users, and select New. Enter the logon name (IIS APPPOOL\DefaultAppPool), the user name (GuestBookUser), and check db_datareader role membership below, as follows:
.

Note that we have not granted the database user any "write" rights yet, but we expect it to be able to add the records. We will do it by creating a stored procedure (a piece of code inside our database) that would add records, and grant the GuestBookUser the right to execute this code. This way, any visitor to the web site would be able to add data, but not change anything that is already in the database.

Under the database tree, expand Programmability, and right click on New Stored Procedure. The nice thing about SQL Server 2008 is that its Management Studio supports Intellisense:


Enter the following stored procedure into the query window, and click on Execute:


Now the only thing left is to grant GuestBookUser the execute access to this stored procedure. Expand Stored Procedures under Programmability, right-click on its name (refresh - F5 - if it's not there), then select Properties, and then Permissions:


We're done! Once you do it once or twice, creating simple databases like this only takes 5 to 10 minutes.

We can now create the front-end application.

Execute Visual Web Developer 2008 Express Edition AS ADMINISTRATOR. You will need this privilege level to publish the web site.

From the File menu select New Website. Pick ASP.NET Web Site template, set the language to Visual C#, and put it in a directory called GuestBook:


We will be using LINQ to access the database from our code. LINQ To SQL makes a database accessible by generating a C# object model for it. It's actually extremely nice. You can get at the database data by executing SQL-like queries directly from C#, by writing something like this:

var query = from cc in context.CommentRecords select cc;
foreach (CommentRecord r in query)
{
Console.WriteLine("Name: {0} Email: {1} Comment: {2}",
r.Author, r.Email, r.Comment);
}

LINQ supports much more complicated queries, of course, with conditions and even joins.

To add the database model, right-click on the website name in Solution Explorer, and click on Add new item. Pick LINQ To SQL Classes, and change the name to GuestBook.dbml:


We now need to connect to our database. Click the Database Explorer, then Add Connection:


Pick Microsoft SQL Server:


Set the SQL Server instance as YOURMACHINENAME\SQLEXPRESS, and select GuestBook from the list of databases. Click on Test Connection to make sure everything works:


You can now see the database in the database explorer. Expand the nodes for tables and stored procedures, and drag the CommentRecord table and AddComment SP over to their places on the designer surface:


Save everything (Control-S). The database classes are ready for use!

We need an element on the form to use as a root for our HTML object model. Every HTML element we create will attach to this element. Open Default.aspx file, and convert the empty div element inside the form to asp:PlaceHolder. It needs to have properties runat="server" and id="ActivePage". Save!


Open the Default.aspx.cs file now. This is the code we're going to write - the whole thing is barely over 100 lines long, and almost 20 of that is generated as part of the template:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page
{
//
// This renders the page. Page_Load gets called both when
// the page is first displayed, and also on post-backs.
// The postback cycle initializes the page object model by running
// the code below, then sets the state of the objects from user
// input, then the handlers for the controls are called (in our case,
// it is the Click handler for the button). The handlers may (and often
// do redirect page to cause re-rendering with the new information.
//
protected void Page_Load(object sender, EventArgs e)
{
Label l = new Label();
l.Text = "<h1>Welcome to my Guest Book!</h1>";
ActivePage.Controls.Add(l);

//
// Open the connection to the database.
//
GuestBookDataContext context = new GuestBookDataContext(
System.Web.Configuration.WebConfigurationManager.ConnectionStrings[
"GuestBookConnectionString"].ConnectionString);

Table t = new Table();
ActivePage.Controls.Add(t);

//
// First, display all existing comments
//
var commentsQuery = from cc in context.CommentRecords select cc;
foreach (CommentRecord record in commentsQuery)
{
TableRow row = new TableRow();
t.Rows.Add(row);
TableCell cell = new TableCell();
row.Cells.Add(cell);
cell.Text = Server.HtmlEncode(record.Author) + " says...";
cell.Style.Add(HtmlTextWriterStyle.FontStyle, "italic");
cell.Style.Add(HtmlTextWriterStyle.Color, "blue");
cell.ColumnSpan = 2;

row = new TableRow();
t.Rows.Add(row);
cell = new TableCell();
row.Cells.Add(cell);
cell.Text = Server.HtmlEncode(record.Comment);
cell.ColumnSpan = 2;
}

context.Dispose();

//
// Now create the form for a user to enter comments.
//
TableRow author = new TableRow();
t.Rows.Add(author);
TableCell name = new TableCell();
author.Cells.Add(name);

Label authorNameLabel = new Label();
authorNameLabel.Text = "Your name:";
name.Controls.Add(authorNameLabel);

TextBox authorNameInput = new TextBox();
authorNameInput.ID = "name";
authorNameInput.MaxLength = 50;
name.Controls.Add(authorNameInput);

TableCell email = new TableCell();
author.Cells.Add(email);

Label authorEmailLabel = new Label();
authorEmailLabel.Text = "Email (will not show):";
email.Controls.Add(authorEmailLabel);

TextBox authorEmailInput = new TextBox();
authorEmailInput.ID = "email";
authorEmailInput.MaxLength = 50;
email.Controls.Add(authorEmailInput);

TableRow comment = new TableRow();
t.Rows.Add(comment);

TableCell commentCell = new TableCell();
commentCell.ColumnSpan = 2;
comment.Cells.Add(commentCell);

TextBox commentInput = new TextBox();
commentInput.Columns = 80;
commentInput.Rows = 20;
commentInput.TextMode = TextBoxMode.MultiLine;
commentInput.ID = "comment";
commentCell.Controls.Add(commentInput);

TableRow submit = new TableRow();
t.Rows.Add(submit);

TableCell submitCell = new TableCell();
submitCell.ColumnSpan = 2;
submit.Cells.Add(submitCell);

Button submitButton = new Button();
submitButton.Click += new EventHandler(submitButton_click);
submitButton.Text = "Submit comment.";
submitCell.Controls.Add(submitButton);

if (Page.IsPostBack)
{
TableRow error = new TableRow();
t.Rows.Add(error);

TableCell errorCell = new TableCell();
errorCell.ColumnSpan = 2;
errorCell.ID = "error";
error.Cells.Add(errorCell);
}
}

//
// This is run when the user clicks on the Submit button
//
void submitButton_click(object source, EventArgs e)
{
string comment = ((TextBox)FindControl("comment")).Text;
string name = ((TextBox)FindControl("name")).Text;
string email = ((TextBox)FindControl("email")).Text;
if (String.IsNullOrEmpty(comment) || String.IsNullOrEmpty(name) ||
String.IsNullOrEmpty(email))
{
TableCell error = (TableCell)FindControl("error");
Label l = new Label();
l.Text = "You need all three values!";
l.Style.Add(HtmlTextWriterStyle.Color, "red");
error.Controls.Add(l);
return;
}

GuestBookDataContext context = new GuestBookDataContext(
System.Web.Configuration.WebConfigurationManager.ConnectionStrings[
"GuestBookConnectionString"].ConnectionString);

context.AddComment(name, email, comment);

context.Dispose();

//
// This causes the page to be redrawn with the new comment in it.
//
Response.Redirect(Request.FilePath);
}
}


When the code is done, you can run it directly from Visual Studio (F5), and all normal debugging primitives (breakpoints, stepping, etc) work as you'd expect them to work.

Let's deploy the web site to the real web server now. Create a directory under c:\inetpub called GuestBook. Right-click on the web site name in the Solution Explorer, and pick Copy Website option. In the dialog that opens, click on the connect button, select File System, and navigate to c:\inetpub\GuestBook. Select all the files on the left and click on the right arrow to copy them to the target directory:


Open IIS manager, navigate to Default Website, right-click on it, and pick Add Application. In the dialog box that opens enter GuestBook as the name, and c:\inetpub\GuestBook as a path to the application.


Now go to the Firewall Control Panel Applet, and open the port for IIS:


This is it!

4 comments:

DzembuGaijin said...

Dude, you are doing a very good job as Microsoft evangelist :-) While .NET is not "free", who cares?

Really, I am very impressed: you can be called a "example for all MSFT" only if you could stop making posts about politics :-) ( hint: it is dirty dirty dirty and you can not really see "truth" - if there is such from where you are now)

But your engineering notes are great!

DzembuGaijin said...

Yeah, just so you understand: I am for freedom of speech and I like shooting shit myself ( may be too much), but just if you want make your blog more like Rusionovich was back than :-)

Like you can probably split your blog in two: politics and programing. ;-)

Anonymous said...

Nice article! I'd like to call it xxx for dummies :)
P.S. The steps of deployment of the website can be simplified. If you have IIS installed and a default website there, you don't need to do anything. Just right click the project -> publish -> set a location for your app like http://machine_name/myapp/ and click publish, you're done!

-Jerry

Web Development India said...

You have really imparted useful tips for Web development tools.
In this day and age, nobody can undermine the importance of website development.