mirror of
https://github.com/AlexMacocian/Net.Sdk.Web.Extensions.git
synced 2026-07-15 13:39:29 +00:00
Setup Azure package
This commit is contained in:
@@ -6,6 +6,7 @@ on:
|
||||
- master
|
||||
paths:
|
||||
- Net.Sdk.Web.Extensions/**
|
||||
- Net.Sdk.Web.Extensions.Azure/**
|
||||
- ".github/workflows/cd.yaml"
|
||||
|
||||
jobs:
|
||||
@@ -22,6 +23,7 @@ jobs:
|
||||
Configuration: Release
|
||||
Solution_Path: Net.Sdk.Web.Extensions.sln
|
||||
Source_Project_Path: Net.Sdk.Web.Extensions\Net.Sdk.Web.Extensions.csproj
|
||||
Azure_Project_path: Net.Sdk.Web.Extensions.Azure\Net.Sdk.Web.Extensions.Azure.csproj
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
|
||||
steps:
|
||||
@@ -43,11 +45,17 @@ jobs:
|
||||
env:
|
||||
RuntimeIdentifier: win-${{ matrix.targetplatform }}
|
||||
|
||||
- name: Build AspNetCore.Extensions.NetStandard project
|
||||
- name: Build Net.Sdk.Extensions project
|
||||
run: dotnet build $env:Source_Project_Path -c $env:Configuration
|
||||
|
||||
- name: Package AspNetCore.Extensions.NetStandard
|
||||
- name: Package Net.Sdk.Extensions
|
||||
run: dotnet pack -c Release -o . $env:Source_Project_Path
|
||||
|
||||
- name: Build Net.Sdk.Extensions.Azure project
|
||||
run: dotnet build $env:Azure_Project_Path -c $env:Configuration
|
||||
|
||||
- name: Package Net.Sdk.Extensions.Azure
|
||||
run: dotnet pack -c Release -o . $env:Azure_Project_Path
|
||||
|
||||
- name: Publish
|
||||
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
- master
|
||||
paths:
|
||||
- Net.Sdk.Web.Extensions/**
|
||||
- Net.Sdk.Web.Extensions.Azure/**
|
||||
- ".github/workflows/ci.yaml"
|
||||
|
||||
jobs:
|
||||
@@ -21,6 +22,7 @@ jobs:
|
||||
env:
|
||||
Solution_Path: Net.Sdk.Web.Extensions.sln
|
||||
Source_Project_Path: Net.Sdk.Web.Extensions\Net.Sdk.Web.Extensions.csproj
|
||||
Azure_Project_path: Net.Sdk.Web.Extensions.Azure\Net.Sdk.Web.Extensions.Azure.csproj
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
|
||||
steps:
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Azure;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.Azure;
|
||||
|
||||
internal sealed class InterceptingAsyncPageable<T> : AsyncPageable<T> where T : notnull
|
||||
{
|
||||
private readonly Action<Page<T>> interceptPage;
|
||||
private readonly Action interceptSuccess;
|
||||
private readonly AsyncPageable<T> originalPageable;
|
||||
|
||||
public InterceptingAsyncPageable(AsyncPageable<T> originalPageable, Action<Page<T>> interceptPage, Action interceptSuccess)
|
||||
{
|
||||
this.originalPageable = originalPageable;
|
||||
this.interceptPage = interceptPage;
|
||||
this.interceptSuccess = interceptSuccess;
|
||||
}
|
||||
|
||||
public override async IAsyncEnumerable<Page<T>> AsPages(string? continuationToken = null, int? pageSizeHint = null)
|
||||
{
|
||||
await foreach (var page in this.originalPageable.AsPages(continuationToken, pageSizeHint))
|
||||
{
|
||||
this.interceptPage(page);
|
||||
yield return page;
|
||||
}
|
||||
}
|
||||
|
||||
public async override IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await foreach (var item in this.originalPageable)
|
||||
{
|
||||
this.interceptSuccess();
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Azure;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.Azure;
|
||||
|
||||
internal sealed class InterceptingPageable<T> : Pageable<T> where T : notnull
|
||||
{
|
||||
private readonly Action<Page<T>> interceptPage;
|
||||
private readonly Action interceptSuccess;
|
||||
private readonly Pageable<T> originalPageable;
|
||||
|
||||
public InterceptingPageable(Pageable<T> originalPageable, Action<Page<T>> interceptPage, Action interceptSuccess)
|
||||
{
|
||||
this.originalPageable = originalPageable;
|
||||
this.interceptPage = interceptPage;
|
||||
this.interceptSuccess = interceptSuccess;
|
||||
}
|
||||
|
||||
public override IEnumerable<Page<T>> AsPages(string? continuationToken = null, int? pageSizeHint = null)
|
||||
{
|
||||
foreach (var page in this.originalPageable.AsPages(continuationToken, pageSizeHint))
|
||||
{
|
||||
this.interceptPage(page);
|
||||
yield return page;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
foreach (var item in this.originalPageable)
|
||||
{
|
||||
this.interceptSuccess();
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Azure.Core;
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage;
|
||||
using Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.Azure;
|
||||
|
||||
public class NamedBlobContainerClient<TCategory> : BlobContainerClient
|
||||
{
|
||||
public NamedBlobContainerClient(string connectionString, string blobContainerName) : base(connectionString, blobContainerName)
|
||||
{
|
||||
}
|
||||
|
||||
public NamedBlobContainerClient(Uri blobContainerUri, BlobClientOptions? options = null) : base(blobContainerUri, options)
|
||||
{
|
||||
}
|
||||
|
||||
public NamedBlobContainerClient(string connectionString, string blobContainerName, BlobClientOptions options) : base(connectionString, blobContainerName, options)
|
||||
{
|
||||
}
|
||||
|
||||
public NamedBlobContainerClient(Uri blobContainerUri, StorageSharedKeyCredential credential, BlobClientOptions? options = null) : base(blobContainerUri, credential, options)
|
||||
{
|
||||
}
|
||||
|
||||
public NamedBlobContainerClient(Uri blobContainerUri, AzureSasCredential credential, BlobClientOptions? options = null) : base(blobContainerUri, credential, options)
|
||||
{
|
||||
}
|
||||
|
||||
public NamedBlobContainerClient(Uri blobContainerUri, TokenCredential credential, BlobClientOptions? options = null) : base(blobContainerUri, credential, options)
|
||||
{
|
||||
}
|
||||
|
||||
protected NamedBlobContainerClient()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
using Azure.Core;
|
||||
using Azure;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.Linq.Expressions;
|
||||
using Azure.Data.Tables;
|
||||
using System.Core.Extensions;
|
||||
using Azure.Data.Tables.Models;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.Azure;
|
||||
|
||||
public class NamedTableClient<TCategory> : TableClient
|
||||
{
|
||||
private readonly ILogger<NamedTableClient<TCategory>> logger;
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, Uri endpoint, TableClientOptions? options = null) : base(endpoint, options)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, string connectionString, string tableName) : base(connectionString, tableName)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, Uri endpoint, AzureSasCredential credential, TableClientOptions? options = null) : base(endpoint, credential, options)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, Uri endpoint, string tableName, TableSharedKeyCredential credential) : base(endpoint, tableName, credential)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, string connectionString, string tableName, TableClientOptions? options = null) : base(connectionString, tableName, options)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, Uri endpoint, string tableName, TableSharedKeyCredential credential, TableClientOptions? options = null) : base(endpoint, tableName, credential, options)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public NamedTableClient(ILogger<NamedTableClient<TCategory>> logger, Uri endpoint, string tableName, TokenCredential tokenCredential, TableClientOptions? options = null) : base(endpoint, tableName, tokenCredential, options)
|
||||
{
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public override Response<TableItem> Create(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.Create(cancellationToken), nameof(this.Create));
|
||||
}
|
||||
|
||||
public override Response<TableItem> CreateIfNotExists(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.CreateIfNotExists(cancellationToken), nameof(this.CreateIfNotExists));
|
||||
}
|
||||
|
||||
public override Response AddEntity<T>(T entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.AddEntity(entity, cancellationToken), nameof(this.AddEntity));
|
||||
}
|
||||
|
||||
public override Response Delete(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.Delete(cancellationToken), nameof(this.Delete));
|
||||
}
|
||||
|
||||
public override Response DeleteEntity(string partitionKey, string rowKey, ETag ifMatch = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.DeleteEntity(partitionKey, rowKey, ifMatch, cancellationToken), nameof(this.DeleteEntity));
|
||||
}
|
||||
|
||||
public override Response UpdateEntity<T>(T entity, ETag ifMatch, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.UpdateEntity(entity, ifMatch, mode, cancellationToken), nameof(this.UpdateEntity));
|
||||
}
|
||||
|
||||
public override Response UpsertEntity<T>(T entity, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.UpsertEntity(entity, mode, cancellationToken), nameof(this.UpsertEntity));
|
||||
}
|
||||
|
||||
public override Response<T> GetEntity<T>(string partitionKey, string rowKey, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.GetEntity<T>(partitionKey, rowKey, select, cancellationToken), nameof(this.GetEntity));
|
||||
}
|
||||
|
||||
public override NullableResponse<T> GetEntityIfExists<T>(string partitionKey, string rowKey, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.GetEntityIfExists<T>(partitionKey, rowKey, select, cancellationToken), nameof(GetEntityIfExists));
|
||||
}
|
||||
|
||||
public override Pageable<T> Query<T>(string? filter = null, int? maxPerPage = null, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.Query<T>(filter, maxPerPage, select, cancellationToken), nameof(this.Query));
|
||||
}
|
||||
|
||||
public override Pageable<T> Query<T>(Expression<Func<T, bool>> filter, int? maxPerPage = null, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.Query(filter, maxPerPage, select, cancellationToken), nameof(this.Query));
|
||||
}
|
||||
|
||||
public override Response<IReadOnlyList<Response>> SubmitTransaction(IEnumerable<TableTransactionAction> transactionActions, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.SubmitTransaction(transactionActions, cancellationToken), nameof(this.SubmitTransaction));
|
||||
}
|
||||
|
||||
public override Task<Response<TableItem>> CreateAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.CreateAsync(cancellationToken), nameof(this.CreateAsync));
|
||||
}
|
||||
|
||||
public override Task<Response<TableItem>> CreateIfNotExistsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.CreateIfNotExistsAsync(cancellationToken), nameof(this.CreateIfNotExistsAsync));
|
||||
}
|
||||
|
||||
public override Task<Response> AddEntityAsync<T>(T entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.AddEntityAsync(entity, cancellationToken), nameof(this.AddEntityAsync));
|
||||
}
|
||||
|
||||
public override Task<Response> DeleteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.DeleteAsync(cancellationToken), nameof(this.DeleteAsync));
|
||||
}
|
||||
|
||||
public override Task<Response> DeleteEntityAsync(string partitionKey, string rowKey, ETag ifMatch = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.DeleteEntityAsync(partitionKey, rowKey, ifMatch, cancellationToken), nameof(this.DeleteEntityAsync));
|
||||
}
|
||||
|
||||
public override Task<Response> UpdateEntityAsync<T>(T entity, ETag ifMatch, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.UpdateEntityAsync(entity, ifMatch, mode, cancellationToken), nameof(this.UpdateEntityAsync));
|
||||
}
|
||||
|
||||
public override Task<Response> UpsertEntityAsync<T>(T entity, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.UpsertEntityAsync(entity, mode, cancellationToken), nameof(this.UpsertEntityAsync));
|
||||
}
|
||||
|
||||
public override Task<Response<T>> GetEntityAsync<T>(string partitionKey, string rowKey, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.GetEntityAsync<T>(partitionKey, rowKey, select, cancellationToken), nameof(this.GetEntityAsync));
|
||||
}
|
||||
|
||||
public override Task<NullableResponse<T>> GetEntityIfExistsAsync<T>(string partitionKey, string rowKey, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.GetEntityIfExistsAsync<T>(partitionKey, rowKey, select, cancellationToken), nameof(this.GetEntityIfExistsAsync));
|
||||
}
|
||||
|
||||
public override AsyncPageable<T> QueryAsync<T>(Expression<Func<T, bool>> filter, int? maxPerPage = null, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.QueryAsync(filter, maxPerPage, select, cancellationToken), nameof(this.QueryAsync));
|
||||
}
|
||||
|
||||
public override AsyncPageable<T> QueryAsync<T>(string? filter = null, int? maxPerPage = null, IEnumerable<string>? select = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.QueryAsync<T>(filter, maxPerPage, select, cancellationToken), nameof(this.QueryAsync));
|
||||
}
|
||||
|
||||
public override Task<Response<IReadOnlyList<Response>>> SubmitTransactionAsync(IEnumerable<TableTransactionAction> transactionActions, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return this.LogOperation(() => base.SubmitTransactionAsync(transactionActions, cancellationToken), nameof(this.SubmitTransactionAsync));
|
||||
}
|
||||
|
||||
private Response LogOperation(Func<Response> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = func();
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", response.Status, sw.ElapsedMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private Response<T> LogOperation<T>(Func<Response<T>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = func();
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private NullableResponse<T> LogOperation<T>(Func<NullableResponse<T>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = func();
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private Pageable<T> LogOperation<T>(Func<Pageable<T>> func, string operationName)
|
||||
where T : notnull
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
var response = func();
|
||||
return new InterceptingPageable<T>(response, page =>
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", page.GetRawResponse().Status, sw.ElapsedMilliseconds);
|
||||
}, () =>
|
||||
{
|
||||
scopedLogger.LogInformation("<< 200 {1}ms", sw.ElapsedMilliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
private Response<IReadOnlyList<Response>> LogOperation(Func<Response<IReadOnlyList<Response>>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = func();
|
||||
foreach (var action in response.Value)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", action.Status, sw.ElapsedMilliseconds);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Response> LogOperation(Func<Task<Response>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = await func();
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", response.Status, sw.ElapsedMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Response<T>> LogOperation<T>(Func<Task<Response<T>>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = await func();
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<NullableResponse<T>> LogOperation<T>(Func<Task<NullableResponse<T>>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = await func();
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", response.GetRawResponse().Status, sw.ElapsedMilliseconds);
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private AsyncPageable<T> LogOperation<T>(Func<AsyncPageable<T>> func, string operationName)
|
||||
where T : notnull
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
var response = func();
|
||||
return new InterceptingAsyncPageable<T>(response, page =>
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", page.GetRawResponse().Status, sw.ElapsedMilliseconds);
|
||||
}, () =>
|
||||
{
|
||||
scopedLogger.LogInformation("<< 200 {1}ms", sw.ElapsedMilliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<Response<IReadOnlyList<Response>>> LogOperation(Func<Task<Response<IReadOnlyList<Response>>>> func, string operationName)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger(operationName, string.Empty);
|
||||
var sw = Stopwatch.StartNew();
|
||||
scopedLogger.LogInformation(">> {0}", this.Uri);
|
||||
try
|
||||
{
|
||||
var response = await func();
|
||||
foreach (var action in response.Value)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", action.Status, sw.ElapsedMilliseconds);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
scopedLogger.LogInformation("<< {0} {1}ms", ex.Status, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Version>0.7</Version>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<RepositoryUrl>https://github.com/AlexMacocian/Net.Sdk.Web.Extensions</RepositoryUrl>
|
||||
<Description>Azure specific extensions for Microsoft.NET.Sdk.Web</Description>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE" Link="LICENSE">
|
||||
<PackagePath></PackagePath>
|
||||
<Pack>True</Pack>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Core" Version="1.42.0" />
|
||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.12.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
|
||||
<PackageReference Include="Net.Sdk.Web.Extensions" Version="0.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
public sealed class AzureCredentialsOptions : IAzureClientSecretCredentialOptions
|
||||
{
|
||||
[JsonPropertyName(nameof(ClientSecret))]
|
||||
public string ClientSecret { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName(nameof(ClientId))]
|
||||
public string ClientId { get; set; } = default!;
|
||||
|
||||
[JsonPropertyName(nameof(TenantId))]
|
||||
public string TenantId { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
public interface IAzureBlobStorageOptions
|
||||
{
|
||||
string ContainerName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
public interface IAzureClientSecretCredentialOptions : IAzureCredentialOptions
|
||||
{
|
||||
string ClientSecret { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
public interface IAzureCredentialOptions
|
||||
{
|
||||
string ClientId { get; set; }
|
||||
|
||||
string TenantId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
public interface IAzureTableStorageOptions
|
||||
{
|
||||
string TableName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Net.Sdk.Web.Extensions.Azure.Options;
|
||||
|
||||
public interface IStorageAccountOptions
|
||||
{
|
||||
string AccountName { get; }
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Net.Sdk.Web.Extensions.Azure.Options;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
|
||||
namespace Net.Sdk.Web.Extensions.Azure;
|
||||
|
||||
public static class WebApplicationBuilderExtensions
|
||||
{
|
||||
public static WebApplicationBuilder ConfigureAzureClientSecretCredentials(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.ThrowIfNull()
|
||||
.ConfigureExtended<AzureCredentialsOptions>()
|
||||
.Services.AddSingleton<TokenCredential>(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<AzureCredentialsOptions>>().Value;
|
||||
return new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder ConfigureAzureClientSecretCredentials<TOptions>(this WebApplicationBuilder builder)
|
||||
where TOptions : class, IAzureClientSecretCredentialOptions, new()
|
||||
{
|
||||
builder.ThrowIfNull()
|
||||
.ConfigureExtended<TOptions>()
|
||||
.Services.AddSingleton<TokenCredential>(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<TOptions>>().Value;
|
||||
return new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithTableClientSingleton<TCategory, TAccountOptions, TTableOptions>(this WebApplicationBuilder builder)
|
||||
where TTableOptions : class, IAzureTableStorageOptions, new()
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
builder.Services.ThrowIfNull()
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
var clientOptions = sp.GetRequiredService<IOptions<TTableOptions>>();
|
||||
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TCategory>>>();
|
||||
return new NamedTableClient<TCategory>(logger, new Uri($"https://{storageOptions.Value.AccountName}.table.core.windows.net"), clientOptions.Value.TableName, tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithTableClientSingleton<TCategory, TAccountOptions>(this WebApplicationBuilder builder, string tableName)
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
tableName.ThrowIfNull();
|
||||
builder.Services.ThrowIfNull()
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TCategory>>>();
|
||||
return new NamedTableClient<TCategory>(logger, new Uri($"https://{storageOptions.Value.AccountName}.table.core.windows.net"), tableName, tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithTableClientSingleton<TCategory>(this WebApplicationBuilder builder, string accountName, string tableName)
|
||||
{
|
||||
tableName.ThrowIfNull();
|
||||
accountName.ThrowIfNull();
|
||||
builder.Services.ThrowIfNull()
|
||||
.AddSingleton(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TCategory>>>();
|
||||
return new NamedTableClient<TCategory>(logger, new Uri($"https://{accountName}.table.core.windows.net"), tableName, tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithTableClientScoped<TCategory, TAccountOptions, TTableOptions>(this WebApplicationBuilder builder)
|
||||
where TTableOptions : class, IAzureTableStorageOptions, new()
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
builder.Services.ThrowIfNull()
|
||||
.AddScoped(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
var clientOptions = sp.GetRequiredService<IOptions<TTableOptions>>();
|
||||
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TCategory>>>();
|
||||
return new NamedTableClient<TCategory>(logger, new Uri($"https://{storageOptions.Value.AccountName}.table.core.windows.net"), clientOptions.Value.TableName, tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithTableClientScoped<TCategory, TAccountOptions>(this WebApplicationBuilder builder, string tableName)
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
tableName.ThrowIfNull();
|
||||
builder.Services.ThrowIfNull()
|
||||
.AddScoped(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TCategory>>>();
|
||||
return new NamedTableClient<TCategory>(logger, new Uri($"https://{storageOptions.Value.AccountName}.table.core.windows.net"), tableName, tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithTableClientScoped<TCategory>(this WebApplicationBuilder builder, string accountName, string tableName)
|
||||
{
|
||||
tableName.ThrowIfNull();
|
||||
accountName.ThrowIfNull();
|
||||
builder.Services.ThrowIfNull()
|
||||
.AddScoped(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var logger = sp.GetRequiredService<ILogger<NamedTableClient<TCategory>>>();
|
||||
return new NamedTableClient<TCategory>(logger, new Uri($"https://{accountName}.table.core.windows.net"), tableName, tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithBlobContainerClientSingleton<TCategory, TAccountOptions, TBlobContainerOptions>(this WebApplicationBuilder builder)
|
||||
where TBlobContainerOptions : class, IAzureBlobStorageOptions, new()
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
builder.ThrowIfNull()
|
||||
.Services.AddSingleton(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
var clientOptions = sp.GetRequiredService<IOptions<TBlobContainerOptions>>();
|
||||
return new NamedBlobContainerClient<TCategory>(new Uri($"https://{storageOptions.Value.AccountName}.blob.core.windows.net/{clientOptions.Value.ContainerName}"), tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithBlobContainerClientSingleton<TCategory, TAccountOptions>(this WebApplicationBuilder builder, string containerName)
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
containerName.ThrowIfNull();
|
||||
builder.ThrowIfNull()
|
||||
.Services.AddSingleton(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
return new NamedBlobContainerClient<TCategory>(new Uri($"https://{storageOptions.Value.AccountName}.blob.core.windows.net/{containerName}"), tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithBlobContainerClientSingleton<TCategory>(this WebApplicationBuilder builder, string accountName, string containerName)
|
||||
{
|
||||
accountName.ThrowIfNull();
|
||||
containerName.ThrowIfNull();
|
||||
builder.ThrowIfNull()
|
||||
.Services.AddSingleton(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
return new NamedBlobContainerClient<TCategory>(new Uri($"https://{accountName}.blob.core.windows.net/{containerName}"), tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithBlobContainerClientScoped<TCategory, TAccountOptions, TBlobContainerOptions>(this WebApplicationBuilder builder)
|
||||
where TBlobContainerOptions : class, IAzureBlobStorageOptions, new()
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
builder.ThrowIfNull()
|
||||
.Services.AddScoped(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
var clientOptions = sp.GetRequiredService<IOptions<TBlobContainerOptions>>();
|
||||
return new NamedBlobContainerClient<TCategory>(new Uri($"https://{storageOptions.Value.AccountName}.blob.core.windows.net/{clientOptions.Value.ContainerName}"), tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithBlobContainerClientScoped<TCategory, TAccountOptions>(this WebApplicationBuilder builder, string containerName)
|
||||
where TAccountOptions : class, IStorageAccountOptions, new()
|
||||
{
|
||||
containerName.ThrowIfNull();
|
||||
builder.ThrowIfNull()
|
||||
.Services.AddScoped(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
var storageOptions = sp.GetRequiredService<IOptions<TAccountOptions>>();
|
||||
return new NamedBlobContainerClient<TCategory>(new Uri($"https://{storageOptions.Value.AccountName}.blob.core.windows.net/{containerName}"), tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder WithBlobContainerClientScoped<TCategory>(this WebApplicationBuilder builder, string accountName, string containerName)
|
||||
{
|
||||
accountName.ThrowIfNull();
|
||||
containerName.ThrowIfNull();
|
||||
builder.ThrowIfNull()
|
||||
.Services.AddScoped(sp =>
|
||||
{
|
||||
var tokenCredential = sp.GetRequiredService<TokenCredential>();
|
||||
return new NamedBlobContainerClient<TCategory>(new Uri($"https://{accountName}.blob.core.windows.net/{containerName}"), tokenCredential, default);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
|
||||
.github\workflows\ci.yaml = .github\workflows\ci.yaml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net.Sdk.Web.Extensions.Azure", "Net.Sdk.Web.Extensions.Azure\Net.Sdk.Web.Extensions.Azure.csproj", "{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -33,6 +35,10 @@ Global
|
||||
{D0570CEC-F740-4332-AC10-8491974D1827}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D0570CEC-F740-4332-AC10-8491974D1827}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D0570CEC-F740-4332-AC10-8491974D1827}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F9D782AF-2E99-497A-B5C2-5DF6FB254EE5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user