diff --git a/codecov.yml b/codecov.yml
index bc179cef5..553f2235e 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -5,6 +5,7 @@ coverage:
target: auto # auto compares coverage to the previous base commit
ignore:
- "**/Startup.cs"
+ - "**/Startup/*.cs"
- "**/Program.cs"
- "**/PortalDbContext.cs"
- "**/Migrations/*.cs"
diff --git a/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj b/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj
index b25d8e162..a67dc9917 100644
--- a/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj
+++ b/src/AzureIoTHub.Portal.Application/AzureIoTHub.Portal.Application.csproj
@@ -6,18 +6,11 @@
enable
-
-
-
-
-
-
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/AzureIoTHub.Portal.Application/Startup/IServiceCollectionExtension.cs b/src/AzureIoTHub.Portal.Application/Startup/IServiceCollectionExtension.cs
new file mode 100644
index 000000000..a0ce6274d
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Application/Startup/IServiceCollectionExtension.cs
@@ -0,0 +1,20 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Application.Startup
+{
+ using Microsoft.Extensions.DependencyInjection;
+
+ public static class IServiceCollectionExtension
+ {
+ public static IServiceCollection AddApplicationLayer(this IServiceCollection services)
+ {
+ return services.ConfigureMappingProfiles();
+ }
+
+ private static IServiceCollection ConfigureMappingProfiles(this IServiceCollection services)
+ {
+ return services.AddAutoMapper(typeof(IServiceCollectionExtension));
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj
index 60b7f39a6..f864bd5b0 100644
--- a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj
+++ b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj
@@ -1,4 +1,4 @@
-
+
net7.0
@@ -7,6 +7,7 @@
+
@@ -90,6 +91,7 @@
+
@@ -125,9 +127,14 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
@@ -137,4 +144,4 @@
-
+
\ No newline at end of file
diff --git a/src/AzureIoTHub.Portal.Application/Resources/default-template-icon.png b/src/AzureIoTHub.Portal.Infrastructure/Resources/default-template-icon.png
similarity index 100%
rename from src/AzureIoTHub.Portal.Application/Resources/default-template-icon.png
rename to src/AzureIoTHub.Portal.Infrastructure/Resources/default-template-icon.png
diff --git a/src/AzureIoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs b/src/AzureIoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs
new file mode 100644
index 000000000..c08282e96
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Infrastructure/Startup/IServiceCollectionExtension.cs
@@ -0,0 +1,185 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Infrastructure.Startup
+{
+ using System.Net;
+ using Azure.Storage.Blobs;
+ using AzureIoTHub.Portal.Application.Providers;
+ using AzureIoTHub.Portal.Application.Services;
+ using AzureIoTHub.Portal.Application.Wrappers;
+ using AzureIoTHub.Portal.Domain;
+ using AzureIoTHub.Portal.Domain.Options;
+ using AzureIoTHub.Portal.Infrastructure.Providers;
+ using AzureIoTHub.Portal.Infrastructure.Services;
+ using AzureIoTHub.Portal.Infrastructure.ServicesHealthCheck;
+ using AzureIoTHub.Portal.Infrastructure.Wrappers;
+ using EntityFramework.Exceptions.PostgreSQL;
+ using Microsoft.Azure.Devices.Provisioning.Service;
+ using Microsoft.Azure.Devices;
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Options;
+ using Polly;
+ using Polly.Extensions.Http;
+ using AzureIoTHub.Portal.Domain.Repositories;
+ using AzureIoTHub.Portal.Infrastructure.Repositories;
+ using AzureIoTHub.Portal.Application.Mappers;
+ using AzureIoTHub.Portal.Infrastructure.Mappers;
+ using AzureIoTHub.Portal.Models.v10.LoRaWAN;
+ using AzureIoTHub.Portal.Models.v10;
+ using AzureIoTHub.Portal.Application.Managers;
+ using AzureIoTHub.Portal.Infrastructure.Managers;
+ using Azure.Storage.Blobs.Models;
+ using Microsoft.Extensions.Configuration;
+
+ public static class IServiceCollectionExtension
+ {
+ public static IServiceCollection AddInfrastructureLayer(this IServiceCollection services, ConfigHandler configuration)
+ {
+ return services.ConfigureDatabase(configuration)
+ .ConfigureRepositories()
+ .ConfigureImageBlobStorage(configuration)
+ .AddLoRaWanSupport(configuration)
+ .ConfigureDeviceRegstryDependencies(configuration)
+ .ConfigureServices()
+ .ConfigureMappers()
+ .ConfigureHealthCheck();
+ }
+
+ private static IServiceCollection AddLoRaWanSupport(this IServiceCollection services, ConfigHandler configuration)
+ {
+ _ = services.Configure(opts =>
+ {
+ opts.Enabled = configuration.IsLoRaEnabled;
+ opts.KeyManagementApiVersion = configuration.LoRaKeyManagementApiVersion;
+ opts.KeyManagementCode = configuration.LoRaKeyManagementCode;
+ opts.KeyManagementUrl = configuration.LoRaKeyManagementUrl;
+ });
+
+ if (!configuration.IsLoRaEnabled)
+ {
+ return services;
+ }
+
+ var transientHttpErrorPolicy = HttpPolicyExtensions
+ .HandleTransientHttpError()
+ .OrResult(c => c.StatusCode == HttpStatusCode.NotFound)
+ .WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(100));
+
+ _ = services.AddHttpClient("RestClient")
+ .AddPolicyHandler(transientHttpErrorPolicy);
+
+ _ = services.AddHttpClient((sp, client) =>
+ {
+ var opts = sp.GetService>().Value;
+
+ client.BaseAddress = new Uri(opts.KeyManagementUrl);
+ client.DefaultRequestHeaders.Add("x-functions-key", opts.KeyManagementCode);
+ client.DefaultRequestHeaders.Add("api-version", opts.KeyManagementApiVersion ?? "2022-03-04");
+ })
+ .AddPolicyHandler(transientHttpErrorPolicy);
+
+ return services;
+ }
+
+ private static IServiceCollection ConfigureDeviceRegstryDependencies(this IServiceCollection services, ConfigHandler configuration)
+ {
+ _ = services.AddTransient();
+ _ = services.AddTransient();
+
+ _ = services.AddScoped(_ => RegistryManager.CreateFromConnectionString(configuration.IoTHubConnectionString));
+ _ = services.AddScoped(_ => ServiceClient.CreateFromConnectionString(configuration.IoTHubConnectionString));
+ _ = services.AddScoped(_ => ProvisioningServiceClient.CreateFromConnectionString(configuration.DPSConnectionString));
+
+ return services;
+ }
+
+ private static IServiceCollection ConfigureDatabase(this IServiceCollection services, ConfigHandler configuration)
+ {
+ _ = services
+ .AddDbContextPool(opts =>
+ {
+ _ = opts.UseNpgsql(configuration.PostgreSQLConnectionString);
+ _ = opts.UseExceptionProcessor();
+ });
+
+ if (string.IsNullOrEmpty(configuration.PostgreSQLConnectionString))
+ return services;
+
+ _ = services.AddScoped>();
+
+ var dbContextOptions = new DbContextOptionsBuilder();
+ _ = dbContextOptions.UseNpgsql(configuration.PostgreSQLConnectionString);
+
+ using var ctx = new PortalDbContext(dbContextOptions.Options);
+ ctx.Database.Migrate();
+
+ return services;
+ }
+
+ private static IServiceCollection ConfigureRepositories(this IServiceCollection services)
+ {
+ return services.AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped();
+ }
+
+ private static IServiceCollection ConfigureMappers(this IServiceCollection services)
+ {
+ _ = services.AddTransient();
+ _ = services.AddTransient();
+ _ = services.AddTransient();
+
+ return services.AddTransient, DeviceTwinMapper>()
+ .AddTransient, LoRaDeviceTwinMapper>()
+ .AddTransient, DeviceModelMapper>()
+ .AddTransient, LoRaDeviceModelMapper>()
+ .AddTransient();
+ }
+
+ private static IServiceCollection ConfigureServices(this IServiceCollection services)
+ {
+ return services.AddTransient();
+ }
+
+ private static IServiceCollection ConfigureHealthCheck(this IServiceCollection services)
+ {
+ _ = services.AddHealthChecks()
+ .AddDbContextCheck()
+ .AddCheck("iothubHealth")
+ .AddCheck("storageAccountHealth")
+ .AddCheck("tableStorageHealth")
+ .AddCheck("dpsHealth")
+ .AddCheck("loraManagementFacadeHealth")
+ .AddCheck("databaseHealthCheck");
+
+ return services;
+ }
+
+ private static IServiceCollection ConfigureImageBlobStorage(this IServiceCollection services, ConfigHandler configuration)
+ {
+ return services.AddTransient(_ => new BlobServiceClient(configuration.StorageAccountConnectionString))
+ .Configure((opts) =>
+ {
+ var serviceClient = new BlobServiceClient(configuration.StorageAccountConnectionString);
+ var container = serviceClient.GetBlobContainerClient(opts.ImageContainerName);
+
+ _ = container.SetAccessPolicy(PublicAccessType.Blob);
+ _ = container.CreateIfNotExists();
+
+ opts.BaseUri = container.Uri;
+ });
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Server/AzureIoTHub.Portal.Server.csproj b/src/AzureIoTHub.Portal.Server/AzureIoTHub.Portal.Server.csproj
index 5260ab508..5eba78410 100644
--- a/src/AzureIoTHub.Portal.Server/AzureIoTHub.Portal.Server.csproj
+++ b/src/AzureIoTHub.Portal.Server/AzureIoTHub.Portal.Server.csproj
@@ -31,7 +31,6 @@
-
diff --git a/src/AzureIoTHub.Portal.Server/Startup.cs b/src/AzureIoTHub.Portal.Server/Startup.cs
index 699d0853f..33c5a23a6 100644
--- a/src/AzureIoTHub.Portal.Server/Startup.cs
+++ b/src/AzureIoTHub.Portal.Server/Startup.cs
@@ -5,34 +5,20 @@ namespace AzureIoTHub.Portal.Server
{
using System;
using System.IO;
- using System.Net;
using System.Threading.Tasks;
- using Azure.Storage.Blobs;
- using Azure.Storage.Blobs.Models;
using AzureIoTHub.Portal.Application.Managers;
- using AzureIoTHub.Portal.Application.Mappers;
- using AzureIoTHub.Portal.Application.Providers;
- using AzureIoTHub.Portal.Application.Services;
- using AzureIoTHub.Portal.Application.Wrappers;
- using AzureIoTHub.Portal.Domain.Options;
- using AzureIoTHub.Portal.Infrastructure.Managers;
- using AzureIoTHub.Portal.Infrastructure.Mappers;
- using AzureIoTHub.Portal.Infrastructure.Providers;
- using AzureIoTHub.Portal.Infrastructure.Services;
+ using AzureIoTHub.Portal.Application.Startup;
using AzureIoTHub.Portal.Infrastructure.ServicesHealthCheck;
- using AzureIoTHub.Portal.Infrastructure.Wrappers;
+ using AzureIoTHub.Portal.Infrastructure.Startup;
using AzureIoTHub.Portal.Server.Jobs;
using Domain;
using Domain.Exceptions;
- using Domain.Repositories;
using EntityFramework.Exceptions.Common;
- using EntityFramework.Exceptions.PostgreSQL;
using Extensions;
using Hellang.Middleware.ProblemDetails;
using Hellang.Middleware.ProblemDetails.Mvc;
using Identity;
using Infrastructure;
- using Infrastructure.Repositories;
using Infrastructure.Seeds;
using Managers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
@@ -42,21 +28,15 @@ namespace AzureIoTHub.Portal.Server
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
- using Microsoft.Azure.Devices;
- using Microsoft.Azure.Devices.Provisioning.Service;
- using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.OpenApi.Models;
using Models.v10;
using Models.v10.LoRaWAN;
using MudBlazor.Services;
- using Polly;
- using Polly.Extensions.Http;
using Prometheus;
using Quartz;
using Services;
@@ -85,71 +65,20 @@ public void ConfigureServices(IServiceCollection services)
var configuration = ConfigHandlerFactory.Create(HostEnvironment, Configuration);
- _ = services.Configure(opts =>
- {
- opts.MetadataUrl = new Uri(configuration.OIDCMetadataUrl);
- opts.ClientId = configuration.OIDCClientId;
- opts.Scope = configuration.OIDCScope;
- opts.Authority = configuration.OIDCAuthority;
- });
-
- _ = services.Configure(opts =>
- {
- opts.Enabled = configuration.IsLoRaEnabled;
- opts.KeyManagementApiVersion = configuration.LoRaKeyManagementApiVersion;
- opts.KeyManagementCode = configuration.LoRaKeyManagementCode;
- opts.KeyManagementUrl = configuration.LoRaKeyManagementUrl;
- });
-
- _ = services
- .AddAuthentication(options =>
- {
- options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
- options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
- })
- .AddJwtBearer(opts =>
- {
- opts.Authority = configuration.OIDCAuthority;
- opts.MetadataAddress = configuration.OIDCMetadataUrl;
- opts.Audience = configuration.OIDCApiClientId;
+ _ = services.AddSingleton(configuration);
- opts.TokenValidationParameters.ValidateIssuer = configuration.OIDCValidateIssuer;
- opts.TokenValidationParameters.ValidateAudience = configuration.OIDCValidateAudience;
- opts.TokenValidationParameters.ValidateLifetime = configuration.OIDCValidateLifetime;
- opts.TokenValidationParameters.ValidateIssuerSigningKey = configuration.OIDCValidateIssuerSigningKey;
- opts.TokenValidationParameters.ValidateActor = configuration.OIDCValidateActor;
- opts.TokenValidationParameters.ValidateTokenReplay = configuration.OIDCValidateTokenReplay;
- });
+ _ = services.AddInfrastructureLayer(configuration)
+ .AddApplicationLayer();
- ConfigureDatabase(services, configuration);
+ AddAuthenticationAndAuthorization(services, configuration);
- _ = services.AddSingleton(configuration);
_ = services.AddSingleton(new PortalMetric());
_ = services.AddSingleton(new LoRaGatewayIDList());
_ = services.AddRazorPages();
- _ = services.AddScoped(_ => RegistryManager.CreateFromConnectionString(configuration.IoTHubConnectionString));
-
- _ = services.AddScoped(_ => ServiceClient.CreateFromConnectionString(configuration.IoTHubConnectionString));
-
- _ = services.AddScoped(_ => ProvisioningServiceClient.CreateFromConnectionString(configuration.DPSConnectionString));
-
- _ = services.AddTransient();
- _ = services.AddTransient(_ => new BlobServiceClient(configuration.StorageAccountConnectionString));
- _ = services.AddTransient();
- _ = services.AddTransient();
- _ = services.AddTransient();
- _ = services.AddTransient();
- _ = services.AddTransient();
_ = services.AddTransient();
- _ = services.AddTransient, DeviceTwinMapper>();
- _ = services.AddTransient, LoRaDeviceTwinMapper>();
- _ = services.AddTransient, DeviceModelMapper>();
- _ = services.AddTransient, LoRaDeviceModelMapper>();
- _ = services.AddTransient();
-
_ = services.AddTransient();
_ = services.AddTransient();
_ = services.AddTransient();
@@ -164,40 +93,8 @@ public void ConfigureServices(IServiceCollection services)
_ = services.AddTransient, DeviceService>();
_ = services.AddTransient, LoRaWanDeviceService>();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
- _ = services.AddScoped();
-
_ = services.AddMudServices();
- var transientHttpErrorPolicy = HttpPolicyExtensions
- .HandleTransientHttpError()
- .OrResult(c => c.StatusCode == HttpStatusCode.NotFound)
- .WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(100));
-
- _ = services.AddHttpClient("RestClient")
- .AddPolicyHandler(transientHttpErrorPolicy);
-
- _ = services.AddHttpClient((sp, client) =>
- {
- var opts = sp.GetService>().Value;
-
- client.BaseAddress = new Uri(opts.KeyManagementUrl);
- client.DefaultRequestHeaders.Add("x-functions-key", opts.KeyManagementCode);
- client.DefaultRequestHeaders.Add("api-version", opts.KeyManagementApiVersion ?? "2022-03-04");
- })
- .AddPolicyHandler(transientHttpErrorPolicy);
-
ConfigureIdeasFeature(services, configuration);
// Add problem details support
@@ -350,18 +247,6 @@ Specify the authorization token got from your IDP as a header.
new HeaderApiVersionReader("X-Version"));
});
- // Add AutoMapper Configuration
- _ = services.AddAutoMapper(typeof(Startup), typeof(Application.Mappers.DeviceProfile));
-
- _ = services.AddHealthChecks()
- .AddDbContextCheck()
- .AddCheck("iothubHealth")
- .AddCheck("storageAccountHealth")
- .AddCheck("tableStorageHealth")
- .AddCheck("dpsHealth")
- .AddCheck("loraManagementFacadeHealth")
- .AddCheck("databaseHealthCheck");
-
// Add the required Quartz.NET services
_ = services.AddQuartz(q =>
{
@@ -431,18 +316,6 @@ Specify the authorization token got from your IDP as a header.
// Add the Quartz.NET hosted service
_ = services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
-
- // Options
- _ = services.Configure((opts) =>
- {
- var serviceClient = new BlobServiceClient(configuration.StorageAccountConnectionString);
- var container = serviceClient.GetBlobContainerClient(opts.ImageContainerName);
-
- _ = container.SetAccessPolicy(PublicAccessType.Blob);
- _ = container.CreateIfNotExists();
-
- opts.BaseUri = container.Uri;
- });
}
private static void ConfigureIdeasFeature(IServiceCollection services, ConfigHandler configuration)
@@ -458,25 +331,35 @@ private static void ConfigureIdeasFeature(IServiceCollection services, ConfigHan
});
}
- private static void ConfigureDatabase(IServiceCollection services, ConfigHandler configuration)
+ private static void AddAuthenticationAndAuthorization(IServiceCollection services, ConfigHandler configuration)
{
+ _ = services.Configure(opts =>
+ {
+ opts.MetadataUrl = new Uri(configuration.OIDCMetadataUrl);
+ opts.ClientId = configuration.OIDCClientId;
+ opts.Scope = configuration.OIDCScope;
+ opts.Authority = configuration.OIDCAuthority;
+ });
+
_ = services
- .AddDbContextPool(opts =>
+ .AddAuthentication(options =>
{
- _ = opts.UseNpgsql(configuration.PostgreSQLConnectionString);
- _ = opts.UseExceptionProcessor();
- });
-
- if (string.IsNullOrEmpty(configuration.PostgreSQLConnectionString))
- return;
-
- _ = services.AddScoped>();
-
- var dbContextOptions = new DbContextOptionsBuilder();
- _ = dbContextOptions.UseNpgsql(configuration.PostgreSQLConnectionString);
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(opts =>
+ {
+ opts.Authority = configuration.OIDCAuthority;
+ opts.MetadataAddress = configuration.OIDCMetadataUrl;
+ opts.Audience = configuration.OIDCApiClientId;
- using var ctx = new PortalDbContext(dbContextOptions.Options);
- ctx.Database.Migrate();
+ opts.TokenValidationParameters.ValidateIssuer = configuration.OIDCValidateIssuer;
+ opts.TokenValidationParameters.ValidateAudience = configuration.OIDCValidateAudience;
+ opts.TokenValidationParameters.ValidateLifetime = configuration.OIDCValidateLifetime;
+ opts.TokenValidationParameters.ValidateIssuerSigningKey = configuration.OIDCValidateIssuerSigningKey;
+ opts.TokenValidationParameters.ValidateActor = configuration.OIDCValidateActor;
+ opts.TokenValidationParameters.ValidateTokenReplay = configuration.OIDCValidateTokenReplay;
+ });
}
///