Tag Archives: ASP.NET

Testing an Asp.Net Web App Using Integration Testing

I’ve recently been playing around with a tool called Scrutor. I’m using this in a project and it’s working really well; however, I came across an issue (not related to the tool per se). I had created an interface, but hadn’t yet written a class to implement it. Scrutor realised this was the case and started moaning at me. Obviously, I hadn’t written any unit tests around the non-existent class, but I did have a reasonably good test coverage for the rest of the project; however the project wouldn’t actually run.

To be clear, what I’m saying here is that, despite the test suite that I had running successfully, the program wouldn’t even start. This feels like a very askew state of affairs.

Some irrelevant background, I had a very strange issue with my Lenovo laptop, whereby, following a firmware update, the USB-C ports just stopped working – including to accept charge – so my laptop died. Sadly, I hadn’t followed good practice, with commits, and so part of my code was lost.

I’ve previously played around with the concept of integration tests in Asp.Net Core+, so I thought that’s probably what I needed here. There are a few articles and examples out there, but I couldn’t find anything that worked with Asp.Net 6 – so this is that.

In this post, we’ll walk through the steps necessary to add a basic test to your Asp.Net 6 web site. Note that this is not comprehensive – some dependencies will trip this up (e.g. database access); however, it’s a start. The important thing is that the test will fail where there are basic set-up and configuration issues with the web app.

The Test Project

The first step is to configure a test project. Obviously, your dependencies will vary based on what tools you decide to use, but the following will work for Xunit:

<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />		
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />

(See this post on Xunit libraries for details on the basic Xunit dependency list for .Net 6.)

The key here is to set-up the Web Application Factory:

var appFactory = new WebApplicationFactory<Program>();
var httpClient = appFactory.CreateClient();

We’ll come back to some specific issues with this exact code shortly, but basically, we’re setting up the in-memory test harness for the service (which in this case, is our web-app). You can obviously do this for an API in exactly the same manner. The rest of our test then looks like this:

using var response = await httpClient.GetAsync("/");

Assert.True(response.IsSuccessStatusCode);

If your test fails, and you want a fighting chance of working out why, you may wish to replace the assertion with this:

var content = await response.Content.ReadAsStringAsync();

That’s basically it; however, as that currently stands, you’ll start getting errors (some that you can see, and some that you cannot). It makes sense to make the HttpClient static, or at least raise it to the class level, as you only need to actually create it once.

Accessing the Main Project

The other issue that you’ll get here is that, because we’re using .Net 6 top level statements in Program.cs, it will tell you that it’s inaccessible. In fact, top level code does generate an implicit class, but it’s internal. This can be worked around my simply adding the following to the end of your Program.cs code:

public partial class Program { } // so you can reference it from tests

(See the references below for details of where this idea came from.)

Summary

In this post, we’ve seen how you can create an integration test that will assert that, at the very least, your web app runs. This method is much faster than constantly having to actually run your project. It obviously makes no assertions about how it runs, and whether what it’s doing is correct.

References

Example of testing top level statements

GitHub Issue reporting error with top level statements being tested

Stack Overflow question on how to access Program.cs from in program using top level statements

Tutorial video on integration tests

Configuring Docker to use a Dev Cert When Calling out to the Host Machine

I’ve recently being wrestling with trying to get an ASP.Net service within a docker container to call a service running outside of that container (but on the same machine). As you’ll see as we get further into the post, this is a lot more difficult that it first appears. Let’s start with a diagram to illustrate the problem:

The diagram above illustrates, at a very basic level, what I was trying to achieve. Essentially, have the service running inside docker call to a service outside of docker. In real life, the service (2) would be remote: very likely on a different physical server, and definitely have an allocated domain address; however, for this experiment, it lives on the same physical (or virtual) machine as the docker host.

Before I continue, I must point out that the solution to this comes by way of some amazing help by Rob Richardson: he gave a talk at NDC Porto that got be about 70% of the way there, and helped me out further to actually get this working!

Referencing a Service outside of Docker from within Docker

Firstly, let’s consider a traditional docker problem: if I load Asp.Net Service (2) then I would do so in a browser referencing localhost. However, if I reference localhost from within docker, that refers to the localhost of the container, not the host machine. The way around this is with host.docker.internal: this gives you a path to the host machine of the docker container.

Certificates – The Problem

Okay, so onto the next (and main) issue: when I try to call Asp.Net Service (2) from the docker container, I get an SSL error:

The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors

Why

The reason has to do with the way that certificates work; and, in some cases, don’t work. Firstly, if you watch the linked video, you’ll see that the dev-cert functionality in Linux has a slight flaw – in that it doesn’t do anything*. Secondly, because you’re jumping (effectively) across machines here, you can’t just issue a dev cert to each anyway, as it will be a different dev cert; and thirdly, dev-certs are issues to localhost by default: but as we saw, we’re actually trying to contact host.docker.internal.

Just to elaborate on the trust chain; let’s consider the following diagram:

In this diagram, Certificate A is based on the Root Certificate – if the machine trusts the root certificate, then it will trust Certificate A – however, the same machine will only trust Certificate B if it is explicitly told to do so.

This means that the dev cert for the container will not be trusted on the host, and vice-versa – as neither have any trust chain and relationship – this is the problem, but it’s also the solution.

Okay, so that’s the why – onto the how…

mkcert

Let’s start with introducing mkcert – this is an incredibly useful tool that hugely simplifies the whole process; it can be installed via chocolatey:

choco install mkcert

If you don’t want to use Chocolatey, then the repo is here.

Essentially, what this allows us to do is to create a trusted root certificate, from which, we can base our other certificates. So, once this is installed, we can create a new trusted root certificate like this:

mkcert -install

This installs our trusted root certificate; which we can see here:

This will also generate the following files (on Windows, these will be in %localappdata%\mkcert):

rootCA.pem
rootCA-key.pem

These are the root certificates, so the next thing we need is a certificate that covers the specific domain. You can do that by simply calling mkcert with the appropriate domain(s):

mkcert localhost host.docker.internal

This creates a valid cert for both localhost and host.docker.internal:

localhost.pem
localhost-key.pem

You may wish to rename these to be something slightly more descriptive, but for the purpose of this post, this is sufficient.

Docker

Almost there now – we have our certificates, but we need to copy them to the correct location. Because we’ve run mkcert -install the root certificate is already on the local machine; however, we now need that on the docker client (Asp.Net Service (1) from the diagram above). Firstly, let’s download a mkcert.exe from here for the relevant version of Linux that you’re running.

Let’s copy both the rootCA.pem and rootCA-key.pem into our Asp.Net Service (1) project and then change the dockerfile:

. . .
FROM base AS final
WORKDIR /app
COPY mkcert /usr/local/bin
COPY rootCA*.pem /root/.local/share/mkcert/
RUN chmod +x /usr/local/bin/mkcert \
  && mkcert -install \
  && rm -rf /usr/local/bin/mkcert 
COPY --from=publish /app/publish .
. . .

A few things to mention here:

1. The rest of this file is from the standard Ast.Net docker file. See this post for possible modifications to that file.
2. Each time you execute a RUN command docker makes a temporary image, hence why combining three lines (on line 7) with the && makes sense.
3. When you run the mkcert -install it will pick up the root certificate that you copy into the /root/.local/share/mkcert.
4. Make sure that these lines apply to the runtime version of the image, and not the SDK version (there’s absolutely no point in adding a certificate to the SDK version).
5. The last line (rm -rf /usr/local/bin/mkcert) just cleans up the mkcert files.

The Service

The final part is to copy the generated certificates (localhost.pem and localhost-key.pem) over to the service application (Asp.Net Service (2)). Finally, in the appsettings.json, we need to tell Kestrel to use that key:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "localhost-host.pem",
        "KeyPath": "localhost-host-key.pem"
      }
    }
  }
}

That’s it! If you open up the Asp.Net Service (2), you can check the certificate, and see that it’s based on the mkcert root:

References and Acknowledgements

As I said at the start, this video and Rob himself helped me a lot with this – so thanks to him!

It’s also worth mentioning that without mkcert this process would be considerably more difficult!

Footnotes

* actually, that’s not strictly true – Rob points out in his video the nuance here; but the takeaway is that it’s unlikely to be helpful

Beginner’s Guide to Docker – Part 2 – Debugging a Docker Build

In this post I covered the very basics of getting started with Docker. Once you start to experiment, you’ll need to learn how to debug and investigate some of the unexpected things that happen.

Caveat

In this post, you’ll see references to WebbApplication4 and WebApplication5. This is simply because, during creating the post, I switched, didn’t realise the screenshots were a mix of both, and now don’t consider it worth my time to change. Just consider the two interchangeable.

Asp.Net 5

For this example, we’ll use the default dockerfile that comes with Asp.Net 5; however, we’ll build it up ourselves. Just build a new Asp.Net project.

When setting this up, do not enable docker support:

If we had enabled docker support, we would get a docker file – so let’s build that (this is taken directly from that file). We’ll put it in the same directory as the sln file.

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["WebApplication4/WebApplication4.csproj", "WebApplication4/"]
RUN dotnet restore "WebApplication4/WebApplication4.csproj"
COPY . .
WORKDIR "/src/WebApplication4"
RUN dotnet build "WebApplication4.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication4.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication4.dll"]

To give a quick rundown of what’s happening in this file:
– Where you can see `AS base`, `AS build`, etc, these are known as stages (we’ll come back to that).
– The `FROM` statements are telling docker to build on an existing image.
– WORKDIR changes the running directory inside the container: if this directory doesn’t exist, then the command will create it.
– COPY does what you would think: copies whatever you tell it from the first parameter (the host machine) into the second (the docker container)
– RUN executes a command in the container.
– Finally, ENTRYPOINT tells docker what to do when it starts the container – it our case, we’re running `dotnet WebApplication4.dll`.

Building and Running

To build the image, go to the application’s solution directory (where your sln file is located):

docker build -t pcm-web-app-4 . 

Once the image builds successfully, you can run it like this:

docker run pcm-web-app-4 -p 1234:80

In this post, we’re going to be more interested in debugging the build itself that running the container. Having said that, we should quickly visit the port mapping (-p); the command above is mapping the port 80 inside the container (in the docker file, we issues an EXPOSE on that port) to post 1234 on the host (so you would navigate to localhost:1234 on the host machine to connect).

Listing Docker Containers, Stopping, Killing, and Attaching

You can list running docker containers by issuing the command:

docker ps

Once you have the container ID (highlighted in the image), you can do various things with it.

Attach

To attach to the instance:

docker attach 229

Notice that I only used the first three letters of the container instance ID? That’s because docker will let you abridge the ID to the smallest unique set of numbers. This will attach to the container, and you can browse around.

Stop and Kill

If you want to remove an instance, the command is:

docker stop 229

Which will stop the current instance. However, the instance is still there. You can see it by calling:

docker ps -a

To remove the instance completely, you’ll need to call:

docker rm 229

However, you will only be able to remove the container once it’s stopped.

Now that we’ve covered some basics, let’s get into the debugging.

Target

The first useful tip here is to use the target parameter. To build the dockerfile above, you may do something like this (as shown above):

docker build -t web-app-5 . 

That will build the entire image; but if you get an issue, it may fail at an intermediate stage; in that case, you can break down the build; for example:

docker build --target build -t pcm-web-app-5 .

You can then have a look around at the build files by attaching to the container:

docker run -it pcm-web-app-5

A similar technique can be used if you’re getting issues with the entry point not functioning as expected.

ENTRYPOINT

In the dockerfile, you can simply comment out the ENTRYPOINT:

#ENTRYPOINT ["dotnet", "WebApplication5.dll"]

Now, if you run the container; for example:

docker run -d -it pcm-web-app-5

-d launches the container in detached mode; you can then attach:

docker attach eb5

You can then manually run the entry point; for example:

Finally, let’s see how we can see details of a running container.

Inspecting the Container

While the container is running, there’s a set of metadata that is accessible. This contains things like the IP, ports, Mac Address, Name, etc… To view this, call:

docker inspect <container ID>

For example:

docker inspect 41b

There’s a lot of information here, and you can traverse it by using the -f parameter to specify a specific attribute. For example:

docker inspect -f '{{ .State.Status }}' 07a

This will show specific properties without the need to scroll through the full JSON.

Getting Started With SignalR

SignalR is an open source framework allowing bi-directional communication between client and server. Basically, it uses a stack of technologies; the idea being that the Signalr framework will establish the “best” way to maintain a bi-directional data stream, starting with web sockets, and falling all the way back to simply polling the server.

The following gives the basics of establishing a web site that can accept Signalr, and a console app that can send messages to it.

Create project

Let’s go MVC:

Hubs

Hubs are the way in which the Signalr service communicates with its clients. Obviously, the term service here may not actually represent a service.

To add a hub class, select the project, right-click and “New Item..”:

This adds the file, along with new references:

The code above that gets added is:

public void Hello()
{
    Clients.All.hello();
}

Clients.All returns a dynamic type, so we lose intellisense at this point. It’s important that the signature of this method is exactly correct, and that it is decorated with the name of the hub, and that it is decorated with the name of the hub; so let’s replace with:

[HubName("MyHub1")]
public class MyHub1 : Hub
{
    public void Hello(string message)
    {
        Clients.All.Hello(message);
    }
}

Change Startup.cs:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
 
        app.MapSignalR();
    }
}

For all this to actually do anything, the next thing to do is hook up the JavaScript:

$(function () {
    // Declare a proxy to reference the hub. 
    var hub = $.connection.MyHub1;
    // Create a function that the hub can call to broadcast messages.
    hub.client.hello = function (message) {
 
        alert("Hello");
    };
 
    
    $.connection.hub.start()
        .done(function () { console.log("MyHub1 Successfully Started"); })
        .fail(function () { console.log("Error: MyHub1 Not Successfully Started"); })
});

Effectively, once we receive a message, we’re just going to display an alert. Once the event handler is wired up, we try to start the hub.

Next, reference the required files in BundleConfig.cs:

bundles.Add(new ScriptBundle("~/bundles/signalr").Include(
    "~/Scripts/jquery-3.1.1.min.js").Include(
    "~/Scripts/jquery.signalR-2.2.1.js"));

These are referenced in _Layout.cshtml; remember also that, because SignalR references Jquery, you’ll need to remove other references to Jquery:

<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")    
@Scripts.Render("~/bundles/signalr")    
<script type="text/javascript" src="~/signalr/hubs"></script>
<script type="text/javascript" src="~/Scripts/Notification.js"></script>

. . .

    </div>
    
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>

Notes on Bundles

The purpose of bundling is to shrink the size of the bundled files. The idea being that small files make for a speedy web-site.

Console App

The next step is to create an application that can fire a notification to the page. In this case, I’m using a console app, just because I like to see everything working with console apps.

Start with a NuGet Reference:

The code:

class Program
{
    static void Main(string[] args)
    {
        Console.Write("Message: ");
        string message = Console.ReadLine();
 
        HubConnection connection = new HubConnection("http://localhost:4053/");
        IHubProxy hub = connection.CreateHubProxy("myHub1");
                    
        connection.Start().Wait();
        hub.Invoke<string>("Hello", message).Wait();            
 
        Console.WriteLine("Sent");
        Console.ReadLine();
    }
}

And that’s it – you should be able to send a message to the web site from the console app. The examples that are typically given elsewhere on the net are chat rooms, but this clearly has many more uses.

Some abstract notes that I made while researching this.

Adding:

Version 1

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
 
    >RouteTable.Routes.MapHubs(new HubConfiguration());
 
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
…

Gives:

Severity Code Description Project File Line Source Suppression State
Error CS0619 ‘SignalRRouteExtensions.MapHubs(RouteCollection, HubConfiguration)’ is obsolete: ‘Use IAppBuilder.MapSignalR in an Owin Startup class. See http://go.microsoft.com/fwlink/?LinkId=320578 for more details.’ SignalRTest3 C:\Users\Paul\documents\visual studio 14\Projects\SignalRTest3\SignalRTest3\Global.asax.cs 18 Build Active

This was for v1 Signal R – superseded in 2.

CORS

During trying to get this working, the prospect of using CORS came up. This enables cross domain requests, which are typically prohibited.

Proxies

The generated Proxy can be viewed (navigate to http://localhost:4053/signalr/hubs):

 $.hubConnection.prototype.createHubProxies = function () {
        var proxies = {};
        this.starting(function () {
            // Register the hub proxies as subscribed
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, true);
this._registerSubscribedHubs();
        }).disconnected(function () {
            // Unsubscribe all hub proxies when we "disconnect".  This is to ensure that we do not re-add functional call backs.
            // (instance, shouldSubscribe)
            registerHubProxies(proxies, false);
        });
proxies['MyHub1'] = this.createHubProxy('MyHub1'); 
        proxies['MyHub1'].client = { };
        proxies['MyHub1'].server = {
            hello: function (message) {
                return proxies['MyHub1'].invoke.apply(proxies['MyHub1'], $.merge(["Hello"], $.makeArray(arguments)));
             }
        };
return proxies;
    };

References:

https://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client

https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-getting-started-with-signalr

https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client

https://github.com/SignalR/SignalR/wiki/Faq

http://stackoverflow.com/questions/42108193/signalr-test-project-not-working-as-expected

http://www.jeffreyfritz.com/2015/05/where-did-my-asp-net-bundles-go-in-asp-net-5/