Bag Of Cows

Project Web Server

This is a web server framework designed to be used by students doing an A Level style website project in C#.

Before the new 2016 A Level Specification, students in Year 13 did a project and a theory paper, and they could choose any language they liked for the project.

Now, however, they do a project, a theory paper and a programming exam in Year 13. It makes sense to do the project in the same language which they will use in the exam, which in my school is C#.

Formerly, we would have done a website/database project in PHP. That includes a built in webserver 'for free', which could be invoked from the command line with something like
php -S localhost:8000.

This gave a fully functional, if not industrial strength website with easy access to persistent sessions and GET/POST form data. It also meant that the student could move the project between school and home simply by copying the folder of php scripts, and no additional setup was needed on either school or home computer.

The aim of this project is to give students a similar framework for C#, so that they can concentrate on the functionality of the project without being disadvantaged by having to also worry about the underlying HTTP request processing (which would be an interesting, but very different project).

The code here is based on work by James Leach (https://github.com/JFL110/simple-app-container) with some adjustments (dumbing down, even) to make it easy to use in a school environment.

In my school, the projects are done using Mono Develop (using Raspberry Pi) and Xamarin Studio, and there are the platforms I have tested it on, but there is no reason why is wouldn't work in Visual Studio as well.

OK, lets start

  1. Make a new C# console project
  2. Add a new empty file to the project, and call it ProjectWebServer.cs
  3. Copy and paste all the code from this link into the file
  4. Look in your program.cs file to see what your project's namespace is. Replace the namespace "ProjectWebServer" near the top of the text you just pasted with the namespace for your project.
  5. Edit the references for your project, and make sure to include System.Web and System.Net
  6. Also go to your project options and make sure that the target framework is .NET 4.5 (or higher is probably fine)
  7. Now try building your project. Nothing will happen yet, but hopefully it will build without complaining
  8. Now, in your program.cs file, completely replace the class MainClass with the code below:

  9. 
    	class MainClass
    	{
    		private const string serverPort = "8181";
    		private const string wwwRootDir = "./";
    		private const char exitKey = 'x';
    
    		public static void Main (string[] args)
    		{
    
    			var logger = new ConsoleLogger ();
    			var server = new Server (serverPort,logger);
    
    
    
    			//var staticResourceProcessor = new StaticResourceRequestProcessor (wwwRootDir, logger);
    
    			// Build the request processing queue.
    			// The static processor should go last as it will try to return a 404 page for bad requests
    			// Unless StaticResourceRequestProcessor.404Condtition is configured to do something smarter.
    
    			FileRequestProcessor files = new FileRequestProcessor (wwwRootDir, logger);
    			string[] defaultIndexes = new string[] { "index.html", "index.htm" };
    			DirectoryListProcessor directories = new DirectoryListProcessor (defaultIndexes,wwwRootDir, logger);
    			ErrorProcessor errors = new ErrorProcessor (logger);
    
    
    			server.AddNextProcessor(files);
    			server.AddNextProcessor(directories);
    			server.AddNextProcessor(errors);
    
    
    
    			server.Start ();
    
    			while (true) {
    				Console.WriteLine ("Enter '"+exitKey+"' to exit:");
    				var key = Console.ReadKey ();
    				Console.WriteLine (string.Empty);
    
    				if (key.KeyChar == exitKey || key.KeyChar == exitKey)
    					break;
    			}
    			server.Stop ();
    		}
    	}
    
    

  10. Now you can build the solution, and hopefully it will build and open up a terminal
  11. In a web browser, enter http://localhost:8181/index.htm as the address. You should see an error message saying the page doesn't exist (it doesn't yet) and if you look in the web server terminal, there will be some messages
  12. Presuming that your project is running in Debug mode at the moment, find the 'bin/Debug' folder inside your solution's folder, and in there create a file called 'index.htm'. Paste in the following code (or make your own up):
  13. 
    <h1>It Lives</h1>
    
  14. In the browser, refresh the page, and you should see 'It Lives'

Phew, things are basically working now!

The next job is to decide on a location for the web pages and images of your site, and to tell the server where they are.

For example, if you make a folder called 'HTML' in the folder that contains your project's .sln file, you would tell the server about that by changing:

private const string wwwRootDir = "./";

to

private const string wwwRootDir = "../../../HTML/";

Go ahead and do that, and put some other files there to make sure it is working

How it works

The server works by having a number of RequestProcessors, which are instances of C# classes that you make. These are arranged in a list kept by the server, and when a request comes in, each one in turn gets to look at the request and see if it wants to service the request. If it does, it creates a string of HTML and sends that to the user's browser, then returns 'true' to tell the server not to bother asking any other RequestProcessors to handle the request

An Example

To get things going, we will create a RequestProcessor that will look for the text 'example' in the URL, and if it finds it, will display the text: Example Request Processor Output

  1. Add a new empty class to your project, called ExampleRequestProcessor
  2. Replace all the code in the file with that below, but replace XXXX with your project namespace

  3. 
    using System;
    using System.Net;
    using System.Web;
    
    namespace XXXX
    {
    	public class ExampleRequestProcessor: RequestProcessor
    	{
    		public ExampleRequestProcessor (ILogger logger) : base (logger)
    		{
    		}
    
    		public override bool TryProcess (HttpListenerContext context, SessionData sessionData, FormData formData)
    		{
    			{
    				if (context.Request.RawUrl.Contains ("example")) {
    					WriteAllBytesAndClose ("<h1>Example Request Processor Output</h1>",  context.Response);
    
    					return true;
    				} else {
    					return false;
    				}
    			}
    
    		}
    
    	}
    }
    

  4. Edit the program.cs file, and add the two lines in bold below between the lines you can see above and below
  5. 
    ErrorProcessor errors = new ErrorProcessor (logger);
    
    ExampleRequestProcessor exReqPro = new ExampleRequestProcessor (logger);
    server.AddNextProcessor (exReqPro);
    
    server.AddNextProcessor(files);
    
    

    This will create an instance of the ExampleRequestProcessor and add it to the server's list as the first one to 'have a go' at handling an incoming request.

  6. Build the solution, and in the browser test it with the URL 'http://localhost:8181/example'

Making it useful

You'll notice that 'TryProcess' got the parameters sessionData and formData

sessionData is a class that allows you to store information about a request, and using cookies, when the same user visits the server again, the information you stored before will still be there in the sessionData.

This is how you can keep track of things like user logins, preferences and shopping baskets.

formData is a class which tells you about any data which was sent to the server by submitting a form.

The code below shows both of these things being used to record the number of vists by the user, and to display what they entered into a form.

Replace the whole of the ExampleRequestProcessor TryProcess method with this:




public override bool TryProcess (HttpListenerContext context, SessionData sessionData, FormData formData)
		{
			//===================================================================
			// This request processor will only respons to requests where the 
			// word 'example' is found in the URL
			//===================================================================
			if (context.Request.RawUrl.Contains ("example")) {
				



				//===================================================================
				// send HTTP headers. This is polite, but probably not necessary
				//===================================================================
				context.Response.ContentType = MimeTypes.GetMimeTypeFromPath ("dummy_name.htm");
				context.Response.StatusCode = (int)HttpStatusCode.Accepted;

				//===================================================================
				// this part sets a sessionData item to the number of visits to this
				// site by getting the current number and replacing it with the
				// number one bigger
				// Notice that all the session data has to be of data type string, so 
				// we have to convert to and from ints as necessary
				//===================================================================

				// firstly, see if there is a session item called VisitNumber
				// if there is, get it, otherwise make a new one starting at 0
				string visitNum = "";
				if (sessionData.HasItem ("VisitNumber")) {
					visitNum = sessionData.GetItem ("VisitNumber");
				} else {
					visitNum = "0";
				}
				// now convert to a numeric value and add one to it
				int visitNum_as_int = Convert.ToInt32 (visitNum);
				visitNum_as_int++;
				// now store is (as a string) back in the session 
				sessionData.SetItem ("VisitNumber", visitNum_as_int.ToString ());

				//===================================================================
				// This part finds the names of all the fields of data in the
				// session data and displays their names and values.
				// It uses the technique of building up a string of the HTML which
				// will be sent back to the browser
				//===================================================================
				string result = "<h2>Session Data</h2>";
				foreach (string s in sessionData.AllItemNames()) {
					result += string.Format ("<P>{0} = {1}</P>\n", s, sessionData.GetItem (s));
				}

				//===================================================================
				// now do a similar thing for the foem data
				//===================================================================
				result += "<h2>Form Data</h2>";
				foreach (string s in formData.AllItemNames()) {
					result += string.Format ("<P>{0} = {1}</P>\n", s, formData.GetItem (s));
				}

				//===================================================================
				// this part makes the HTML for a form, which will be part of
				// the HTML we send back
				//===================================================================
				string formHTML = "<FORM ACTION=\"example\" METHOD=\"POST\">" +
				                  "<TABLE><TR><TD>Name:</TD><TD><INPUT TYPE=\"TEXT\" NAME=\"NAME\"></TD></TR>" +
				                  "<TR><TD>Favourite colour:</TD><TD><INPUT TYPE=\"TEXT\" NAME=\"COLOUR\"></TD><?TR>" +
				                  "<TR><TD COLSPAN=2><INPUT TYPE=\"SUBMIT\" VALUE=\"Enter\"></TD></TR></TABLE>" +
				                  "</FORM>"; 

				//===================================================================
				// now add up the HTML fragments and send them back. Notice that this 
				// isn't very well formed HTML - you would want to put in the
				// <HEAD, CSS,<BODY> and so on
				//===================================================================
				WriteAllBytesAndClose ("<h1>Example Request Processor Output</h1>" + formHTML + result, context.Response);

				// As we have handled this request we must return true, 
				// so the server won't ask any other processors to try it.
				return true;
			} else {
				return false;
			}

		}



If you run 'http://localhost:8181/example' again, you will see the result below.

Now you just need to review the comments in the code to appreciate how it all works, and you have all the tools to make your interactive website in C#.

Good luck!