cloud

Azure Storage Hands-On Lab

Ennek a logikáját követjük: https://github.com/microsoft/AcademicContent/blob/5a77cd0fcb18137f39a2c0f95c7b91bd30edb603/Labs/Azure%20Services/Azure%20Storage/Azure%20Storage%20and%20Cognitive%20Services%20(MVC).md

Sajnos már teljesen elavult a leírás :disappointed:

Újabb laborfeladatok: https://docs.microsoft.com/en-us/learn/paths/store-data-in-azure/

Azure SDK for .NET csomagok: https://azure.github.io/azure-sdk/releases/latest/all/dotnet.html

Azure Portal

Nyelvi beállítások

A jobb felső részen fogaskerék ikonra bökve. Érdemes angolra állítani.

Költségfigyelés

https://docs.microsoft.com/en-us/azure/billing/billing-check-free-service-usage (Student előfieztésen nem működik a free usage monitor) Ebben laborban:

Ingyenes szolgáltatások: https://azure.microsoft.com/en-us/free/ Azure sponsorship portál: https://www.microsoftazuresponsorships.com/

Tehát az egész labort ingyenes erőforrásokkal végig lehet csinálni.

Névválasztás

Bizonyos erőforrásoknak globálisan vagy a régióban egyedi neve kell legyen. Így könnyen előfordulhat, hogy a név már foglalt. Érdemes ilyenkor valamilyen személyre egyedi prefixet/postfixet alkalmazni pl. neptun kód vagy monogram.

Ex. 1.

Ex. 2.

Ex. 3.

  1. ASP.NET Core MVC projekt (Intellipix)
dotnet new webapp
  1. Próba

VSCode-ban a könyvtár megnyitása. Dotnet assetek generáltatása.

  1. NuGet csomagok
dotnet add package SixLabors.ImageSharp
dotnet add package Azure.Storage.Blobs
dotnet add package System.Interactive.Async
dotnet add package Microsoft.Extensions.Azure
dotnet add package Flurl
  1. Connection string => User Secrets. A teljes connection string legyen benne ne csak az access key!
dotnet user-secrets init
dotnet user-secrets set "AzStore:connectionString" "connstring"
  1. Blob client regisztrálás a DI-ba a a Program.cs-ben a többi builder.Services sor alá
builder.Services.AddAzureClients(azb =>
{
    azb.AddBlobServiceClient(builder.Configuration.GetSection("AzStore"));
});
  1. IndexModel-ben elkérjük a klienst
private readonly BlobServiceClient _blobSvc;
public IndexModel(ILogger<IndexModel> logger, BlobServiceClient blobSvc)
{
    _logger = logger;
    _blobSvc = blobSvc;
}
  1. BlobInfo egy új Models alkönyvtárba
namespace intellipix.Models;

public class BlobInfo
{
    public string ImageUri { get; set; }
    public string ThumbnailUri { get; set; }
    public string? Caption { get; set; }

    public BlobInfo(string imageUri, string thumbnailUri, string? caption=default)
    {
        ImageUri = imageUri;
        ThumbnailUri = thumbnailUri;
        Caption = caption;
    }
}
  1. Blob adatok listázása az IndexModel-be

Az IndexModel tetejére:

using Flurl;
using intellipix.Models;

Cseréljük az eredeti OnGet-et erre:

public IEnumerable<BlobInfo> Blobs { get; set; } = Enumerable.Empty<BlobInfo>();

public async Task OnGet()
{
    BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
    BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
    Blobs = await blobccP.GetBlobsAsync()
        .Select(b => new BlobInfo(
                        blobccP.Uri.AppendPathSegment(b.Name)
                        ,blobccT.Uri.AppendPathSegment(b.Name))
               )
    .ToListAsync();
}
  1. Felület az Index.cshtml-be
<div class="container" style="padding-top: 24px">
    <div class="row">
        <div class="col-sm-8">
           <form method="post" enctype="multipart/form-data">
                <input type="file" asp-for="Upload" id="upload" style="display: none" onchange="$('#submit').click();"/>
                <input type="button" value="Upload a Photo" class="btn btn-primary btn-lg" onclick="$('#upload').click();" />
                <input type="submit" id="submit" style="display: none" asp-page-handler="Upload"/>
            </form>
        </div>
        <div class="col-sm-4 float-right">
        </div>
    </div>
    <hr />
    <div class="row">
        <div class="col-sm-12">
            @foreach (BlobInfo blob in Model.Blobs ?? Enumerable.Empty<BlobInfo>())
            {
                <img src="@blob.ThumbnailUri" width="192" title="@blob.Caption" style="padding-right: 16px; padding-bottom: 16px" />
            }
        </div>
    </div>
</div>
  1. Feltöltés az IndexModel-be
[BindProperty]
public IFormFile? Upload { get; set; }

public async Task<IActionResult> OnPostUploadAsync()
{
    if (Upload != null)
    {
        BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
        BlobClient blobc = blobccP.GetBlobClient(Upload.FileName);
        using Stream stream = Upload.OpenReadStream();
        var resp = await blobc.UploadAsync(stream);
        /*ide jön majd a computer vision logika*/
        stream.Seek(0, SeekOrigin.Begin);
        using Image image = Image.Load(stream);
        image.Mutate(x => x.Resize(192, 0));
        BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
        using MemoryStream memoryStream = new MemoryStream();
        image.Save(memoryStream, image.Metadata.DecodedImageFormat);
        memoryStream.Seek(0, SeekOrigin.Begin);
        await blobccT.UploadBlobAsync(Upload.FileName, memoryStream);
    }
    return RedirectToAction("Index");
}
  1. Példaképek letöltése

  2. Nézzük meg mit műveltünk Azure Storage Explorer-ben és a weboldal forrásában is. Ha átírjuk a thumbnail URI-ban a /thumbnail/-t /photos/-ra, megkapjuk az eredeti képet.

Ex. 4.

Ex. 5.

  1. Computer Vision szolgáltatás létrehozása
    • F0 plan
    • ugyanabba a resource group-ba és régióba, mint ahol a storage account van
  2. Új secret-ek a Keys & Endpoint lapról
dotnet user-secrets set "AzVision:Endpoint" "https://valami.cognitiveservices.azure.com/"
dotnet user-secrets set "AzVision:Key" "titok"
  1. NuGet csomag hozzáadása
dotnet add package Microsoft.Azure.CognitiveServices.Vision.ComputerVision

Ez a csomag az API 3.x-es verzióját hívja, a legújabb 4-es API verzióval kompatibilis csomag jelenleg még beta állapotban van.

  1. Vision client regisztrálás a DI-ba a Startup.ConfigureServices-ben
builder.Services.AddSingleton(provider => {
    return new ComputerVisionClient(new ApiKeyServiceClientCredentials(builder.Configuration["AzVision:Key"]))
        {Endpoint = builder.Configuration["AzVision:Endpoint"]};
});
  1. A kliens elkérése az IndexModel konstruktorban
private readonly ComputerVisionClient _visionClient;

public IndexModel(ILogger<IndexModel> logger, BlobServiceClient blobSvc, ComputerVisionClient visionClient)
{
    _logger = logger;
    _blobSvc = blobSvc;
    _visionClient = visionClient;
}
  1. Feltöltés okosítása

Az OnPostUploadAsync elejére:

VisualFeatureTypes?[] features = new VisualFeatureTypes?[] { VisualFeatureTypes.Description };

Ugyanezen függvényben a kommenttel jelzett helyre:

var analResult = await _visionClient.AnalyzeImageAsync(blobc.Uri.ToString(), features);
var blobMetaDict = analResult.Description.Tags
   .Select((t, i) => new KeyValuePair<string, string>(nameof(analResult.Description.Tags) + i, t))
   .Concat(new Dictionary<string, string> { { nameof(analResult.Description.Captions), analResult.Description.Captions[0].Text } })
   .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
await blobc.SetMetadataAsync(blobMetaDict);
  1. Listázás okosítása
    public async Task OnGet()
    {
     BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
     BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
     Blobs = await blobccP.GetBlobsAsync(BlobTraits.Metadata)
         .Select(b => new BlobInfo(
                         blobccP.Uri.AppendPathSegment(b.Name)
                         ,blobccT.Uri.AppendPathSegment(b.Name)
                         ,b.Metadata.ContainsKey("Captions")? b.Metadata["Captions"]:b.Name)
                )
     .ToListAsync();
    }
    
  2. Próba

Ex. 6.

  1. Új form az eddigi üres div-be
<div class="col-sm-4 float-right">
   <form method="post" enctype="multipart/form-data" class="form-inline">
     <div class="input-group">
        <input type="text" class="form-control" placeholder="Search photos" asp-for="SearchTerm" style="max-width: 800px">
        <span class="input-group-append">
            <button class="btn btn-primary" type="submit" asp-page-handler="Search">Go</button>
        </span>
    </div>
  </form>                
</div>
  1. Kezelőfv az IndexModel-be, átirányítjuk a lekérdező műveletre.
[BindProperty]
public string? SearchTerm {get; set;}

public ActionResult OnPostSearchAsync()
{
    return RedirectToAction("Index", new { term = SearchTerm });
}
  1. Lekérdező művelet okosítása
    public async Task OnGet(string? term)
/**/{
/**/    BlobContainerClient blobccP = _blobSvc.GetBlobContainerClient("photos");
/**/    BlobContainerClient blobccT = _blobSvc.GetBlobContainerClient("thumbnails");
/**/    Blobs = await blobccP.GetBlobsAsync(BlobTraits.Metadata)
            .Where(b=>string.IsNullOrEmpty(term) || 
                      b.Metadata.Any(m=>m.Key.StartsWith("Tags") 
                      && m.Value.Equals(term, StringComparison.InvariantCultureIgnoreCase))
                   )
/**/       .Select(b => new BlobInfo(
/**/                        blobccP.Uri.AppendPathSegment(b.Name)
/**/                        ,blobccT.Uri.AppendPathSegment(b.Name)
/**/                       ,b.Metadata.ContainsKey("Captions")? b.Metadata["Captions"]:b.Name)
/**/              )
/**/    .ToListAsync();
        SearchTerm = term;
/**/}

Ezzel így lekérdezzük az összes blob összes metaadatát és memóriában szűrünk. Alternatív lehetőségnek tűnhet a blob index tags töltése, amivel már Azure oldalon tudnánk szűrni. Sajnos a fenti szűrést nem lehet egy az egyben átfordítani szűrőkifejezéssé. A kulcsokat explicit meg kell adni, nem lehet a kulcsokra kifejezést megadni.

  1. Próba