ASP.NET Core – How To Create A Development Environment For Using Azure Blob Storage With Repository Pattern
Azure Blob Storage is one of Microsoft’s solutions for storing files in the cloud. It is one of the features available when creating any storage account on Azure. Depending on your needs, it can replace SQL Server for several use cases and may even cost less.
In this tutorial we will:
-
-
- Create a development container with C# and Azurite (to emulate Azure Storage)
- Confirm connectivity to Azurite
- Create a new C# solution
- Create an MVC Web Application
- Create class libraries to interact with the Azure Storage
- Build the Entity Models
- Build the Repository Layer
- Build the Service Layer
- Register repositories and services
- Implement controllers and views
- Test the application
-
Create a Development Container with C# and Azurite (to emulate Azure Storage)
-
1 – Create a new folder on your computer. I will be calling mine “azure-blob-storage-repository-demo”
2 – Open the folder with Visual Studio Code.
3 – Create a .devcontainer folder
4 – Create a Dockerfile within that .devcontainer folder.
Give it the following code:
# [Choice] .NET version: 6.0-focal, 3.1-focal
ARG VARIANT="6.0-focal"
FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 18, 16, 14
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
-
5 – Create a docker-compose.yml file in that .devcontainer folder.
Give it the following code:
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick a version of .NET: 3.1-focal, 6.0-focal
VARIANT: "6.0-focal"
# Optional version of Node.js
NODE_VERSION: "lts/*"
volumes:
- ..:/workspace:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:azurite
# Uncomment the next line to use a non-root user for all processes.
# user: vscode
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
azurite:
image: mcr.microsoft.com/azure-storage/azurite
restart: unless-stopped
command: "azurite"
# Add "forwardPorts": ["1433"] to **devcontainer.json** to forward MSSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
-
6 – Create a devcontainer.json file in that .devcontainer folder.
Give it the following code:
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/dotnet-mssql
{
"name": "C# (.NET) and Azurite",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-dotnettools.csharp",
"ms-azuretools.vscode-azurefunctions"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [10000, 10001, 10002],
// [Optional] To reuse of your local HTTPS dev cert:
//
// 1. Export it locally using this command:
// * Windows PowerShell:
// dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere"
// * macOS/Linux terminal:
// dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere"
//
// 2. Uncomment these 'remoteEnv' lines:
// "remoteEnv": {
// "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere",
// "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx",
// },
//
// 3. Next, copy your certificate into the container:
// 1. Start the container
// 2. Drag ~/.aspnet/https/aspnetapp.pfx into the root of the file explorer
// 3. Open a terminal in VS Code and run "mkdir -p /home/vscode/.aspnet/https && mv aspnetapp.pfx /home/vscode/.aspnet/https"
// postCreateCommand.sh parameters: $1=SA password, $2=dacpac path, $3=sql script(s) path
"features": {
"github-cli": "latest",
"azure-cli": "latest"
}
}
Now, open the command pallet (CTRL+SHIFT+P) and select the “Remote-Containers: Rebuild and Reopen in Container” option.
Confirm Connectivity to Azurite
Launch the Azure Storage Explorer on your computer.
According to https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite the default HTTP Connection string is…
DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;
-
1 – Right-click on Storage Accounts and click on Connect to Azure Storage
2 – Select that you want to connect to a Storage account or service
-
3 – Select connection string
4 – Give it a display name such as “Dev Container”, and then copy and paste the connection string from above.
-
5 – Click on Next, and then click on Connect.
You will now see the storage account in your available connections.
Double click on it and perform any experiments that you like.
Create a new C# Solution
We will now create a solution file to hold information about all of our projects.
Open a new terminal in Visual Studio Code (CTRL+SHIFT+`) and execute the following commands:
dotnet new sln
mv workspace.sln azure-blob-storage-repository-demo.sln
Create an MVC Web Application
We need a web application to interact with our objects.
Execute the following commands:
dotnet new mvc -o BlobStorageRepositoryDemo.Web
dotnet sln add ./BlobStorageRepositoryDemo.Web/
Create Class Libraries to Interact with Azure Storage
We need to create some model classes. We have a presentation layer already. Let us use Class Libraries for the other Model, Repository and Service Layers.
Execute the following commands at the /workspace command prompt:
dotnet new classlib -o BlobStorageRepositoryDemo.Models
dotnet new classlib -o BlobStorageRepositoryDemo.Repository
dotnet new classlib -o BlobStorageRepositoryDemo.Service
dotnet sln add ./BlobStorageRepositoryDemo.Models
dotnet sln add ./BlobStorageRepositoryDemo.Repository
dotnet sln add ./BlobStorageRepositoryDemo.Service
Assuming that terminal is still at /workspace, the commands will be…
cd BlobStorageRepositoryDemo.Repository
dotnet add reference ../BlobStorageRepositoryDemo.Models/
cd ..
cd BlobStorageRepositoryDemo.Service
dotnet add reference ../BlobStorageRepositoryDemo.Models/
dotnet add reference ../BlobStorageRepositoryDemo.Repository/
cd ..
cd BlobStorageRepositoryDemo.Web
dotnet add reference ../BlobStorageRepositoryDemo.Models/
dotnet add reference ../BlobStorageRepositoryDemo.Repository/
dotnet add reference ../BlobStorageRepositoryDemo.Service/
cd ..
Execute the following commands, assuming that the prompt is still at /workspace/
cd BlobStorageRepositoryDemo.Repository
dotnet add package Azure.Storage.Blobs
cd ..
cd BlobStorageRepositoryDemo.Web
dotnet add package Azure.Storage.Blobs
cd ..
Build the Entity Models
We will be creating a TODO application in this example. So, we will be creating Base Entity classes and a TODO Entity. A Repository Layer for the TODOs, a Service Layer for the TODOs and have the controllers integrate with the service layer.
Blobs are just files. In this demonstration we will be using a Blob Container as an analogy for a Database, a Folder within the container as a Table, and a File as a record. The file will be a JSON Serialized version of our TODO class. The file will be named the
To the “BlobStorageRepositoryDemo.Models” class library, delete the Class1.cs file and create a new file called: IBaseAzureStorageEntityModel.cs
Give the file the following code:
namespace BlobStorageRepositoryDemo.Models;
public interface IBaseAzureStorageEntityModel
{
public string Id {get; set;}
}
Give the file the following code:
namespace BlobStorageRepositoryDemo.Models;
public class BaseAzureStorageEntityModel : IBaseAzureStorageEntityModel
{
public BaseAzureStorageEntityModel()
{
this.Id = Guid.NewGuid().ToString();
}
public BaseAzureStorageEntityModel(string id)
{
this.Id = id;
}
public string Id {get; set;}
}
Give the file the following code:
namespace BlobStorageRepositoryDemo.Models;
public class Todo : BaseAzureStorageEntityModel
{
public string? Title {get; set;}
public string? Description {get; set;}
public bool Completed {get; set;} = false;
}
Build the Repository Layer
Now that we have some entities it is time for us to work on a way to store these entities. Since our repository will be working with Blob files. Let’s create a Service that more directly interacts with Azure’s Blob Files as a foundation for our Repository.
To the BlobStorageRepositoryDemo.Repository class library delte Class1.cs.
Create a new file called IBlobStorageService.cs.
Give it the following code:
namespace BlobStorageRepositoryDemo.Repository;
public interface IBlobStorageService
{
Task DeleteDocumentAsync(string blobName);
Task<Stream> GetBlobContentAsync(string blobName);
Task UpdateBlobContentAsync(string blobName, Stream content);
Task<List<string>?> GetListOfBlobsInFolderAsync(string folderName);
}
Give the file the following code:
using Azure.Storage.Blobs;
namespace BlobStorageRepositoryDemo.Repository;
public class BlobStorageService : IBlobStorageService
{
private readonly BlobServiceClient _blobServiceClient;
private readonly string _containerName;
public BlobStorageService(BlobServiceClient blobServiceClient, string containerName)
{
_blobServiceClient = blobServiceClient;
_containerName = containerName;
}
public async Task DeleteDocumentAsync(string blobName)
{
var containerClient = await GetContainerClientAsync();
var blobClient = containerClient.GetBlobClient(blobName);
await blobClient.DeleteAsync();
}
public async Task<Stream> GetBlobContentAsync(string blobName)
{
var containerClient = await GetContainerClientAsync();
var blobClient = containerClient.GetBlobClient(blobName);
return await blobClient.OpenReadAsync();
}
public async Task<List<string>?> GetListOfBlobsInFolderAsync(string folderName)
{
var results = new List<string>();
var containerClient = await GetContainerClientAsync();
var blobsInFolder = containerClient.GetBlobs(prefix: folderName);
if(blobsInFolder is not null) {
foreach(var blob in blobsInFolder) {
results.Add(blob.Name);
}
return results;
}
return null;
}
public async Task UpdateBlobContentAsync(string blobName, Stream content)
{
var containerClient = await GetContainerClientAsync();
var blobClient = containerClient.GetBlobClient(blobName);
await blobClient.UploadAsync(content, true);
}
private async Task<BlobContainerClient> GetContainerClientAsync()
{
var blobContainerClient = _blobServiceClient.GetBlobContainerClient(_containerName);
await blobContainerClient.CreateIfNotExistsAsync();
return blobContainerClient;
}
}
Now that we have an easy way to interact with blob files. Let’s use that to start managing our application entities by creating a re-usable abstract class that will take a Type parameter matching BaseAzureStorageEntityModel.
Create a new file called IAzureStorageRepository.cs.
Give it the following code:
using BlobStorageRepositoryDemo.Models;
namespace BlobStorageRepositoryDemo.Repository;
public interface IAzureStorageRepository<T> where T : IBaseAzureStorageEntityModel
{
Task<T?> UpsertAsync(T entityDetails);
Task<T?> GetOneAsync(string id);
Task<List<T>?> GetAllAsync();
Task DeletAsync(string id);
}
Give it the following code:
using System.Text.Json;
using Azure.Storage.Blobs;
using BlobStorageRepositoryDemo.Models;
namespace BlobStorageRepositoryDemo.Repository;
public abstract class AzureBlobStorageRepository<T> : IAzureStorageRepository<T> where T : IBaseAzureStorageEntityModel
{
private readonly IBlobStorageService _blobStorageService;
private readonly string _folderName;
public AzureBlobStorageRepository(BlobServiceClient blobServiceClient, string containerName, string folderName)
{
_blobStorageService = new BlobStorageService(blobServiceClient, containerName);
_folderName = folderName;
}
public async Task DeletAsync(string id)
{
string filePath = GetFilePath(id);
await _blobStorageService.DeleteDocumentAsync(filePath);
}
public async Task<List<T>?> GetAllAsync()
{
try {
List<string>? filesInFolder = await _blobStorageService.GetListOfBlobsInFolderAsync(_folderName);
if(filesInFolder is not null) {
var results = new List<T>();
foreach(var file in filesInFolder) {
var id = GetIdFromFilePath(file);
try {
var result = await this.GetOneAsync(id);
if(result is not null) {
results.Add(result);
}
}
catch {
// Do nothing, try to get the next one
}
}
return results;
}
}
catch {
// Do nothing, will return null
}
return null;
}
public async Task<T?> GetOneAsync(string id)
{
try {
string filePath = GetFilePath(id);
var blobContent = await _blobStorageService.GetBlobContentAsync(filePath);
var contentAsObject = JsonSerializer.Deserialize<T>(blobContent);
return contentAsObject;
}
catch {
// Do nothing, will return null
}
return default(T);
}
public async Task<T?> UpsertAsync(T entityDetails)
{
try {
string filePath = GetFilePath(entityDetails.Id);
var entityDetailsAsString = JsonSerializer.Serialize(entityDetails, new JsonSerializerOptions { WriteIndented = true });
if(entityDetails is not null) {
var entityDetailsAsStream = new MemoryStream();
var streamWriter = new StreamWriter(entityDetailsAsStream);
streamWriter.Write(entityDetailsAsString);
streamWriter.Flush();
entityDetailsAsStream.Position = 0;
await _blobStorageService.UpdateBlobContentAsync(filePath, entityDetailsAsStream);
return entityDetails;
}
}
catch {
// Do nothing, return null
}
return default(T);
}
private string GetFilePath(string id)
{
string filePath = string.Join('/', _folderName, $"{id}.json");
return filePath;
}
private string GetIdFromFilePath(string filePath)
{
string[] fileParts = filePath.Split('/');
string lastPart = fileParts.Last();
string[] fileNameParts = lastPart.Split('.');
string idPart = fileNameParts.First();
return idPart;
}
}
With all of this in place we are ready to create a repository for our TODO class.
Create a new file called ITodoAzureBlobStorageRepository.cs.
Give it the following code:
using BlobStorageRepositoryDemo.Models;
namespace BlobStorageRepositoryDemo.Repository;
public interface ITodoAzureBlobStorageRepository : IAzureStorageRepository<Todo>
{
}
Give it the following code:
using Azure.Storage.Blobs;
using BlobStorageRepositoryDemo.Models;
namespace BlobStorageRepositoryDemo.Repository;
public class TodoAzureBlobStorageRepository : AzureBlobStorageRepository<Todo>, ITodoAzureBlobStorageRepository
{
public TodoAzureBlobStorageRepository(BlobServiceClient blobServiceClient) : base(blobServiceClient, "todos", "TodoItems")
{
}
}
When we created the TodoAzureBlobStorageRepository class we said that we want to use the functionality from the AzureBlobStorageRepository with the BlobStorageRespositoryDemo.Models.Todo class as the type parameter, and that functionality should match the signature of ITodoAzureBlobStorageRepository. We created a constructor that will also need the BlobServiceClient, which again will be registered during the web app’s startup. We then also give values to the base abstract class for the container name and folder name. This basically makes it so all of the code that we used in AzureBlobStorageRepository was coded by hand again in a new class with new types, container and folder names. But since we have this abstract class, we avoided having to copy and paste that code.
If we ever wanted to do this again at this point now all we would need to do is create a new model class, then create a new Repository Interface and Model in this fashion and we will be all set.
Furthermore, if we have specific new functions that we want the repository to do we can add those as methods to these specific instances of the Interface and Class.
Build the Service Layer
The service layer in this demonstration will start by creating a repository client. Basically, a class that will use a repository class. It will be an abstract class again with virtual public methods that can be overridden for each class instance. The abstract class will simply try to pass the functionality along to the repository, and if any error occurs return null. When we override a method, we can add logic and directly use the repository, or base class methods.
To the “BlobStorageRepositoryDemo.Service” project delete Class1.cs
Create a new file called IAzureStorageRepositoryClient.cs.
Give it the following code:
using BlobStorageRepositoryDemo.Models;
namespace BlobStorageRepositoryDemo.Service;
public interface IAzureStorageRepositoryClient<T> where T : IBaseAzureStorageEntityModel
{
Task<T?> UpsertAsync(T entityDetails);
Task<T?> GetOneAsync(string id);
Task<List<T>?> GetAllAsync();
Task DeletAsync(string id);
}
Give it the following code:
using BlobStorageRepositoryDemo.Models;
using BlobStorageRepositoryDemo.Repository;
namespace BlobStorageRepositoryDemo.Service;
public abstract class AzureBlobStorageRepositoryClient<T> : IAzureStorageRepository<T> where T : IBaseAzureStorageEntityModel
{
private readonly IAzureStorageRepository<T> _blobStorageRepository;
public AzureBlobStorageRepositoryClient(IAzureStorageRepository<T> blobStorageRepository)
{
_blobStorageRepository = blobStorageRepository;
}
public Task DeletAsync(string id)
{
try {
return _blobStorageRepository.DeletAsync(id);
}
catch {
// Do nothing
}
return Task.CompletedTask;
}
public async Task<List<T>?> GetAllAsync()
{
try {
return await _blobStorageRepository.GetAllAsync();
}
catch {
// Do nothing, return null
}
return null;
}
public async Task<T?> GetOneAsync(string id)
{
try {
return await _blobStorageRepository.GetOneAsync(id);
}
catch {
// Do nothing, return null
}
return default(T);
}
public async Task<T?> UpsertAsync(T entityDetails)
{
try {
var results = await _blobStorageRepository.UpsertAsync(entityDetails);
return results;
}
catch {
// Do nothing, return null
}
return default(T);
}
}
Create a new file called ITodoService.cs
Give it the following code:
using BlobStorageRepositoryDemo.Models;
namespace BlobStorageRepositoryDemo.Service;
public interface ITodoService : IAzureStorageRepositoryClient<Todo>
{
}
Give it the following code:
using BlobStorageRepositoryDemo.Models;
using BlobStorageRepositoryDemo.Repository;
namespace BlobStorageRepositoryDemo.Service;
public class TodoService : AzureBlobStorageRepositoryClient<Todo>, ITodoService
{
public TodoService(ITodoAzureBlobStorageRepository repository) : base (repository)
{
}
}
Register Repositories and Services
Now that we have our Entities, Repositories and Services set up, it is time to let our Web Application know that they exist.
To begin though we must first register the BlobClientService that the repository is going to depend on (and thus the Service Layer will depend on). After that we can register the Repository and Service Interfaces.
In order to register the BlobClientService we need to include a few more NuGet packages for the Web Application. To add them execute the following commands from a terminal. Ensure that you are in the /workspace/BlobStroageRepositoryDemo.Web/ folder.
dotnet add package Microsoft.Extensions.Azure
dotnet add package Azure.Identity
Add the following “usings”
using Azure.Identity;
using Microsoft.Extensions.Azure;
using BlobStorageRepositoryDemo.Repository;
using BlobStorageRepositoryDemo.Service;
// Register Azure Clients
builder.Services.AddAzureClients(azureClientsBuilder => {
azureClientsBuilder.AddBlobServiceClient(builder.Configuration.GetConnectionString("AzureStorage"));
azureClientsBuilder.UseCredential(new DefaultAzureCredential());
});
// Register Repositories
builder.Services.AddTransient<ITodoAzureBlobStorageRepository, TodoAzureBlobStorageRepository>();
// Register Services
builder.Services.AddTransient<ITodoService, TodoService>();
using Azure.Identity;
using Microsoft.Extensions.Azure;
using BlobStorageRepositoryDemo.Repository;
using BlobStorageRepositoryDemo.Service;
var builder = WebApplication.CreateBuilder(args);
// Register Azure Clients
builder.Services.AddAzureClients(azureClientsBuilder => {
azureClientsBuilder.AddBlobServiceClient(builder.Configuration.GetConnectionString("AzureStorage"));
azureClientsBuilder.UseCredential(new DefaultAzureCredential());
});
// Register Repositories
builder.Services.AddTransient<ITodoAzureBlobStorageRepository, TodoAzureBlobStorageRepository>();
// Register Services
builder.Services.AddTransient<ITodoService, TodoService>();
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Your appsettings.json file should look like this.
{
"ConnectionStrings": {
"AzureStorage": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Implement Controllers and Views
Now that we have all of this in place it is time for the web application to start using these services that we have created.
In the BlobStorageRepositoryDemo.Web project, and the Controllers folder add a new file called “TodosController.cs”
Give it the following code:
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using BlobStorageRepositoryDemo.Models;
using BlobStorageRepositoryDemo.Service;
namespace BlobStorageRepositoryDemo.Web.Controllers;
public class TodosController : Controller
{
private readonly ITodoService _todoService;
public TodosController(ITodoService todoService)
{
_todoService = todoService;
}
[HttpGet]
public async Task<IActionResult> Index()
{
var todos = await _todoService.GetAllAsync();
return View(todos);
}
[HttpGet]
public async Task<IActionResult> Details([FromRoute] string id)
{
var todo = await _todoService.GetOneAsync(id);
return View(todo);
}
[HttpGet]
public async Task<IActionResult> Edit([FromRoute] string id)
{
var todo = await _todoService.GetOneAsync(id);
return View(todo);
}
[HttpPost]
public async Task<IActionResult> Edit([FromForm] Todo todoItem)
{
var results = await _todoService.UpsertAsync(todoItem);
return RedirectToAction("Index");
}
[HttpGet]
public IActionResult Create()
{
var newTodo = new Todo();
return View(newTodo);
}
[HttpPost]
public async Task<IActionResult> Create([FromForm] Todo todoItem)
{
var results = await _todoService.UpsertAsync(todoItem);
return RedirectToAction("Index");
}
[HttpGet]
public async Task<IActionResult> Delete([FromRoute] string id)
{
var todoItem = await _todoService.GetOneAsync(id);
return View(todoItem);
}
[HttpPost]
public async Task<IActionResult> ConfirmDelete(Todo todoItem)
{
await _todoService.DeletAsync(todoItem.Id);
return RedirectToAction("Index");
}
}
To the Views -> Shared folder add a new folder called DisplayTemplates. To the new DisplayTempaltes folder add a new file called Todo.cshtml.
Give it the following code:
@model BlobStorageRepositoryDemo.Models.Todo
<dl>
<dt>@Html.DisplayNameFor(m => m.Id)</dt>
<dd>@Html.DisplayFor(m => m.Id)</dd>
<dt>@Html.DisplayNameFor(m => m.Title)</dt>
<dd>@Html.DisplayFor(m => m.Title)</dd>
<dt>@Html.DisplayNameFor(m => m.Description)</dt>
<dd>@Html.DisplayFor(m => m.Description)</dd>
<dt>@Html.DisplayNameFor(m => m.Completed)</dt>
<dt>@Html.DisplayFor(m => m.Completed)</dt>
</dl>
Give it the following code:
@model BlobStorageRepositoryDemo.Models.Todo
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title"></label>
<input class="form-control" asp-for="Title" />
</div>
<div class="form-group">
<label asp-for="Description"></label>
<textarea class="form-control" asp-for="Description"></textarea>
</div>
<div class="form-group">
<label asp-for="Completed"></label>
@Html.EditorFor(m => m.Completed)
</div>
<input class="btn btn-primary" type="submit" value="Save" />
Give it the following code:
@model IEnumerable<BlobStorageRepositoryDemo.Models.Todo>
@{
ViewData["Title"] = "Todos";
}
<h1>@ViewData["Title"]</h1>
<a class="btn btn-primary" asp-action="Create">Create</a>
@if(Model is not null) {
<table class="table table-bordered">
<thead>
<tr>
<th>Title</th>
<th>Completed</th>
<th> </th>
</tr>
</thead>
<tbody>
@foreach(var todoItem in Model) {
<tr>
<td>@todoItem.Title</td>
@if(todoItem.Completed) {
<td>True</td>
}
else {
<td>False</td>
}
<td>
<a asp-action="Details" asp-route-id="@todoItem.Id">Details</a> |
<a asp-action="Edit" asp-route-id="@todoItem.Id">Edit</a> |
<a asp-action="Delete" asp-route-id="@todoItem.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}
@model BlobStorageRepositoryDemo.Models.Todo
@{
ViewData["Title"] = $"Todo - {Model.Title}";
}
<h1>@ViewData["Title"]</h1>
@Html.DisplayForModel()
<div>
<a asp-action="Index">Back to List</a> |
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Delete" asp-route-id="@Model.Id">Delete</a>
</div>
Give it the following code:
@model BlobStorageRepositoryDemo.Models.Todo
@{
ViewData["Title"] = "Create Todo";
}
<h1>@ViewData["Title"]</h1>
<form asp-action="Create" method="post">
@Html.EditorForModel()
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
Give it the following code:
@model BlobStorageRepositoryDemo.Models.Todo
@{
ViewData["Title"] = $"Edit Todo - {Model.Title}";
}
<h1>@ViewData["Title"]</h1>
<form asp-action="Edit" method="post">
@Html.EditorForModel()
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
Give it the following code:
@model BlobStorageRepositoryDemo.Models.Todo
@{
ViewData["Title"] = $"Delete Todo - {Model.Title}?";
}
<h1>@ViewData["Title"]</h1>
@Html.DisplayForModel()
<form asp-action="ConfirmDelete" method="post">
@Html.HiddenFor(m => m.Id)
@Html.HiddenFor(m => m.Title)
@Html.HiddenFor(m => m.Description)
@Html.HiddenFor(m => m.Completed)
<a asp-action="Index">Back to List</a>
<input class="btn btn-danger" type="submit" value="Confirm" />
</form>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Todos" asp-action="Index">Todos</a>
</li>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - BlobStorageRepositoryDemo.Web</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/BlobStorageRepositoryDemo.Web.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">BlobStorageRepositoryDemo.Web</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Todos" asp-action="Index">Todos</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2022 - BlobStorageRepositoryDemo.Web - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Test the application
In a terminal, ensure that you are in the /workspace/BlobStorageRepositoryDemo.Web directory and run.
dotnet dev-certs https --trust
dotnet run
Conclusion
There are many different ways to use Azure Storage, and to even use Azure Blob storage. This article covers the use case of using it as a repository for your MVC Application Data.
GitHub Repository: https://github.com/woodman231/azure-blob-storage-repository-demo
About Intertech
Intertech is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Intertech teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Intertech employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.