File upload through ASP.NET Core middleware

In my previous article we discussed the different options to implement file upload for cloud applications.

In this article I want to provide you an example of how to implement a file upload through a middleware.:

middleware_upload

Example: File Upload to Azure Blog Storage using Angular and ASP.NET Core

We will scaffold our application using the Angular template which is part of the .NET-Core CLI and create a component using the Angular CLI.

Prerequisites

* You can either manually create the Storage Account within the Azure Portal or by using the following ARM Template:

Scaffold the project

To scaffold the project we use the dotnet new command:

dotnet new angular --name file-upload

Implement the middleware

To store the uploaded files to Azure Blob Storage we need to specify the connection string within the appsettings.json:

"ConnectionStrings": {
    "StorageAccount": "UseDevelopmentStorage=true"
    },

If you don’t have the Azure Storage Emulator installed you have to replace the value with an actual Azure Storage Account connection string.

Install WindowsAzure.Storage NuGet Package

You can install the NuGet package using the following command:

dotnet add package WindowsAzure.Storage

File upload implementation

We will implement the file upload in a new Controller called AssetController. The controller only exposes a single method called UploadAssetAsync which takes an IFormFile with the name asset (note: IFormFile is suitable for uploading small files, if you have to deal with large files you have to consider implementing streaming or uploading the files directly from the client to a data store. Further information here.).

The UploadAssetAsync method uploads the passed file to the previous specified Azure Blob Storage and returns the URI of the new blob:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.WindowsAzure.Storage;

namespace file_upload.Controllers
{
    [Route("api/[controller]")]
    public class AssetController : Controller
    {
        private readonly IConfiguration _configuration;
        public AssetController(IConfiguration config)
        {
            _configuration = config;
        }

        [HttpPost]
        public async Task UploadAssetAsync([FromForm]IFormFile asset)
        {
            CloudStorageAccount storageAccount = null;
            if (CloudStorageAccount.TryParse(_configuration.GetConnectionString("StorageAccount"), out storageAccount))
            {
                var client = storageAccount.CreateCloudBlobClient();
                var container = client.GetContainerReference("fileupload");
                await container.CreateIfNotExistsAsync();

                var blob = await container.GetBlobReferenceFromServerAsync(asset.FileName);
                await blob.UploadFromStreamAsync(asset.OpenReadStream());

                return Ok(blob.Uri);
            }

            return StatusCode(StatusCodes.Status500InternalServerError);
        }
    }
}

Disable HttpsRedirection in Development

Before we can run the application we should disable the Https redirection for the development environment (note: you can also install a localhost certificate). Otherwise we will get a warning that the site is not secure. This can be done by replacing the following line in the Configure method within the Startup.cs

app.UseHttpsRedirection();

with:

if (!env.IsDevelopment())
{
   app.UseHttpsRedirection();
}

Implement the frontend

We will implement the file upload in a new component. To create the new component we use the Angular CLI:

ng g component fileUpload

Note: Ensure you invoke the CLI commands within the ClientApp directory.

Configure Routing

Now we add routing to our new component within the app.module.ts:

{ path: 'file-upload', component: FileUploadComponent },

Then we add a link to the component inside the nav-menu.component.html:

<li>
    <a>
        <span class='glyphicon glyphicon-cloud-upload'></span> File Upload
    </a>
</li>

Install PrimeNG

We use the PrimeNG NPM package to implement the file upload:

npm install primeng --save 

Add the file upload

The last thing we have to do is to add the file upload to the file-upload.component.html. The name attribute value must match with the IFormFile parameter name in the middleware (in our case “asset“). The url is /api/Asset which is the address of the UploadAssetAsync middleware web method:

 
<p-fileUpload #fubauto mode="basic" name="asset" url="/api/Asset" maxFileSize="1000000" auto="true"
        chooseLabel="Browse"></p-fileUpload>

That is it. We don’t need to implement any further upload mechanism. To start the application we can use the dotnet CLI:

dotnet run

The source code can be found in my GitHub repository.

Angular 6 application hosted on Azure Storage Static website

A few days ago Microsoft announced a new public preview feature for Azure Storage called Static website. It enables you to host a static web app using Azure Storage which is multiple times cheaper then a traditional required Web App. Reason enough to give it a try.

Create a Storage account

To use the Static website feature we need a general purpose V2 Storage Account. You can either manually create the resource within the Azure Portal or by using the following ARM Template:

Enable Static website feature

Unfortunately we can’t enable the Static website feature using an ARM Template (thanks to Gaurav Mantri) because it is not part of the Storage Resource Provider API. So we have to manually enable it after we’ve provisioned the Storage Account.

We can do that within our previous created Storage Account: Select Static website (preview) to configure a container for static website hosting:

enablestaticwebsite

Since our example applicaton will have an index.html,  we can leave the index document name field as it is.

Note: If you have to automate this process, it is still possible to enable this feature using the Azure Storage Services REST API.

After we save our changes we get the primary endpoint URL of our site:

spaurl.png

Create an Angular Single Page Application

We use the Angular CLI to scaffold an Angular sample applicaton that already has routing enabled. The name of our app will be spastore:

ng new spastore --routing

We also create a new component named subcomponent to demonstrate the routing capability:

ng g component subcomponent

And configure the routing module accordingly:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SubcomponentComponent } from './subcomponent/subcomponent.component';

const routes: Routes = [
  { path: 'subcomponent', component: SubcomponentComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { useHash: true })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Note that we use the HashLocationStrategy oppsed to the default PathLocationStrategy because the later will result in 404 errors. This is because we can’t define a rewrite URL for our static website and Azure will try to resolve the specified resource – e. g.:
https://spastore.z6.web.core.windows.net/subcomponent

The HashLocationStrategy represents its state in the hash fragment of the browser’s URL. So this will be the new route for our subcomponent:
https://spastore.z6.web.core.windows.net/#/subcomponent

Finally we add the router outlet to the app.component.html which displays our component and add a router link:


  <a>Subcomponent</a>

Now it’s time to compile the application and get a production build. Once again we use the Angular CLI:

ng build --prod

The build artefacts are stored within the dist folder and are ready to publish. We can do that by clicking on the $web link within the Static website (preview) blade. Then we have to click on the Upload button and select all files within the artefact (dist) folder:

angularupload

If we browse to the URL we can see our Angular App up and running and also the routing is working as expected:

angulardemo.png

You can test the site here: https://spastore.z6.web.core.windows.net

The source code including the ARM Template and the Angular sample application can be found in my GitHub repository.

Update:

You can actualy use the PathLocationStrategy by setting the error page to index.html (thanks to @nthonyChu). The first try to serve a URL like https://spastore.z6.web.core.windows.net/subcomponent will fail since it is not a valid resource. Then the path will get passed to the error page which is our index.html (page returns content with 404 status code). However, this is hack. I still recommend using the HashLocationStrategy until a rewrite mechanism is in place.

Determine latest API version for a resource provider

Azure Resource Manager templates are great to deploy one or more resources to a resource group.

A mandatory part of an ARM template is the resources section which defines the resource types that are deployed or updated. Here is an example:

"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "2016-01-01",
"location": "[parameters('location')]",
"sku": {
	"name": "Standard_LRS"
},
"kind": "Storage",
"properties": {}

The name of a resource type has the following format: {resource-provider}/{resource-type}. In this example Microsoft.Storage/storageAccounts. The apiVersion specifies the version of the API that is used to deploy / manage the resource.

A resource provider offers a set of operations to deploy and manage resources using a REST API. If a resource provider adds new features, it releases a new version of the API. Therefore, if you have to utilize new features you may have to use the latest API Version. To determine the latest version of a resource type you can use the following Get-AzureRmResourceProviderLatestApiVersion cmdlet:

function Get-AzureRmResourceProviderLatestApiVersion
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([string])]
    Param
    (
        [Parameter(ParameterSetName = 'Full', Mandatory = $true, Position = 0)]
        [string]$Type,

        [Parameter(ParameterSetName = 'ProviderAndType', Mandatory = $true, Position = 0)]
        [string]$ResourceProvider,

        [Parameter(ParameterSetName = 'ProviderAndType', Mandatory = $true, Position = 1)]
        [string]$ResourceType,

        [switch]$IncludePreview
    )

    # retrieving the resource providers is time consuming therefore we store
    # them in a script variable to accelerate subsequent requests.
    if (-not $script:resourceProvider)
    {
        $script:resourceProvider = Get-AzureRmResourceProvider   
    }

    if ($PSCmdlet.ParameterSetName -eq 'Full')
    {
        $ResourceProvider = ($Type -replace "\/.*")
        $ResourceType = ($Type -replace ".*?\/(.+)", '$1')
    }
    
    $provider = $script:resourceProvider | 
        Where-Object {
        $_.ProviderNamespace -eq $ResourceProvider -and 
        $_.ResourceTypes.ResourceTypeName -eq $ResourceType
    }

    if ($IncludePreview)
    {
        $provider.ResourceTypes.ApiVersions[0]
    }
    else
    {
        $provider.ResourceTypes.ApiVersions | Where-Object {
            $_ -notmatch '-preview'
        } | Select-Object -First 1
    }
}

You can now determine the latest API Version using either the full type:

Get-AzureRmResourceProviderLatestApiVersion -Type Microsoft.Storage/storageAccounts

Or by passing the Resource Provider and Resource Type:

Get-AzureRmResourceProviderLatestApiVersion -ResourceProvider Microsoft.Storage -ResourceType storageAccounts

Finally, to include preview versions you can append the -IncludePreview switch:

Get-AzureRmResourceProviderLatestApiVersion -Type Microsoft.Storage/storageAccounts -IncludePreview

You can download the script from my GitHub repository.

Additional information: Resource providers and types

Using Azure Key Vault in ASP.NET Core 2.0 with the options pattern

The best way to store secrets in your app is not to store secrets in your app

Almost every web application needs some kind of secrets like a SQL Database connection string or the primary key of a Storage Account in order to communicate with external services.

Certainly we don’t store these secrets within our source code since this would expose them to every developer that has access to the code. In Azure we could store the secrets within the Application Settings in the Azure Portal:

secret.PNG

But if a secret is used in multiple application and we need to change it (e. g. regenerate a storage account key) we would have to do that in multiple places. A better place to store secrets in Azure is the Key Vault.

Instead of storing each secret within our app we store them in the Key Vault and configure our app to access the secrets in the vault. Now we have a single place where we can manage our secrets.

Lets take a look how we can access those secrets in an ASP.NET Core 2.0 web application without introducing a dependency to Key Vault in the class that uses it. To create a vault, store secrets to it and create a service principal for the access policy see Get started with Azure Key Vault.

Our secret is stored in a class called ValueSettings:

public sealed class ValueSettings
{
	public string TestSecret { get; set; }
}

There is a ValuesController with one HttpGet method that returns our secret:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ValueSettings _valueSettings;

    public ValuesController(IOptions<ValueSettings> valueSettings)
    {
        _valueSettings = valueSettings.Value;
    }

    [HttpGet]
    public IActionResult Get()
    {
        return Ok(_valueSettings.TestSecret);
    }
}

As you can see in line 6 the controller uses the options pattern to inject the actual settings. The controller doesn’t know where the secret is comming from and doesn’t have any dependencies to Azure Key Vault.

Now lets take a look how we need to configure our application for that. First we need to store the vault settings in our appsettings.json:

{
  "KeyVault": {
    "Vault": "https://myvault.vault.azure.net/",
    "ClientId": "myclientid",
    "ClientSecret": "myclientsecret"
  }
}

We also have a class that represents these settings:

public class KeyVaultSettings
{
    public string Vault { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

Now to configure the key vault we use the AddAzureKeyVault extension method in the Programm.cs:

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((context, config) =>
            {
                config.SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", optional: false)
                    .AddEnvironmentVariables();

                var builtConfig = config.Build();
                var settings = builtConfig.GetSection("KeyVault").Get();

                config.AddAzureKeyVault(
                    settings.Vault, settings.ClientId, settings.ClientSecret);

            })
            .UseStartup()
            .Build();
}

And finally this is how our Startup looks like:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;   
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure(Configuration);

        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}

You can download the complete example from my GitHub repository.