Category Archives: ASP.NET Core

Signal R and React

I haven’t touched Signal R in a while. I wanted to see how to work with Signal R in a React app.

Let’s start by creating a simple ASP.NET Core Web API server

  • Create an ASP.NET Core Web API application, I’m going to use minimal API
  • Add the NuGet package Microsoft.AspNetCore.SignalR.Client
  • Add the following to the Program.cs
    builder.Services.AddSignalR();
    builder.Services.AddCors();
    
  • We’ve added CORS support as we’re going to need tthis for testing locally, we’ll also need the following code
    app.UseCors(options =>
    {
      options.AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials()
        .SetIsOriginAllowed(origin => true);
    });
    

Before we can use Signal R we’ll need to add a hub, I’m going to add a file NotificationHub.cs with the following code

public class NotificationHub : Hub;

Now return to Program.cs and add the following

  • We need to map our hub into the application using
    app.MapHub<NotificationHub>("/notifications");
    
  • Finally let’s map an endpoint to allow us to send messages via Swagger to our clients. After the line above, add the following
    app.MapGet("/test", async (IHubContext<NotificationHub> hub, string message) =>
      await hub.Clients.All.SendAsync("NotifyMe",$"Message: {message}"));
    

Now we need a client, so create yourself a React application (I’m using TypeScript as usual with mine).

  • Add the package @microsoft/signalr, i.e. from yarn yarn add @microsoft/signalr
  • In the App.tsx we’re going to create the HubConnectionBuilder against out ASP.NET Core API server. We’ll then start the connection and finally watch for messages on the “NotifyMe” name as previously set up in the ASP.NET app, the code looks like this
    import { HubConnectionBuilder } from '@microsoft/signalr';
    
    function App() {
      const [message, setMessage] = useState("");
    
      useEffect(() => {
        const connection = new HubConnectionBuilder()
          .withUrl("http://localhost:5021/notifications")
          .build();
      
        connection.start();  
        connection.on("NotifyMe", data => {
          setMessage(data);
        });
      }, [])  
    
      return (
        <div className="App">
          {message}
        </div>
      );
    }
    

Make sure you start the ASP.NET server first, then start your React application. Now from the Swagger page we can send messages into the server and out to the React client’s connected to SignalR.

Messing around with MediatR

MediatR is an implementation of the Mediator pattern. It doesn’t match the pattern exactly, but as the creator, Jimmy Bogard states that “It matches the problem description (reducing chaotic dependencies), the implementation doesn’t exactly match…”. It’s worth reading his post You Probably Don’t Need to Worry About MediatR.

This pattern is aimed at decoupling the likes of business logic from a UI layer or request/response’s.

There are several ways we can already achieve this in our code, for example, using interfaces to decouple the business logic from the UI or API layers as “services” as we’ve probably all done for years. The only drawback of this approach is it requires the interfaces to be either passed around in our code or via DI and is a great way to do things. Another way to do this is, as used within UI, using WPF, Xamarin Forms, MAUI and others where we often use in-process message queues to send messages around our application tell it to undertake some task and this is essentially what MediatR is giving us.

Let’s have a look at using MediatR. I’m going to create an ASP.NET web API (obviously you could use MediatR in other types of solutions)

  • Create an ASP.NET Core Web API. I’m using Minimal API, so feel free to check that or stick with controllers as you prefer.
  • Add the nuget package MediatR
  • To the Program.cs file add
    builder.Services.AddMediatR(cfg => 
      cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
    

At this point we have MediatR registering services for us at startup. We can passing multiple assemblies to the RegisterServicesFromAssembly method, so if we have all our reqeust/response code in multiple assemblies we can supply just those assemblies. Obviously this makes our life simpler but at the cost of reflecting across our code at startup.

The ASP.NET Core Web API creates the WeatherForecast example, we’ll just use this for our sample code as well.

The first thing you’ll notice is that the route to the weatherforecast is tightly coupled to the sample code. Ofcourse it’s an example, so this is fine, but we’re going to clean things up here and move the implementation into a file named GetWeatherForecastHandler but before we do that…

Note: Ofcourse we could just move the weather forecast code into an WeatherForecastService, create an IWeatherForecastService interface and there’s no reason not to do that, MediatR just offers and alternative way of doing things.

MediatR will try to find a matching handler for your request. In this example we have no request parameters. This begs the question as to how MediatR will match against our GetWeatherForecastHandler. It needs a unique request type to map to our handler, in this case the simplest thing to do is create yourself the request type. Mine’s named GetWeatherForecast and looks like this

public record GetWeatherForecast : IRequest<WeatherForecast[]>
{
    public static GetWeatherForecast Default { get; } = new();
}

Note: I’ve created a static method so we’re not creating an instance for every call, however this is not required and obviously when you are passing parameters you will be creating an instance of a type each time – this does obviously concern me a little if we need high performance and are trying to write allocation free code, but then we’d do lots differently then including probably not using MediatR.

Now we’ll create the GetWeatherForecastHandler file and the code looks like this

public class GetWeatherForecastHandler : IRequestHandler<GetWeatherForecast, WeatherForecast[]>
{
  private static readonly string[] Summaries = new[]
  {
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
  };

  public Task<WeatherForecast[]> Handle(GetWeatherForecast request, CancellationToken cancellationToken)
  {
    var forecast = Enumerable.Range(1, 5).Select(index =>
      new WeatherForecast
      {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
      })
    .ToArray();

    return Task.FromResult(forecast);
  }
}

At this point we’ve created a way for MediatR to find the required handler (i.e. using the GetWeatherForecast type) and we’ve created a handler to create the response. In this example we’re not doing any async work, so we just wrap the result in a Task.FromResult.

Next go back to the Program.cs or if you’ve used controllers, go to your controller. If using controller you’ll need the constructor to take the parameters IMediator mediator and assign to a readonly field in the usually way.

For our minimal API example, go back to the Program.cs file remove the summaries variable/code and then change the route code to look like this

app.MapGet("/weatherforecast",  (IMediator mediator) => 
  mediator.Send(GetWeatherForecast.Default))
.WithName("GetWeatherForecast")
.WithOpenApi();

We’re not really playing too nice in the code above, in that we’re not returning results code, so let’s add some basic result handling

app.MapGet("/weatherforecast",  async (IMediator mediator) => 
  await mediator.Send(GetWeatherForecast.Default) is var results 
    ? Results.Ok(results) 
    : Results.NotFound())
  .WithName("GetWeatherForecast")
  .WithOpenApi();

Now for each new HTTP method call, we would create a request object and a handler object. In this case we send no parameters, but as you can no doubt see, for a request that takes (for example) a string for your location, we’d create a specific type for wrapping that parameter and the handler can then be mapped to that request type.

In our example we used the MediatR Send method. This sends a request to a single handler and expects a response of some type, but MediatR also has the ability to Publish to multiple handlers. These types of handlers are different, firstly they need to implement the INotificationHandler interface and secondly no response is expected when using Publish. These sorts of handlers are more like event broadcasts, so you might use then to send a message to an email service or database code which sends out an email upon request or updates a database.

Or WeatherForecast sample doesn’t give me any good ideas for using Publish in it’s current setup, so let’s just assume we have a way to set the current location. Like I said this example’s a little contrived as we’re going to essentially set the location for everyone connecting to this service, but you get the idea.

We’re going to add a SetLocation request type that looks like this

public record SetLocation(string Location) : INotification;

Notice that for publish our type is implementing the INotification interface. Our handles look like this (my file is named SetLocationHandler.cs but I’ll put both handlers in there just to be a little lazy)

public class UpdateHandler1 : INotificationHandler<SetLocation>
{
  public Task Handle(SetLocation notification, CancellationToken cancellationToken)
  {
    Console.WriteLine(nameof(UpdateHandler1));
    return Task.CompletedTask;
  }
}

public class UpdateHandler2 : INotificationHandler<SetLocation>
{
  public Task Handle(SetLocation notification, CancellationToken cancellationToken)
  {
    Console.WriteLine(nameof(UpdateHandler2));
    return Task.CompletedTask;
  }
}

As you can see, the handlers need to implement INotificationHandler with the correct request type. In this sample we’ll just write messages to console, but you might have a more interesting set of handlers in mind.

Finally let’s add the following to the Program.cs to publish a message

app.MapGet("/setlocation", (IMediator mediator, string location) =>
  mediator.Publish(new SetLocation(location)))
.WithName("SetLocation")
.WithOpenApi();

When you run up your server and use Swagger or call the setlocation method via it’s URL you’ll see that all your handlers that handle the request get called.

Ofcourse we can also Send and Post messages/request from our handlers, so maybe we get the weather forecast data then publish a message for some logging system to update the logs.

MediatR also includes the ability to stream from a requests where our request type implements the IStreamRequest and our handlers implement IStreamRequestHandler.

If we create a simple request type but this one implements IStreamRequest for example

public record GetWeatherStream : IStreamRequest<WeatherForecast>;

and now add a handler which implements IStreamRequestHandler, something like this (which delay’s to just give a feel of getting data from somewhere else)

public class GetWeatherStreamHandler : IStreamRequestHandler<GetWeatherStream, WeatherForecast>
{
  public async IAsyncEnumerable<WeatherForecast> Handle(GetWeatherStream request, 
    [EnumeratorCancellation] CancellationToken cancellationToken)
  {
    var index = 0;
    while (!cancellationToken.IsCancellationRequested)
    {
      await Task.Delay(500, cancellationToken);
      yield return new WeatherForecast
      {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Data.Summaries[Random.Shared.Next(Data.Summaries.Length)]
      };

      index++;
      if(index > 10)
        break;
    }
  }
}

Finally we can declare our streaming route using Minimal API very simply, for example

app.MapGet("/stream", (IMediator mediator) =>
  mediator.CreateStream(new GetWeatherStream()))
.WithName("Stream")
.WithOpenApi();

ASP.NET core and Ingress rules

Note: This post was written a while back but sat in draft. I’ve published this now, but I’m not sure it’s relevant to the latest versions etc. so please bear this in mind.

You’ve implemented a service using ASP.NET deployed it to Kubernetes and all worked great, you then deploy a front end to use that service (as per the example in the Project Tye repo) again, all worked well. Whilst the Ingress mapped the path / to your front end services, the CSS an JS libs all worked fine, but then you change you Ingress route to (for example)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  namespace: default
spec:
  rules:
    - http:
        paths:
          - path: /frontend
            pathType: Prefix
            backend:
              service: 
                name: frontend
                port: 
                  number: 80

In the above, the rule for the path /frontend will run the frontend service. All looks good so you navigate to http://your-server-ip/frontend and, wait a moment. The front end and backend services are working, i.e. you see some HTML and you see results from the backend service but Edge/Chrome/whatever reports 404 of bootstrap and your CSS.

The simplest solution, but with the downside that you are putting knowledge of the deployment route into your front end service is to just add the following to Startup.cs

app.UsePathBase("/frontend");

Obviously if you’re using tye or your own environment configuration, you might prefer to get the “/frontend” string from the environment configuration instead of hard coding.

Project Tye

In the last few posts I’ve been doing a lot of stuff with ASP.NET core services and clients within Kubernetes and whilst you’ve seen it’s not too hard to create a docker container/image out of services and clients, deploy to the local registry and then deploy using Kubernetes scripts, after a while you’re likely to find this tedious and want to wrap everything into a shell/batch script – an alternative is to use Project Tye.

What is Project Tye?

I recommend checking out

The basics are Project Tye can be used to take .NET projects, turn them into docker images and generate the deployments to k8s with a single command. Also Project Tye allows us to undeploy with a single command also .

Installing Project Tye

I’m using a remote Ubuntu server to run my Kubernetes cluster, so we’ll need to ensure that .NET 3.1 SDK is installed (hopefully Tye will work with 5.0 in the near future but for the current release I needed .NET 3.1.x installed.

To check your current list of SDK’s run

dotnet --list-sdks

Next up you need to run the dotnet tool to install Tye, using

dotnet tool install -g Microsoft.Tye --version "0.7.0-alpha.21279.2"

Obviously change the version to whatever the latest build is – that was the latest available as of 6th June 2021.

The tool will be deployed to

  • Linux – $HOME/.dotnet/tools
  • Windows – %USERPROFILE%\.dotnet\tools

Running Project Tye

It’s as simple as running the following command in your solution folder

tye deploy --interactive

This is the interactive version and you’ll be prompted to supply the registry you wish to push your docker images to, as we’re using localhost:32000, remember to set that as your registry, or better still we can create a tye.yaml file with configuration for Project Tye within the solution folder, here’s an example

name: myapp
registry: localhost:32000
services:
- name: frontend
  project: frontend/razordemo.csproj
- name: backend
  project: backend/weatherservice.csproj

Now with this in place we can just run

tye deploy

If you want to create a default tye.yaml file then run

tye init

Project Tye will now build our docker images, push to localhost:3200 and then generate deployments, services etc. within Kubernetes based upon the configuration. Check out the JSON schema for the Tye configuration file tye-schema.json for all the current options.

Now you’ve deployed everything and it’s up and running, but Tye also includes environment configurations, for example

env:
  - name: DOTNET_LOGGING__CONSOLE__DISABLECOLORS
    value: 'true'
  - name: ASPNETCORE_URLS
    value: 'http://*'
  - name: PORT
    value: '80'
  - name: SERVICE__RAZORDEMO__PROTOCOL
    value: http
  - name: SERVICE__RAZORDEMO__PORT
    value: '80'
  - name: SERVICE__RAZORDEMO__HOST
    value: razordemo
  - name: SERVICE__WEATHERSERVICE__PROTOCOL
    value: http
  - name: SERVICE__WEATHERSERVICE__PORT
    value: '80'
  - name: SERVICE__WEATHERSERVICE__HOST
    value: weatherservice

Just add the following NuGet package to your project(s)

<PackageReference Include="Microsoft.Tye.Extensions.Configuration" Version="0.2.0-*" />

and then you can interact with the configuration using the TyeConfigurationExtensions classes from that package. For example using the following

client.BaseAddress = Configuration.GetServiceUri("weatherservice");

Ingress

You can also include ingress configuration within your tye.yaml, for example

ingress: 
  - name: ingress  
    # bindings:
    #   - port: 8080
    rules:
      - path: /
        service: razordemo

however, as an Ingress might be shared across services/applications this will not be undeployed using the undeploy command, so not to affect potentially other applications, you can force it to be undeployed using

kubectl delete -f https://aka.ms/tye/ingress/deploy

See Ingress for more information on Tye and Ingress.

Dependencies

Along with our solution/projects we can also deploy dependencies as part of the deployment, for example if we need to also deploy a redis cache, dapr or other images. Just add the dependency to the tye.yaml like this

- name: redis
  image: redis
  bindings:
    - port: 6379

Deploying an ASP.NET core application into a Docker image within Kubernetes

In the previous post we looked and an “off the shelf” image of nginx, which we deployed to Kubernetes and were able to access externally using Ingress. This post follows on from that one, so do refer back to it if you have any issues with the following configurations etc.

Let’s look at the steps for deploying our own Docker image to k8s and better still let’s deploy a dotnet core webapp.

Note: Kubernetes is deprecating its support for Docker, however this does not mean we cannot deployed docker images, just that we need to use the docker shim or generated container-d (or other container) images.

The App

We’ll create a standard dotnet ASP.NET core Razor application which you can obviously do what you wish to, but we’ll take the default implementation and turn this into a docker image and then deploy it to k8s.

So to start with…

  • Create a .NET core Razor application (mine’s named razordemo), you can do this from Visual Studio or using
    dotnet new webapp -o razordemo --no-https -f net5.0
    
  • If you’re running this on a remote machine don’t forget to change launchSettings.json localhost to 0.0.0.0 if you need to.
  • Run dotnet build

It’d be good to see this is all working, so if let’s run the demo using

dotnet run

Now use your browser to access http://your-server-ip:5000/ and you should see the Razor demo home page, or use curl to see if you get valid HTML returned, i.e.

curl http://your-server-ip:5000

Generating the Docker image

Note: If you changed launchSettings.json to use 0.0.0.0, reset this to localhost.

Here’s the Dockerfile for building an image, it’s basically going to publish a release build of our Razor application then set up the image to run the razordemo.dll via dotnet from a Docker instance.

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY razordemo.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "razordemo.dll"]

Now run docker build using the following

docker build . -t razordemo --no-cache

If you want to check the image works as expect then run the following

docker run -d -p 5000:80 razordemo 

Now check the image is running okay by using curl as we did previously. If all worked you should see the Razor demo home page again, but now we’re seeing if within the docker instance.

Docker in the local registry

Next up, we want to deploy this docker image to k8s.

k8s will try to get an image from a remote registry and we don’t want to deploy this image outside of our network, so we need to rebuild the image, slightly differently using

docker build . -t localhost:32000/razordemo --no-cache

Reference: see Using the built-in registry for more information on the built-in registry.

Before going any further, I’m using Ubuntu and micro8s, so will need to enable the local registry using

microk8s enable registry

I can’t recall if this is required, but I also enabled k8s DNS using

microk8s.enable dns

Find the image ID for our generated image using

docker images

Now use the following commands, where {the image id} was the one found from the above command

docker tag {the image id} localhost:32000/razordemo
docker push localhost:32000/razordemo

The configuration

This is a configuration based upon my previous post (the file is named razordemo.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  selector:
    matchLabels:
      run: webapp
  replicas: 1
  template:
    metadata:
      labels:
        run: webapp
    spec:
      containers:
      - name: webapp
        image: localhost:32000/razordemo
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webapp
  labels:
    run: webapp
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: webapp
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: razor-ingress
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: webapp
            port: 
              number: 80

Now apply this configuration to k8s using (don’t forget to change the file name to whatever you named your file)

kubectl apply -f ./razordemo.yaml

Now we should be able to check the deployed image, by either using the k8s dashboard or run

kubectl get ep webapp

Note the endpoint and curl to that endpoint, if all worked well you should be able to see the ASP.NET generate home page HTML and better still access http://your-server-ip from another machine and see the webpage.

Creating an ASP.NET client using Eureka and Steeltoe

In the previous post Eureka Server (using Steeltoe) revisited I went through the process of running a Eureka instance and creating a default template based ASP.NET Web API which registers itself with the Eureka server as the application weatherapi.

Let’s now create a basic ASP.NET MVC project to interact with the Eureka server, get an instance of the API service and use it.

Note: This post is almost 100% based on the Channel 9 video Service Discovery with Steeltoe, so credit should go to Tim Hess for the sample code.

  • Create an ASP.NET Core application and then select Web Application (Model View Controller)
  • Add the following NuGet packages, Steeltoe.Discovery.ClientCore, Steeltoe.Discovery.Eureka and System.Net.Http.Json
  • Update the appsettings.json with the following
      "eureka": {
        "client": {
          "serviceUrl": "http://locahost:8761/eureka/",
          "shouldFetchRegistry": "true",
          "shouldRegisterWithEureka": false,
          "validateCertificates": false
        }
      }
    

    Notice we set shouldFetchRegistry to true as we want to get the latest registry information, and we set shouldRegisterWidthEureka to false as, in this case, we don’t want to register this client. Ofcourse change this is your client also exposes services.

  • Within Startup.cs, ConfigureServices add the following
    services.AddDiscoveryClient(Configuration);
    services.AddHttpClient("weather", 
       client => client.BaseAddress = new Uri("http://weatherapi/"))
          .AddServiceDiscovery();
    

    The interesting thing here is that we associate the name “weather” with an address which looks like a URL but really is the name of the service within Eureka that we want to access. Then, by using the AddServiceDiscovery this will be converted to an instance URL representing the instance of the service associated with the app name.

    Note: we can also use load balancing strategies, such as round robin or random.

  • Finally within the Startup.cs, method Configure, add the following
    app.UseDiscoveryClient();
    
  • We’re going to simply copy the WeatherForecast class from the service and add to the client, here it is

    public class WeatherForecast
    {
       public DateTime Date { get; set; }
       public int TemperatureC { get; set; }
       public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
       public string Summary { get; set; }
    }
    
  • Within HomeController.cs we need to add a variable of type IHttpClientFactory which will be injected into the constructor and then uses to call defined HTTP client, this will then used service discovery to return the URL of an instance (as already discussed) and then we’re use that to call the weather API to get a list of values. Here’s the changes required to HomeController.cs
    private readonly IHttpClientFactory _httpClientFactory;
    
    public HomeController(IHttpClientFactory httpClientFactory, ILogger<HomeController> logger)
    {
       _httpClientFactory = httpClientFactory;
       _logger = logger;
    }
    
    public async Task<IActionResult> Index()
    {
       var client = _httpClientFactory.CreateClient("weather");
       var weather = await client.GetFromJsonAsync<IList<WeatherForecast>>("weatherforecast");
       return View(weather);
    }
    
  • Finally, let’s change the Index.cshtml to display the weather forecast data returned in the Index method. Firstly we declare the @model and then simply create a table to output the items from that model in columns and rows, so here’s the change to be added to the top of the file
    @model IList<EurekaWebClient.Controllers.WeatherForecast>
    

    and within the main div, just add the following

    <table class="table">
       <tr><th>Day of Week</th><th>Summary</th><th>Temp</th></tr>
       @foreach (var weather in Model)
       {
          <tr><td>@weather.Date.DayOfWeek</td><td>@weather.Summary</td><td>@weather.TemperatureF</td></tr>
       }
    </table>
    

That should be it. Ensure Eureka is running, your service is up and you should now see the weather service data in a nice little table.

Blazor/ASP.NET core on docker

I wanted to get a Blazor server application up and running on Ubuntu within a docker container (I’m running the whole thing on a Raspberry Pi 4 with Ubuntu Server).

The first stage for this post will simply be about creating a Dockerfile and creating a Blazor server application via the dotnet template.

We’re going to want the latest version of dotnet core, so let’s start by creating a very bare bones Dockerfile which will create an image based upon
mcr.microsoft.com/dotnet/core/sdk:3.1, it will also expose the standard HTTP port used in the Blazor server template, i.e. port 5000

Here’s the Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1
ENV APP_HOME /home
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
EXPOSE 5000
CMD [ "bash" ]

To build this image run the following

docker rmi dotnet-env --force
docker build -t dotnet-env .

The first line is solely there to remove any existing image (which will be especially useful whilst developing the image). The second line will build the Dockerfile and name it dotnet-env.

Once built, let’s run the image to see all is good with it. So simply run

docker run -it --rm -p 5000:5000 -v /home/share:/home/share dotnet-env

In this example we’ll run docker in interactive mode and map ports using -p to map the host port 5000 to the exposed port in the image. We’ll also also created a volume link from the container to the host.

Once we’ve run the image up we should be placed into a BASH command prompt, now we can simply run

dotnet new blazorserver -o MyServer

To create the project MyServer, once created cd into the MyServer folder. Now run

dotnet run

A kestrel server should start up, and you might be able to access the server using http://server-ip-address. I say might, because you may well see an error at startup, saying something like

Unable to bind to http://localhost:5000 on the IPv6 loopback interface: ‘Cannot assign requested address’.

What you need to do is go into the Properties folder and open launchSettings.json, change the line

"applicationUrl": "https://localhost:5001;http://localhost:5000",

to

"applicationUrl": "http://0.0.0.0:5001;http://0.0.0.0:5000",

Next Step

This obvious next step to our docker build is to create a Docker image which contains our application and runs it when the container is started. We’re going to build and publish the using dotnet publish -c Release -o publish and then include the published files in our docker container, alternatively you might prefer to have the Dockerfile build and publish the project as part of its build process.

For now let’s just build our Blazor server application, then publish it to a folder.

We’re going to host the application in Kestrel, so before we go any further open the appsetting.json file from the publish folder and add the following

"Kestrel": {
  "EndPoints": {
    "Http": {
      "Url": "http://0.0.0.0:5000"
    }   
  }
},

Now we’ll make the changes to the Dockerfile to copy the published folder to the image and start up the Kestrel server when the image is run, here’s the Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1

ENV APP_HOME /home
RUN mkdir -p $APP_HOME

WORKDIR $APP_HOME

COPY ./BlazorServer/BlazorServer/publish ${APP_HOME}

EXPOSE 5000

CMD [ "dotnet", "BlazorServer.dll" ]

Now you should be able to access your server using http://your_server_name:5000.

ASP.NET Core and Web API

In the past I’ve looked at ServiceStack for developing REST services however there are alternatives to ServiceStack, one of which is Web Api. Let’s create a simple starter project using ASP.NET Core and Web Api to implement our services.

  • Create a new ASP.NET Core Web Application
  • Select API

The generated service supplies a Controllers/ValuesController.cs file which includes a controller class with the various REST methods.

Controllers partition your application’s API, so for example you might have an AuthenticateController solely for authentication methods and another controller for CRUD operations on a datasource.

For example this is a simple AuthenticateController class

[Route("[controller]")]
[ApiController]
public class AuthenticateController : ControllerBase
{
   [HttpPost]
   public ActionResult<AuthenticateReponse> Post([FromBody] User user)
   {
      return new AuthenticateReponse
      {
         Token = $"{user.Name}|1234"
      };
   }
}

Here, the Route is set to [controller] so our URL would be http://localhost/Authenticate. In this example we intend for the user to send data within an HTTP POST method, passing a User object (which is a simple name/password object). An AuthenticateResponse object is return via the ActionResult.

The ActionResult return value allows us to return HTTP 200 OK or using subclasses such as BadRequest for an HTTP 400 and there are other derivations for other HTTP responses.

The Action<> allows us to return a 200 OK without the need of using the Ok() method, although this can be used if preferred.

When using different return responses it’s good to also document these by adding

[ProducesResponseType(400)]

This indicates that the method may return an HTTP 400. This is useful for use with Open API tools such as Swagger.

As can be seen in the Post method, the User object is expected within the method’s body and the HttpPost attribute declare the method uses HTTP POST, the method name is superfluous in terms of the REST API.

The different HTTP methods

The code generated by the template creates a ValuesController with various HTTP methods, which I’ll recreate here for reference

HTTP GET without URL parameters

[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
   return new string[] { "value1", "value2" };
}

HTTP GET with URL parameters

[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
   return "value";
}

HTTP POST without URL parameters

[HttpPost]
public void Post([FromBody] string value)
{
}

HTTP PUT with URL parameters

[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}

HTTP DELETE with URL parameters

[HttpDelete("{id}")]
public void Delete(int id)
{
}

HTTP HEAD

[HttpGet, HttpHead]
public IActionResult Head()
{
   return Ok();
}

We would request the headers using HttpHead along with an HttpGet.

HTTP PATCH

[HttpPatch]
public IActionResult Patch()
{
   return Ok();
}

Adding Swagger support

As per ASP.NET Core Web API help pages with Swagger / OpenAPI we can add Swagger support using either Swashbuckle.AspNetCore or NSwag (there may be others). Both are simple to add to our project.

Websockets and Kestrel

In my post Websockets with Fleck we looked at using Fleck to create a websocket based server, let’s not turn our attention to integrating websockets with an ASP.NET core application using Kestrel.

This is NOT meant to implement anything near as complete as the Fleck library, but is just an example of how we might implement websockets in a Kestrel application and we’re going to try to emulate the code we had for that Fleck example.

  • Create an ASP.NET Core Web Application
  • Select the Empty template

Let’s clean out the Properties | launchSettings.json by remove the iisExpression and IIS Express profile, so mine looks like this

{
  "profiles": {
    "YOUR_APP_NAME": {
      "commandName": "Project",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Obviously keep your application name in the YOUR_APP_NAME string.

Now in Program.cs we’ll add code to allow us to use the 8181 port, so the CreateHostBuilder method should now look like this

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
      webBuilder.UseStartup<Startup>();
      webBuilder.UseUrls("http://*:8181");
    });

Delete everything within the Startup.cs’s Configure method and replace with

app.UseWebSockets();

this adds the websocket middleware.

We’re actually going to then create our own middleware to handle web socket requests, so
let’s create the file WebSocketManagerMiddleware.cs. Here’s the code…

public class WebSocketManagerMiddleware
{
  private readonly RequestDelegate _next;
  private readonly WebSocketConnection _connection;

  public WebSocketManagerMiddleware(
    RequestDelegate next, 
    WebSocketConnection connection)
  {
    _next = next;
    _connection = connection;
  }

  public async Task InvokeAsync(HttpContext context)
  {
    if (context.WebSockets.IsWebSocketRequest)
    {
      var socket = await context.WebSockets.AcceptWebSocketAsync();

      _connection.OnOpen(socket);

      await Receive(socket, (result, buffer) =>
      {
        switch (result.MessageType)
        {
          case WebSocketMessageType.Text:
            var s = Encoding.UTF8.GetString(buffer);
            _connection.OnMessage(socket, s.Substring(0, Math.Max(0, s.IndexOf('\0'))));
            break;
          case WebSocketMessageType.Binary:
            _connection.OnBinary(socket, buffer);
            break;
          case WebSocketMessageType.Close:
            _connection.OnClose(socket);
            break;
        }
      });
    }
    await _next(context);
  }

  private async Task Receive(
    WebSocket socket, 
    Action<WebSocketReceiveResult, 
    byte[]> handler)
  {
    var buffer = new byte[1024];

    while (socket.State == WebSocketState.Open)
    {
      var result = await socket.ReceiveAsync(buffer: 
        new ArraySegment<byte>(buffer),
        cancellationToken: CancellationToken.None);

      handler(result, buffer);
    }
  }
}

Middleware expects an Invoke or InvokeAsync method that returns a Task. In our example, we firstly ensure this is a websocket request before accepting the request. In this example we pass in a WebSocketConnection instance (we’ll have a look at that next), but basically this middleware intercepts the websockets and then calls the WebSocketConnection class in a manner similar to the way our Fleck server was implemented, i.e. using OnOpen, OnClose, OnMessage and OnBinary calls.

At the end of the code we pass the context through to the next piece of middleware in the pipeline.

The reason we have a WebSocketConnection class is to just give us an abstraction for creating our actual application websocket code.

Add the file WebScocketConnection.cs, this is going to expose OnOpen, OnClose etc. extension points as well as a SendAsync method for sending data to the connected client, here’s the code

public class WebSocketConnection
{
  public void Start(Action<WebSocketConnection> connection)
  {
    connection(this);
  }

  public Action<WebSocket> OnOpen { get; set; } = 
    webSocket => { };
  public Action<WebSocket> OnClose { get; set; } = 
    webSocket => { };
  public Action<WebSocket, string> OnMessage { get; set; } = 
    (webSocket, message) => { };
  public Action<WebSocket, byte[]> OnBinary { get; set; } = 
    (webSocket, bytes) => { };

  public async Task SendAsync(WebSocket socket, string message)
  {
    if (socket.State == WebSocketState.Open)
    {
      await socket.SendAsync(
        new ArraySegment<byte>(Encoding.ASCII.GetBytes(message),
          0,
          message.Length),
        WebSocketMessageType.Text,
        true,
        CancellationToken.None);
      }
    }
  }
}

Finally let’s return to Startup.cs and the Configure method, here’s the full code

var websocketServer = new WebSocketConnection();
websocketServer.Start(connection =>
{
  connection.OnOpen = socket => Console.WriteLine("OnOpen");
  connection.OnClose = socket => Console.WriteLine("OnClose");
  connection.OnMessage = async (socket, message) =>
  {
    Console.WriteLine($"OnMessage {message}");
    await connection.SendAsync(socket, $"Echo: {message}");
  };
  connection.OnBinary = (socket, bytes) => 
    Console.WriteLine($"OnBinary {Encoding.UTF8.GetString(bytes)}");
});

app.UseWebSockets();
app.UseMiddleware<WebSocketManagerMiddleware>(websocketServer);

References

WebSockets support in ASP.NET Core
Write custom ASP.NET Core middleware

Writing a GraphQL service using graphql-dotnet and ASP.NET core

Introduction

GraphQL is classified as a query language for querying your data.

I’m not going to go into a full description of it’s capabilities/uses, for that go to the GraphQL website, but one thing which has always interested me, is an API where the client application can tell the server API what data to return. Thus allow the client to reduce bandwidth as well as reduce the amount of data being deserialised to exactly what the client application requires – obviously particularly useful for mobile applications.

Using graphiql-dotnet and ASP.NET core

We’re going to be using graphql-dotnet and ASP.NET core to implement a basic

  • Create an ASP.NET Core Web Application, mine’s named GraphQLService
  • Select an Empty project
  • I’m going to uncheck “Configure for HTTPS”
  • Now add the following packages via NuGet
    • GraphQL.Server.Transports.AspNetCore
    • GraphQL.Server.Transports.WebSockets
    • GraphQL.Server.Ui.GraphiQL
    • GraphQL.Server.Ui.Playground
    • GraphQL.Server.Ui.Voyager
    • GraphQL-Parser

Note: We’ve added the GraphQL.Server.Ui.Playground package to allow us to write queries within an interactive environment (http://localhost:5000/ui/playground) which also allows us to view the schema definitions etc. We’ve added GraphQL.Server.Ui.Voyager which adds a cool schema/type viewer (http://localhost:5000/ui/voyager). Finally we’ve added GraphQL.Server.Ui.GraphiQL to allow us to use the well known GraphiQL interactive environment (http://localhost:5000/ui/graphiql), feel free to remove all or some of these as required.

Remove the code from the Configure method of the Startup.cs file except for the following

if (env.IsDevelopment())
{
   app.UseDeveloperExceptionPage();
}

Now we’re going to add some tools, the playground (an interactive query web app) the voyager for digging into the schema and the GraphQL endpoint. So add the following to the Startup.cs below the code shown above

app.UseWebSockets();
app.UseGraphQLWebSockets<PersonSchema>("/graphql");
app.UseGraphQL<PersonSchema>("/graphql");
app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()
{
   Path = "/ui/playground",
});
app.UseGraphiQLServer(new GraphiQLOptions
{
   GraphiQLPath = "/ui/graphiql",
   GraphQLEndPoint = "/graphql"
});
app.UseGraphQLVoyager(new GraphQLVoyagerOptions()
{
   GraphQLEndPoint = "/graphql",
   Path = "/ui/voyager"
});

Finally within Startup.cs, within the ConfigureServices method add the following code, which will register some types (which we’ll be supplying soon, the schema and query classes) and configures the GraphQL middleware.

services.AddSingleton<PersonSchema>();

services.AddGraphQL(options =>
{
   options.EnableMetrics = true;
   options.ExposeExceptions = true;
})
.AddWebSockets()
.AddDataLoader();

Schema

At this point we can see a need for the classes PersonSchema and PersonQuery. As you’ve realised, we are needing to create a schema object which will define our queries and types that we can query against. We’ll just create the bare bones schema class in PersonSchema.cs

public class PersonSchema : Schema
{
   public PersonSchema()
   {
      Query = new PersonQuery();
   }
}

Queries

Now we’ll create a bare PersonQuery.cs file which will be the starting point for some query functionality

public class PersonQuery : ObjectGraphType
{
}

At this point we could compiler and run the ASP.NET core application and navigate to http://localhost:5000/ui/playground to view the playground web application. However there’s not much we can do at this point.

Let’s add a domain object which represents a Person object, so add the following class. This represents the class that might come from a database or from another service

public class Person
{
   public string Name { get; set; }
}

For graphql-dotnet to allow us to use this class we need to wrap our Person object within a graphql-dotnet type, so create the following PersonType

public class PersonType : ObjectGraphType<Person>
{
   public PersonType()
   {
      Field(o => o.Name);
   }
}

In this case graphql-dotnet will now map fields to the Person object via the Field methods. This type then needs to be added to the PersonQuery class, to allow for querying again PersonType (and then ultimately Person) data. So add the PersonQuery constructor with the following code

public PersonQuery()
{
   Field<ListGraphType<PersonType>>("people", resolve: context => new[]
   {
      new Person {Name = "Scooby Doo"},
      new Person {Name ="Daphne Blake" },
      new Person {Name ="Shaggy Rogers" },
      new Person {Name ="Velma Dinkley" },
      new Person {Name ="Fred Jones" }
   });
}

Now if we run the application and navigate to http://localhost:5000/ui/playground we’ll find a Schema tab which displays information about how to query our data and if we write the following query

{
  people {
     name
  }
}

and run it from the playground we’ll see a list of the names from our Person data.

The previous example omitted the query keyword, i.e. it would look like this in a more formal definition of a query

query {
  people {
     name
  }
}

We can also implement our own query methods that might be called using the following (the C# for this is shown further down the post)

find(input : "Scooby Doo") {
   name
}

Or we can create a query which takes parameters/arguments from query variables. This example shows the use of an operation name, i.e. getPerson.

Note: operation names can be used for queries, mutations and subcriptions.

query getPerson($input : String) {
  find(input : $input) {
    name
  }
}

along with the input

{
  "input" : "Scooby Doo"
}

We’ll now create an input type in C# which will work with the above queries, such as

public class PersonInputType : InputObjectGraphType
{
   public PersonInputType()
   {
      Field<StringGraphType>("input");
   }
}

Now add another field to the PersonQuery constructor like this

Field<PersonType>(
   "find", 
   arguments: new QueryArguments(
      new QueryArgument<StringGraphType> 
      {
         Name = "input"
     }),
   resolve: context =>
   {
      var i = context.GetArgument<string>("input");
      return new Person {Name = i.ToString()};
   });

In the above, we essentially have created a new field in the PersonQuery which acts like a method, it is of type PersonType (the return type in this instance) and named find it takes a single argument of type string and expects the argument name of input. Finally it resolves to a method which we can then write something more substantial code for querying our data. In this simple example we’re just returning the argument passed in.

Note: We can also create multiple queries, so for example say we want to locate Person A and Person B and return data as a single result, we could write

{
   A : find(input : "Scooby") {
      name
   }
   B : find(input : "Shaggy") {
      name
   }
}

In the above query we use a GraphQL feature using aliases, the A and B acting like variable names. This allows us to create multiple queries against the same query/method.

When our queries start to become more complicated, we might prefer to place the field selection into, what’s know as a fragment, for example

{
   find(input : "Scooby") {
    ...fields
   }
}

fragment fields on PersonType {
  name
}

Obviously in our example it took more work to create and use the fragment that just using the field name, but you get the idea. However fragments can also be reused if we had multiple queries, so hence with more fields this become a much more useful technique.

Mutations

We’ve created our queries, which is great for scenarios where we’re simply reading data, but we can also create mutations, i.e. code that changes our data.

We can write a GraphQL mutation like this

mutation addPerson($input : String) {
   addPerson(input : $input)
   {
      name
   }
}

along with input such as

{
  "input" : "Scooby Doo"
}

What we now need to do is create our mutation class, like this

public class PersonMutation : ObjectGraphType<Person>
{
   public PersonMutation()
   {
      Field<PersonType>("addPerson",
         arguments: new QueryArguments(
            new QueryArgument<StringGraphType> {Name = "input"}),
         resolve: context =>
         {
            var n = context.GetArgument<string>("input");
            return new Person { Name = n };
         });
    }
}

We also need to alter the PersonSchema constructor to look like this

public PersonSchema()
{
   Query = new PersonQuery();
   Mutation = new PersonMutation();
}

Subscriptions

As you might expect, subscriptions are a way to handle something similar to events, hence we can connect to our service and received updates. Here’s a simple example of a query

subscription {
  personAdded {
    name
  }
}

Here’s a simple example of a subscription, to keep things simple this will simply send a message every second to any subcribers

public class PersonSubscription : ObjectGraphType
{
   public PersonSubscription()
   {
      AddField(new EventStreamFieldType
      {
         Name = "personAdded",
         Type = typeof(PersonType),
         Resolver = new FuncFieldResolver<Person>(ResolvePerson),
         Subscriber = new EventStreamResolver<Person>(SubscribePerson)
      });
   }

   private Person ResolvePerson(ResolveFieldContext context)
   {
      return context.Source as Person;
   }

   private IObservable<Person> SubscribePerson(ResolveEventStreamContext context)
   {
      return Observable.Interval(
         TimeSpan.FromSeconds(1))
           .Select(s => new Person 
           {
              Name = s.ToString()
           });
   }
}

Now we need to update the schema constructor like this

public PersonSubscription()
{
   AddField(new EventStreamFieldType
   {
      Name = "personAdded",
      Type = typeof(PersonType),
      Resolver = new FuncFieldResolver<Person>(ResolvePerson),
      Subscriber = new EventStreamResolver<Person>(SubscribePerson)
   });
}

Further reading

Refer to the GraphQL site for in depth examples and more complete explanations of the everything I’ve discussed above.

Also check out the GraphQL.net web site for graphql-dotnet documentation.

If you’re interested in code for the project, it’s available on GitHub.