Introduction
This project is a flexible HTTP server which can be embedded in any .NET application. The server implements both HTTP and HTTPS. It has a modular design where features are added using modules. The server supports REST and all HTTP verbs.
The modular design allows you to use a small specific part, or get a full blown MVC web server.
- Just use the (framework included, not
System.Net.
) HttpListener
to take care of everything yourself. - Use the
HttpServer
that also gives you better response handling and sessions. - Add the file module, which also allows the server to serve files under a specific directory/subdirectories (you can also use multiple file modules).
- Add the controller module to be able to use controllers.
- Use the template manager in your controllers to get views.
- Use
ViewController
to integrate views in the controllers (adds some Render methods).
Background
I needed a web server which can be embedded in my own server, which excluded ASP.NET since I didn't want to use Remoting. I started to build a web server using the .NET HttpListener
. It worked fine until I tried to use my application with a regular user Windows account =) The problem with the .NET HttpListener
is that it needs to have administrator privileges to be able to register the URIs that your project is going to use.
Using the code
The project is quite large, and an article is not enough to demonstrate everything. I will therefore start with a simple example demonstrating how to use the HttpListener
, and then move forward to show a complete MVC server example.
Using the HttpListener
Let's look at the most basic example, creating an HTTP Listener and accepting connections.
public class Example1
{
private HttpListener _listener;
public void Start()
{
_listener = HttpListener.Create(IPAddress.Any, 8081);
_listener.RequestReceived += OnRequest;
_listener.Start(5);
}
private void OnRequest(object source, RequestEventArgs args)
{
IHttpClientContext context = (IHttpClientContext)source;
IHttpRequest request = args.Request;
if (request.Uri.AbsolutePath == "/hello")
context.Respond("Hello to you too!");
else if (request.UriParts.Length == 1 &&
request.UriParts[0] == "goodbye")
{
HttpResponse response = new HttpResponse(context, request);
StreamWriter writer = new StreamWriter(response.Body);
writer.WriteLine("Goodbye to you too!");
writer.Flush();
response.Send();
}
}
}
Using HTTPS is not much harder. We just need to load a certificate: How to generate a certifcate with OpenSSL.
_cert = new X509Certificate2("../../certInProjectFolder.p12", "yourCertPassword");
_listener = HttpListener.Create(IPAddress.Any, 443, _cert);
Creating an HTTP module
Since the HTTP server is module based, you can easily add your own modules to do whatever you want. Let's create a custom module...
class MyModule : HttpModule
{
public override bool Process(IHttpRequest request,
IHttpResponse response, IHttpSession session)
{
if (session["times"] == null)
session["times"] = 1;
else
session["times"] = ((int) session["times"]) + 1;
StreamWriter writer = new StreamWriter(response.Body);
writer.WriteLine("Hello dude, you have been here " +
session["times"] + " times.");
writer.Flush();
return true;
}
}
... and add it to an HttpServer
:
HttpServer.HttpServer server = new HttpServer.HttpServer();
_server.Add(new MyModule());
_server.Start(IPAddress.Any, 8081);
Creating an MVC server
To be able to create an MVC server, we need an HttpServer
, a file module (to handle static or embedded files), and the controller module (that controllers are added to). In this example, I'll skip multilingual support; check the last tutorial in the project source code to see how you add multilingual support.
I usually create a folder structure in my project that looks like this:
ProjectRoot
Controllers
Models
public
images
javascripts
stylesheets
views
The Controllers
namespace contains all the controllers in the project. A controller is sort of a bridge between the models (business objects) and the views. Their responsibility is to load business objects and put them into the views, as a kind of abstraction layer between the models (business layer) and the views (design/presentation layer).
Models are, as I said, your business objects, i.e., a user, a message etc. I've seen many examples where models actually have two roles:
- To contain business objects.
- To load/save business objects from/to a data source (usually a database). I strongly recommend you to not do so, although that's a bit off topic. Ask in the comments section if you are interested in why.
The "public" folder contains all the static files which are loaded and sent directly by the web server (through the file module). Files can be both on disk and in resources. I usually configure the web server to first try to load files from the hard drive, and if unsuccessful, from the resources. In this way, I can customize design and etc., by just placing the custom files on disk (and therefore override files stored in the resources).
The "views" folder contains all the views that business objects are rendered into. The project currently has two different template engines. One for ASP similar tags, and one for HAML. You can add your own template engines by implementing the ITemplateGenerator
interface. All templates are compiled into .NET objects at runtime to increase performance.
Creating controllers
Let's start with controllers. It can be a good idea to create a base controller that has common methods that most of your controllers use. I usually call that controller "ApplicationController
".
public abstract class ApplicationController : ViewController
{
protected ApplicationController(TemplateManager mgr) : base(mgr)
{
}
protected ApplicationController(ViewController controller) : base(controller)
{
}
[BeforeFilter]
protected bool CheckLogin()
{
if (UserName == null && Request.Uri.AbsolutePath != "/user/login/")
{
Session["ReturnTo"] = Request.Uri.OriginalString;
Response.Redirect("/user/login/");
return false;
}
return true;
}
protected void ReturnOrRedirect(string url)
{
if (Session["ReturnTo"] != null)
Response.Redirect((string) Session["ReturnTo"]);
else
Response.Redirect(url);
}
public string UserName
{
get
{
return (string)Session["UserName"];
}
set
{
Session["UserName"] = value;
}
}
}
BeforeFilter
s are processed before anything else, and can therefore be used for access control, as in this example.
In this example, we'll just create a controller handling users.
public class UserController : ApplicationController
{
public UserController(TemplateManager mgr, LanguageNode modelLanguage) :
base(mgr, modelLanguage)
{
DefaultMethod = "Main";
}
public UserController(ViewController controller) : base(controller)
{
}
DefaultMethod
is called if no action has been specified, as in "http://localhost/user/".
public string Login()
{
if (Request.Method == Method.Post)
{
FormValidator validator = new FormValidator(Request.Form, Errors);
string userName = validator.Letters("UserName", true);
string password = validator.LettersOrDigits("Password", true);
if (validator.ContainsErrors)
return RenderErrors("Login", "UserName", userName, "Password", password);
UserName = userName;
ReturnOrRedirect("/user/");
return null;
}
return Render("UserName", string.Empty, "Password", string.Empty);
}
As you can see, I use the HTTP method to determine if a form has been posted or not. Since we do not use a data source in this example, we'll just pretend that the user has been authenticated properly. The ReturnOrRedirect
method will go back to the previous page (if any).
FormValidator
is a helper class that can be used for validation. There is another alternative further down in this article.
public string Main()
{
return Render("message", "Welcome " + UserName);
}
public string AjaxDeluxe()
{
if (Request.IsAjax)
{
Dictionary<int, string> items = new Dictionary<int, string>();
items.Add(1, "Jonas");
items.Add(2, "David");
items.Add(3, "Arne");
return FormHelper.Options(items, HandleList, 2, true);
}
return Render();
}
private static void HandleList(object obj, out object id, out string title)
{
KeyValuePair<int, string> pair = (KeyValuePair<int, string>)obj;
id = pair.Key.ToString();
title = pair.Value;
}
The server supports AJAX, and will automatically exclude the page layout (master page in ASP) when rendering the result if the request is made through AJAX. The FormHelper.Options
method will generate a set of OPTION
HTML tags, and it uses a delegate to get name/value from objects.
public string Info()
{
User user = new User();
user.UserName = UserName;
return Render("user", user);
}
public string Settings()
{
User user = new User();
user.UserName = UserName;
if (Request.Method == Method.Post)
{
foreach (HttpInputItem item in Request.Form["user"])
Property.Set(user, item.Name,
item.Value == string.Empty ? null : item.Value);
ObjectValidator validator = ObjectValidator.Create(typeof(User));
IList<ValidationError> errors = validator.Validate(user);
if (errors.Count > 0)
return RenderErrors(errors, "settings", "user", user);
Response.Redirect("/user/");
return null;
}
return Render("user", user);
}
First of all, a class in Fadd called Property
is used to assign all the values from the HTML form to the model. Quite easy, huh? Next, we use something called ObjectValidator
to make sure that all fields have been entered properly. ObjectValidator
uses attributes specified in the model for the validation.
public override object Clone()
{
return new UserController(this);
}
}
And finally, we have to setup the server.
ResourceTemplateLoader templateLoader = new ResourceTemplateLoader();
templateLoader.LoadTemplates("/", Assembly.GetExecutingAssembly(),
GetType().Namespace + ".views");
TemplateManager templateManager = new TemplateManager(templateLoader);
templateManager.AddType(typeof (WebHelper));
templateManager.Add("haml", new HttpServer.Rendering.Haml.HamlGenerator());
ControllerModule controllerModule = new ControllerModule();
controllerModule.Add(new UserController(templateManager));
_server.Add(controllerModule);
ResourceFileModule fileModule = new ResourceFileModule();
fileModule.AddResources("/", Assembly.GetExecutingAssembly(),
GetType().Namespace + ".public");
_server.Add(fileModule);
_server.Start(IPAddress.Any, 8081);
Console.WriteLine("Server is loaded. Go to http://localhost:8081/.");
Console.ReadLine();
_server.Stop();
Final words
You can find the project, tutorials, and a discussion forum at CodePlex.
History
- 2009-01-21 - First version.
- 2009-01-27 - Fixed a lot of spelling errors =)