![]() |
General Programming »
Internet / Network »
HTTP / HTTPS
Intermediate
License: The GNU Lesser General Public License
Embeddable web serverBy jgauffinA web server with HTTPS, MVC, and REST support. |
C# (C#2.0, C#3.0), .NET, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
System.Net.) HttpListener to take care of everything yourself. HttpServer that also gives you better response handling and sessions. ViewController to integrate views in the controllers (adds some Render methods). 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.
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.
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;
// Respond is a small convenience function that
// let's you send one string to the browser.
// you can also use the Send, SendHeader
// and SendBody methods to have total control.
if (request.Uri.AbsolutePath == "/hello")
context.Respond("Hello to you too!");
// Or use the HttpResponse class.
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);
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 to tell webserver that we've handled the url
return true;
}
}
... and add it to an HttpServer:
HttpServer.HttpServer server = new HttpServer.HttpServer();
_server.Add(new MyModule());
_server.Start(IPAddress.Any, 8081);
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:
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.
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;
}
}
}
BeforeFilters 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);
// validate login
UserName = userName; // save it in the session
// and redirect
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()
{
// todo: here you should really load user from the DB
User user = new User();
user.UserName = UserName;
return Render("user", user);
}
public string Settings()
{
// todo: here you should really load user from the DB
User user = new User();
user.UserName = UserName;
if (Request.Method == Method.Post)
{
// load everything from the web form to the object.
foreach (HttpInputItem item in Request.Form["user"])
Property.Set(user, item.Name,
item.Value == string.Empty ? null : item.Value);
// validate all attributes.
ObjectValidator validator = ObjectValidator.Create(typeof(User));
IList<ValidationError> errors = validator.Validate(user);
if (errors.Count > 0)
return RenderErrors(errors, "settings", "user", user);
// and here you should save it to the database.
// and do something when you are done.
Response.Redirect("/user/");
return null;
}
// just render the web form.
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.
// since we do not use files on disk, we'll just add the resource template loader.
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());
// we've just one controller. Add it.
ControllerModule controllerModule = new ControllerModule();
controllerModule.Add(new UserController(templateManager));
_server.Add(controllerModule);
// add file module, to be able to handle files
ResourceFileModule fileModule = new ResourceFileModule();
fileModule.AddResources("/", Assembly.GetExecutingAssembly(),
GetType().Namespace + ".public");
_server.Add(fileModule);
// ok. We should be done. Start the server.
_server.Start(IPAddress.Any, 8081);
Console.WriteLine("Server is loaded. Go to http://localhost:8081/.");
Console.ReadLine();
_server.Stop();
You can find the project, tutorials, and a discussion forum at CodePlex.
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 21 Jan 2009 Editor: Smitha Vijayan |
Copyright 2009 by jgauffin Everything else Copyright © CodeProject, 1999-2010 Web20 | Advertise on the Code Project |