Click here to Skip to main content
15,887,175 members
Articles / WCF
Tip/Trick

Build Your Own Web Server Using WCF!

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
15 Mar 2018CPOL3 min read 10.2K   12   3
Develop a simple lite web server using WCF

Introduction

This tip/trick will show you how to use a self hosted RESTFUL WCF end point as a lite web server. This is NOT intended as a replacement for IIS or any other full featured web server. Instead, it's the beginning of a lite, basic functional web server that can be deployed as background service.

Background

I needed the ability to host a web site without installing IIS or any other 3rd party web servers. I wanted it to be lite with a small foot print. Using WCF with URL wildcards inside a Windows background service, I came up with exactly what I needed!

Using the Code

I started with a Windows Service project template. I'm not going to get into how to build a Windows service project here. There is plenty of documentation on how to build Windows service projects. I'm going to focus on the WCF portion on our lite web server. With that, next I added a WCF service to the project. Below is my WCF contract. Using the wild card, we only need a single operation contract to serve up an entire web site.

C#
namespace MyWebService
{
    [ServiceContract]
    public interface IWebService
    {
        [OperationContract]
        [WebGet(UriTemplate = "{*path}")]
        Stream GetResource(string path);
    }
}

The interface implementation is below. In the constructor, we assume the "wwwroot" directory is a sub directory of our installation directory. This is where you will want to place your HTML content. You could easily make this a parameter in app.config and put your site where you want. We will get into the MimetypeHelper singleton later.

C#
namespace MyWebService
{
    internal class WebService : IWebService
    {
        private string basePath = null;
        private MimetypeHelper baseMimetypeHelper = null;
        
        public WebService()
        {
            basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
            
            this.baseMimetypeHelper = MimetypeHelper.GetInstance();
        }

        public Stream GetResource(string path)
        {
            Stream resourceStream = null;

            if (string.IsNullOrEmpty(path))
            {
                path = Properties.Settings.Default.DefaultPageName;
            }           
            
            try
            {

                string mimetype = this.baseMimetypeHelper.GetMimetype(Path.GetExtension(path));

                if (mimetype == null)
                {
		    
		     resourceStream = this.GetErrorResponseStream(404, "Not Found");
		    
                }
                else
                {
                    WebOperationContext.Current.OutgoingResponse.ContentType = mimetype;

                    resourceStream = new MemoryStream(File.ReadAllBytes(Path.Combine(basePath, path)));
                }
            }
            catch (System.IO.FileNotFoundException)
            {
                resourceStream = this.GetErrorResponseStream(404, "Not Found");
            }
            catch (System.IO.DirectoryNotFoundException)
            {
                resourceStream = this.GetErrorResponseStream(404, "Not Found");
            }
            catch (Exception ex)
            {
                resourceStream = this.GetErrorResponseStream(500, "Internal Server Error");
            }

            return resourceStream;
        }

        #region private

        private Stream GetErrorResponseStream(int errorCode, string message)
        {
            string pageFormat = "<!DOCTYPE html>
            <html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>Error</title>
            </head><body>" +
               "<font style=\"font-size: 35px;\">{0} {1}</font></body></html>";
            
            return new MemoryStream(ASCIIEncoding.ASCII.GetBytes
                       (string.Format(pageFormat, errorCode, message)));
        }       
        
        #endregion

Here is the portion of our app config related to the WCF end point. This is a pretty standard serviceModel section. The important points here are the endpoint binding type is "webHttpBinding" which is key for a RESTFUL end point. In our settings, we have a default page in the event a user browses to the root of your web site which is what most people do, I.E., http://www.google.com. In the code snippet above, if a user navigates to the root of your site, the path will be null and we return the default page.

XML
<system.serviceModel>
      <behaviors>
          <serviceBehaviors>
              <behavior name="">
                  <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
                  <serviceDebug includeExceptionDetailInFaults="false" />
              </behavior>
          </serviceBehaviors>
        <endpointBehaviors>
          <behavior name="webHttp">
            <webHttp helpEnabled="true"/>
          </behavior>
        </endpointBehaviors>
      </behaviors>
      <services>
          <service name="MatrixWebService.WebService" behaviorConfiguration="">
              <endpoint address="" binding="webHttpBinding"
                bindingConfiguration="" behaviorConfiguration="webHttp"
                contract="MatrixWebService.IWebService">
                  <identity>
                      <dns value="localhost" />
                  </identity>
              </endpoint>
              <host>
                  <baseAddresses>
                      <add baseAddress="http://localhost/" />
                  </baseAddresses>
              </host>
          </service>
      </services>
  </system.serviceModel>
  <applicationSettings>
      <MatrixWebService.Properties.Settings>
          <setting name="DefaultPageName" serializeAs="String">
              <value>index.html</value>
          </setting>
      </MatrixWebService.Properties.Settings>
  </applicationSettings>

Now for the mimetype helper. In order for the server to return the correct mimetype based on the users request, we need a dictionary of types. Below is a good starting point for the most common mimetypes for most web sites.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
 <MimeTypes> 
   <MimeType fileExtension=".htm" type="text/html" /> 
   <MimeType fileExtension=".html" type="text/html" /> 
   <MimeType fileExtension=".css" type="text/css" /> 
   <MimeType fileExtension=".png" type="image/png" /> 
   <MimeType fileExtension=".jpg" type="image/jpg" /> 
   <MimeType fileExtension=".js" type="text/javascript" /> 
   <MimeType fileExtension=".ico" type="image/vnd.microsoft.icon" /> 
   <MimeType fileExtension=".eot" type="application/vnd.ms-fontobject" />
 </MimeTypes>
</Configuration>

Below is our mimetype helper class. It's a singleton that loads the XML above at startup in our Windows service onStart() method. Internally, we have a dictionary with the file extension as a key and the mimetype as the value. The web service implementation can lookup a mimetype by the URL request.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyWebService
{
    internal class MimetypeHelper
    {
        private Dictionary<string, string=""> baseMimetypes = null;
        private static MimetypeHelper INSTANCE = null;

        private MimetypeHelper()
        {
            this.baseMimetypes = new Dictionary<string, string="">();
        }

        public static MimetypeHelper GetInstance()
        {
            if (INSTANCE == null)
            {
                INSTANCE = new MimetypeHelper();
            }

            return INSTANCE;
        }

        public void Load(string path)
        {
            XElement element = XElement.Load(path);

            IEnumerable<xelement> mimetypeElements = element.Element("MimeTypes").Elements();

            foreach (XElement mimetype in mimetypeElements)
            {
                string extension = mimetype.Attribute("fileExtension").Value;
                string type = mimetype.Attribute("type").Value;

                this.baseMimetypes.Add(extension, type);
            }
        }

        public string GetMimetype(string fileExtension)
        {
            string value = null;

            if (this.baseMimetypes.ContainsKey(fileExtension))
            {
                value = this.baseMimetypes[fileExtension];
            }

            return value;
        }
    }
}  

That's it! You now have a generic, very basic web server, that uses a WCF RESTFUL end point that only has one public method! This can easily be extended to incorporate directory permissions based on Active Directory group membership, among many other features found in IIS and other web service products!

Points of Interest

I've used this basic structure on multiple projects that have seen live operational use. It performs extremely well and with the extensibility in WCF, the sky is the limit!

History

  • 16th March, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugCode susceptible to HTTP Directory Traversal Pin
BjWm22-Mar-21 5:51
BjWm22-Mar-21 5:51 
QuestionNetstandard Pin
kiberg15-Mar-18 22:56
professionalkiberg15-Mar-18 22:56 
AnswerRe: Netstandard Pin
Dave M. (Member 10734106)16-Mar-18 5:13
Dave M. (Member 10734106)16-Mar-18 5:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.