Compare commits
73 Commits
10e751b9a1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b3c4d95c6a | |||
| 6a4817cbcd | |||
| a63bccf47d | |||
| 33df010573 | |||
| 6433e0b39b | |||
| c14e6033cb | |||
| df089af385 | |||
| 8b9f7b911d | |||
| 49c4960f05 | |||
| 66cfe0525c | |||
| e9a7ef910f | |||
| 0273beb6f8 | |||
| 20b5b8124d | |||
| beadc3c4bb | |||
| 9256dc6baf | |||
| e8fd49d75d | |||
| b5082daa1a | |||
| 48a69f884e | |||
| b3a27ba24f | |||
| a7f02e1079 | |||
| 265862d63d | |||
| 060ba64268 | |||
| 21cc348c6c | |||
| ccecf47dca | |||
| 6044d0bcb6 | |||
| aaaaf283ee | |||
| 2877d62f95 | |||
| 3ca148f341 | |||
| ad9f7ef7e4 | |||
| 4bb6a6cf18 | |||
| e85a4986e6 | |||
| 1ed1937c40 | |||
| 3082c0b77c | |||
| 6836b422a4 | |||
| 6b2c897e5b | |||
| db3137ef9d | |||
| 02b4aa342a | |||
| 57b273cde4 | |||
| aa192626c2 | |||
| 9142b9c49a | |||
| f994781713 | |||
| 65d59c6c67 | |||
| 32b631a6c2 | |||
| 3eacbd89f7 | |||
| 3af571ea37 | |||
| c21e4a93ef | |||
| dd60555ed3 | |||
| a7cbced3e6 | |||
| 786086a260 | |||
| 087df71b7b | |||
| a7a16ab281 | |||
|
|
2d8d5442d1 | ||
|
|
fe198615fc | ||
|
|
8c6719f516 | ||
|
|
8db4b6e15a | ||
|
|
92910ecb19 | ||
|
|
ce33b50953 | ||
|
|
2e59c090a8 | ||
|
|
c6ec3ca054 | ||
|
|
9117a23be3 | ||
|
|
4b8217bb80 | ||
|
|
6cf47dc617 | ||
|
|
94c6813306 | ||
|
|
682fb772f7 | ||
|
|
9d5bf509d5 | ||
|
|
757ba77179 | ||
|
|
11ab4388d0 | ||
|
|
d904fded39 | ||
|
|
e6a8c81a0b | ||
|
|
4256a79122 | ||
|
|
c3c6ffdf99 | ||
|
|
8b248db4e2 | ||
|
|
c5787c8736 |
@@ -15,6 +15,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{AC628874-E
|
|||||||
docs\econnect-api_swagger.pdf = docs\econnect-api_swagger.pdf
|
docs\econnect-api_swagger.pdf = docs\econnect-api_swagger.pdf
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Leanetec.EConnect.Infrastructure", "src\Leanetec.EConnect.Infrastructure\Leanetec.EConnect.Infrastructure.csproj", "{88A1AA25-45F3-4082-8B5A-F95FC8A21057}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Leanetec.EConnect.Proxy", "src\Leanetec.EConnect.Proxy\Leanetec.EConnect.Proxy.csproj", "{79D9868E-6A67-45C1-BA3E-1C2E822ECC58}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -29,6 +33,14 @@ Global
|
|||||||
{9242EEA9-447B-44A6-A66D-D671DD16D0BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9242EEA9-447B-44A6-A66D-D671DD16D0BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9242EEA9-447B-44A6-A66D-D671DD16D0BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9242EEA9-447B-44A6-A66D-D671DD16D0BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9242EEA9-447B-44A6-A66D-D671DD16D0BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9242EEA9-447B-44A6-A66D-D671DD16D0BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{88A1AA25-45F3-4082-8B5A-F95FC8A21057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{88A1AA25-45F3-4082-8B5A-F95FC8A21057}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{88A1AA25-45F3-4082-8B5A-F95FC8A21057}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{88A1AA25-45F3-4082-8B5A-F95FC8A21057}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{79D9868E-6A67-45C1-BA3E-1C2E822ECC58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{79D9868E-6A67-45C1-BA3E-1C2E822ECC58}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{79D9868E-6A67-45C1-BA3E-1C2E822ECC58}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{79D9868E-6A67-45C1-BA3E-1C2E822ECC58}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -36,6 +48,8 @@ Global
|
|||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{34DC964A-1905-7DFC-0125-D551D3EEFCDD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{34DC964A-1905-7DFC-0125-D551D3EEFCDD} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{9242EEA9-447B-44A6-A66D-D671DD16D0BB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{9242EEA9-447B-44A6-A66D-D671DD16D0BB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
|
{88A1AA25-45F3-4082-8B5A-F95FC8A21057} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
|
{79D9868E-6A67-45C1-BA3E-1C2E822ECC58} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {8C81AD6F-B903-4C78-873C-38EE216EFAD5}
|
SolutionGuid = {8C81AD6F-B903-4C78-873C-38EE216EFAD5}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public static class GetExtensions
|
|||||||
/// <param name="role">Optional role of the logged-in user.</param>
|
/// <param name="role">Optional role of the logged-in user.</param>
|
||||||
/// <param name="apiVersion">Optional API version.</param>
|
/// <param name="apiVersion">Optional API version.</param>
|
||||||
/// <returns>A <see cref="Task{Boolean}"/> representing whether the application is alive.</returns>
|
/// <returns>A <see cref="Task{Boolean}"/> representing whether the application is alive.</returns>
|
||||||
public static Task<bool> IsAliveAsync(this IMediator mediator, Role? role = null, int? apiVersion = null) =>
|
public static Task<bool> IsAliveAsync(this IMediator mediator, Role? role = null, int apiVersion = 1) =>
|
||||||
mediator.Send(new GetRequest(role)
|
mediator.Send(new GetRequest(role)
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
@@ -24,7 +24,7 @@ public static class GetExtensions
|
|||||||
/// <param name="role">Optional role of the logged-in user.</param>
|
/// <param name="role">Optional role of the logged-in user.</param>
|
||||||
/// <param name="apiVersion">Optional API version.</param>
|
/// <param name="apiVersion">Optional API version.</param>
|
||||||
/// <returns>True if the application is alive; otherwise, false.</returns>
|
/// <returns>True if the application is alive; otherwise, false.</returns>
|
||||||
public static bool IsAlive(this IMediator mediator, Role? role = null, int? apiVersion = null) =>
|
public static bool IsAlive(this IMediator mediator, Role? role = null, int apiVersion = 1) =>
|
||||||
mediator.Send(new GetRequest(role)
|
mediator.Send(new GetRequest(role)
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public static class GetExtensions
|
|||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous operation. The task result contains the matching <see cref="Article"/>, or <c>null</c> if not found.
|
/// A task that represents the asynchronous operation. The task result contains the matching <see cref="Article"/>, or <c>null</c> if not found.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static async Task<Article?> GetArticleByIdAsync(this IMediator mediator, int id, int? apiVersion = null)
|
public static async Task<Article?> GetArticleByIdAsync(this IMediator mediator, int id, int apiVersion = 1)
|
||||||
{
|
{
|
||||||
var articles = await mediator.Send(new GetRequest(id) { ApiVersion = apiVersion });
|
var articles = await mediator.Send(new GetRequest(id) { ApiVersion = apiVersion });
|
||||||
return articles.FirstOrDefault();
|
return articles.FirstOrDefault();
|
||||||
@@ -34,7 +34,7 @@ public static class GetExtensions
|
|||||||
/// <returns>
|
/// <returns>
|
||||||
/// The matching <see cref="Article"/>, or <c>null</c> if not found.
|
/// The matching <see cref="Article"/>, or <c>null</c> if not found.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static Article? GetArticleById(this IMediator mediator, int id, int? apiVersion = null)
|
public static Article? GetArticleById(this IMediator mediator, int id, int apiVersion = 1)
|
||||||
{
|
{
|
||||||
var articles = mediator.Send(new GetRequest(id) { ApiVersion = apiVersion }).GetAwaiter().GetResult();
|
var articles = mediator.Send(new GetRequest(id) { ApiVersion = apiVersion }).GetAwaiter().GetResult();
|
||||||
return articles.FirstOrDefault();
|
return articles.FirstOrDefault();
|
||||||
@@ -52,7 +52,7 @@ public static class GetExtensions
|
|||||||
/// <returns>
|
/// <returns>
|
||||||
/// A task that represents the asynchronous operation. The task result contains a collection of all available <see cref="Article"/> entities.
|
/// A task that represents the asynchronous operation. The task result contains a collection of all available <see cref="Article"/> entities.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static Task<IEnumerable<Article>> GetAllArticlesAsync(this IMediator mediator, int? apiVersion = null) => mediator.Send(new GetRequest()
|
public static Task<IEnumerable<Article>> GetAllArticlesAsync(this IMediator mediator, int apiVersion = 1) => mediator.Send(new GetRequest()
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
});
|
});
|
||||||
@@ -65,7 +65,7 @@ public static class GetExtensions
|
|||||||
/// <returns>
|
/// <returns>
|
||||||
/// A collection of all available <see cref="Article"/> entities.
|
/// A collection of all available <see cref="Article"/> entities.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static IEnumerable<Article> GetAllArticles(this IMediator mediator, int? apiVersion = null) => mediator.Send(new GetRequest()
|
public static IEnumerable<Article> GetAllArticles(this IMediator mediator, int apiVersion = 1) => mediator.Send(new GetRequest()
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public static class PostExtensions
|
|||||||
/// <param name="article">The <see cref="Article"/> entity to be created.</param>
|
/// <param name="article">The <see cref="Article"/> entity to be created.</param>
|
||||||
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
||||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
public static Task CreateArticleAsync(this IMediator mediator, Article article, int? apiVersion = null) => mediator.Send(new PostRequest(article)
|
public static Task CreateArticleAsync(this IMediator mediator, Article article, int apiVersion = 1) => mediator.Send(new PostRequest(article)
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
});
|
});
|
||||||
@@ -26,7 +26,7 @@ public static class PostExtensions
|
|||||||
/// <param name="mediator">The <see cref="IMediator"/> instance used to send the request.</param>
|
/// <param name="mediator">The <see cref="IMediator"/> instance used to send the request.</param>
|
||||||
/// <param name="article">The <see cref="Article"/> entity to be created.</param>
|
/// <param name="article">The <see cref="Article"/> entity to be created.</param>
|
||||||
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
||||||
public static void CreateArticle(this IMediator mediator, Article article, int? apiVersion = null) => mediator.Send(new PostRequest(article)
|
public static void CreateArticle(this IMediator mediator, Article article, int apiVersion = 1) => mediator.Send(new PostRequest(article)
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public static class PutExtensions
|
|||||||
/// <param name="article">The <see cref="Article"/> entity to be updated.</param>
|
/// <param name="article">The <see cref="Article"/> entity to be updated.</param>
|
||||||
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
||||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
public static Task UpdateArticleAsync(this IMediator mediator, Article article, int? apiVersion = null) => mediator.Send(new PutRequest(article)
|
public static Task UpdateArticleAsync(this IMediator mediator, Article article, int apiVersion = 1) => mediator.Send(new PutRequest(article)
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
});
|
});
|
||||||
@@ -26,7 +26,7 @@ public static class PutExtensions
|
|||||||
/// <param name="mediator">The <see cref="IMediator"/> instance used to send the request.</param>
|
/// <param name="mediator">The <see cref="IMediator"/> instance used to send the request.</param>
|
||||||
/// <param name="article">The <see cref="Article"/> entity to be updated.</param>
|
/// <param name="article">The <see cref="Article"/> entity to be updated.</param>
|
||||||
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
/// <param name="apiVersion">Optional API version to include in the request.</param>
|
||||||
public static void UpdateArticle(this IMediator mediator, Article article, int? apiVersion = null) => mediator.Send(new PutRequest(article)
|
public static void UpdateArticle(this IMediator mediator, Article article, int apiVersion = 1) => mediator.Send(new PutRequest(article)
|
||||||
{
|
{
|
||||||
ApiVersion = apiVersion
|
ApiVersion = apiVersion
|
||||||
}).GetAwaiter().GetResult();
|
}).GetAwaiter().GetResult();
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Leanetec.EConnect.Client;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a lazily initialized <see cref="IServiceProvider"/> for accessing the EConnect client services,
|
|
||||||
/// including an <see cref="IMediator"/> instance for sending and publishing messages.
|
|
||||||
/// </summary>
|
|
||||||
public static class Client
|
|
||||||
{
|
|
||||||
private static Action<ClientOptions>? _options = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the configuration options for the EConnect client.
|
|
||||||
/// Can only be set once; subsequent attempts to set will result in an exception.
|
|
||||||
/// </summary>
|
|
||||||
public static Action<ClientOptions> Options
|
|
||||||
{
|
|
||||||
get => _options ?? throw new InvalidOperationException("EConnect Client options have not been configured. Please set the 'Client.Options' property before accessing client services.");
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_options is null)
|
|
||||||
_options = value;
|
|
||||||
else
|
|
||||||
throw new InvalidOperationException("EConnect Client options have already been configured. Reassignment is not allowed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lazily initializes the <see cref="IServiceProvider"/> that registers and builds the EConnect client services.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Lazy<IServiceProvider> _serviceProvider = new(() =>
|
|
||||||
{
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
services.AddEConnectClient(Options);
|
|
||||||
return services.BuildServiceProvider();
|
|
||||||
});
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the initialized <see cref="IServiceProvider"/> that provides access to registered EConnect client services.
|
|
||||||
/// </summary>
|
|
||||||
public static IServiceProvider Provider => _serviceProvider.Value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="IMediator"/> instance used for sending commands, queries, and publishing events within the EConnect client.
|
|
||||||
/// </summary>
|
|
||||||
public static IMediator Mediator => Provider.GetRequiredService<IMediator>();
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Leanetec.EConnect.Client;
|
|
||||||
|
|
||||||
public class ClientOptions
|
|
||||||
{
|
|
||||||
public int ApiVersion { get; set; } = 1;
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,8 @@ namespace Leanetec.EConnect.Client;
|
|||||||
|
|
||||||
public static class DependencyInjection
|
public static class DependencyInjection
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddEConnectClient(this IServiceCollection services, Action<ClientOptions> clientOptions)
|
public static IServiceCollection AddClientServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.Configure(clientOptions);
|
|
||||||
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
|
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly));
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace Leanetec.EConnect.Client.Dto;
|
|||||||
|
|
||||||
public record HttpRequestBase
|
public record HttpRequestBase
|
||||||
{
|
{
|
||||||
public int? ApiVersion { get; set; }
|
public int ApiVersion { get; set; } = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public record HttpRequest : HttpRequestBase, IRequest
|
public record HttpRequest : HttpRequestBase, IRequest
|
||||||
|
|||||||
10
src/Leanetec.EConnect.Client/Interface/IEConnectClient.cs
Normal file
10
src/Leanetec.EConnect.Client/Interface/IEConnectClient.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Leanetec.EConnect.Domain.Entities;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Client.Interface;
|
||||||
|
|
||||||
|
public interface IEConnectClient<TError> where TError : class
|
||||||
|
{
|
||||||
|
public Task<Response<TData, TError>> GetAsync<TData>(string? route = null, object? queryParams = null, CancellationToken cancel = default) where TData : class;
|
||||||
|
|
||||||
|
Task<Response<TError>> PostAsync(Stream stream, string fileName, string? route = null, object? queryParams = null, CancellationToken cancel = default);
|
||||||
|
}
|
||||||
@@ -1,12 +1,25 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||||
|
<PackageReference Include="MediatR" Version="12.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
|
<PackageReference Include="MediatR" Version="13.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
<PackageReference Include="MediatR" Version="13.0.0" />
|
<PackageReference Include="MediatR" Version="13.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
30
src/Leanetec.EConnect.Client/Order/GetDocumentRequest.cs
Normal file
30
src/Leanetec.EConnect.Client/Order/GetDocumentRequest.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Leanetec.EConnect.Client.Dto;
|
||||||
|
using Leanetec.EConnect.Client.Interface;
|
||||||
|
using Leanetec.EConnect.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Client.Order;
|
||||||
|
|
||||||
|
//TODO: EConnectClient.GetListAsAsyncEnumerable
|
||||||
|
public record GetDocumentRequest(string TenantId, int OrderId) : HttpRequest<Response<IEnumerable<OrderDocument>, ProblemDetail>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GetDocumentRequestHandler : IRequestHandler<GetDocumentRequest, Response<IEnumerable<OrderDocument>, ProblemDetail>>
|
||||||
|
{
|
||||||
|
private readonly IEConnectClient<ProblemDetail> _client;
|
||||||
|
|
||||||
|
public GetDocumentRequestHandler(IEConnectClient<ProblemDetail> client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<IEnumerable<OrderDocument>, ProblemDetail>> Handle(GetDocumentRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return await _client.GetAsync<IEnumerable<OrderDocument>>(
|
||||||
|
$"api/public/v{request.ApiVersion}/econnect/order/document/list",
|
||||||
|
new { request.TenantId, request.OrderId },
|
||||||
|
cancel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/Leanetec.EConnect.Client/Order/PostDocumentRequest.cs
Normal file
37
src/Leanetec.EConnect.Client/Order/PostDocumentRequest.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Leanetec.EConnect.Client.Dto;
|
||||||
|
using Leanetec.EConnect.Client.Interface;
|
||||||
|
using Leanetec.EConnect.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Client.Order;
|
||||||
|
|
||||||
|
public record PostDocumentRequest(string TenantId, int OrderId) : HttpRequest<Response<ProblemDetail>>
|
||||||
|
{
|
||||||
|
public UploadDocumentRequest ToUploadDocument(Stream stream, string fileName)
|
||||||
|
{
|
||||||
|
return new UploadDocumentRequest(this, stream, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record UploadDocumentRequest(PostDocumentRequest Original, Stream Stream, string FileName) : PostDocumentRequest(Original);
|
||||||
|
|
||||||
|
public class PostDocumentRequestHandler : IRequestHandler<UploadDocumentRequest, Response<ProblemDetail>>
|
||||||
|
{
|
||||||
|
private readonly IEConnectClient<ProblemDetail> _client;
|
||||||
|
|
||||||
|
public PostDocumentRequestHandler(IEConnectClient<ProblemDetail> client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<ProblemDetail>> Handle(UploadDocumentRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return await _client.PostAsync(
|
||||||
|
request.Stream,
|
||||||
|
request.FileName,
|
||||||
|
$"api/public/v{request.ApiVersion}/econnect/order/document",
|
||||||
|
new { request.TenantId, request.OrderId },
|
||||||
|
cancel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/Leanetec.EConnect.Domain/Entities/OrderDocument.cs
Normal file
39
src/Leanetec.EConnect.Domain/Entities/OrderDocument.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
namespace Leanetec.EConnect.Domain.Entities;
|
||||||
|
|
||||||
|
public class OrderDocument
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The unique internal id of the file
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the file
|
||||||
|
/// </summary>
|
||||||
|
public string? FileName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the file in bytes
|
||||||
|
/// </summary>
|
||||||
|
public int FileSizeInBytes { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The internal unique id of the folder containing this file
|
||||||
|
/// </summary>
|
||||||
|
public int ParentFolderId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The string representation of the timstamp when this file has been created (uploaded)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? CreatedOn { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The string representation of the timstamp when this file has been updated
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LastUpdateOn { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The MIME type of the file
|
||||||
|
/// </summary>
|
||||||
|
public string? FileMimeType { get; init; }
|
||||||
|
}
|
||||||
10
src/Leanetec.EConnect.Domain/Entities/ProblemDetail.cs
Normal file
10
src/Leanetec.EConnect.Domain/Entities/ProblemDetail.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Leanetec.EConnect.Domain.Entities;
|
||||||
|
|
||||||
|
public class ProblemDetail
|
||||||
|
{
|
||||||
|
public string? Type { get; init; }
|
||||||
|
public string? Title { get; init; }
|
||||||
|
public int? Status { get; init; }
|
||||||
|
public string? Detail { get; init; }
|
||||||
|
public string? Instance { get; init; }
|
||||||
|
}
|
||||||
25
src/Leanetec.EConnect.Domain/Entities/Response.cs
Normal file
25
src/Leanetec.EConnect.Domain/Entities/Response.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Domain.Entities;
|
||||||
|
|
||||||
|
public record Response<TError>() where TError : class
|
||||||
|
{
|
||||||
|
public bool Ok { get; init; }
|
||||||
|
|
||||||
|
private HttpStatusCode? _statusCode;
|
||||||
|
|
||||||
|
public HttpStatusCode StatusCode
|
||||||
|
{
|
||||||
|
get => _statusCode ?? (Ok ? HttpStatusCode.OK : HttpStatusCode.InternalServerError);
|
||||||
|
init => _statusCode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int StatusCodeInt => (int)StatusCode;
|
||||||
|
|
||||||
|
public TError? Error { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Response<TData, TError> : Response<TError> where TData : class where TError : class
|
||||||
|
{
|
||||||
|
public TData? Data { get; init; }
|
||||||
|
}
|
||||||
6
src/Leanetec.EConnect.Infrastructure/ClientExtensions.cs
Normal file
6
src/Leanetec.EConnect.Infrastructure/ClientExtensions.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public static class ClientExtensions
|
||||||
|
{
|
||||||
|
public static HttpClient CreateEConnectClient(this IHttpClientFactory factory) => factory.CreateClient(DependencyInjection.EConnectClientName);
|
||||||
|
}
|
||||||
34
src/Leanetec.EConnect.Infrastructure/ClientOptions.cs
Normal file
34
src/Leanetec.EConnect.Infrastructure/ClientOptions.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public class ClientOptions
|
||||||
|
{
|
||||||
|
public string? BaseAddress { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? Timeout { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string?>? DefaultQueryStrings { get; set; }
|
||||||
|
|
||||||
|
public Action<HttpClient>? AfterHttpInit { get; set; }
|
||||||
|
|
||||||
|
private string? _jsonSerializerDateFormat = null;
|
||||||
|
|
||||||
|
public string? JsonSerializerDateFormat
|
||||||
|
{
|
||||||
|
get => _jsonSerializerDateFormat;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_jsonSerializerDateFormat = value;
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
JsonSerializerOptions.Converters.Add(new DateTimeConverter(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonSerializerOptions JsonSerializerOptions { get; set; } = new()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
};
|
||||||
|
}
|
||||||
26
src/Leanetec.EConnect.Infrastructure/DateTimeConverter.cs
Normal file
26
src/Leanetec.EConnect.Infrastructure/DateTimeConverter.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public class DateTimeConverter : JsonConverter<DateTime>
|
||||||
|
{
|
||||||
|
private readonly string _format;
|
||||||
|
|
||||||
|
public DateTimeConverter(string format)
|
||||||
|
{
|
||||||
|
_format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var value = reader.GetString();
|
||||||
|
return DateTime.ParseExact(value!, _format, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.ToString(_format));
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/Leanetec.EConnect.Infrastructure/DependencyInjection.cs
Normal file
58
src/Leanetec.EConnect.Infrastructure/DependencyInjection.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using Leanetec.EConnect.Client.Interface;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
internal static readonly string EConnectClientName = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
internal static IServiceCollection AddEConnectHttpClient(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHttpClient(EConnectClientName, (provider, client) => {
|
||||||
|
var opt = provider.GetRequiredService<IOptions<ClientOptions>>().Value;
|
||||||
|
|
||||||
|
// add common parameters
|
||||||
|
if (opt.BaseAddress is string baseAddress)
|
||||||
|
client.BaseAddress = new Uri(baseAddress);
|
||||||
|
|
||||||
|
if (opt.Timeout is TimeSpan timeout)
|
||||||
|
client.Timeout = timeout;
|
||||||
|
|
||||||
|
opt.AfterHttpInit?.Invoke(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, Action<Config>? options = null)
|
||||||
|
{
|
||||||
|
Config config = new(services);
|
||||||
|
options?.Invoke(config);
|
||||||
|
services.AddEConnectHttpClient();
|
||||||
|
services.AddScoped(typeof(IEConnectClient<>), typeof(EConnectClient<>));
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Config
|
||||||
|
{
|
||||||
|
private readonly IServiceCollection _services;
|
||||||
|
|
||||||
|
internal Config(IServiceCollection services)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureClient(IConfiguration config)
|
||||||
|
{
|
||||||
|
_services.Configure<ClientOptions>(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureClient(Action<ClientOptions> options)
|
||||||
|
{
|
||||||
|
_services.Configure(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/Leanetec.EConnect.Infrastructure/EConnectClient.cs
Normal file
118
src/Leanetec.EConnect.Infrastructure/EConnectClient.cs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
using Leanetec.EConnect.Client.Interface;
|
||||||
|
using Leanetec.EConnect.Domain.Entities;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public class EConnectClient<TError> : IEConnectClient<TError> where TError : class
|
||||||
|
{
|
||||||
|
private readonly ClientOptions _options;
|
||||||
|
|
||||||
|
private readonly Lazy<HttpClient> LazyHttp;
|
||||||
|
|
||||||
|
private HttpClient Http => LazyHttp.Value;
|
||||||
|
|
||||||
|
private readonly ILogger<EConnectClient<TError>>? _logger;
|
||||||
|
|
||||||
|
public EConnectClient(IOptions<ClientOptions> options, IHttpClientFactory httpFactory, ILogger<EConnectClient<TError>>? logger = null)
|
||||||
|
{
|
||||||
|
_options = options.Value;
|
||||||
|
LazyHttp = new Lazy<HttpClient>(httpFactory.CreateEConnectClient);
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? AddQueryString(string? route = null, object? queryParams = null)
|
||||||
|
{
|
||||||
|
// add global query strings
|
||||||
|
if (_options.DefaultQueryStrings is not null)
|
||||||
|
route = route.AddQueryString(_options.DefaultQueryStrings);
|
||||||
|
|
||||||
|
// add query strings
|
||||||
|
if (queryParams is not null)
|
||||||
|
route = route.AddQueryString(queryParams.ToPropertyDictionary());
|
||||||
|
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<TData, TError>> GetAsync<TData>(string? route = null, object? queryParams = null, CancellationToken cancel = default)
|
||||||
|
where TData : class
|
||||||
|
{
|
||||||
|
route = AddQueryString(route, queryParams);
|
||||||
|
|
||||||
|
var res = await Http.GetAsync(route, cancel);
|
||||||
|
_logger?.LogCurl(Http, HttpMethod.Get, route);
|
||||||
|
|
||||||
|
return res.IsSuccessStatusCode
|
||||||
|
? new()
|
||||||
|
{
|
||||||
|
Ok = true,
|
||||||
|
StatusCode = res.StatusCode,
|
||||||
|
Data = (res.Content.Headers.ContentLength > 0)
|
||||||
|
? await res.Content.ReadFromJsonAsync<TData>(_options.JsonSerializerOptions, cancel)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
: new()
|
||||||
|
{
|
||||||
|
Ok = false,
|
||||||
|
StatusCode = res.StatusCode,
|
||||||
|
Error = (res.Content.Headers.ContentLength > 0)
|
||||||
|
? await res.Content.ReadFromJsonAsync<TError>(_options.JsonSerializerOptions, cancel)
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Response<TError>> PostAsync(Stream stream, string fileName, string? route = null, object? queryParams = null, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
route = AddQueryString(route, queryParams);
|
||||||
|
|
||||||
|
// create message and add accept header
|
||||||
|
using var message = new HttpRequestMessage(HttpMethod.Post, route);
|
||||||
|
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
|
||||||
|
|
||||||
|
// add file type
|
||||||
|
var fileContent = new StreamContent(stream);
|
||||||
|
var mimeType = GetMimeType(fileName);
|
||||||
|
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
|
||||||
|
|
||||||
|
// Create multipart form data form
|
||||||
|
using var form = new MultipartFormDataContent
|
||||||
|
{
|
||||||
|
{ fileContent, "file", fileName }
|
||||||
|
};
|
||||||
|
|
||||||
|
message.Content = form;
|
||||||
|
|
||||||
|
var res = await Http.SendAsync(message, cancel);
|
||||||
|
|
||||||
|
_logger?.LogCurl(Http, message);
|
||||||
|
|
||||||
|
return res.IsSuccessStatusCode
|
||||||
|
? new()
|
||||||
|
{
|
||||||
|
Ok = true,
|
||||||
|
StatusCode = res.StatusCode
|
||||||
|
}
|
||||||
|
: new()
|
||||||
|
{
|
||||||
|
Ok = false,
|
||||||
|
StatusCode = res.StatusCode,
|
||||||
|
Error = (res.Content.Headers.ContentLength > 0)
|
||||||
|
? await res.Content.ReadFromJsonAsync<TError>(_options.JsonSerializerOptions, cancel)
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMimeType(string fileName)
|
||||||
|
{
|
||||||
|
var provider = new FileExtensionContentTypeProvider();
|
||||||
|
if (!provider.TryGetContentType(fileName, out var contentType))
|
||||||
|
{
|
||||||
|
contentType = "application/octet-stream"; // fallback
|
||||||
|
}
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="HttpClientToCurl" Version="2.0.6" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.19" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.19" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Leanetec.EConnect.Client\Leanetec.EConnect.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Leanetec.EConnect.Domain\Leanetec.EConnect.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
100
src/Leanetec.EConnect.Infrastructure/LogExtensions.cs
Normal file
100
src/Leanetec.EConnect.Infrastructure/LogExtensions.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using HttpClientToCurl;
|
||||||
|
using HttpClientToCurl.Config;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public static class LogExtensions
|
||||||
|
{
|
||||||
|
public static void LogCurl(
|
||||||
|
this ILogger logger,
|
||||||
|
HttpClient client,
|
||||||
|
HttpRequestMessage request,
|
||||||
|
Action<StringConfig>? config = null,
|
||||||
|
int maxLength = 1000,
|
||||||
|
LogLevel logLevel = LogLevel.Information)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var curl = client.GenerateCurlInString(
|
||||||
|
request,
|
||||||
|
config
|
||||||
|
).Truncate(maxLength);
|
||||||
|
logger?.Log(logLevel, "{curl}", curl);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "An unexpected error occurred while logging the HTTP request as cURL.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogCurl(
|
||||||
|
this ILogger logger,
|
||||||
|
HttpClient client,
|
||||||
|
HttpMethod method,
|
||||||
|
string? uri = null,
|
||||||
|
HttpRequestHeaders? headers = null,
|
||||||
|
HttpContent? content = null,
|
||||||
|
Action<StringConfig>? config = null,
|
||||||
|
int maxLength = 1000,
|
||||||
|
LogLevel logLevel = LogLevel.Information)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var curl = client.GenerateCurlInString(
|
||||||
|
method,
|
||||||
|
uri ?? "",
|
||||||
|
headers,
|
||||||
|
content,
|
||||||
|
config
|
||||||
|
).Truncate(maxLength);
|
||||||
|
logger?.Log(logLevel, "{curl}", curl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "An unexpected error occurred while logging the HTTP request as cURL.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogCurl(
|
||||||
|
this ILogger logger,
|
||||||
|
HttpClient client,
|
||||||
|
HttpMethod method,
|
||||||
|
Uri uri,
|
||||||
|
HttpRequestHeaders? headers = null,
|
||||||
|
HttpContent? content = null,
|
||||||
|
Action<StringConfig>? config = null,
|
||||||
|
int maxLength = 1000,
|
||||||
|
LogLevel logLevel = LogLevel.Information)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var curl = client.GenerateCurlInString(
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
headers,
|
||||||
|
content,
|
||||||
|
config
|
||||||
|
).Truncate(maxLength);
|
||||||
|
logger?.Log(logLevel, "{curl}", curl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "An unexpected error occurred while logging the HTTP request as cURL.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Truncates the string to the specified max length and appends a suffix if truncated.
|
||||||
|
/// </summary>
|
||||||
|
public static string Truncate(this string? value, int maxLength, string suffix = "...[truncated]")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value) || maxLength <= 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return value.Length <= maxLength
|
||||||
|
? value
|
||||||
|
: value[..maxLength] + suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/Leanetec.EConnect.Infrastructure/ObjectExtensions.cs
Normal file
52
src/Leanetec.EConnect.Infrastructure/ObjectExtensions.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Infrastructure;
|
||||||
|
|
||||||
|
public static class ObjectExtensions
|
||||||
|
{
|
||||||
|
public static Dictionary<string, string?> ToPropertyDictionary(this object obj)
|
||||||
|
{
|
||||||
|
return obj
|
||||||
|
.GetType()
|
||||||
|
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||||
|
.ToDictionary(
|
||||||
|
prop => prop.Name.ToCamelCase(),
|
||||||
|
prop => prop.GetValue(obj).ToSafeString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, string?> ToPropertyDictionary(this Dictionary<string, object?> obj)
|
||||||
|
{
|
||||||
|
return obj
|
||||||
|
.ToDictionary(
|
||||||
|
prop => prop.Key.ToCamelCase(),
|
||||||
|
prop => prop.Value?.ToString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? ToSafeString(this object? obj)
|
||||||
|
=> obj is bool b
|
||||||
|
? (b ? "true" : "false")
|
||||||
|
: obj?.ToString();
|
||||||
|
|
||||||
|
public static string? AddQueryString(this string? route, Dictionary<string, string?> queryParams)
|
||||||
|
{
|
||||||
|
if (queryParams.Count > 0)
|
||||||
|
route = QueryHelpers.AddQueryString(route ?? string.Empty, queryParams);
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? AddQueryString(this string? route, object queryParams) => route.AddQueryString(queryParams.ToPropertyDictionary());
|
||||||
|
|
||||||
|
public static string ToCamelCase(this string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
return input;
|
||||||
|
|
||||||
|
if (char.IsLower(input[0]))
|
||||||
|
return input;
|
||||||
|
|
||||||
|
return char.ToLowerInvariant(input[0]) + input[1..];
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/Leanetec.EConnect.Proxy/Controllers/OrderController.cs
Normal file
43
src/Leanetec.EConnect.Proxy/Controllers/OrderController.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Leanetec.EConnect.Client.Order;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Proxy.Controllers;
|
||||||
|
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class OrderController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public OrderController(IMediator mediator)
|
||||||
|
{
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("document")]
|
||||||
|
public async Task<IActionResult> GetDocument([FromQuery] GetDocumentRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var res = await _mediator.Send(request, cancel);
|
||||||
|
if(res.Ok)
|
||||||
|
return res.Data is null || !res.Data.Any()
|
||||||
|
? NotFound()
|
||||||
|
: Ok(res.Data);
|
||||||
|
else
|
||||||
|
return StatusCode(res.StatusCodeInt, res?.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("document")]
|
||||||
|
public async Task<IActionResult> PostDocument(IFormFile file, [FromQuery] PostDocumentRequest request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
using var stream = file.OpenReadStream();
|
||||||
|
|
||||||
|
var uploadRequest = request.ToUploadDocument(stream, file.FileName);
|
||||||
|
|
||||||
|
var res = await _mediator.Send(uploadRequest, cancel);
|
||||||
|
|
||||||
|
return res.Ok
|
||||||
|
? StatusCode(res.StatusCodeInt)
|
||||||
|
: StatusCode(res.StatusCodeInt, res?.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Leanetec.EConnect.Proxy/Leanetec.EConnect.Proxy.csproj
Normal file
28
src/Leanetec.EConnect.Proxy/Leanetec.EConnect.Proxy.csproj
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<PackageId>Leanetec.EConnect.Proxy</PackageId>
|
||||||
|
<Version>1.0.1</Version>
|
||||||
|
<Company>Digital Data GmbH</Company>
|
||||||
|
<Product>Leanetec.EConnect.Proxy</Product>
|
||||||
|
<Title>Leanetec.EConnect.Proxy</Title>
|
||||||
|
<AssemblyVersion>1.0.1</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.1</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||||
|
<PackageReference Include="NLog" Version="6.0.3" />
|
||||||
|
<PackageReference Include="NLog.Web.AspNetCore" Version="6.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Leanetec.EConnect.Client\Leanetec.EConnect.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Leanetec.EConnect.Infrastructure\Leanetec.EConnect.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using DigitalData.Core.Exceptions;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Leanetec.EConnect.Proxy.Middleware;
|
||||||
|
|
||||||
|
//TODO: Fix and use DigitalData.Core.Exceptions.Middleware
|
||||||
|
/// <summary>
|
||||||
|
/// Middleware for handling exceptions globally in the application.
|
||||||
|
/// Captures exceptions thrown during the request pipeline execution,
|
||||||
|
/// logs them, and returns an appropriate HTTP response with a JSON error message.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use DigitalData.Core.Exceptions.Middleware")]
|
||||||
|
public class ExceptionHandlingMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ExceptionHandlingMiddleware"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The next middleware in the request pipeline.</param>
|
||||||
|
/// <param name="logger">The logger instance for logging exceptions.</param>
|
||||||
|
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the middleware to handle the HTTP request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The HTTP context of the current request.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context); // Continue down the pipeline
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await HandleExceptionAsync(context, ex, _logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles exceptions by logging them and writing an appropriate JSON response.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The HTTP context of the current request.</param>
|
||||||
|
/// <param name="exception">The exception that occurred.</param>
|
||||||
|
/// <param name="logger">The logger instance for logging the exception.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||||
|
private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
|
||||||
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
|
||||||
|
string message;
|
||||||
|
|
||||||
|
switch (exception)
|
||||||
|
{
|
||||||
|
case BadRequestException badRequestEx:
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||||
|
message = badRequestEx.Message;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotFoundException notFoundEx:
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
|
message = notFoundEx.Message;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.LogError(exception, "Unhandled exception occurred.");
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
message = "An unexpected error occurred.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.Response.WriteAsync(JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
message
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/Leanetec.EConnect.Proxy/Program.cs
Normal file
65
src/Leanetec.EConnect.Proxy/Program.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using Leanetec.EConnect.Client;
|
||||||
|
using Leanetec.EConnect.Infrastructure;
|
||||||
|
using Leanetec.EConnect.Proxy.Middleware;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Web;
|
||||||
|
|
||||||
|
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||||
|
logger.Info("Logging initialized!");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
var config = builder.Configuration;
|
||||||
|
|
||||||
|
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
|
||||||
|
if (!builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Host.UseNLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
var clientOpt = config.GetSection("EConnect");
|
||||||
|
builder.Services.AddClientServices().AddInfrastructureServices(opt => opt.ConfigureClient(clientOpt));
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||||
|
|
||||||
|
bool useSwagger = config.GetValue<bool>("UseSwagger");
|
||||||
|
if(useSwagger)
|
||||||
|
app.Services.GetRequiredService<ILogger<Program>>()
|
||||||
|
.LogWarning("Swagger UI is enabled. Using Swagger in a production environment may expose sensitive API information and pose security risks.");
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment() || useSwagger)
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// NLog: catch setup errors
|
||||||
|
logger.Error(ex, "Application stopped because of exception");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
|
||||||
|
LogManager.Shutdown();
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<WebPublishMethod>Package</WebPublishMethod>
|
||||||
|
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||||
|
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||||
|
<SiteUrlToLaunchAfterPublish />
|
||||||
|
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||||
|
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||||
|
<ProjectGuid>79d9868e-6a67-45c1-ba3e-1c2e822ecc58</ProjectGuid>
|
||||||
|
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\EConnect\Proxy\$(Version)\EConnect.Proxy.zip</DesktopBuildPackageLocation>
|
||||||
|
<PackageAsSingleFile>true</PackageAsSingleFile>
|
||||||
|
<DeployIisAppPath>EConnect.Proxy</DeployIisAppPath>
|
||||||
|
<_TargetId>IISWebDeployPackage</_TargetId>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
41
src/Leanetec.EConnect.Proxy/Properties/launchSettings.json
Normal file
41
src/Leanetec.EConnect.Proxy/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:25272",
|
||||||
|
"sslPort": 44340
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5254",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:7067;http://localhost:5254",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/Leanetec.EConnect.Proxy/appsettings.Development.json
Normal file
8
src/Leanetec.EConnect.Proxy/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/Leanetec.EConnect.Proxy/appsettings.json
Normal file
68
src/Leanetec.EConnect.Proxy/appsettings.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UseSwagger": true,
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"EConnect": {
|
||||||
|
"BaseAddress": "https://portal.demoportal01.leanetec.com",
|
||||||
|
"DefaultQueryStrings": {
|
||||||
|
"apiKey": "HGpGp3vk3MsKgSe0tKVZ1ZRCuq6bFoJ1"
|
||||||
|
},
|
||||||
|
"JsonSerializerDateFormat": "yyyy-MM-dd HH:mm"
|
||||||
|
},
|
||||||
|
"NLog": {
|
||||||
|
"throwConfigExceptions": true,
|
||||||
|
"variables": {
|
||||||
|
"logDirectory": "E:\\LogFiles\\Digital Data\\EConnect-Proxy",
|
||||||
|
"logFileNamePrefix": "${shortdate}-ECM.EConnect-Proxy.Web"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"infoLogs": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
|
||||||
|
"maxArchiveDays": 30
|
||||||
|
},
|
||||||
|
"warningLogs": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "${logDirectory}\\${logFileNamePrefix}-Warning.log",
|
||||||
|
"maxArchiveDays": 30
|
||||||
|
},
|
||||||
|
"errorLogs": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
|
||||||
|
"maxArchiveDays": 30
|
||||||
|
},
|
||||||
|
"criticalLogs": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
|
||||||
|
"maxArchiveDays": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"logger": "*",
|
||||||
|
"level": "Info",
|
||||||
|
"writeTo": "infoLogs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "*",
|
||||||
|
"level": "Warn",
|
||||||
|
"writeTo": "warningLogs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "*",
|
||||||
|
"level": "Error",
|
||||||
|
"writeTo": "errorLogs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "*",
|
||||||
|
"level": "Fatal",
|
||||||
|
"writeTo": "criticalLogs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user