Step 2 - Set the HttpListener loop (C# Project on Classroom Voting Application)

This is the second step of writing our project on classroom voting application. We shall now set an http listener to listen and respond to requests coming from a browser. For this, dotnet core provides a class called HttpListener. Most of the basic plumbing is handled by this class. We need only four things to get it working. First we have to add the listening routes to the Prefixes collection - wildcards are also supported. After that the listener is put into started mode, and a while loop is set to handle the requests. We have to provide an AsyncCallback delegate to handle a request, and write the response to the outputstream. This tutorial explains all the steps in sufficient detail. Let's see!
(Rev. 19-Mar-2024)

Categories | About |     |  

Parveen,

Read your IP Address

We shall start by adding a utility class to obtain the IP address of the PC. It is required for displaying it on the console output so that mobile phones can be asked to type this address and connect.

For this add a class called Utility as you see here.

This class has been marked static because we shall be adding standalone helper functions to this class.

  
// static helper to get the machine IP 

// for source code see the downloads 

internal static string GetMachineIPAddress()
{
  // get machine IP address on LAN 
  string? ip = null;

  string Hostname = System.Environment.MachineName;

  IPHostEntry Host = Dns.GetHostEntry(Hostname);

  foreach (IPAddress IP in Host.AddressList)
  {
    if (IP.AddressFamily == AddressFamily.InterNetwork)
    {
      ip = $"{IP}";

      break;
    }
  }

  if(ip is null)
  {
    Console.WriteLine("Problem ...");

    ip = "ip_not_found";
  }

  return ip;
}
  
  

Add a static function called GetMachineIPAddress.

Declare a string ip to store the ip address.

Host name is obtained from the Environment class. It is then used to obtain IPHostEntry

A for-each loop is then used to obtain the internet address.

This code might not make much sense to somebody not well-versed in network terminology. So the best way is to just copy paste it in your projects.

Print a suitable message if the ip address could not be found.

Video Explanation (see it happen!)

Please watch the following youtube video:

Add a class called Server

Next let's add a class called Server to handle the http requests coming from a client. This class will handle the voting logic also.

Start by typing the class.

Add a concurrent dictionary to hold key value data. We are using a concurrent dictionary because an http server is inherently a multi-threaded application. This dictionary holds the student id in its key. Each student id is unique. The "value" associated with a key is a tuple made of two properties. The first property stores the choice of the student - like a, b, c, d or whatever. The second property stores whether the choice was altered by the student. This flag will later be used to update the database only if the student revised his vote. You might have to review my c# course for tuples.

After this we hold a reference to the project context. This is required for database communication.

Add a constructor! The constructor receives the project context as a parameter.

It is cached for later use _ctx = ctx.

Next initialize the concurrent dictionary.

  
// source code is in the attached downloads 

internal class Server
{
  // holds Key = Student, Value = tuple(Choice, IsDirty) 
  // the ctor fills this dictionary with data from the database 
  // the data in this dictionary is flushed to database during cleanup 
  // the IsDirty flag is used to upsert only the records with changes 
  readonly ConcurrentDictionary _allVotes;

  // database context 
  private readonly ProjectContext _ctx;

  public Server(ProjectContext ctx)
  {
    _ctx = ctx;

    _allVotes = new();

    foreach (var vote in _ctx.Votes)
    {
      _allVotes[vote.StudentID] = (vote.Choice, false);
    }
  }

  // listener for httprequests 
  public void HandleRequest(IAsyncResult result)
  {
    HttpListener listener = (result.AsyncState as HttpListener)!;

    // extract httplistenercontext 
    var context = listener.EndGetContext(result);

    // "using" releases handle on end of scope 
    using var wh = result.AsyncWaitHandle;

    // obtain httplistenerrequest 
    var request = context.Request;

    // obtain httplistenerresponse 
    using var response = context.Response;

    // extract the requested resource from url 
    string message = "Welcome, server!";

    byte[] bytes = System.Text.Encoding
                         .UTF8.GetBytes(message);

    response.ContentLength64 = bytes.Length;

    response.OutputStream.Write(bytes);

  }
}
  
  

A for-each loop is now used to read the db context for all the records of votes currently stored in the database. This might be required if this voting continues in different sessions. Each vote is marked with the flag isDirty = false.

Next we have added a function called HandleRequest to match the delegate required for handling http requests for the HttpListener. Please note that HttpListener will be added to the program.cs file.

Extract the HttpListener.

Next obtain the HttpListenerContext.

The AsyncWaitHandle will be automatically released when the function returns. The using keyword automatically scopes it to the end of the function scope. This is a new feature of C# 8 and later versions.

Get the http request from the context. Similarly, get the http response object from the context.

We shall be now sending a hello message to the calling client. This is only for testing this step. In later steps we shall make more sophisticated changes to the code.

Get the byte array using UTF8 - GetBytes.

Set the content length property. Write the byte array to the output stream.

This completes the server class for this tutorial. We shall soon verify that the server sends this string when we send a request from the browser.

Write the Program.cs File

Come to the solution explorer and open the Program.cs file.

We shall listen for requests on this port number. You can safely set any available port in the range 50000.

Create an instance of HttpListener.

Clear all the prefixes, and then add a catch-all route to handle all requests coming to the port 8090.

Put the listener into started mode.

  
// program.cs (schematic) 

// source code is in the attached downloads 

// initialize HttpListener 
// 49152 - 65535 
const int PORT = 8090;

using HttpListener? mListener = new();

mListener.Prefixes.Clear();

mListener.Prefixes.Add("http://*:" + PORT + "/");

try
{
  mListener.Start();
}
catch (Exception)
{
  Console.WriteLine("Enter to close...");
  Console.ReadLine();
  return;
}

// initialize database 
using var dbContext = new ProjectContext();

Server server = new(dbContext);

string ip = Utility.GetMachineIPAddress();

Console.WriteLine($"Listening on http://{ip}:{PORT}");

Console.WriteLine($"OR use http://localhost:{PORT}");

// start listening for http requests 
while (mListener.IsListening)
{
  Console.WriteLine($"Waiting for a request Ctrl+C to quit...");

  _ = mListener.BeginGetContext(server.HandleRequest, mListener)
               .AsyncWaitHandle
               .WaitOne();
}
  
  

Create an instance of the ProjectContext.

Pass this reference to the Server object.

Obtain the IP of the machine by using the Utility class that we added just now. Display a message to the console that we are listening on this IP address and this port.

If the browser is running on this computer, then we can listen on localhost also.

A while loop is used to set the listener loop now. Notice that the BeginGetContext receives the HandleRequest delegate that we completed just now.

I suggest that you may please download the project from the attached downloads and examine all these files to join all the pieces together.

Test the Project so far

Compile the project and run it as an administrator.

If everything goes fine, then you should see a console window with a message.

Open a browser and navigate to the localhost port. You should see a response from the server.

This completes the verification so far. Thanks!


This Blog Post/Article "Step 2 - Set the HttpListener loop (C# Project on Classroom Voting Application)" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.