5

Blazor File Upload using Web API and Swagger

 1 year ago
source link: https://blazorhelpwebsite.com/ViewBlogPost/60
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Blazor File Upload using Web API and Swagger


BlazorFileUploadSwagger_large.gif

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK