Monthly Archives: February 2024

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();

Using the Redux Toolkit

I’ve covered Redux on this blog in the past, but the “modern” approach is to use the Redux Toolkit which cleans up and simplifies some of the code. In some cases I found it more complicated, because it obviously does a lot and abstracts a lot.

Going over the basics

Let’s go over some of the basics of Redux first…

Redux is a global state management system. It’s made up of three key parts, the store which as the name suggests, is where your state is stored. The shape of the data within the store is defined by your needs (i.e. no prerequisites). The store is used for global state, so to partition the store for ease of working with your data, we create slices. We’ll generally have slices based upon the domain, so maybe a slice for feature flags, another for component state, another for an API and so on.

Next we have actions, these are (as the name suggests) used to perform actions against our data. In Redux we have an object with a type and some (optional) payload. The type is the name of the action,

{type: "INCREMENT", payload: 3 }

Finally we have reducers which we use, based upon the type of actions, to carry out updates of state. It’s important to remember that Redux data should be immutable, hence the reducer doesn’t change the state itself, but instead copies the state updating the new version of the state before passing this new copy back

RTK

Let’s now create a bare minimum Redux toolkit store

Note: I’m using TypeScript in my examples

import { configureStore } from "@reduxjs/toolkit"

export const store = configureStore({
  reducer: {}.
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

The code above just creates a Redux store, there’s no reducers. This store is not too useful at this point, but bare with me, let’s now quickly pivot to look at how we might use our store in a React app

import { Provider } from "react-redux"
import { store } from "./state/store.ts"

<Provider store={store}>
   <App />
</Provider>

By using the Provider at the App level our store is now available to our entire app.

As mentioned, the store above is pretty useless at the moment, let’s use the example from Redux/RTK to add a simple counter to the store. This represents one slice of the store hence, we’ll name the file counter.slice.ts (although RTK tends to go with counterSlice.ts style names).

First create a folder for your slices within the store folder, i.e. store/slices. Ofcourse you can place these where best suit, but this is the folder structure I’m using here.

Create the file counter.slice.ts inside store/slices. This, as you probably realised, will contain the counter slice within the state manager, i.e. the data specific to the counter

import { createSlice } from "@reduxjs/toolkit";

interface CounterState {
  value: number;
}

const intialState: CounterState = {
  value: 0;
};

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  },
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

In this example we appear to be mutating state, but behind the scenes createSlice handles copying our state (RTK uses Immer to handle such immutability), if you prefer you can use the more obvious state.value + 1.

This file includes the CounterState interface (obviously not required for pure JavaScript) which is a representation of what our counter state will look like, in this case we just store a number, the current counter value. We also have these actions within the reducer, increment and decrement.

We create an initialState so our store has any defaults or starting points, for example in this case we set count initially to zero.

Next, we need to export our actions using export const { increment, decrement } = counterSlice.actions and finally for this section, we now need to register the slice with the store, this is accomplished by adding it to the store reducer, like this

import counterReducer from "./counter/counterSlice";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  }
});

At this point, everything is hooked up in the store, so let’s use our increment/decrement actions. We use the useSelector to get our current state and useDispatch to dispatch the actions to the the store

import { useSelector } from "react-redux";
import { RootState } from "../state/store";
import { decrement, increment } from "../state/counter/counterSlice";

export const Counter = () => {
  const count = useSelector((states: RootState) => state.counter.value);
  const dispatch = useDispatch(); // dispatch actions

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>

}

Passing parameters

These examples are using simple actions. Let’s extend then by adding a incrementBy

Add a new action to counterSlice like this

incrementBy: (states, action: PayloadAction<number>) => {
  state.value += action.payload;
}

<em>Note: Whilst the += makes it look like we're mutating state, this is just a reminder, in this instance code within RTK is handling this as an immutable update.</em>

The PayloadAction only handles a single parameter, hence if we want to handle multiple parameters, we need to create an interface (in TypeScript) to define them. Let's move from the counter example and assume our Router is accepting multiple query parameters which we want to store in our store. We'll use an example of the github URL where by you can access my blog post code via <a href="https://github.com/putridparrot/blog-projects" rel="noopener" target="_blank">https://github.com/putridparrot/blog-projects</a>, so the first parameter might be gitUser, in this case <em>putridparrot</em>, followed by the repo name - hence we'd create our queryParams.slice.ts as follows

[code]
import { PayloadAction, createSlice } from "@reduxjs/toolkit";

export interface IQueryParams {
  gitUser?: string;
  repoName?: string;
}

const initialState: IQueryParams = {
  gitUser: "",
  repoName: "",
}

const queryParamsSlice = createSlice({
  name: "queryParams",
  initialState: initialState,
  reducers: {
    setQueryParams: (_, action: PayloadAction<IQueryParams>) => 
      action.payload,
  },
});

export const { setQueryParams } = queryParamsSlice.actions;

export default queryParamsSlice.reducer;

Everything’s pretty much as our counter example apart from the PayloadAction<IQueryParams>. In this example we’re actually just passing everything into the store so just returning the payload via setQueryParams.

Async actions

RTK supplies a way to incorporate async actions into the store. If we have something like a network call or another process we know may take some time, we’ll want to make it async. Just for a simple example, we’ll assume increment is now handled via either a network call or some longer running piece of code. We create an async thunk like this

export const incrementAsync = createAsyncThunk(
  "counter/incrementAsync",
  async (amount: number) => {
    // simulate a network call
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return amount;
  }
);

We’re supplying the name of our function counter/incrementAsync here. We now need to add this function to our slice, so after the reducers we add the following

extraReducers: (builder)  => {
  // we can accept state but for this example, we've no need
  builder.addCase(incrementAsync.pending, () => { 
    console.log("Pending");
  }).addCase(incrementAsync.fulfilled, (state, action: PayloadAction<number>) => {
    state.value += action.payload;
  });
}

Notice that each “case” is based upon some state which comes from createAsyncThunk. This is very cool as we now have the ability to update things based upon the current state of the async call, i.e. maybe we display a spinner for pending and remove when fulfilled or rejected occurs and in the case of rejected we now display an error toast message or the

Dispatch

To actually call into the store, i.e. call and action, we need to use code like the following

const dispatch = useDispatch<AppDispatch>();

Usually we’ll just wrap both the dispatch and selector into the following to make working with TypeScript simpler (just place this is some utility file such as hooks.ts or the classic helper.ts named file)

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

So now we can get the current count and update like this

import { decrement, increment } from '../store/slices/counter.slice'
import { useAppDispatch, useAppSelector } from '../store/hook'

export function Counter() {
  const count = useAppSelector((state) => state.counter.value)
  const dispatch = useAppDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

Calling services or API’s

The example within the RTK documentation is a call to the pokeapi, so let’s start with that. RTK documentation seems to suggest such API’s go into a folder named services, so we’ll stick with that although they are essentially just other slices of global state (at least in my view).

In our services folder add pokemin.ts and here’s the contents of the file

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const pokemonApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
  tagTypes: [],
  endpoints: (builder) => ({
    getPokemonByName: builder.query({
      query: (name: string) => `pokemon/${name}`,
    }),
  }),
})

// Export hooks for usage in functional components
export const { useGetPokemonByNameQuery } = pokemonApi

We used createAsyncThunk earlier and ofcourse we can easily implement services/network/API calls via the same mechanism, but the createApi method, which extends createAsyncThunk, generates React hooks for us. We also have options to handle caching and more from here.

In this example we use the fetchBaseQuery defining the baseUrl. This is a lightweight method wrapping the fetch method.

We define our endpoints, i.e. the API to our service and then at the end of the code we export the code generation hook, useGetPokemonByNameQuery.

To use this code within our store we need to go back to store.ts and add the following to the rootReducer

api: pokemonApi.reducer,

and to the middleware we need to concat the API like this

getDefaultMiddleware()
  .concat(pokemonApi.middleware),

Finally we’ll want to use this API, and we simply use the hook like this

export const Pokemon = ({ name, pollingInterval }: { name: string, pollingInterval: number }) => {
  const { data, error, isLoading, isFetching } = useGetPokemonByNameQuery(
    name,
    {
      pollingInterval,
    },
  );

Notice that we now get those async thunk states, but named differently. So we have the data returned, a potential error. We also have isLoading and isFetching for handling progress info.

Multiple parameters and multiple API’s

As mentioned previously RTK accepts a single object as input and this is the same for createApi actions, so if you have a situation where you need to pass multiple parameters to your API, then you handle in the same way (although in this example I’m creating via an anonymous type), i.e.

getContract: builder.query<any, {contractId?: string, version?: string}>({
  query: (arg) => {
    const { contractId, version } = arg;        
    return {
      url: `someurl?contractId=${contractId}&version=${version}`,
   }
  },

With regards multiple API’s, let’s say we have a pokemon and contracts api (I admit an odd combination). Then to register them with the store, as seen with a single API we concat them with the middleware, but we don’t appear to concat each, instead we concat an array like this

getDefaultMiddleware()
  .concat([contractsApi.middleware,  pokemonApi.middleware]),

Dynamic baseUrl and/or setting headers etc.

In the pokemon API example we had a fixed URL for the service, but when developing the front and back end we’ll often have dev and/or test and then production environments. In this case we’ll want to change the baseUrl based upon the environment. In such cases and also when we want to apply our own headers, for example let’s assume our API requires an SSO token, then we’ll want to include that in the call in some way or other.

Here’s our dynamicBaseQuery which we’re actually using the store to store something called enviroment which we’re assume contained data such as the the current environment URL along with the current SSO token for the user for the environment. In the second line we set the baseUrl and then it’s used in the fetchBaseQuery. We also use this to set up the headers.

const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (args, WebApi, extraOptions) => {
  const baseUrl = (WebApi.getState() as any).environment.appSettings.contractsApiUrl;
  const rawBaseQuery = fetchBaseQuery({
    baseUrl,
    prepareHeaders: async (headers, { getState }) => {
      const token = (getState() as any).environment.token;
      if (headers) {
        headers.set("Accept", "application/json");
        if(token) {
          headers.set("authorization", `Bearer ${token}`);
        }
      }

      return headers;
    },
  });
  return rawBaseQuery(args, WebApi, extraOptions);
};

To use this we simple replace the baseQuery value with dynamicBaseQuery, like this snippet

export const contractsApi = createApi({
  reducerPath: "contractsApi",
  baseQuery: dynamicBaseQuery,
  // rest of the code ommitted

Tooling

I highly recommend installing the Redux Dev Tools for your browser. These tools allow you to easily see the state of your Redux store (and as RTK is built on Redux we can see our RTK defined store) and best of all, RTK is automatically configured to work with the Redux Dev Tools, so now additional code is required.

Code

Code for this post is available in my blog-projects.

Note: Not all code is hooked up and being used, but hopefully there’s enough to show how things might look.