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
A jobb felső részen fogaskerék ikonra bökve. Érdemes angolra állítani.
Ingyenes szolgáltatások: https://azure.microsoft.com/en-us/pricing/free-services
Ellenőrzés: https://docs.microsoft.com/en-us/azure/billing/billing-check-free-service-usage
Költségfigyelés: https://learn.microsoft.com/en-us/azure/cost-management-billing/benefits/credits/mca-check-azure-credits-balance?tabs=portal
Ebben laborban:
Tehát az egész labort ingyenes erőforrásokkal végig lehet csinálni.
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.
https://learn.microsoft.com/en-us/azure/search/cognitive-search-skill-image-analysis
https://learn.microsoft.com/en-us/azure/search/search-how-to-index-azure-blob-storage#indexing-blob-metadata
Intellipix)mkdir intellipixdotnet new webapp
VSCode-ban a könyvtár megnyitása (Solution Explorer-ben). Indítási projekt beállítása.
dotnet add package Azure.Storage.Blobs
dotnet add package Microsoft.Extensions.Azure
dotnet add package SixLabors.ImageSharp
builder.Services sor aláusing Microsoft.Extensions.Azure;
//builder.Services.
builder.Services.AddAzureClients(azb =>
{
azb.AddBlobServiceClient(new Uri("https://ipix.blob.core.windows.net"));
});
IndexModel-ben elkérjük a klienstusing Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Sas;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Processing;
//class definíció átírva
public class IndexModel(BlobServiceClient blobSvc): PageModel
BlobInfo record típuspublic record BlobInfo(string ImageUri, string ThumbnailUri, string? Caption = default);
IndexModel-beAz IndexModel-be:
private const string PhotosContainerName = "photos";
private const string ThumbnailsContainerName = "thumbnails";
private const int ThumbnailWidthPx = 192;
public IEnumerable<BlobInfo> Blobs { get; set; } = [];
private static BlobSasQueryParameters CreateContainerSas(
BlobContainerClient containerClient,
UserDelegationKey delegationKey,
string accountName)
{
var sasBuilder = new BlobSasBuilder
{
BlobContainerName = containerClient.Name,
Resource = "c",
StartsOn = DateTimeOffset.UtcNow.AddMinutes(-5),
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(30),
CacheControl = "max-age=1800"
};
sasBuilder.SetPermissions(BlobContainerSasPermissions.Read);
return sasBuilder.ToSasQueryParameters(delegationKey, accountName);
}
Cseréljük az eredeti OnGet-et erre:
public async Task OnGetAsync()
{
var photosClient = blobSvc.GetBlobContainerClient(PhotosContainerName);
var thumbnailsClient = blobSvc.GetBlobContainerClient(ThumbnailsContainerName);
var delegationKey = await blobSvc.GetUserDelegationKeyAsync(
startsOn: DateTimeOffset.UtcNow.AddMinutes(-5),
expiresOn: DateTimeOffset.UtcNow.AddDays(1));
var photosSas = CreateContainerSas(photosClient, delegationKey, blobSvc.AccountName);
var thumbnailsSas = CreateContainerSas(thumbnailsClient, delegationKey, blobSvc.AccountName);
Blobs = await photosClient.GetBlobsAsync()
.Select(b => new BlobInfo(
new BlobUriBuilder(photosClient.Uri)
{ BlobName = b.Name, Sas = photosSas }.ToUri().ToString()
, new BlobUriBuilder(thumbnailsClient.Uri)
{ BlobName = b.Name, Sas = thumbnailsSas }.ToUri().ToString()
))
.ToListAsync();
}
<div class="pt-4">
<div class="row">
<div class="col-sm-8">
<form method="post" enctype="multipart/form-data" asp-page-handler="Upload">
<input type="file" asp-for="Upload" id="upload" class="d-none" onchange="this.form.requestSubmit();" />
<button type="button" class="btn btn-primary btn-lg" onclick="document.getElementById('upload').click();">
Upload
</button>
</form>
</div>
</div>
<hr />
<div class="row">
<div class="col-sm-12">
@foreach (var blob in Model.Blobs)
{
<img src="@blob.ThumbnailUri" width="192" title="@blob.Caption" alt="" loading="lazy" class="me-3 mb-3" />
}
</div>
</div>
</div>
IndexModel-be[BindProperty]
public IFormFile? Upload { get; set; }
public async Task<IActionResult> OnPostUploadAsync()
{
if (Upload is { Length: > 0 })
{
// Demo only: in a real app, do NOT trust user-provided filenames; sanitize and/or generate your own blob names.
var fileName = Upload.FileName;
var photosContainer = blobSvc.GetBlobContainerClient(PhotosContainerName);
var thumbnailsContainer = blobSvc.GetBlobContainerClient(ThumbnailsContainerName);
var photoBlob = photosContainer.GetBlobClient(fileName);
await using (var uploadStream = Upload.OpenReadStream())
{
await photoBlob.UploadAsync(uploadStream, overwrite: true, cancellationToken: HttpContext.RequestAborted);
}
await using var imageStream = Upload.OpenReadStream();
using var image = await Image.LoadAsync(imageStream, cancellationToken: HttpContext.RequestAborted);
image.Mutate(x => x.Resize(ThumbnailWidthPx, 0));
using var thumbnailStream = new MemoryStream();
var format = image.Metadata.DecodedImageFormat ?? PngFormat.Instance;
await image.SaveAsync(thumbnailStream, format, cancellationToken: HttpContext.RequestAborted);
thumbnailStream.Position = 0;
var thumbnailBlob = thumbnailsContainer.GetBlobClient(fileName);
await thumbnailBlob.UploadAsync(thumbnailStream, overwrite: true, cancellationToken: HttpContext.RequestAborted);
}
return RedirectToPage();
}
Példaképek letöltése
Töltsük fel újra az eddig feltöltött képeket (felülírás), és még néhányat. Ellenőrizzük a thumbnail-eket.
Nézzük meg a kép URL-eket a weboldal forrásában.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glightbox@3.3.1/dist/css/glightbox.min.css" />
<script src="https://cdn.jsdelivr.net/npm/glightbox@3.3.1/dist/js/glightbox.min.js"></script>
<a href="@blob.ImageUri"
class="glightbox"
data-gallery="photos"
data-title="@blob.Caption">
<img src="..." />
</a>
document.addEventListener('DOMContentLoaded', () => {
if (typeof GLightbox === 'undefined') {
return;
}
GLightbox({
selector: '.glightbox',
loop: true,
touchNavigation: true
});
});
dotnet add package Azure.Search.Documents
"SearchService": {
"Endpoint": "https://ipix2idx.search.windows.net",
"IndexName": "ipix2idx"
},
//builder.Services.AddAzureClients(azb =>
//{
// azb.AddBlobServiceClient(...);
azb.AddSearchClient(builder.Configuration.GetSection("SearchService"));
//});
IndexModel konstruktorbanusing Azure.Search.Documents;
//..
public class IndexModel(BlobServiceClient blobSvc, SearchClient searchClient) : PageModel
using System.Text.Json.Serialization;
//namespace xxx.Models;
public class PhotoDocument
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("metadata_storage_path")]
public string MetadataStoragePath { get; set; } = string.Empty;
[JsonPropertyName("description")]
public IReadOnlyList<PhotoDescription> Description { get; set; } = [];
[JsonPropertyName("tags")]
public IReadOnlyList<PhotoTag> Tags { get; set; } = [];
}
public class PhotoDescription
{
[JsonPropertyName("tags")]
public IReadOnlyList<string> Tags { get; set; } = [];
[JsonPropertyName("captions")]
public IReadOnlyList<PhotoCaption> Captions { get; set; } = [];
}
public class PhotoCaption
{
[JsonPropertyName("text")]
public string Text { get; init; } = string.Empty;
[JsonPropertyName("confidence")]
public double Confidence { get; init; }
}
public class PhotoTag
{
[JsonPropertyName("name")]
public string Name { get; init; } = string.Empty;
[JsonPropertyName("hint")]
public string Hint { get; init; } = string.Empty;
[JsonPropertyName("confidence")]
public double Confidence { get; init; }
}
<hr /> fölötti <div> vége elé<div class="col-sm-4">
<form method="post" asp-page-handler="Search">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search photos" asp-for="SearchTerm" />
<button class="btn btn-primary" type="submit">Go</button>
</div>
</form>
</div>
Search kezelőfüggvény az IndexModel-be[BindProperty(SupportsGet = true)]
public string? SearchTerm { get; set; }
public IActionResult OnPostSearch()
{
return RedirectToPage(new { SearchTerm });
}
OnGetAsync()// SAS tokenek legyártása
if (!string.IsNullOrWhiteSpace(SearchTerm))
{
var searchResults = await searchClient.SearchAsync<PhotoDocument>(SearchTerm, new SearchOptions
{
Select = { "metadata_storage_path", "description" },
Size = 100
});
var blobs = new List<BlobInfo>();
await foreach (var result in searchResults.Value.GetResultsAsync())
{
var doc = result.Document;
var blobName = new Uri(doc.MetadataStoragePath).Segments[^1];
var caption = string.Join(", ", doc.Description
.SelectMany(d => d.Captions)
.OrderByDescending(c => c.Confidence)
.Select(c => $"{c.Text} ({c.Confidence:P0})"));
blobs.Add(new BlobInfo(
new BlobUriBuilder(photosClient.Uri) { BlobName = blobName, Sas = photosSas }
.ToUri().ToString(),
new BlobUriBuilder(thumbnailsClient.Uri) { BlobName = blobName, Sas = thumbnailsSas }
.ToUri().ToString(),
caption
));
}
Blobs = blobs;
}
else
{
//Blobs = await photosClient.GetBlobsAsync()
}