VISUAL STUDIO, DOCKER and immediate exit in Docker-compose file

This is a quick note for myself as much as anything else. In recent weeks I’ve started using a .NET Core Web App with Docker support option in Visual Studio during project creation. I’ve probably not read the documentation so fully accept I’m probably doing something wrong – now I’ve diagnosed what the issue is, I’ll see if the docs provide guidance as to what I’m doing wrong!

Whilst running within Visual Studio and Docker everything seems fine. But once I start to use the image within a docker-compose file the container starts and exits straight away. Initially I thought this was due to it struggling to get a connection to a RabbitMQ instance I was using. Eventually I removed all that code and still experienced the same behaviour.

I’ve finally determined that the application files were not being copied to the /app folder at some point during the DOCKERFILE execution. (Steps for how to determine this follow).

In order get round this from the command line, at the same level as the .csproj file I run the following command:

docker build -f Dockerfile ..

To determine this fault (and/or others for immediate exit of containers) its possible to run a container interactively and allow yourself a chance to look around the internals of the container and the file system:

docker run –it <image-name>

By navigating to the /app folder and running ‘ls’ I was able to see it was empty.

RABBITMQ, .NET CORE, DOCKER–Sending and receiving a message

This is blog post 3 of a short series on RabbitMQ, Docker and .NET Core. This post will focus on extending the previously created web application to connect and send a message to the RabbitMQ instance. We’ll then create a simple Console App that will read messages from the RabbitMQ instance.

Extend the .NET Web Application to Send A Message

1. Open the RabbitMQ Web Project previously created and we’ll create a new controller – namely RabbitMQController. To do this, right click on the ‘Controllers’ folder and select ‘Add Controller…’. From the dialog select API – Empty and give it a name of ‘RabbitMQController’.

2. When the controller has been created, we need to use Dependency Injection to provide the configuration so we can read the appropriate RabbitMQ address to connect to.

The code should look similar to the below (NB. type ctor[tab][tab] to automagically create the constructor signature for you).

private readonly IConfiguration Configuration;

public RabbitMQController(IConfiguration configuration)
{
   Configuration = configuration;
}
  1. Next we need to create a method which will:
    1. Create the connection to RabbitMQ
    2. Create the Queue to send to (to ensure it exists)
    3. Finally create a message and send. (NB. This particular code isn’t best practice, but should suffix to prove everything is working – we’ll comeback and refactor afterwards).

The code should look similar to the below:

        public void SendMessage()
        {
            var hostname = Configuration["rabbitMQHost"];
            var factory = new ConnectionFactory() { HostName = hostname };
            using var connection = factory.CreateConnection();
            using var channel = connection.CreateModel();

            channel.QueueDeclare(queue: "vle",
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            string message = "Create module";
            var body = Encoding.UTF8.GetBytes(message);

            channel.BasicPublish(exchange: "",
                                 routingKey: "vle",
                                 basicProperties: null,
                                 body: body);
        }

4. Next we need to redeploy this within our docker-compose file, so we need to add an extra environment variable for the ‘rabbitMQHost’ value above.

  web:
    image: "rabbitmqweb:dev"
    environment:
      - rabbitconnstr=amqp://guest:guest@rabbitmq:5672
      - rabbitMQHost=rabbitmq
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mynetwork

With this in place, we should be good to run:

docker-compose up -d

And we can navigate to http://localhost/api/RabbitMQ

Provided no errors are thrown, we should be able to go to the Management Console for RabbitMQ (localhost:15672) and log in with guest/guest. If we then select Queues, we should see our new queue (VLE) and it should have 1 message in it.

Create A Console App to Consume The Messages

Now open a new Visual Studio and Create a New C# Console Application.

newconsoleapp

Give the project a name of RabbitMQListener.

Next add the RabbitMQ.Client NuGet package.

Then in Main.cs add the following code:

            var factory = new ConnectionFactory() { HostName = "localhost" };
            using var connection = factory.CreateConnection();
            using var channel = connection.CreateModel();

            channel.QueueDeclare(queue: "vle",
                durable: false,
                exclusive: false,
                autoDelete: false,
                arguments: null);

            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);
                Console.WriteLine(" [x] Received {0}", message);
            };
            channel.BasicConsume(queue: "vle",
                                 autoAck: true,
                                 consumer: consumer);

            Console.WriteLine(" Press [enter] to exit.");
            Console.ReadLine();

Now run the Console Application. When it starts you should immediately see it output that it received the ‘Create Module’ message from earlier.

With the console app now running, also head to the Web App /api/RabbitMQ address and start reloading the page – you should see the Console App output the message being received.

received_messages

Tidying Up The Web Application Sending

Now we have everything working end-to-end, we can come back to the Sending API in the Web Application and instead of declaring the connection each time its called, we can re-use Dependency Injection to pass in the connection from the health check we already have configured.

The code in the RabbitMQ Controller constructor can change as follows (no need to pass the Configuration object anymore):

        private readonly IConnection RabbitMQConnection;

        public RabbitMQController(IConnection connection)
        {
            RabbitMQConnection = connection;
        }

Next the SendMessage() method opening can be simplified to the following:


        public void SendMessage()
        {
            using var channel = RabbitMQConnection.CreateModel();

            channel.QueueDeclare(queue: "vle",
                                 durable: false,
                                 exclusive: false,
                                 autoDelete: false,
                                 arguments: null);

            string message = "Create module";
            var body = Encoding.UTF8.GetBytes(message);

            channel.BasicPublish(exchange: "",
                                 routingKey: "vle",
                                 basicProperties: null,
                                 body: body);
        }

RabbitMQ and .NET Core Health Checks

This is blog post 2 of a short series on RabbitMQ, Docker and .NET Core. This post will focus on creating a .NET Core MVC application that has a dependency on the RabbitMQ installation – and we’ll use ASP.NET Core Health Checks to monitor the status of RabbitMQ.

1. Create the ASP.NET Core MVC Application

1.1 Start Visual Studio – create a new C# .NET Core project with Web Application (MVC). Also tick Add Docker Support and select Linux.

1.2 Start the web application to make sure everything was created as expected.

2. Implementing Health Checks

2.1 Stop the website and return to the Project. Right click on the Dependencies node in the solution and click ‘Manage NuGet packages…’. From there search and add 2 packages:

  • Microsoft.AspNetCore.Diagnostics.HealthChecks
  • AspNetCore.HealthChecks.Rabbitmq
healthchecknugets

2.2 In Startup.cs add the following 2 lines:

In ConfigureServices(…) add

services.AddHealthChecks();

In Configure(…) alter the UseEndpoints() to look as follows:

app.UseEndpoints(endpoints =>
 {
     endpoints.MapHealthChecks("/health");
     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}");
 });

2.3 Again start the App, and then add /health to the website URL. You should receive a page saying ‘Healthy’.

healthyresponse

2.4 To add the RabbitMQ Health Check, stop the app and return to Startup.cs and change the AddHealthCheck() line previously added to be as follows:

var connStr = Configuration["rabbitconnstr"].ToString();
Log.Logger.Error("Conn Str: " + connStr);

var factory = new ConnectionFactory()
{
     Uri = new Uri(connStr),
     AutomaticRecoveryEnabled = true
};

try
{
    var connection = factory.CreateConnection();
    services
         .AddSingleton(connection)
         .AddHealthChecks()
         .AddRabbitMQ();
}
catch (Exception e)
{
    Log.Logger.Error("Exception raised: " + e.Message);
}

(PS. During some debugging I added some support for Serilog logging – you could remove the Log.Logger lines if you haven’t got logging enabled).

We’re using a Configuration setting to set the URL of the RabbitMQ AMQP connection – we need to alter this when running in a docker environment – so next step is to add a configuration value to our appSettings.json file.

2.5 Add the rabbitconnstr setting to the appSettings.json file:

"rabbitconnstr": "amqp://guest:guest@<your ip address>:5672"

(To find your IP Address on windows, run ipconfig from the CMD window).

2.6 With all this in place, again run the application, check everything works (provided the Docker Container for RabbitMQ is running from Blog Post 1). If not, you should expect an unhealthy report, or an unable to connect to the address specified above).

3. Migrate everything to a Docker Compose File

Next it’d be much better to bring our environment together in a single file to declare the environment. At this stage we have 2 components, namely:

  • The RabbitMQ Installation
  • Our Simple Web App

But this will soon grow (indeed to add a UI to display the health checks).

For this we’ll create a docker-compose file.

3.1 Important – I found that the Docker Image for the RabbitMQ Web App from Visual Studio was being created without the web app in place. Not sure if this was something unique to me during debugging, but to get round it I did 2 things:

3.1.a Remove the docker images on the machine for RabbitMQ Web App. To do this – run docker ps –a and stop and remove any web app containers (to do this, docker stop <container_id> and docker rm <container_id>).

3.1.b  Build the Web App from the command line rather than Visual Studio. To do this, open a Powershell Window in the same directory as the DOCKERFILE for the project. Then run the following command:

docker build -f Dockerfile ..

3.2 Next create a docker-compose.yml file. I’ve place this in a completely standalone directory (c:\docker\rabbitmq-example) and create it as follows:

version: '3'
services:
  web:
    image: "rabbitmqweb:dev"
    environment:
      - rabbitconnstr=amqp://guest:guest@rabbitmq:5672
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mynetwork
  rabbitmq:
    image: "rabbitmq:management"
    container_name: rabbitmq
    ports:
      - "15672:15672"
      - "5672:5672"
    networks:
      - mynetwork
networks: 
  mynetwork: {}

NB. The big change here is the pointing of the RabbitMQ connection string to be rabbitmq:5672 as opposed to an IP Address. Because we’re now using docker-compose and specifying that these containers are running on the same network, they can now reference each other by name.

3.3 Get a Powershell window, navigate to your directory (mine, C:\Docker\Rabbit-Example) and run:

docker-compose up -d

3.4 Open a browser and navigate to http://localhost/ and check the web site appears. Also navigate to http://localhost/health and check the ‘Healthy’ status is returned.

3.5 Now test that if RabbitMQ stops, ‘Unhealthy’ is returned. To do this, run:

docker ps -a

to list all the containers. Then run docker stop <container_id> for the docker container that is rabbitmq:management.

stop-rabbitmq

Refresh the http://localhost/health URL and check ‘Unhealthy’ is returned.

To restart RabbitMQ, run:

docker start <container_id>

Refresh the http://localhost/health URL and check ‘Healthy’ is returned. (There may be a short delay as RabbitMQ starts).

4. Using the ASP.Net Core Health Checks UI to show results

We can now use a component to provide a GUI to show the health status of our services. Whats really nice is we can run this UI in another Docker container and we need to write minimal code to get this functionality.

4.1 Update the Web App project to customise the output of the health check for consumption by the UI component. Add the NuGet package ‘AspNetCore.HealthChecks.UI.Client’. Then update the Startup.cs file to match the following:

app.UseEndpoints(endpoints =>
 {
     endpoints.MapHealthChecks("/health", new HealthCheckOptions()
     {
         Predicate = _ => true,
         ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
     });
     endpoints.MapControllerRoute(
         name: "default",
         pattern: "{controller=Home}/{action=Index}/{id?}");
 });

(Note, I played it safe at this point, did a docker-compose down and then removed the previously rabbitmqweb:dev image (docker rmi <image_id> before then rebuilding it from the command line as per the earlier command).

4.2 Update the Docker Compose file to look as follows (the significant change is the addition of a new container which is the Health Checks UI):

version: '3'
services:
  web:
    image: "rabbitmqweb:dev"
    environment:
      - rabbitconnstr=amqp://guest:guest@rabbitmq:5672
    ports:
      - "80:80"
      - "443:443"
    networks:
      - mynetwork
  rabbitmq:
    image: "rabbitmq:management"
    container_name: rabbitmq
    ports:
      - "15672:15672"
      - "5672:5672"
    networks:
      - mynetwork
   healthui:
    image: "xabarilcoding/healthchecksui"
    environment: 
      - HealthChecksUI:HealthChecks:0:Name=rabbitmqweb
      - HealthChecksUI:HealthChecks:0:Uri=http://web:80/health
    ports:
      - "5000:80"
    networks:
      - mynetwork
networks: 
  mynetwork: {}

4.3 Now navigate to http://localhost:5000/healthchecks-ui/ and you should receive the following page:

healthchecks-ui

References / Links:

Microsoft Docs: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-3.1

RabbitMQ Health Checks: https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/tree/master/src/HealthChecks.RabbitMQ

Visual Studio and Docker:
https://aka.ms/containerfastmode

RabbitMQ on Windows (with Docker)

Hoping to pull together a little series on .NET Core and RabbitMQ – hosting RabbitMQ in Docker. Plan will be to:

  • Install Docker (on Windows) and a RabbitMQ Container (this post)
  • Set-up Health Checks in .NET Core
  • Connect to RabbitMQ and Publish a Message and consume via another app
  • Connect to RabbitMQ and do a pub-sub pattern for multiple consumers

1. Install Docker Desktop

I’m using Windows 10, and as such want to install Docker on it so I can run a container with RabbitMQ to avoid having to install it locally on my machine. Note to install Docker for Desktop you’ll need Windows 10 Pro or Windows 10 Enterprise – this won’t work with Windows 10 Home.

The version of Docker Desktop at time of writing is 2.2.0.0.

1.1 Google for Docker Desktop for Windows. One of the top results will take you to Docker to download Docker Desktop (note, you’ll need to sign up for a Docker account).

1.2 Start the installer and accept the defaults – we might come back to change being able to run Windows-based containers later, but RabbitMQ tends to be available as a Linux container.

1.3 Once the installation completes, you’ll need to do a restart.

1.4 After restarting you should have a Docker Desktop icon on your desktop, and you’ll see Docker starting up during log in. You can follow its progress from the notification window in the program bar.

1.5 Start a Powershell command window and type in ‘docker info’ and check things appear to be ok.

2. Installing a RabbitMQ Docker Container

The RabbitMQ Docker Images are available from: https://hub.docker.com/_/rabbitmq

From the Powershell command line enter the command:

docker pull rabbitmq:management

(This version of the image auto installs the management plug-in for RabbitMQ).

Once the layers have all downloaded enter the command:

docker images

You should get output similar to below:

rabbitmq-image

Next run:

docker run –d --hostname my-rabbit --name some-rabbit –p 15672:15672 –p 5672:5672 rabbitmq:management

Once the container starts you can confirm the image is running with:

docker ps -a
dockerps

(Note the container ID will be different on your machine).

Next run:

docker logs <container_id>

(only enough characters to make it unique), e.g. docker logs fb

Finally to prove its running the management plug-in, launch a browser and head to localhost:15672 and you’ll get the log in page for RabbitMQ (with default username / password of guest/guest).

Other notes:

To stop a running container:

docker stop <container_id>

To remove a container:

docker rm <container_id>

To remove an image:

docker image rm <image_id>

Introducing resilience with polly

So now we have the services talking to one another, we should think about building in some resilience – essentially a way for the apps to retry a connect in case of a transient fault, e.g. a network blip.

We’ll revert back to using the Typed Http Client Factories at Stage 3 of the previous post. As such the source code should have the following:

Startup.cs

services.AddHttpClient();

StudentController.cs

public class StudentController : Controller
{
    private readonly StudentRecordSystemService _studentRecordService;
    public StudentController(StudentRecordSystemService studentRecordService)
    {
         _studentRecordService = studentRecordService;
    }

    public async Task Index()
    {
        List students = await _studentRecordService.GetStudentsAsync();
        return View(students);
    }
}

Services/StudentRecordSystemService.cs

public class StudentRecordSystemService
{
     public HttpClient Client { get; }
     public StudentRecordSystemService(HttpClient client)
     {
         client.BaseAddress = new Uri("<a>http://localhost:5100/");</a>
         // Optionally add additional header information, e.g. keys etc... not required in this example.
         Client = client;
     }

    public async Task&lt;List&gt; GetStudentsAsync()
    {
         var response = await Client.GetAsync("api/Student");         
         response.EnsureSuccessStatusCode();
         return await response.Content.ReadAsAsync&lt;List&gt;();
     }
}

Once again, start up both services and check that the expected data is displayed.

Introducing Polly

1. In the ViewerWebApplication, right click on the Dependencies and ‘Manage NuGet Packages…’. Then search for Polly.Extensions.Http and click Install.

2. Update StudentRecordSystemService.cs to match the following (note the introduction of the logger via DI):

private readonly ILogger _logger;

public StudentRecordSystemService(HttpClient client, ILogger logger)

{
     _logger = logger;
     client.BaseAddress = new Uri("<a>http://localhost:5100/");</a>
     // Optionally add additional header information, e.g. keys etc... not required in this example.
     Client = client;
}

public async Task&lt;List&gt; GetStudentsAsync()
{
     var response = await Policy
             .Handle()
             .WaitAndRetryAsync(3, retryAttempt =&gt; TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timeSpan, context) =&gt;
             {
                 _logger.LogError($"Failed. Waiting {timeSpan}. Exception: {exception.Message}.");
             })
             .ExecuteAsync(async () =&gt; await Client.GetAsync("api/Student"));
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsAsync&lt;List&gt;();
}

And the Startup.cs has also updated to include the LoggerFactory:

public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
{
     Configuration = configuration;
     LoggerFactory = loggerFactory;
}

public IConfiguration Configuration { get; }
public ILoggerFactory LoggerFactory { get; }

Now if you start the DataViewer app, but not the StudentRecordSystemService, the page will now attempt multiple times (with an increasing time delay between calls, and logging between each event) before eventually giving up and throwing an exception.

Ultimately we’d want to declare these kind of Polly policies just once, and thats something we’ll look at next.

Communicating Between the Apps – HttpClient, HttpClientFactory, Refit

(Apologies – this turned into quite a lengthy blog post. Hopefully it took longer to write than it’ll take to follow along – if you need to, skip to the final bits about Typed HttpClientFactories and Refit).

(Apologies 2 – turned out I pasted the wrong code in for the Typed Http Clients – now amended. Thanks for the feedback).

To keep things fairly simple, the main aim of this blog post is to retrieve some data from the Student Record System (SRS) Web API application by a call from the DataViewer Web Application and display the data received. Not much love will go into the display of the data (that may come in a later blog post), this will be focusing on the concepts of communicating.

There are a number of ways to achieve this in .NET Core – some that are suitable for production code, and some, well, less so. I’m going to try and walk through each method in turn so we can explore them all, and perhaps try and point out some of the limitations with each approach (and defer to related blog articles from authors who can write much better than myself and cover the details in a much greater depth).

First Step – Removing SSL

To avoid having to set-up SSL certificates, change the launchSettings for the SRS project to be http – to do this expand the Properties node and open the launchSettings.json file. Change the address from the previous blog post from https://localhost:5100 to http://localhost:5100.

Setting Up Some Data

In the Web API application (SRS), click on the Project and click Add->New Folder and call the folder Models.
Then right click on this new folder and add a class and name it ‘Student’.

We’ll then add some properties to this Student class, keeping them simple for now.

(Quick Tip: A short-cut in Visual Studio exists to type prop and then press Tab and Tab afterwards. This generates the scaffold code for a property.)

We’re going to add strings for First Name, Last Name, Email and Student ID.

(Quick Tip: It would also be nice to include a photo, but we’ll flag this as something to come back to. You can flag TODO items in the code by using the comment syntax with TODO – as shown in the code below. All these entries then show up in the Task List in Visual Studio.)

We also need to add 2 constructors – one empty to aid serialisation (something we’ll come back to later) and then another to all the creation of a new student with the properties being set at creation.

(Quick Tip: A short-cut in Visual Studio exists to type ctor[tab][tab] and this will generate to constructor signature for you.)

namespace StudentRecordSystem.Models
{
    public class Student
    {
        // TODO: Add Student Photo/Image
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string StudentId { get; set; }
    }


        public Student()
        {
        }


        public Student(string firstName, string lastName, string email, string studentId)
        {
            FirstName = firstName;
            LastName = lastName;
            Email = email;
            StudentId = studentId;
        }
}

Next we’ll introduce a controller to then serve up a list of students. On the controllers folder, right click and Add->Controller…
Select API – Empty Controller and enter StudentController as the name.

In this controller we need to first annotate the controller itself with [Produces(“application/json”)] – I think this is the default anyway if not specified, but best to be explicit.

Then implement the Get method to return some student data – see below for the example code:

    [Route("api/[controller]")]
    [ApiController]
    [Produces("application/json")]
    public class StudentController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            List students = new List();
            students.Add(new Student("Anna", "Annabel", "a.a@uni.ac.uk", "1234"));
            students.Add(new Student("Brian", "Beater", "b.b@uni.ac.uk", "1235"));
            return Ok(students);
        }
    }

With this in place, start the app again (as per the previous blog post) and then view the /api/Student URL and you should get JSON output of the students specified above.

With this in place now, we can return to the Web App and make a call to this API and start to display the data.

Multiple Ways To Call Web APIs

1. The HttpClient – The simplest, but perhaps problematic

In the DataViewer Web App, right click on the Controllers folder and ‘Add’ and New Controller. This time select Empty MVC controller, and call it StudentController.

Within this controller will be default method called Index(). Within it place the following code:

public async Task Index()
{
var url = “http://localhost:5100/api/Student&#8221;;
var client = new HttpClient();
var response = await client.GetStringAsync(url);
return response;
//return View();
}

The code above essentially defines the URL to call, and then creates an HttpClient object. It then uses the helper method .GetStringAsync to call the address and read the string returned. Finally we return this string straight to the browser. In a future step we’ll look at tidying up this display.

Thats it, but this method comes with a caveat. Steve Gordon covers this in excellent detail in his little series on HttpClientFactory, so I don’t plan to recover it here ( https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore) (and I’ve also linked to the ASP.NET Monsters post about this issues), but essentially every time this method is executed we create a new HttpClient, and secondly the lifetime of the client isn’t handled very well, which could lead to issues in a scaled environment.

1b. Formatting the data output

Before going too much further, lets take the data returned and format it. For this we’ll use a 3rd party library – Newtonsoft.Json.

a. Right click on the DataViewer application project and select Manage NuGet Packages…
b. Ensure the ‘Browse’ tab is selected. You may not need to search for anything, it was the first list library for me. (otherwise search for Newtonsoft). Click and select the ‘Install’ option. (and OK any other pop-ups).
c. Next we need to add a model to represent the data returned. This will essentially be the same as the Student class created in the SRS project. As such, on the Models folder, right click and add new class, named Student. Enter the following code:

    public class Student
    {
        // TODO: Add Student Photo/Image
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string StudentId { get; set; }


        public Student()
        {
        }


        public Student(string firstName, string lastName, string email, string studentId)
        {
            FirstName = firstName;
            LastName = lastName;
            Email = email;
            StudentId = studentId;
        }
    }

d. Next, update our controller to now convert the Json String to a List of Student Models. The code is as follows:

        public async Task Index()
        {
            var url = "http://localhost:5100/api/Student";
            var client = new HttpClient();
            var response = await client.GetStringAsync(url);
            List students = JsonConvert.DeserializeObject(response);
            return View(students);
        }

The code above is very similar to the code previously provided except:
i. We’ve changed the return type.
ii. We’ve introduced JsonConvert (from Newtonsoft.Json) to convert the string returned to a List of students.
iii. We return to a view (which is created next) which uses the list of students to create a table (nothing pretty yet, but we’ll tidy up to use Bootstrap in a future blog post).

e. Create a new View. First, expand the Views folder and then create a new folder of ‘Student’ (NB. it must match the Controller name provided previously). Then right click on this new folder and create a View, give it the name of Index and create it. Enter the following code to just output a table:

@model List
@{
    ViewData["Title"] = "Index";
}


<h1>Index</h1>
<table>
    <tr>
        <th>Photo</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Email</th>
        <th>Student ID</th>
    </tr>
    @{
        foreach (var item in @Model)
        {
            <tr>
                <td>TBD</td>
                <td>@item.FirstName</td>
                <td>@item.LastName</td>
                <td>@item.Email</td>
                <td>@item.StudentId</td>
            </tr>
        }
    }
</table>

Run up both apps, and now see that you’re getting a Web Page with a table of the student data, as opposed to the raw JSON.

2. Using Named HttpClientFactories

An alternative method by using HttpClientFactories was introduced with .NET Core 2.1. In this approach you can declare a factory to be used instead of creating the client upon every request. Some of the benefits are slight efficiencies, the factory will manage the lifetimes of the HttpClients on your behalf and you’ve a central place for all requests to a location in your app, rather than potentially being scattered throughout your application (a good approach should you ever need to change the location of the other service). Its probably best explained with an example.

In Startup.cs of the DataViewerApp, add the following code to the ConfigureServices() method:

            services.AddHttpClient("StudentRecordSystem", s =>
            {
                s.BaseAddress = new Uri("http://localhost:5100/");
                // Optionally add additional header information, e.g. keys etc... not required in this example.
            });

Next we need to use Dependency Injection to pass the factory to the controller, before then using it instead of the calls we were previously making.

So in the StudentController.cs, introduce a Constructor (Remember the short-hand tip from earlier). Then make the controller look as follows:

    public class StudentController : Controller
    {
        private readonly IHttpClientFactory _clientFactory;

        public StudentController(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task Index()
        {
            List students = new List();
            var request = new HttpRequestMessage(HttpMethod.Get, "api/Student");
            var client = _clientFactory.CreateClient("StudentRecordSystem");

            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                students = await response.Content.ReadAsAsync();
            }
            return View(students);
        }
    }

Run up the apps and ultimately nothing has changed, but we’ve made the code a little bit better along the way.

3. Using Typed HttpClientFactories

One major drawback of the ‘Named’ approach above is that the strings need to match in both places and as such is liable to typo errors (and the compiler won’t help you with this – you’ll get run time problems). Also the calls to specific endpoints are also still scattered throughout the code.

An alternative method to overcome some of these limitations is typed clients. Again, an example may be the easiest way to explain.

In the DataViewer app, right click on the project and Add->New Folder and call it ‘Services’. Then right click on the folder and Add->Class… and call this StudentRecordSystemService. Then implement the code outlined below:

    public class StudentRecordSystemService
    {
        public HttpClient Client { get; }

        public StudentRecordSystemService(HttpClient client)
        {
            client.BaseAddress = new Uri("http://localhost:5100/");
            // Optionally add additional header information, e.g. keys etc... not required in this example.
            Client = client;
        }

        public async Task GetStudentsAsync()
        {
            var response = await Client.GetAsync("api/Student");
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsAsync();
        }
    }

This seems a bit more involved, but ultimately derives a number of benefits in the calling code base – the changes which are outlined below. In Startup.cs, replace the code we added previously with the following:

services.AddHttpClient();

And then change the code in the StudentController to match the code below:

    public class StudentController : Controller
    {
        private readonly StudentRecordSystemService _studentRecordService;

        public StudentController(StudentRecordSystemService studentRecordService)
        {
            _studentRecordService = studentRecordService;
        }

        public async Task Index()
        {
            List students = await _studentRecordService.GetStudentsAsync();
            return View(students);
        }
    }

Again, start the apps and check our student table comes back as expected. We’ve now got the benefit of all the calls being made in a single place, using a typed client factory, alongside creating a folder for a common area for all API calls to be stored and sit side-by-side. Finally, the code in the callers becomes much more simplified.

4. Using Refit

Finally, we can refine things further by using a 3rd party library called refit.

First, add a reference to the NuGet package ‘Refit’ (right click on DataViewer Project and click Manage NuGet Packages). Then browse for the ‘Refit’ library and install it.

Then add an Interface (Add->New Class) to the Services folder and implement the following code:

    public interface IStudentRecordSystemService
    {
        [Get("/api/Student")]
        Task GetStudentAsync();
    }

Next change the StudentController to match the following:

        public async Task Index()
        {
            // Missing try...catch for error handling.
            var srsClient = RestService.For("http://localhost:5100/");
            List students = await srsClient.GetStudentAsync();
            return View(students);
        }

And thats it. We can now remove code in the Startup.cs if we wish, along with the StudentRecordSystemService.cs. Again, run the app and ensure everything is running.

References:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2

HttpClientFactory in ASP.NET Core 2.1 (Part 1)

https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

https://www.projectcodify.com/creating-a-rest-api-wrapper-using-refit

In this 1st article I’m going to use a lot of scaffolding code from Microsoft to get up and running very quickly. I’m going to create a Web Site application (based upon MVC) and a separate Web API Application. I’m going to keep them in a single solution for now just to even further simplify the learning curve. They may break out at a late stage once we start to consider automated testing techniques.

I’m going to use Visual Studio (and 2019 version in particular) and use .NET Core 2.2. I’ll hopefully migrate to .NET Core 3.0 as soon as possible after release. However there is no reason why these can’t be created using the .NET Core command line tools and I’ll link to relevant articles showing how to do this at the end of this post. I’m also not aware of anything here that is specifically .NET Core 2.2 specific, so things may be okay with earlier versions (but please let me know if you find anything and I’ll amend).

The Web Application

At this stage I’m not sure what role this Web Application is going to fulfill in the ecosystem so I intend to use the very scaffold default MVC website project from Microsoft. This should allow flexibility going forward, or alternatively it may get fully replaced in the future. For now it’s sole purpose to display data from the other microservices that will be introduced.

1. Start Visual Studio and select ‘Create New Project’.
2. Select ‘ASP.NET Core Web Application’ and click ‘Next’.

createnewproject1-2

3. Select a suitable name (I’ve got for ViewerWebApplication) and I’ve place it under a folder called UniversityExample.
4. Then select Web Application (Model-View-Controller) and click Create.

createnewproject1-4

Once Visual Studio has created the application, hit F5 (or the Green play button with IIS Express) and check that the website runs. It should look similar to below:

createnewproject1-5

We’ll explore some of the code that has been generated as we start to bring in new functionality later in the series.

The Web API Application

Next. in the same solution, we’re going to add the out-the-box Web API application.

1. Right click on the solution and click ‘Add’ and ‘New Project’.
2. Again select ASP.NET Core Web Application and click Next.
3. For project name, call it ‘StudentRecordSystem’ and click Create.
4. On the next screen click ‘API’ and then click ‘Create’.

When launching applications, Visual Studio will launch them on random ports. In order to fix this, for the Web API application just created, expand ‘Properties’ and click on the ‘launchSettings.json’ file. Change the bottom section to specify a port for the https:// address and remove the http:// entry altogether. Your new file should look similar to the following:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:62421",
      "sslPort": 44382
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "StudentRecordSystem": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "applicationUrl": "https://localhost:5100",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Finally, rather than using Visual Studio to run this app, we’ll use the command line to it. Right click on the project ‘StudentRecordSystem’ and find the entry ‘Open Folder in File Explorer’. Once Windows Explorer opens, in the address bar, type cmd and press enter. This will launch a command line window at the directory specified.

On the command line type ‘dotnet run’ and wait for some output telling you the app is running, similar to below:

createnewproject2-1

Now head to a browser, and enter the address above, e.g. https://localhost:5001/api/Values and you should get the following output:

createnewproject2-2

Don’t worry about any security certificate warnings just for now.

Nothing too exciting just yet, but next we’ll look to introduce a new API controller which will provide some hardcoded student information, which we’ll then display on the Web Site application, creating a connection between the 2 applications.

The Kitchen sink project

So I’m not quite sure where this little blog series is going to go, but I’m going to try and develop a semi serious distributed .NET Core microservices solution. One thing about the series is it will be the ‘kitchen sink’ project – any technology I read about, hear about or otherwise will be thrown at it, primarily for my own learning purposes but perhaps to aid other readers as well.

I’m proposing to base it on the layout of a University’s architecture, so students and their learning might be a key focus. I’ve chosen this domain as (a) its one I know relatively well and (b) for others it should be relatively simple domain to understand and thus allow focus on the technology.

The below is all subject to change, but a broad outline for the kind of blog posts I intend to publish are below (and I’ll change these to link to the blog posts as they go live). The series will start with a focus on developing the ecosystem and development environment – by getting these in place early this should hopefully lead to faster subsequent development.

  • The Start – A .NET Core Web Site (A viewer of data) and a .NET Core Web API project (The Student Record System as a repository of data?)
  • Using Source Control with Git and Github
  • .NET Core Health Checks and a Health Check Microservice to view status
  • Logging Concepts
    • Serilog
    • ELK Stack exploration
  • Testing
    • Unit Testing with MSUnit
    • Selenium Testing for User Interfaces
  • Containers with Docker on Windows
    • Images and Containers
    • Mutli-stage images – build and run steps
    • Specifying environment variables
    • Mounting volumes & persistent storage
    • Docker compose
  • Continuous Integration & Continuous Deployment
    • TeamCity?
    • Azure?
  • Developing the Student Record System Web API:
    • Data
      • Student Data
      • Module Data
      • Programme Data
      • Associated Student Enrolments
    • Hardcoding the data above
    • Persistent storage to SQL Express with Entity Framework Core
    • Docker and SQL Express?
  • Raising Events from the Student Record System
    • RabbitMQ (via Docker?)
    • Sending events from SRS to RabbitMQ
  • Processing Events in (new) Virtual Learning Environment API Application

Other topics that will be introduced as some point will be things akin to Polly and Bundle Minifer.

Using Selenium to test across multiple browsers

When having a set of Selenium Tests one nice thing to be able to do would be to run these same tests against multiple browsers. The nicest solution to do this that I’ve discovered is to use the [TestFixture] attribute (Note: Only available in NUnit and not in MSTest).

This is a 2-step process:
1. Introduce a base factory class to initialise the web driver with the specific browser
2. Update the test classes to derive from the base class and add the TestFixture attributes.

Step 1:

So, for example, to test against Chrome and Internet Explorer 11, introduce the Factory Base Class:

    public class WebDriverFactory
    {
        public IWebDriver _driver;

        protected WebDriverFactory(BrowserType type)
        {
            _driver = WebDriver(type);
        }

        public enum BrowserType
        {
            IE11,
            Chrome
        }

        [TearDown]
        public void Close()
        {
            _driver.Close();
        }

        public static IWebDriver WebDriver(BrowserType type)
        {
            IWebDriver driver = null;

            switch (type)
            {
                case BrowserType.Chrome:
                    driver = ChromeDriver();
                    break;

                case BrowserType.IE11:
                    driver = IeDriver();
                    break;
            }

            return driver;
        }

        private static IWebDriver IeDriver()
        {
            InternetExplorerOptions options = new InternetExplorerOptions();
            options.EnsureCleanSession = true;
            options.IgnoreZoomLevel = true;
            IWebDriver driver = new InternetExplorerDriver(options);
            return driver;
        }

        private static IWebDriver ChromeDriver()
        {
            ChromeOptions options = new ChromeOptions();
            IWebDriver driver = new ChromeDriver(options);
            return driver;
        }
    }

Step 2 – Update Test Cases

With this in place, alter the test classes to:


    [TestFixture(BrowserType.Chrome)]
    [TestFixture(BrowserType.IE11)]
    public class NUnitTests : WebDriverFactory
    {
        public NUnitTests(BrowserType browser) : base(browser)
        {

        }

        [Test]
        public void Test1()
        {
            _driver.Navigate().GoToUrl("https://www.google.co.uk/");
            Assert.AreEqual("https://www.google.co.uk/", _driver.Url);
        }
    }

References

Stack Overflow: Testing framework on multiple browsers using selenium and NUnit

ApiController Validation in .Net Core 2.1/2.2 – Unit Testing

One of the additions in .NET Core 2.1 was the introduction of the [ApiController] attribute which could be applied at Controller level to validate the Model State, removing a lot of boilerplate code around the system. (NB. .NET Core 2.2 introduced the ability to set this at assembly level, e.g.


[assembly: ApiController]
namespace WebApp

This can be applied on any class, but I’ve choosen Startup.cs as a fairly obvious place for the configuration of most things.

Before .NET Core 2.1, a lot of code would have looked like the following:


public async Task<IActionResult> ActionToPerform(InputModel inputModel)
{
   if (!ModelState.IsValid)
   {
      return BadRequest();
   }
}

This boilerplate code is likely to be scattered the code base.

With this code in place, the invalid state of the model can tested using Unit Tests as follows (NB. The error must be manually set, as the model binding won’t happen without the middleware pipeline)


var controller = new StudentController();

// force model state to be invalid.
controller.ModelState.AddModelError(string.Empty, "Test Error");

Assert.IsFalse(controller.ModelState.IsValid, "Model state has remained valid.");

// Test for BadRequest being returned
Var result = await controller.ActionToPerform(model);
BadRequestResult badRequestResult = result as BadRequestResult;

Assert.IsNotNull(badRequestResult, "Wrong type returned.");

With this test in place, and hopefully passing, now comes the opportunity to introduce the ApiController attribute. In theory that means that the boilerplate code can be removed, and the pass will continue to pass.

Unfortunately that’s not the case. As per the binding on the model state, I’m assuming this happens elsewhere in the pipeline. Indeed it can be proved to be working outside of the Unit Tests by using Postman to trigger a request and see a BadRequest (400) be returned – provided an invalid object is supplied).

As such the Unit Test needs to change to check for the presence of ApiController – either on the Controller itself, or at Assembly level, depending on where its been declared. Here is the test for assembly level, on the StartUp class:

var customAttributes = typeof(Startup).Assembly.GetCustomAttributes(true);

var entry = customAttributes.FirstOrDefault(a => a.GetType().Name == "ApiControllerAttribute");

Assert.IsNotNull(entry);

References:

https://alenjalex.github.io/dev/dev/Asp.Net-Core-ModelState-Validation-Using-UnitTest/

https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/testing?view=aspnetcore-2.2

https://docs.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-2.1?view=aspnetcore-2.2

https://docs.microsoft.com/en-us/aspnet/core/web-api/index?view=aspnetcore-2.2