Uploading and sending image messages with ASP.NET Core SignalR

This article shows how images could be uploaded using a file upload with a HTML form in an ASP.MVC Core view, and then sent to application clients using SignalR. The images are uploaded as an ICollection of IFormFile objects, and sent to the SignalR clients using a base64 string. Angular is used to implement the SignalR clients.

Code https://github.com/damienbod/AspNetCoreAngularSignalR

Posts in this series

History

2023-01-08 Updated Angular 15, .NET 7
2021-01-25 Updated Angular 11.1.0 .NET 5, ngrx implementation
2020-03-21 updated packages, fixed Admin UI STS
2019-08-18 Updated ASP.NET Core 3.0, Angular 8.2.2
2019-02-06 Updated Angular 7.2.4, latest NGRX, SignalR CORS fix
2018-12-12 Updated .NET Core 2.2, ASP.NET Core SignalR 1.1.0, Angular 7.1.3
2018-05-31 Updated Microsoft.AspNetCore.SignalR 2.1
2018-05-08 Updated Microsoft.AspNetCore.SignalR 2.1 rc1, Angular 6
2018-03-15 Updated signalr Microsoft.AspNetCore.SignalR 1.0.0-preview1-final, Angular 5.2.8, @aspnet/signalr 1.0.0-preview1-update1

SignalR Server

The SignalR Hub is really simple. This implements a single method which takes an ImageMessage type object as a parameter.

using AspNetCoreAngularSignalR.Model;
using Microsoft.AspNetCore.SignalR;

namespace AspNetCoreAngularSignalR.SignalRHubs;

public class ImagesMessageHub : Hub
{
    public Task ImageMessage(ImageMessage file)
    {
        return Clients.All.SendAsync("ImageMessage", file);
    }
}

The ImageMessage class has two properties, one for the image byte array, and a second for the image information, which is required so that the client application can display the image.

namespace AspNetCoreAngularSignalR.Model;

public class ImageMessage
{
    public byte[]? ImageBinary { get; set; }
    public string ImageHeaders { get; set; } = string.Empty;
}

In this example, SignalR is added to the ASP.NET Core application in the Startup class, but this could also be done directly in the kestrel server. The AddSignalR middleware is added and then each Hub explicitly with a defined URL in the Configure method.

using AspNetCoreAngularSignalR.Providers;
using AspNetCoreAngularSignalR;
using Microsoft.EntityFrameworkCore;
using AspNetCoreAngularSignalR.SignalRHubs;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.AddServerHeader = false;
});

var services = builder.Services;
var configuration = builder.Configuration;
var env = builder.Environment;

services.AddTransient<ValidateMimeMultipartContentFilter>();

services.AddCors(options =>
{
    options.AddPolicy("AllowAllOrigins",
        builder =>
        {
            builder
                .AllowCredentials()
                .WithOrigins(
                    "https://localhost:4200")
                .SetIsOriginAllowedToAllowWildcardSubdomains()
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
});

services.AddSignalR()
  .AddMessagePackProtocol();


var app = builder.Build();

// IdentityModelEventSource.ShowPII = true;

app.UseCors("AllowAllOrigins");

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseRouting();

app.MapHub<ImagesMessageHub>("/zub");


app.Run();

A File Upload ASP.NET Core MVC controller is implemented to support the file upload. The SignalR IHubContext interface is added per dependency injection for the type ImagesMessageHub. When files are uploaded, the IFormFile collection which contain the images are read to memory and sent as a byte array to the SignalR clients. Maybe this could be optimized.

using AspNetCoreAngularSignalR.Model;
using AspNetCoreAngularSignalR.SignalRHubs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

namespace AspNetCoreAngularSignalR.Controllers;

[Route("api/[controller]")]
public class FileUploadController : Controller
{
    private readonly IHubContext<ImagesMessageHub> _hubContext;

    public FileUploadController(IHubContext<ImagesMessageHub> hubContext)
    {
        _hubContext = hubContext;
    }

    [Route("files")]
    [HttpPost]
    [ServiceFilter(typeof(ValidateMimeMultipartContentFilter))]
    public async Task<IActionResult> UploadFiles(FileDescriptionShort fileDescriptionShort)
    {
        if (ModelState.IsValid)
        {
            foreach (var file in fileDescriptionShort.File!)
            {
                if (file.Length > 0)
                {
                    using var memoryStream = new MemoryStream();
                    await file.CopyToAsync(memoryStream);

                    var imageMessage = new ImageMessage
                    {
                        ImageHeaders = "data:" + file.ContentType + ";base64,",
                        ImageBinary = memoryStream.ToArray()
                    };

                    await _hubContext.Clients.All.SendAsync("ImageMessage", imageMessage);
                }
            }
        }

        return Redirect("/FileClient/Index");
    }
}

SignalR Angular Client

The Angular SignalR client uses the HubConnection to receive ImageMessage messages. Each message is pushed to the client array which is used to display the images. The @microsoft/signalr npm package is required to use the HubConnection.

import { Component, OnInit } from '@angular/core';
import { HubConnection } from '@microsoft/signalr';
import * as signalR from '@microsoft/signalr';

import { ImageMessage } from '../imagemessage';

@Component({
  selector: 'app-images-component',
  templateUrl: './images.component.html',
})
export class ImagesComponent implements OnInit {
  private _hubConnection: HubConnection | undefined;
  public async: any;
  message = '';
  messages: string[] = [];

  images: ImageMessage[] = [];

  constructor() {}

  ngOnInit() {
    this._hubConnection = new signalR.HubConnectionBuilder()
      .withUrl('https://localhost:44324/zub')
      .configureLogging(signalR.LogLevel.Trace)
      .build();

    this._hubConnection.stop();

    this._hubConnection.start().catch((err) => {
      console.error(err.toString());
    });

    this._hubConnection.on('ImageMessage', (data: any) => {
      console.log(data);
      this.images.push(data);
    });
  }
}

The Angular template displays the images using the header and the binary data properties.

<div class="container-fluid">
  <h1>Images</h1>

  <a href="https://localhost:44324/FileClient/Index" target="_blank"
    >Upload Images</a
  >

  <div *ngIf="images.length > 0">
    <img
      *ngFor="let image of images"
      width="150"
      style="margin-right: 5px"
      [src]="image.imageHeaders + image.imageBinary"
    />
  </div>
</div>

File Upload

The images are uploaded using an ASP.NET Core MVC View which uses a multiple file input HTML control. This sends the files to the MVC Controller as a multipart/form-data request.

<form enctype="multipart/form-data" method="post" action="https://localhost:44324/api/FileUpload/files" id="ajaxUploadForm" novalidate="novalidate">

    <fieldset>
        <legend style="padding-top: 10px; padding-bottom: 10px;">Upload Images</legend>

        <div class="col-xs-12" style="padding: 10px;">
            <div class="col-xs-4">
                <label>Upload</label>
            </div>
            <div class="col-xs-7">
                <input type="file" id="fileInput" name="file" multiple>
            </div>
        </div>

        <div class="col-xs-12" style="padding: 10px;">
            <div class="col-xs-4">
                <input type="submit" value="Upload" id="ajaxUploadButton" class="btn">
            </div>
            <div class="col-xs-7">

            </div>
        </div>

    </fieldset>

</form>

When the application is run, n instances of the clients can be opened. Then one can be used to upload images to all the other SignalR clients.

This soultion works good, but has many ways, areas which could be optimized for performance.

Links

https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction

https://github.com/aspnet/SignalR#readme

https://radu-matei.com/blog/signalr-core/

https://www.npmjs.com/package/@aspnet/signalr-client

https://msgpack.org/

9 comments

  1. […] Uploading and sending image messages with ASP.NET Core SignalR – Damien Bowden […]

  2. […] Uploading and sending image messages with ASP.NET Core SignalR (Damien Bowden) […]

  3. Kalpesh Chheda · · Reply

    Hi,
    Is it ok to send large images from Signalr?

    1. No, this has limits, but I did not try them. Also SignalR has open issues concerning large messages

      Where the limits are, I don’t know.
      Greetings Damien

    2. radcki · · Reply

      This is not ok as concept itself. For messages over few kilobytes I would generally use Signlr as way of saying “hey there is new thing you might like and this is where to get it” than “hey take this thing from me”.
      So you would transfer metadata only with signalr and make normal call to get actual byte array from server or (for example) create link to content – depending of user settings

      Think of smarphone notifications – you would not like to receive few megabates of data out of nowhere – you want to be notified and then decide if you want to fetch it or not.

  4. […] Uploading and sending image messages with ASP.NET Core SignalR […]

  5. […] Téléverser et envoyer des images avec ASP.NET Core SignalR. […]

  6. Why do you need the AddData function on ImageMessage class?

  7. ValidateMimeMultipartContentFilter file and FileDescriptionShort file can’t be found, please tell me when can I find them

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: