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 Configuration
IConfigurationSection 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 needed
if (!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 Directory
string FileDirectory = form.FileDirectory;
// Set _SystemFiles
_SystemFiles = System.IO.Path.Combine(_SystemFiles, FileDirectory);// Create SystemFiles directory if needed
if (!Directory.Exists(_SystemFiles))
{DirectoryInfo di =Directory.CreateDirectory(_SystemFiles);}// Process File Attachment
if (form.FileAttachment != null){using (var readStream = form.FileAttachment.OpenReadStream())
{var filename = form.FileAttachment.FileName.Replace("\"", "").ToString();
filename = _SystemFiles + $@"\{filename}";
//Save file to harddrive
using (FileStream fs = System.IO.File.Create(filename))
{form.FileAttachment.CopyTo(fs);fs.Flush();}}}// Process all File Attachments
if (form.FileAttachments != null){foreach (var file in form.FileAttachments){// Process file
using (var readStream = file.OpenReadStream())
{var filename = file.FileName.Replace("\"", "").ToString();
filename = _SystemFiles + $@"\{filename}";
//Save file to harddrive
using (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 Configuration
builder.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