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
- Getting started with SignalR using ASP.NET Core and Angular
- SignalR Group messages with ngrx and Angular
- Using EF Core and SQLite to persist SignalR Group messages in ASP.NET Core
- Securing an Angular SignalR client using JWT tokens with ASP.NET Core and IdentityServer4
- Implementing custom policies in ASP.NET Core using the HttpContext
- Sending Direct Messages using SignalR with ASP.NET core and Angular
- Using Message Pack with ASP.NET Core SignalR
- Uploading and sending image messages with ASP.NET Core SignalR
SignalR Server
The SignalR Hub is really simple. This implements a single method which takes an ImageMessage type object as a parameter.
using System.Threading.Tasks; 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.
public class ImageMessage { public byte[] ImageBinary { get; set; } public string ImageHeaders { get; set; } }
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.
public void ConfigureServices(IServiceCollection services) { ... services.AddTransient<ValidateMimeMultipartContentFilter>(); services.AddSignalR() .AddMessagePackProtocol(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { ... app.UseSignalR(routes => { routes.MapHub<ImagesMessageHub>("/zub"); }); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=FileClient}/{action=Index}/{id?}"); }); }
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 System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using AspNetCoreAngularSignalR.Model; using AspNetCoreAngularSignalR.SignalRHubs; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.Net.Http.Headers; 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 @aspnet/signalr npm package is required to use the HubConnection.
import { Component, OnInit } from '@angular/core'; import { HubConnection } from '@aspnet/signalr'; import * as signalR from '@aspnet/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 class="row" *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://github.com/aspnet/SignalR
https://github.com/aspnet/SignalR#readme
https://radu-matei.com/blog/signalr-core/