feat: Add IAgreementClient (#5)
This commit is contained in:
@@ -15,6 +15,7 @@ using Microsoft.Extensions.Options;
|
||||
using NodaTime;
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Accounts;
|
||||
using VMelnalksnis.NordigenDotNet.Agreements;
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
using VMelnalksnis.NordigenDotNet.Requisitions;
|
||||
|
||||
@@ -54,6 +55,7 @@ public static class ServiceCollectionExtensions
|
||||
return serviceCollection
|
||||
.AddTransient<INordigenClient, NordigenClient>()
|
||||
.AddTransient<IAccountClient, AccountClient>()
|
||||
.AddTransient<IAgreementClient, AgreementClient>()
|
||||
.AddTransient<IInstitutionClient, InstitutionClient>()
|
||||
.AddTransient<IRequisitionClient, RequisitionClient>()
|
||||
.AddTransient(provider => provider.GetRequiredService<IOptionsSnapshot<NordigenOptions>>().Value)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Agreements;
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed class AgreementClient : IAgreementClient
|
||||
{
|
||||
private readonly NordigenHttpClient _nordigenHttpClient;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="AgreementClient"/> class.</summary>
|
||||
/// <param name="nordigenHttpClient">Http client configured for making requests to the Nordigen API.</param>
|
||||
public AgreementClient(NordigenHttpClient nordigenHttpClient)
|
||||
{
|
||||
_nordigenHttpClient = nordigenHttpClient;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<EndUserAgreement> Get(int pageSize = 100, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var requestUri = $"{Routes.Agreements.Uri}?limit={pageSize}&offset=0";
|
||||
return _nordigenHttpClient.GetAsJsonPaginated<EndUserAgreement>(requestUri, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EndUserAgreement> Get(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var agreement = await _nordigenHttpClient
|
||||
.GetAsJson<EndUserAgreement>(Routes.Agreements.IdUri(id), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return agreement!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EndUserAgreement> Post(EndUserAgreementCreation agreementCreation)
|
||||
{
|
||||
var agreement = await _nordigenHttpClient
|
||||
.PostAsJson<EndUserAgreementCreation, EndUserAgreement>(Routes.Agreements.Uri, agreementCreation)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return agreement!;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(Guid id)
|
||||
{
|
||||
await _nordigenHttpClient.Delete(Routes.Agreements.IdUri(id)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EndUserAgreement> Put(Guid id, EndUserAgreementAcceptance acceptance)
|
||||
{
|
||||
var agreement = await _nordigenHttpClient
|
||||
.PutAsJson<EndUserAgreementAcceptance, EndUserAgreement>(Routes.Agreements.AcceptUri(id), acceptance)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return agreement!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
|
||||
#pragma warning disable SA1623
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Agreements;
|
||||
|
||||
/// <summary>Details the end-user has agreement to share from a specific institution.</summary>
|
||||
/// <param name="Id">The id of the end-user agreement.</param>
|
||||
/// <param name="Created">The point in time when the agreement was created.</param>
|
||||
/// <param name="MaxHistoricalDays">Maximum number of days of transaction data to retrieve.</param>
|
||||
/// <param name="AccessValidForDays">Number of days from acceptance that the access can be used.</param>
|
||||
/// <param name="AccessScope">Level of information to access.</param>
|
||||
/// <param name="InstitutionId">An <see cref="Institution"/> ID for the agreement.</param>
|
||||
public record EndUserAgreement(
|
||||
Guid Id,
|
||||
Instant Created,
|
||||
[property: JsonPropertyName("max_historical_days")] int MaxHistoricalDays,
|
||||
[property: JsonPropertyName("access_valid_for_days")] int AccessValidForDays,
|
||||
[property: JsonPropertyName("access_scope")] List<string> AccessScope,
|
||||
[property: JsonPropertyName("institution_id")] string InstitutionId)
|
||||
{
|
||||
/// <summary>The point int time when the end user accepted the agreement.</summary>
|
||||
public Instant? Accepted { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Agreements;
|
||||
|
||||
/// <summary>Details needed to accept an end-user agreement.</summary>
|
||||
/// <param name="UserAgent">User agent string for the end user.</param>
|
||||
/// <param name="IpAddress">End user IP address.</param>
|
||||
public record EndUserAgreementAcceptance(
|
||||
[property: JsonPropertyName("user_agent")] string UserAgent,
|
||||
[property: JsonPropertyName("ip_address")] string IpAddress);
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
|
||||
#pragma warning disable SA1623
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Agreements;
|
||||
|
||||
/// <summary>Details needed to create a new end-user agreement.</summary>
|
||||
public record EndUserAgreementCreation
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="EndUserAgreementCreation"/> class.</summary>
|
||||
/// <param name="institutionId">An <see cref="Institution"/> id for this agreement.</param>
|
||||
/// <param name="maxHistoricalDays">Maximum number of days of transaction data to retrieve.</param>
|
||||
/// <param name="accessValidForDays">Number of days from acceptance that the access can be used.</param>
|
||||
/// <param name="accessScope">Level of information to access.</param>
|
||||
public EndUserAgreementCreation(
|
||||
string institutionId,
|
||||
int maxHistoricalDays,
|
||||
int accessValidForDays,
|
||||
List<string> accessScope)
|
||||
{
|
||||
InstitutionId = institutionId;
|
||||
MaxHistoricalDays = maxHistoricalDays;
|
||||
AccessValidForDays = accessValidForDays;
|
||||
AccessScope = accessScope;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="EndUserAgreementCreation"/> class with the default values.</summary>
|
||||
/// <param name="institutionId">An <see cref="Institution"/> id for this agreement.</param>
|
||||
public EndUserAgreementCreation(string institutionId)
|
||||
: this(institutionId, 90, 90, new() { "balances", "details", "transactions" })
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>An <see cref="Institution"/> id for this agreement.</summary>
|
||||
[JsonPropertyName("institution_id")]
|
||||
public string InstitutionId { get; }
|
||||
|
||||
/// <summary>Maximum number of days of transaction data to retrieve.</summary>
|
||||
[JsonPropertyName("max_historical_days")]
|
||||
public int MaxHistoricalDays { get; }
|
||||
|
||||
/// <summary>Number of days from acceptance that the access can be used.</summary>
|
||||
[JsonPropertyName("access_valid_for_days")]
|
||||
public int AccessValidForDays { get; }
|
||||
|
||||
/// <summary>Level of information to access.</summary>
|
||||
[JsonPropertyName("access_scope")]
|
||||
public List<string> AccessScope { get; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Agreements;
|
||||
|
||||
/// <summary>Nordigen API client for managing agreements.</summary>
|
||||
public interface IAgreementClient
|
||||
{
|
||||
/// <summary>Gets all agreements.</summary>
|
||||
/// <param name="pageSize">The number of agreements to get in a single request.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <returns>A enumerable which will asynchronously iterate over all available pages.</returns>
|
||||
IAsyncEnumerable<EndUserAgreement> Get(int pageSize = 100, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets the agreement with the specified id.</summary>
|
||||
/// <param name="id">The id of the agreement to get.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
|
||||
/// <returns>The agreement with the specified id.</returns>
|
||||
Task<EndUserAgreement> Get(Guid id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Creates a new agreement.</summary>
|
||||
/// <param name="agreementCreation">The agreement to create.</param>
|
||||
/// <returns>The created agreement.</returns>
|
||||
Task<EndUserAgreement> Post(EndUserAgreementCreation agreementCreation);
|
||||
|
||||
/// <summary>Deletes the specified agreement.</summary>
|
||||
/// <param name="id">The id of the agreement to delete.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task Delete(Guid id);
|
||||
|
||||
/// <summary>Accepts the specified agreement.</summary>
|
||||
/// <param name="id">The id of the agreement to accept.</param>
|
||||
/// <param name="agreementAcceptance">Details from the end-user needed to accept the agreement.</param>
|
||||
/// <returns>The accepted agreement.</returns>
|
||||
Task<EndUserAgreement> Put(Guid id, EndUserAgreementAcceptance agreementAcceptance);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Accounts;
|
||||
using VMelnalksnis.NordigenDotNet.Agreements;
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
using VMelnalksnis.NordigenDotNet.Requisitions;
|
||||
|
||||
@@ -14,6 +15,9 @@ public interface INordigenClient
|
||||
/// <summary>Gets the accounts API client.</summary>
|
||||
IAccountClient Accounts { get; }
|
||||
|
||||
/// <summary>Gets the agreements API client.</summary>
|
||||
IAgreementClient Agreements { get; }
|
||||
|
||||
/// <summary>Gets the institutions API client.</summary>
|
||||
IInstitutionClient Institutions { get; }
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Accounts;
|
||||
using VMelnalksnis.NordigenDotNet.Agreements;
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
using VMelnalksnis.NordigenDotNet.Requisitions;
|
||||
|
||||
@@ -13,11 +14,17 @@ public sealed class NordigenClient : INordigenClient
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="NordigenClient"/> class.</summary>
|
||||
/// <param name="accounts">Accounts API client.</param>
|
||||
/// <param name="agreementClient">Agreements API client.</param>
|
||||
/// <param name="institutions">Institutions API client.</param>
|
||||
/// <param name="requisitions">Requisitions API client.</param>
|
||||
public NordigenClient(IAccountClient accounts, IInstitutionClient institutions, IRequisitionClient requisitions)
|
||||
public NordigenClient(
|
||||
IAccountClient accounts,
|
||||
IAgreementClient agreementClient,
|
||||
IInstitutionClient institutions,
|
||||
IRequisitionClient requisitions)
|
||||
{
|
||||
Accounts = accounts;
|
||||
Agreements = agreementClient;
|
||||
Institutions = institutions;
|
||||
Requisitions = requisitions;
|
||||
}
|
||||
@@ -25,6 +32,9 @@ public sealed class NordigenClient : INordigenClient
|
||||
/// <inheritdoc />
|
||||
public IAccountClient Accounts { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAgreementClient Agreements { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstitutionClient Institutions { get; }
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
@@ -47,21 +49,76 @@ public sealed class NordigenHttpClient
|
||||
internal async Task<TResult?> GetAsJson<TResult>(string requestUri, CancellationToken cancellationToken)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
return await client.GetFromJsonAsync<TResult>(requestUri, _serializerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return await client.GetFromJsonAsync<TResult>(requestUri, _serializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal async IAsyncEnumerable<TResult> GetAsJsonPaginated<TResult>(
|
||||
string requestUri,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
where TResult : class
|
||||
{
|
||||
var next = requestUri;
|
||||
do
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var paginatedList = await GetAsJson<PaginatedList<TResult>>(next, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (paginatedList?.Results is null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var requisition in paginatedList.Results)
|
||||
{
|
||||
yield return requisition;
|
||||
}
|
||||
|
||||
next = paginatedList.Next?.PathAndQuery;
|
||||
}
|
||||
while (next is not null);
|
||||
}
|
||||
|
||||
internal async Task<TResult?> PostAsJson<TRequest, TResult>(string requestUri, TRequest request)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
var response = await client.PostAsJsonAsync(requestUri, request, _serializerOptions).ConfigureAwait(false);
|
||||
return await response.Content.ReadFromJsonAsync<TResult>(_serializerOptions).ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.ReadFromJsonAsync<TResult>(_serializerOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
throw new HttpRequestException(content, null, response.StatusCode);
|
||||
}
|
||||
|
||||
internal async Task<TResult?> PutAsJson<TRequest, TResult>(string requestUri, TRequest request)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
var response = await client.PutAsJsonAsync(requestUri, request, _serializerOptions).ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.ReadFromJsonAsync<TResult>(_serializerOptions).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
throw new HttpRequestException(content, null, response.StatusCode);
|
||||
}
|
||||
|
||||
internal async Task Delete(string requestUri)
|
||||
{
|
||||
var client = await GetClient().ConfigureAwait(false);
|
||||
await client.DeleteAsync(requestUri).ConfigureAwait(false);
|
||||
var response = await client.DeleteAsync(requestUri).ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
throw new HttpRequestException(content, null, response.StatusCode);
|
||||
}
|
||||
|
||||
private async Task<HttpClient> GetClient()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -23,35 +22,10 @@ public sealed class RequisitionClient : IRequisitionClient
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<Requisition> Get(
|
||||
int pageSize = 100,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
public IAsyncEnumerable<Requisition> Get(int pageSize = 100, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var next = $"{Routes.Requisitions.Uri}?limit={pageSize}&offset=0";
|
||||
do
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var paginatedResponse = await _nordigenHttpClient
|
||||
.GetAsJson<PaginatedList<Requisition>>(next, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (paginatedResponse?.Results is null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var requisition in paginatedResponse.Results)
|
||||
{
|
||||
yield return requisition;
|
||||
}
|
||||
|
||||
next = paginatedResponse.Next?.PathAndQuery;
|
||||
}
|
||||
while (next is not null);
|
||||
var requestUri = $"{Routes.Requisitions.Uri}?limit={pageSize}&offset=0";
|
||||
return _nordigenHttpClient.GetAsJsonPaginated<Requisition>(requestUri, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -8,6 +8,15 @@ namespace VMelnalksnis.NordigenDotNet;
|
||||
|
||||
internal static class Routes
|
||||
{
|
||||
internal static class Agreements
|
||||
{
|
||||
internal const string Uri = "api/v2/agreements/enduser/";
|
||||
|
||||
internal static string IdUri(Guid id) => $"{Uri}{id:N}/";
|
||||
|
||||
internal static string AcceptUri(Guid id) => $"{IdUri(id)}accept/";
|
||||
}
|
||||
|
||||
internal static class Accounts
|
||||
{
|
||||
private const string _uri = "api/v2/accounts/";
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2022 Valters Melnalksnis
|
||||
// Licensed under the Apache License 2.0.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Agreements;
|
||||
|
||||
namespace VMelnalksnis.NordigenDotNet.Tests.Integration.Agreements;
|
||||
|
||||
public sealed class AgreementClientTests : IClassFixture<ServiceProviderFixture>
|
||||
{
|
||||
private readonly IAgreementClient _agreementClient;
|
||||
|
||||
public AgreementClientTests(ServiceProviderFixture serviceProviderFixture)
|
||||
{
|
||||
_agreementClient = serviceProviderFixture.AgreementClient;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get()
|
||||
{
|
||||
var creation = new EndUserAgreementCreation("CITADELE_PARXLV22");
|
||||
|
||||
var createdAgreement = await _agreementClient.Post(creation);
|
||||
var agreements = await _agreementClient.Get().ToListAsync();
|
||||
|
||||
agreements
|
||||
.Should()
|
||||
.ContainSingle(agreement => agreement.Id == createdAgreement.Id)
|
||||
.Which.Should()
|
||||
.BeEquivalentTo(createdAgreement);
|
||||
|
||||
var acceptance = new EndUserAgreementAcceptance("NordigenDotNet integration test", "127.0.0.1");
|
||||
(await FluentActions
|
||||
.Awaiting(() => _agreementClient.Put(createdAgreement.Id, acceptance))
|
||||
.Should()
|
||||
.ThrowExactlyAsync<HttpRequestException>())
|
||||
.Which.StatusCode.Should()
|
||||
.Be(HttpStatusCode.Forbidden, "test company cannot create agreements");
|
||||
|
||||
await _agreementClient.Delete(createdAgreement.Id);
|
||||
|
||||
(await FluentActions
|
||||
.Awaiting(() => _agreementClient.Get(createdAgreement.Id))
|
||||
.Should()
|
||||
.ThrowExactlyAsync<HttpRequestException>())
|
||||
.Which.StatusCode.Should()
|
||||
.Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using NodaTime;
|
||||
|
||||
using VMelnalksnis.NordigenDotNet.Accounts;
|
||||
using VMelnalksnis.NordigenDotNet.Agreements;
|
||||
using VMelnalksnis.NordigenDotNet.DependencyInjection;
|
||||
using VMelnalksnis.NordigenDotNet.Institutions;
|
||||
using VMelnalksnis.NordigenDotNet.Requisitions;
|
||||
@@ -38,6 +39,8 @@ public sealed class ServiceProviderFixture
|
||||
|
||||
public IAccountClient AccountClient => _serviceProvider.GetRequiredService<IAccountClient>();
|
||||
|
||||
public IAgreementClient AgreementClient => _serviceProvider.GetRequiredService<IAgreementClient>();
|
||||
|
||||
public IInstitutionClient InstitutionClient => _serviceProvider.GetRequiredService<IInstitutionClient>();
|
||||
|
||||
public IRequisitionClient RequisitionClient => _serviceProvider.GetRequiredService<IRequisitionClient>();
|
||||
|
||||
Reference in New Issue
Block a user