6/5/2022 Admin
Blazor File Upload using Web API and Swagger

Uploading a file using an API has been covered in many places. Uploading a file using a Swagger page has also been covered in the following Github project: dotnet-labs/HerokuContainer. Implementing authentication, using JWTs has also been covered in this project: StefanescuEduard/DotnetSwaggerDocumentation.
However the example in this Blog post combines all this and uses Blazor.
Note: You can download the completed project from here: Github: ADefWebserver/BlazorFileUploadSwagger.
Create The Project

The first step is to create a new Blazor Server project.

Next, we add the following NuGet packages:
- Newtonsoft.Json
- Swashbuckle.AspNetCore
- Microsoft.IdentityModel.Tokens
- System.IdentityModel.Tokens.Jwt
- Microsoft.AspNetCore.Authentication.JwtBearer

We alter the contents of the appsettings.json file to the following:
{"AppSettings": {"EncryptionKey": "012389ABCDEFGHIJKLMN4567OPQRSTUVWXYZ","UserName": "TestUser","Password": "TestPassword"},"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"AllowedHosts": "*"}
Note: In a real world app you would use a unique EncryptionKey and UserName and Password.
Add JSON Web Token (JWT) code

Now, we will add the code to create and validate JWTs.
Create a Services folder, and add the following files:
JWTAuthenticationService.cs
#pragma warning disable 1591using BlazorFileUploadSwagger.Models;using Microsoft.Extensions.Options;namespace BlazorFileUploadSwagger.Services{public class JWTAuthenticationService{private readonly AppSettings appSettings;private readonly TokenService tokenService;public JWTAuthenticationService(IOptions<AppSettings> options,TokenService tokenService){appSettings = options.Value;this.tokenService = tokenService;}public async Task<string> Authenticate(ApiToken userCredentials){// **********************// authenticate the username and password// **********************string securityToken = "";if ((appSettings.UserName.ToLower() ==userCredentials.UserName.ToLower())&&(appSettings.Password == userCredentials.Password)){securityToken = await tokenService.GetToken();}return securityToken;}}}
TokenService.cs (Code taken from here)
#pragma warning disable 1591using BlazorFileUploadSwagger.Models;using Microsoft.Extensions.Options;using Microsoft.IdentityModel.Tokens;using System;using System.IdentityModel.Tokens.Jwt;using System.Text;namespace BlazorFileUploadSwagger.Services{public class TokenService{private readonly AppSettings appSettings;public TokenService(IOptions<AppSettings> options){appSettings = options.Value;}public async Task<string> GetToken(){SecurityTokenDescriptor tokenDescriptor =await GetTokenDescriptor();var tokenHandler = new JwtSecurityTokenHandler();SecurityToken securityToken =tokenHandler.CreateToken(tokenDescriptor);string token = tokenHandler.WriteToken(securityToken);return token;}private async Task<SecurityTokenDescriptor> GetTokenDescriptor(){const int expiringHours = 24;byte[] securityKey =await Task.Run(() => Encoding.UTF8.GetBytes(appSettings.EncryptionKey));var symmetricSecurityKey = new SymmetricSecurityKey(securityKey);var tokenDescriptor = new SecurityTokenDescriptor{Expires = DateTime.UtcNow.AddHours(expiringHours),SigningCredentials =new SigningCredentials(symmetricSecurityKey,SecurityAlgorithms.HmacSha256Signature)};return tokenDescriptor;}}}

Create a Models folder, and add the following files:
ApiToken.cs
namespace BlazorFileUploadSwagger.Models{public class ApiToken{public string UserName { get; set; }public string Password { get; set; }}}
AppSettings.cs
namespace BlazorFileUploadSwagger.Models{public class AppSettings{public string EncryptionKey { get; set; }public string UserName { get; set; }public string Password { get; set; }}}

Create a Extensions folder, and add the following file:
AuthenticationExtensions.cs
#pragma warning disable 1591using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.Extensions.DependencyInjection;using Microsoft.IdentityModel.Tokens;using System;namespace BlazorFileUploadSwagger{public static class AuthenticationExtensions{public static IServiceCollection AddAuthentication(this IServiceCollection services,byte[] signingKey){services.AddAuthentication(authOptions =>{authOptions.DefaultAuthenticateScheme =JwtBearerDefaults.AuthenticationScheme;authOptions.DefaultChallengeScheme =JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(jwtOptions =>{jwtOptions.SaveToken = true;jwtOptions.TokenValidationParameters =new TokenValidationParameters{ValidateAudience = false,ValidateIssuer = false,ValidateIssuerSigningKey = true,IssuerSigningKey = new SymmetricSecurityKey(signingKey),ValidateLifetime = true,LifetimeValidator = LifetimeValidator};});return services;}private static bool LifetimeValidator(DateTime? notBefore,DateTime? expires,SecurityToken securityToken,TokenValidationParameters validationParameters){return expires != null && expires > DateTime.Now;}}}

Add the following using statements to Program.cs:
using System.Text;using System.Reflection;using System.Security.Claims;using System.Security.Principal;using Microsoft.AspNetCore.Server.IISIntegration;using Microsoft.IdentityModel.Tokens;using Microsoft.Extensions.DependencyInjection;using Microsoft.OpenApi.Models;using BlazorFileUploadSwagger;using BlazorFileUploadSwagger.Services;using BlazorFileUploadSwagger.Models;
Add the following code (below: var builder = WebApplication.CreateBuilder(args)):
// JWT ConfigurationIConfigurationSection settingsSection =builder.Configuration.GetSection("AppSettings");AppSettings settings = settingsSection.Get<AppSettings>();byte[] signingKey = Encoding.UTF8.GetBytes(settings.EncryptionKey);builder.Services.AddAuthentication(signingKey);builder.Services.AddScoped<JWTAuthenticationService>();builder.Services.AddScoped<TokenService>();builder.Services.Configure<AppSettings>(settingsSection);
Add Controllers

The Swagger API will document the controller methods we will create next.
Add the following file to the Models folder:
UploadForm.cs
namespace BlazorFileUploadSwagger.Models{/// <summary>/// Upload Form/// </summary>public class UploadForm{/// <summary>/// File Directory/// </summary>public string FileDirectory { get; set; }/// <summary>/// File Attachment/// </summary>public IFormFile FileAttachment { get; set; }/// <summary>/// File Attachments/// </summary>public List<IFormFile> FileAttachments { get; set; }}}

We will now create a Auth Controller that will accept the username and password specified in the appsettings.json file and issue a JWT.
We will then create an Upload Controller that will be protected by: [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] so that a user can only call it if they pass a valid JWT issued by the Auth Controller.
Create a Controllers folder, and add the following files:
AuthController.cs
using BlazorFileUploadSwagger.Models;using BlazorFileUploadSwagger.Services;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;namespace BlazorFileUploadSwagger.Controlers{/// <summary>/// Auth Controller/// </summary>[ApiController][Produces("application/json")][Route("api/[controller]")]public class AuthController : ControllerBase{private readonly JWTAuthenticationService authenticationService;public AuthController(JWTAuthenticationService authenticationService){this.authenticationService = authenticationService;}#region public async Task<string> GetAuthToken([FromQuery] ApiToken objApiToken)/// <summary>/// Obtain a security token to use for subsequent calls./// Copy the output received and then click the Authorize button (above)./// Paste the contents (between the quotes) into that box and then/// click Authorize then close. Now the remaining methods will work./// </summary>/// <param name="objApiToken"></param>/// <response code="200">JWT token created</response>[AllowAnonymous][HttpGet("GetAuthToken")][ProducesResponseType(typeof(string), 200)]public async Task<string> GetAuthToken([FromQuery] ApiToken objApiToken){var dict = new Dictionary<string, string>();dict.Add("username", objApiToken.UserName);dict.Add("password", objApiToken.Password);string token = await authenticationService.Authenticate(objApiToken);return token;}#endregion}}
UploadController.cs
using BlazorFileUploadSwagger.Models;using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;namespace BlazorFileUploadSwagger.Controllers{/// <summary>/// An Upload controller for <code>multipart/form-data</code> submission/// </summary>[ApiController][Produces("application/json")][Route("api/[controller]")]public class UploadController : ControllerBase{private readonly IWebHostEnvironment _hostEnvironment;private string _SystemFiles;public UploadController(IWebHostEnvironment hostEnvironment){_hostEnvironment = hostEnvironment;// Set _SystemFiles_SystemFiles =System.IO.Path.Combine(hostEnvironment.ContentRootPath,"Files");// Create SystemFiles directory if neededif (!Directory.Exists(_SystemFiles)){DirectoryInfo di =Directory.CreateDirectory(_SystemFiles);}}/// <summary>/// Upload/// </summary>/// <param name="form">A form</param>/// <returns></returns>//JwtBearerDefaults means this method will only work if a Jwt is being passed[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)][HttpPost]public void UploadAPI([FromForm] UploadForm form){// Get File Directorystring FileDirectory = form.FileDirectory;// Set _SystemFiles_SystemFiles = System.IO.Path.Combine(_SystemFiles, FileDirectory);// Create SystemFiles directory if neededif (!Directory.Exists(_SystemFiles)){DirectoryInfo di =Directory.CreateDirectory(_SystemFiles);}// Process File Attachmentif (form.FileAttachment != null){using (var readStream = form.FileAttachment.OpenReadStream()){var filename = form.FileAttachment.FileName.Replace("\"", "").ToString();filename = _SystemFiles + $@"\{filename}";//Save file to harddriveusing (FileStream fs = System.IO.File.Create(filename)){form.FileAttachment.CopyTo(fs);fs.Flush();}}}// Process all File Attachmentsif (form.FileAttachments != null){foreach (var file in form.FileAttachments){// Process fileusing (var readStream = file.OpenReadStream()){var filename = file.FileName.Replace("\"", "").ToString();filename = _SystemFiles + $@"\{filename}";//Save file to harddriveusing (FileStream fs = System.IO.File.Create(filename)){file.CopyTo(fs);fs.Flush();}}}}}}}
Add Swagger

First, we need to instruct the application to create the .xml file Swagger will need to create the API.
Right-click on the Project node and select Edit Project File and add the following line;
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Add the following code to the Program.cs file:
// Swagger Configurationbuilder.Services.AddControllers();builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo{Version = "v1",Title = "API",Description = "A simple example ASP.NET Core Web API"});options.AddSecurityDefinition("bearerAuth", new OpenApiSecurityScheme{Name = "Authorization",Type = SecuritySchemeType.Http,Scheme = "bearer",BearerFormat = "JWT",In = ParameterLocation.Header,Description = "JWT Authorization header using the Bearer scheme. " +"Example: \"Authorization: Bearer {token}\""});options.AddSecurityRequirement(new OpenApiSecurityRequirement{{new OpenApiSecurityScheme{Reference = new OpenApiReference{Type = ReferenceType.SecurityScheme,Id = "bearerAuth"}},new string[] {}}});var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);options.IncludeXmlComments(xmlPath, true);});
Also, add the following code under app.UseRouting():
app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});app.UseSwagger();app.UseSwaggerUI();

Finally, change all the code in the Index.razor page to the following;
Index.razor
@page "/"<PageTitle>Index</PageTitle><h2><a href="swagger/index.html">Swagger UI</a></h2>
Download
Github: ADefWebserver/BlazorFileUploadSwagger
Links
StefanescuEduard/DotnetSwaggerDocumentation
