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

image

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

image

Next, we add the following NuGet packages:

  • Newtonsoft.Json
  • Swashbuckle.AspNetCore
  • Microsoft.IdentityModel.Tokens
  • System.IdentityModel.Tokens.Jwt
  • Microsoft.AspNetCore.Authentication.JwtBearer

image

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

image

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 1591
								
									using
									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 1591
								
									using
									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;
								
									}
								
									}
								
}

image

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; }
								
									}
								
}

image

Create a Extensions folder, and add the following file:

AuthenticationExtensions.cs

								
#pragma warning disable 1591
								
									using
									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;
								
									}
								
									}
								
}

image

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

image

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; }
								
									}
								
}

image

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

image

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

image

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

dotnet-labs/HerokuContainer

StefanescuEduard/DotnetSwaggerDocumentation

Combining Bearer Token and Cookie Authentication in ASP.NET

File Upload via Swagger

Swagger

An error has occurred. This application may no longer respond until reloaded. Reload 🗙