diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 2b6894e..5126b6a 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -169,7 +169,7 @@ jobs:
echo A | xcopy .\bin\publish-x64 .\publish-result /s /e /h
echo A | xcopy .\bin\publish .\publish-result /s /e /h
dotnet tool restore
- dotnet vpk pack -u Bit.TemplatePlayground.Client.Windows -v "${{ vars.APPLICATION_DISPLAY_VERSION }}" -p .\publish-result -e Bit.TemplatePlayground.Client.Windows-x64.exe -r win-x64 --framework net8.0.1-x64-desktop,webview2 --icon .\wwwroot\favicon.ico --packTitle 'Bit.TemplatePlayground'
+ dotnet vpk pack -u Bit.TemplatePlayground.Client.Windows -v "${{ vars.APPLICATION_DISPLAY_VERSION }}" -p .\publish-result -e Bit.TemplatePlayground.Client.Windows-x86.exe -r win-x86 --framework net8.0.1-x86-desktop,webview2 --icon .\wwwroot\favicon.ico --packTitle 'Bit.TemplatePlayground'
- name: Upload artifact
uses: actions/upload-artifact@v2
diff --git a/Bit.TemplatePlayground.Web.slnf b/Bit.TemplatePlayground.Web.slnf
new file mode 100644
index 0000000..4c144ac
--- /dev/null
+++ b/Bit.TemplatePlayground.Web.slnf
@@ -0,0 +1,11 @@
+{
+ "solution": {
+ "path": "Bit.TemplatePlayground.sln",
+ "projects": [
+ "src\\Bit.TemplatePlayground.Server\\Bit.TemplatePlayground.Server.csproj",
+ "src\\Bit.TemplatePlayground.Shared\\Bit.TemplatePlayground.Shared.csproj",
+ "src\\Client\\Bit.TemplatePlayground.Client.Core\\Bit.TemplatePlayground.Client.Core.csproj",
+ "src\\Client\\Bit.TemplatePlayground.Client.Web\\Bit.TemplatePlayground.Client.Web.csproj"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Bit.TemplatePlayground.sln b/Bit.TemplatePlayground.sln
index f7b2f62..612c7d1 100644
--- a/Bit.TemplatePlayground.sln
+++ b/Bit.TemplatePlayground.sln
@@ -84,6 +84,8 @@ Global
{E3CB3C34-F5DE-4A96-B552-7D52BCAD1E1F} = {248D8229-BABD-4F0A-A9C6-0417B464507B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
+ RESX_NeutralResourcesLanguage = en-US
+ RESX_CodeReferences = {"Items":[{"Expression":"\\W($File.$Key)\\W","Extensions":".cs,.xaml,.cshtml,.razor","IsCaseSensitive":true,"SingleLineComment":"\/\/"},{"Expression":"\\W($File.$Key)\\W","Extensions":".vbhtml","IsCaseSensitive":false,"SingleLineComment":null},{"Expression":"ResourceManager.GetString\\(\"($Key)\"\\)","Extensions":".cs","IsCaseSensitive":true,"SingleLineComment":"\/\/"},{"Expression":"typeof\\((\\w+\\.)*($File)\\).+\"($Key)\"|\"($Key)\".+typeof\\((\\w+\\.)*($File)\\)","Extensions":".cs","IsCaseSensitive":true,"SingleLineComment":"\/\/"},{"Expression":"\\W($Key)\\W","Extensions":".vb","IsCaseSensitive":false,"SingleLineComment":"'"},{"Expression":"\\W($File::$Key)\\W","Extensions":".cpp,.c,.hxx,.h","IsCaseSensitive":true,"SingleLineComment":"\/\/"},{"Expression":"<%\\$\\s+Resources:\\s*($File)\\s*,\\s*($Key)\\s*%>","Extensions":".aspx,.ascx,.master","IsCaseSensitive":true,"SingleLineComment":null},{"Expression":"StringResourceKey\\.($Key)","Extensions":".cs","IsCaseSensitive":true,"SingleLineComment":"\/\/"},{"Expression":"\\.($Key)","Extensions":".ts,.html","IsCaseSensitive":true,"SingleLineComment":"\/\/"}]}
SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
EndGlobalSection
EndGlobal
diff --git a/global.json b/global.json
index 70f5783..76a1fd4 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.101",
- "rollForward": "disable"
+ "rollForward": "disable"
}
}
\ No newline at end of file
diff --git a/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj b/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj
index 9056238..ea5e208 100644
--- a/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj
+++ b/src/Bit.TemplatePlayground.Iac/Bit.TemplatePlayground.Iac.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj b/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj
index e800959..2455a7e 100644
--- a/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj
+++ b/src/Bit.TemplatePlayground.Server/Bit.TemplatePlayground.Server.csproj
@@ -5,31 +5,37 @@
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
+
+
+
+
+
+
+
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -43,22 +49,22 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Always
+
+
+
@@ -67,7 +73,6 @@
-
True
True
@@ -79,17 +84,11 @@
-
-
- Always
-
-
-
linux-x64
True
- 21D250BE-CAF7-44E4-8DAC-AFA502B0E2F2
+ 65C62680-719C-4F56-9BF7-88F34AF733EF
Linux
..\..
diff --git a/src/Bit.TemplatePlayground.Server/Controllers/Dashboard/DashboardController.cs b/src/Bit.TemplatePlayground.Server/Controllers/Dashboard/DashboardController.cs
index 14204ad..4d2aec3 100644
--- a/src/Bit.TemplatePlayground.Server/Controllers/Dashboard/DashboardController.cs
+++ b/src/Bit.TemplatePlayground.Server/Controllers/Dashboard/DashboardController.cs
@@ -22,7 +22,7 @@ public async Task GetOverallAnalyticsStats
}
[HttpGet]
- public async Task> GetProductsCountPerCategoryStats(CancellationToken cancellationToken)
+ public IQueryable GetProductsCountPerCategoryStats()
{
return DbContext.Categories
.Select(c => new ProductsCountPerCategoryResponseDto()
@@ -30,11 +30,11 @@ public async Task> GetProd
CategoryName = c.Name,
CategoryColor = c.Color,
ProductCount = c.Products!.Count()
- }).AsAsyncEnumerable();
+ });
}
[HttpGet]
- public async Task> GetProductsSalesStats(CancellationToken cancellationToken)
+ public IQueryable GetProductsSalesStats()
{
Random rand = new Random();
return DbContext.Products.Include(p => p.Category)
@@ -43,12 +43,12 @@ public async Task> GetProductsSales
ProductName = p.Name,
CategoryColor = p.Category!.Color,
SaleAmount = rand.Next(1, 10) * p.Price
- }).AsAsyncEnumerable();
+ });
}
[HttpGet]
- public async Task GetProductsPercentagePerCategoryStats(CancellationToken cancellationToken)
+ public async Task> GetProductsPercentagePerCategoryStats(CancellationToken cancellationToken)
{
var productsTotalCount = await DbContext.Products.CountAsync(cancellationToken);
@@ -63,6 +63,6 @@ public async Task GetProductsPercenta
CategoryName = c!.Name,
CategoryColor = c.Color,
ProductPercentage = (float)decimal.Divide(c.Products!.Count(), productsTotalCount) * 100
- }).ToArrayAsync(cancellationToken);
+ }).ToListAsync(cancellationToken);
}
}
diff --git a/src/Bit.TemplatePlayground.Server/Startup/Middlewares.cs b/src/Bit.TemplatePlayground.Server/Program.Middlewares.cs
similarity index 95%
rename from src/Bit.TemplatePlayground.Server/Startup/Middlewares.cs
rename to src/Bit.TemplatePlayground.Server/Program.Middlewares.cs
index 4b0d41d..783a48b 100644
--- a/src/Bit.TemplatePlayground.Server/Startup/Middlewares.cs
+++ b/src/Bit.TemplatePlayground.Server/Program.Middlewares.cs
@@ -3,21 +3,24 @@
using System.Runtime.Loader;
using System.Web;
using Bit.TemplatePlayground.Client.Core.Services;
-using Bit.TemplatePlayground.Server.Components;
-using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Components.Endpoints;
-using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http.Extensions;
-namespace Bit.TemplatePlayground.Server.Startup;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using HealthChecks.UI.Client;
+
+namespace Bit.TemplatePlayground.Server;
-public class Middlewares
+public static partial class Program
{
///
/// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0#middleware-order
///
- public static void Use(WebApplication app, IHostEnvironment env, IConfiguration configuration)
+ public static void ConfiureMiddlewares(this WebApplication app)
{
+ var configuration = app.Configuration;
+ var env = app.Environment;
+
app.UseForwardedHeaders();
if (AppRenderMode.MultilingualEnabled)
@@ -71,6 +74,7 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
app.UseAntiforgery();
+
app.UseSwagger();
app.UseSwaggerUI(options =>
@@ -84,6 +88,7 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
QueryStringParameter = queryStringParameter
}).WithTags("Test");
+
app.MapGet("/.well-known/apple-app-site-association", async () =>
{
// https://branch.io/resources/aasa-validator/
@@ -92,6 +97,7 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
return Results.Stream(File.OpenRead(path), contentType, "apple-app-site-association");
}).ExcludeFromDescription();
+
app.MapControllers().RequireAuthorization();
var appSettings = configuration.GetSection(nameof(AppSettings)).Get()!;
@@ -113,8 +119,9 @@ public static void Use(WebApplication app, IHostEnvironment env, IConfiguration
});
}
+
// Handle the rest of requests with blazor
- var blazorApp = app.MapRazorComponents()
+ var blazorApp = app.MapRazorComponents()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(AssemblyLoadContext.Default.Assemblies.Where(asm => asm.GetName().Name?.Contains("Bit.TemplatePlayground") is true).Except([Assembly.GetExecutingAssembly()]).ToArray());
diff --git a/src/Bit.TemplatePlayground.Server/Extensions/IServiceCollectionExtensions.cs b/src/Bit.TemplatePlayground.Server/Program.Services.cs
similarity index 57%
rename from src/Bit.TemplatePlayground.Server/Extensions/IServiceCollectionExtensions.cs
rename to src/Bit.TemplatePlayground.Server/Program.Services.cs
index eb292b0..597cccb 100644
--- a/src/Bit.TemplatePlayground.Server/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Bit.TemplatePlayground.Server/Program.Services.cs
@@ -1,21 +1,133 @@
-using System.Security.Cryptography.X509Certificates;
-using Bit.TemplatePlayground.Server;
-using Bit.TemplatePlayground.Server.Models.Identity;
+using System.IO.Compression;
using Bit.TemplatePlayground.Server.Services;
+using Bit.TemplatePlayground.Client.Web;
+using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.AspNetCore.ResponseCompression;
+using System.Net.Mail;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using Bit.TemplatePlayground.Server.Models.Identity;
+using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.OData;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
-using Swashbuckle.AspNetCore.SwaggerGen;
-namespace Microsoft.Extensions.DependencyInjection;
+namespace Bit.TemplatePlayground.Server;
-public static class IServiceCollectionExtensions
+public static partial class Program
{
- public static void AddBlazor(this IServiceCollection services, IConfiguration configuration)
+ private static void ConfigureServices(this WebApplicationBuilder builder)
+ {
+ // Services being registered here can get injected in server project only.
+
+ var services = builder.Services;
+ var configuration = builder.Configuration;
+ var env = builder.Environment;
+
+ services.AddExceptionHandler();
+
+ services.Configure(options =>
+ {
+ options.ForwardedHeaders = ForwardedHeaders.All;
+ options.ForwardedHostHeaderName = "X-Host";
+ });
+
+ services.AddResponseCaching();
+
+ services.AddHttpContextAccessor();
+
+ services.AddResponseCompression(opts =>
+ {
+ opts.EnableForHttps = true;
+ opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]).ToArray();
+ opts.Providers.Add();
+ opts.Providers.Add();
+ })
+ .Configure(opt => opt.Level = CompressionLevel.Fastest)
+ .Configure(opt => opt.Level = CompressionLevel.Fastest);
+
+
+ var appSettings = configuration.GetSection(nameof(AppSettings)).Get()!;
+
+ services.AddCors();
+
+ services
+ .AddControllers()
+ .AddOData(options => options.EnableQueryFeatures())
+ .AddDataAnnotationsLocalization(options => options.DataAnnotationLocalizerProvider = StringLocalizerProvider.ProvideLocalizer)
+ .ConfigureApiBehaviorOptions(options =>
+ {
+ options.InvalidModelStateResponseFactory = context =>
+ {
+ throw new ResourceValidationException(context.ModelState.Select(ms => (ms.Key, ms.Value!.Errors.Select(e => new LocalizedString(e.ErrorMessage, e.ErrorMessage)).ToArray())).ToArray());
+ };
+ });
+
+ services.AddDbContext(options =>
+ {
+ options.UseSqlite(configuration.GetConnectionString("SqliteConnectionString"), dbOptions =>
+ {
+ dbOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
+ });
+ });
+
+ services.Configure(configuration.GetSection(nameof(AppSettings)));
+
+ services.TryAddTransient(sp => sp.GetRequiredService>().Value);
+
+ services.AddEndpointsApiExplorer();
+
+ services.AddSwaggerGen();
+
+ AddIdentity(builder);
+
+ AddHealthChecks(builder);
+
+ services.TryAddTransient();
+
+ var fluentEmailServiceBuilder = services.AddFluentEmail(appSettings.EmailSettings.DefaultFromEmail, appSettings.EmailSettings.DefaultFromName);
+
+ if (appSettings.EmailSettings.UseLocalFolderForEmails)
+ {
+ var sentEmailsFolderPath = Path.Combine(AppContext.BaseDirectory, "sent-emails");
+
+ Directory.CreateDirectory(sentEmailsFolderPath);
+
+ fluentEmailServiceBuilder.AddSmtpSender(() => new SmtpClient
+ {
+ DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory,
+ PickupDirectoryLocation = sentEmailsFolderPath
+ });
+ }
+ else
+ {
+ if (appSettings.EmailSettings.HasCredential)
+ {
+ fluentEmailServiceBuilder.AddSmtpSender(() => new(appSettings.EmailSettings.Host, appSettings.EmailSettings.Port)
+ {
+ Credentials = new NetworkCredential(appSettings.EmailSettings.UserName, appSettings.EmailSettings.Password),
+ EnableSsl = true
+ });
+ }
+ else
+ {
+ fluentEmailServiceBuilder.AddSmtpSender(appSettings.EmailSettings.Host, appSettings.EmailSettings.Port);
+ }
+ }
+
+ AddBlazor(builder);
+
+ }
+
+ private static void AddBlazor(WebApplicationBuilder builder)
{
- services.AddTransient();
+ var services = builder.Services;
+ var configuration = builder.Configuration;
- services.AddTransient(sp =>
+ services.TryAddTransient();
+
+ services.TryAddTransient(sp =>
{
Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.RelativeOrAbsolute, out var apiServerAddress);
@@ -36,11 +148,14 @@ public static void AddBlazor(this IServiceCollection services, IConfiguration co
services.AddMvc();
- services.AddClientWebServices();
+ services.AddClientWebProjectServices();
}
- public static void AddIdentity(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment hostEnv)
+ private static void AddIdentity(WebApplicationBuilder builder)
{
+ var services = builder.Services;
+ var configuration = builder.Configuration;
+ var env = builder.Environment;
var appSettings = configuration.GetSection(nameof(AppSettings)).Get()!;
var settings = appSettings.IdentitySettings;
@@ -48,7 +163,7 @@ public static void AddIdentity(this IServiceCollection services, IConfiguration
var certificate = new X509Certificate2(certificatePath, appSettings.IdentitySettings.IdentityCertificatePassword, OperatingSystem.IsWindows() ? X509KeyStorageFlags.EphemeralKeySet : X509KeyStorageFlags.DefaultKeySet);
bool isTestCertificate = certificate.Thumbprint is "55140A8C935AB5202949071E5781E6946CD60606"; // The default test certificate is still in use
- if (isTestCertificate && hostEnv.IsDevelopment() is false)
+ if (isTestCertificate && env.IsDevelopment() is false)
{
throw new InvalidOperationException(@"The default test certificate is still in use. Please replace it with a new one by running the 'dotnet dev-certs https --export-path IdentityCertificate.pfx --password P@ssw0rdP@ssw0rd' command (or your preferred method for generating PFX files) in the server project's folder.");
}
@@ -118,8 +233,10 @@ public static void AddIdentity(this IServiceCollection services, IConfiguration
services.AddAuthorization();
}
- public static void AddSwaggerGen(this IServiceCollection services)
+ private static void AddSwaggerGen(WebApplicationBuilder builder)
{
+ var services = builder.Services;
+
services.AddSwaggerGen(options =>
{
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Bit.TemplatePlayground.Server.xml"));
@@ -155,14 +272,18 @@ public static void AddSwaggerGen(this IServiceCollection services)
});
}
- public static IServiceCollection AddHealthChecks(this IServiceCollection services, IWebHostEnvironment env, IConfiguration configuration)
+ private static void AddHealthChecks(WebApplicationBuilder builder)
{
+ var configuration = builder.Configuration;
+ var services = builder.Services;
+ var env = builder.Environment;
+
var appSettings = configuration.GetSection(nameof(AppSettings)).Get()!;
var healthCheckSettings = appSettings.HealthCheckSettings;
if (healthCheckSettings.EnableHealthChecks is false)
- return services;
+ return;
services.AddHealthChecksUI(setupSettings: setup =>
{
@@ -191,7 +312,6 @@ public static IServiceCollection AddHealthChecks(this IServiceCollection service
}
});
}
-
- return services;
}
+
}
diff --git a/src/Bit.TemplatePlayground.Server/Program.cs b/src/Bit.TemplatePlayground.Server/Program.cs
index 5d7d173..5db1590 100644
--- a/src/Bit.TemplatePlayground.Server/Program.cs
+++ b/src/Bit.TemplatePlayground.Server/Program.cs
@@ -1,17 +1,26 @@
-var builder = WebApplication.CreateBuilder(args);
+
+namespace Bit.TemplatePlayground.Server;
-builder.Configuration.AddClientConfigurations();
-
-// The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address.
-if (BuildConfiguration.IsDebug() && OperatingSystem.IsWindows())
+public static partial class Program
{
- builder.WebHost.UseUrls("http://localhost:5030", "http://*:5030");
-}
+ public static async Task Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+
+ builder.Configuration.AddClientConfigurations();
-Bit.TemplatePlayground.Server.Startup.Services.Add(builder.Services, builder.Environment, builder.Configuration);
+ // The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address.
+ if (BuildConfiguration.IsDebug() && OperatingSystem.IsWindows())
+ {
+ builder.WebHost.UseUrls("http://localhost:5030", "http://*:5030");
+ }
-var app = builder.Build();
+ builder.ConfigureServices();
-Bit.TemplatePlayground.Server.Startup.Middlewares.Use(app, builder.Environment, builder.Configuration);
+ var app = builder.Build();
-await app.RunAsync();
+ app.ConfiureMiddlewares();
+
+ await app.RunAsync();
+ }
+}
diff --git a/src/Bit.TemplatePlayground.Server/Properties/launchSettings.json b/src/Bit.TemplatePlayground.Server/Properties/launchSettings.json
index 4a6e991..a4b5d1d 100644
--- a/src/Bit.TemplatePlayground.Server/Properties/launchSettings.json
+++ b/src/Bit.TemplatePlayground.Server/Properties/launchSettings.json
@@ -1,9 +1,8 @@
{
"profiles": {
- "Bit.TemplatePlayground.Server-Swagger": {
+ "Bit.TemplatePlayground.Server": {
"commandName": "Project",
"launchBrowser": true,
- "launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
diff --git a/src/Bit.TemplatePlayground.Server/Extensions/ODataOperationFilter.cs b/src/Bit.TemplatePlayground.Server/Services/ODataOperationFilter.cs
similarity index 93%
rename from src/Bit.TemplatePlayground.Server/Extensions/ODataOperationFilter.cs
rename to src/Bit.TemplatePlayground.Server/Services/ODataOperationFilter.cs
index 3d2ba9b..d0b353c 100644
--- a/src/Bit.TemplatePlayground.Server/Extensions/ODataOperationFilter.cs
+++ b/src/Bit.TemplatePlayground.Server/Services/ODataOperationFilter.cs
@@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
-namespace Swashbuckle.AspNetCore.SwaggerGen;
+namespace Bit.TemplatePlayground.Server.Services;
///
/// https://docs.microsoft.com/en-us/odata/concepts/queryoptions-overview
@@ -10,11 +11,9 @@ public class ODataOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
- if (operation.Parameters == null) operation.Parameters = new List();
+ if (operation.Parameters is null) operation.Parameters = [];
- var descriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor;
-
- if (descriptor is null)
+ if (context.ApiDescription.ActionDescriptor is not ControllerActionDescriptor descriptor)
return;
var odataQueryOptionsParameter = descriptor!.Parameters.SingleOrDefault(p => typeof(ODataQueryOptions).IsAssignableFrom(p.ParameterType));
diff --git a/src/Bit.TemplatePlayground.Server/Startup/Services.cs b/src/Bit.TemplatePlayground.Server/Startup/Services.cs
deleted file mode 100644
index 23ccb83..0000000
--- a/src/Bit.TemplatePlayground.Server/Startup/Services.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-using System.IO.Compression;
-using System.Net;
-using System.Net.Mail;
-using Bit.TemplatePlayground.Server.Services;
-using Microsoft.AspNetCore.Components.Web;
-using Microsoft.AspNetCore.HttpOverrides;
-using Microsoft.AspNetCore.OData;
-using Microsoft.AspNetCore.ResponseCompression;
-
-namespace Bit.TemplatePlayground.Server.Startup;
-
-public static class Services
-{
- public static void Add(IServiceCollection services, IWebHostEnvironment env, IConfiguration configuration)
- {
- // Services being registered here can get injected into controllers and services in Server project.
-
- var appSettings = configuration.GetSection(nameof(AppSettings)).Get()!;
-
- services.AddExceptionHandler();
-
- services.AddBlazor(configuration);
-
-
- services.AddCors();
-
- services
- .AddControllers()
- .AddOData(options => options.EnableQueryFeatures())
- .AddDataAnnotationsLocalization(options => options.DataAnnotationLocalizerProvider = StringLocalizerProvider.ProvideLocalizer)
- .ConfigureApiBehaviorOptions(options =>
- {
- options.InvalidModelStateResponseFactory = context =>
- {
- throw new ResourceValidationException(context.ModelState.Select(ms => (ms.Key, ms.Value!.Errors.Select(e => new LocalizedString(e.ErrorMessage, e.ErrorMessage)).ToArray())).ToArray());
- };
- });
-
- services.Configure(options =>
- {
- options.ForwardedHeaders = ForwardedHeaders.All;
- options.ForwardedHostHeaderName = "X-Host";
- });
-
- services.AddResponseCaching();
-
- services.AddHttpContextAccessor();
-
- services.AddResponseCompression(opts =>
- {
- opts.EnableForHttps = true;
- opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/octet-stream"]).ToArray();
- opts.Providers.Add();
- opts.Providers.Add();
- })
- .Configure(opt => opt.Level = CompressionLevel.Fastest)
- .Configure(opt => opt.Level = CompressionLevel.Fastest);
-
- services.AddDbContext(options =>
- {
- options.UseSqlite(configuration.GetConnectionString("SqliteConnectionString"), dbOptions =>
- {
- dbOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
- });
- });
-
- services.Configure(configuration.GetSection(nameof(AppSettings)));
-
- services.AddTransient(sp => sp.GetRequiredService>().Value);
-
- services.AddEndpointsApiExplorer();
-
- services.AddSwaggerGen();
-
- services.AddIdentity(configuration, env);
-
- services.AddHealthChecks(env, configuration);
-
- services.AddTransient();
-
- var fluentEmailServiceBuilder = services.AddFluentEmail(appSettings.EmailSettings.DefaultFromEmail, appSettings.EmailSettings.DefaultFromName);
-
- if (appSettings.EmailSettings.UseLocalFolderForEmails)
- {
- var sentEmailsFolderPath = Path.Combine(AppContext.BaseDirectory, "sent-emails");
-
- Directory.CreateDirectory(sentEmailsFolderPath);
-
- fluentEmailServiceBuilder.AddSmtpSender(() => new SmtpClient
- {
- DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory,
- PickupDirectoryLocation = sentEmailsFolderPath
- });
- }
- else
- {
- if (appSettings.EmailSettings.HasCredential)
- {
- fluentEmailServiceBuilder.AddSmtpSender(() => new(appSettings.EmailSettings.Host, appSettings.EmailSettings.Port)
- {
- Credentials = new NetworkCredential(appSettings.EmailSettings.UserName, appSettings.EmailSettings.Password)
- });
- }
- else
- {
- fluentEmailServiceBuilder.AddSmtpSender(appSettings.EmailSettings.Host, appSettings.EmailSettings.Port);
- }
- }
- }
-}
diff --git a/src/Bit.TemplatePlayground.Server/appsettings.json b/src/Bit.TemplatePlayground.Server/appsettings.json
index 769cad5..917544c 100644
--- a/src/Bit.TemplatePlayground.Server/appsettings.json
+++ b/src/Bit.TemplatePlayground.Server/appsettings.json
@@ -4,11 +4,11 @@
},
"AppSettings": {
"IdentitySettings": {
+ "IdentityCertificatePassword": "P@ssw0rdP@ssw0rd", // It can also be configured using: dotnet user-secrets set "AppSettings:IdentitySettings:IdentityCertificatePassword" "P@ssw0rdP@ssw0rd"
"Issuer": "Bit.TemplatePlayground",
"Audience": "Bit.TemplatePlayground",
- "IdentityCertificatePassword": "P@ssw0rdP@ssw0rd", // It can also be configured using: dotnet user-secrets set "AppSettings:IdentitySettings:IdentityCertificatePassword" "P@ssw0rdP@ssw0rd"
- "BearerTokenExpiration": "0.01:00:00", // Format: D.HH:mm:ss
"RefreshTokenExpiration": "14.00:00:00", // Format: D.HH:mm:ss
+ "BearerTokenExpiration": "0.01:00:00", // Used as jwt's expiration claim, access token's expires in and cookie's max age. Format: D.HH:mm:ss
"PasswordRequireDigit": "false",
"PasswordRequiredLength": "6",
"PasswordRequireNonAlphanumeric": "false",
@@ -20,7 +20,7 @@
},
"EmailSettings": {
"Host": "LocalFolder", // Local folder means storing emails as .eml file in bin/Debug/net8.0/sent-emails folder (Recommended for testing purposes only) instead of sending them using smtp server.
- "Port": "25",
+ "Port": "587",
"DefaultFromEmail": "info@Bit.TemplatePlayground.com",
"DefaultFromName": "Bit.TemplatePlayground",
"UserName": null,
diff --git a/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj b/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj
index fe18f8a..d0975a6 100644
--- a/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj
+++ b/src/Bit.TemplatePlayground.Shared/Bit.TemplatePlayground.Shared.csproj
@@ -5,11 +5,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Bit.TemplatePlayground.Shared/Dtos/AppJsonContext.cs b/src/Bit.TemplatePlayground.Shared/Dtos/AppJsonContext.cs
index 3173ccc..7f12b1f 100644
--- a/src/Bit.TemplatePlayground.Shared/Dtos/AppJsonContext.cs
+++ b/src/Bit.TemplatePlayground.Shared/Dtos/AppJsonContext.cs
@@ -11,14 +11,16 @@ namespace Bit.TemplatePlayground.Shared.Dtos;
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(Dictionary))]
[JsonSerializable(typeof(UserDto))]
-[JsonSerializable(typeof(ProductsCountPerCategoryResponseDto))]
+[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(OverallAnalyticsStatsDataResponseDto))]
-[JsonSerializable(typeof(ProductSaleStatResponseDto))]
-[JsonSerializable(typeof(ProductPercentagePerCategoryResponseDto))]
+[JsonSerializable(typeof(List))]
+[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(ProductDto))]
[JsonSerializable(typeof(PagedResult))]
+[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(CategoryDto))]
[JsonSerializable(typeof(PagedResult))]
+[JsonSerializable(typeof(List))]
[JsonSerializable(typeof(SignInRequestDto))]
[JsonSerializable(typeof(TokenResponseDto))]
[JsonSerializable(typeof(RefreshRequestDto))]
diff --git a/src/Bit.TemplatePlayground.Shared/Extensions/IServiceCollectionExtensions.cs b/src/Bit.TemplatePlayground.Shared/Extensions/IServiceCollectionExtensions.cs
index e633ab7..73d559d 100644
--- a/src/Bit.TemplatePlayground.Shared/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Bit.TemplatePlayground.Shared/Extensions/IServiceCollectionExtensions.cs
@@ -2,7 +2,7 @@
public static class IServiceCollectionExtensions
{
- public static IServiceCollection AddSharedServices(this IServiceCollection services)
+ public static IServiceCollection AddSharedProjectServices(this IServiceCollection services)
{
// Services being registered here can get injected everywhere (Api, Web, Android, iOS, Windows and macOS)
@@ -10,8 +10,9 @@ public static IServiceCollection AddSharedServices(this IServiceCollection servi
services.TryAddTransient();
// Define authorization policies here to seamlessly integrate them across various components,
- // including web api actions and razor pages using Authorize attribute, AuthorizeView in razor pages, and programmatically in C# for enhanced security and access control.
- services.AddAuthorizationCore(options => options.AddPolicy("AdminOnly", authPolicyBuilder => authPolicyBuilder.RequireRole("Admin")));
+ // including web api actions and razor pages using Authorize attribute, AuthorizeView in razor pages,
+ // and programmatically in C# by injecting IAuthorizationService for enhanced security and access control.
+ services.AddAuthorizationCore(options => options.AddPolicy("AdminsOnly", authPolicyBuilder => authPolicyBuilder.RequireRole("Admin")));
services.AddLocalization();
diff --git a/src/Bit.TemplatePlayground.Shared/Extensions/JsonSeralizerExtensions.cs b/src/Bit.TemplatePlayground.Shared/Extensions/JsonSeralizerExtensions.cs
index c41706a..0562dda 100644
--- a/src/Bit.TemplatePlayground.Shared/Extensions/JsonSeralizerExtensions.cs
+++ b/src/Bit.TemplatePlayground.Shared/Extensions/JsonSeralizerExtensions.cs
@@ -14,6 +14,16 @@ public static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options)
return (JsonTypeInfo)result;
}
- return JsonTypeInfo.CreateJsonTypeInfo(options);
+ throw new InvalidOperationException($"Add [JsonSerializable(typeof({GetTypeDisplayName(typeof(T))}))] to the {nameof(AppJsonContext)}");
+ }
+
+ private static string GetTypeDisplayName(Type type)
+ {
+ if (type.IsGenericType)
+ {
+ return $"{type.Name.Split('`')[0]}<{string.Join(", ", type.GetGenericArguments().Select(GetTypeDisplayName))}>";
+ }
+
+ return type.Name;
}
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj b/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj
index dc67710..2ffd4ff 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Bit.TemplatePlayground.Client.Core.csproj
@@ -16,16 +16,16 @@
-
-
-
-
-
-
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsCountPerCategoryWidget.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsCountPerCategoryWidget.razor.cs
index 2342f66..ca344a6 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsCountPerCategoryWidget.razor.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsCountPerCategoryWidget.razor.cs
@@ -33,7 +33,7 @@ private async Task GetData()
{
isLoading = true;
- var data = await (await dashboardController.GetProductsCountPerCategoryStats(CurrentCancellationToken)).ToListAsync(CurrentCancellationToken);
+ var data = await dashboardController.GetProductsCountPerCategoryStats(CurrentCancellationToken);
BitChartBarDataset chartDataSet = [.. data.Select(d => d.ProductCount)];
chartDataSet.BackgroundColor = data.Select(d => d.CategoryColor ?? string.Empty).ToArray();
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsSalesWidget.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsSalesWidget.razor.cs
index db346c1..cb84457 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsSalesWidget.razor.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Dashboard/ProductsSalesWidget.razor.cs
@@ -33,7 +33,7 @@ private async Task GetData()
{
isLoading = true;
- var data = await (await dashboardController.GetProductsSalesStats(CurrentCancellationToken)).ToListAsync(CurrentCancellationToken);
+ var data = await dashboardController.GetProductsSalesStats(CurrentCancellationToken);
BitChartBarDataset chartDataSet = [.. data.Select(d => d.SaleAmount)];
chartDataSet.BackgroundColor = data.Select(d => d.CategoryColor ?? string.Empty).ToArray();
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.cs
index 5e231e2..e8753e5 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Components/Pages/Products/AddOrEditProductModal.razor.cs
@@ -41,7 +41,7 @@ private async Task LoadAllCategoriesAsync()
try
{
- var categoryList = await (await categoryController.Get(CurrentCancellationToken)).ToListAsync(CurrentCancellationToken);
+ var categoryList = await categoryController.Get(CurrentCancellationToken);
allCategoryList = categoryList.Select(c => new BitDropdownItem()
{
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Categories/ICategoryController.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Categories/ICategoryController.cs
index fadf4f1..bb49209 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Categories/ICategoryController.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Categories/ICategoryController.cs
@@ -21,5 +21,5 @@ public interface ICategoryController : IAppController
Task> GetCategories(CancellationToken cancellationToken = default) => default!;
[HttpGet]
- Task> Get(CancellationToken cancellationToken) => default!;
+ Task> Get(CancellationToken cancellationToken) => default!;
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Dashboard/IDashboardController.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Dashboard/IDashboardController.cs
index a812d19..c21b9c8 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Dashboard/IDashboardController.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Dashboard/IDashboardController.cs
@@ -9,11 +9,11 @@ public interface IDashboardController : IAppController
Task GetOverallAnalyticsStatsData(CancellationToken cancellationToken = default);
[HttpGet]
- Task> GetProductsCountPerCategoryStats(CancellationToken cancellationToken = default);
+ Task> GetProductsCountPerCategoryStats(CancellationToken cancellationToken = default) => default!;
[HttpGet]
- Task> GetProductsSalesStats(CancellationToken cancellationToken = default);
+ Task> GetProductsSalesStats(CancellationToken cancellationToken = default) => default!;
[HttpGet]
- Task GetProductsPercentagePerCategoryStats(CancellationToken cancellationToken = default);
+ Task> GetProductsPercentagePerCategoryStats(CancellationToken cancellationToken = default);
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Products/IProductController.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Products/IProductController.cs
index 63e214a..dbe9d55 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Products/IProductController.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Controllers/Products/IProductController.cs
@@ -19,4 +19,7 @@ public interface IProductController : IAppController
[HttpGet]
Task> GetProducts(CancellationToken cancellationToken = default) => default!;
+
+ [HttpGet]
+ Task> Get(CancellationToken cancellationToken) => default!;
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs
index 5ae1331..35e4a33 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Core/Extensions/IServiceCollectionExtensions.cs
@@ -5,9 +5,9 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class IServiceCollectionExtensions
{
- public static IServiceCollection AddClientSharedServices(this IServiceCollection services)
+ public static IServiceCollection AddClientCoreProjectServices(this IServiceCollection services)
{
- // Services registered in this class can be injected in client side (Web, Android, iOS, Windows, macOS)
+ // Services being registered here can get injected in client side (Web, Android, iOS, Windows, macOS) and server side (during pre rendering)
services.TryAddTransient();
@@ -21,8 +21,8 @@ public static IServiceCollection AddClientSharedServices(this IServiceCollection
services.TryAddTransient();
services.TryAddTransient();
- services.AddScoped();
- services.AddScoped(sp => (AuthenticationManager)sp.GetRequiredService());
+ services.AddScoped(); // Use 'Add' instead of 'TryAdd' to override the aspnetcore's default AuthenticationStateProvider.
+ services.TryAddScoped(sp => (AuthenticationManager)sp.GetRequiredService());
services.TryAddTransient();
services.TryAddTransient();
@@ -32,8 +32,9 @@ public static IServiceCollection AddClientSharedServices(this IServiceCollection
services.AddBitButilServices();
services.AddBitBlazorUIServices();
- services.AddSharedServices();
+
+ services.AddSharedProjectServices();
return services;
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj b/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj
index f97de7d..edd9f7d 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/Bit.TemplatePlayground.Client.Maui.csproj
@@ -16,7 +16,7 @@
com.bitplatform.BP.Template
- EA281D77-3592-4C09-9567-B1E5E79754D7
+ 44861962-8347-470D-AC06-050D17A7F686
1.0
@@ -82,11 +82,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/Extensions/IServiceCollectionExtensions.cs
deleted file mode 100644
index 1a47a95..0000000
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/Extensions/IServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Bit.TemplatePlayground.Client.Maui;
-using Bit.TemplatePlayground.Client.Maui.Services;
-
-namespace Microsoft.Extensions.DependencyInjection;
-
-public static class IServiceCollectionExtensions
-{
- public static IServiceCollection AddClientMauiServices(this IServiceCollection services)
- {
- // Services registered in this class can be injected in Android, iOS, Windows, and macOS.
-
- services.TryAddTransient();
- services.TryAddTransient();
- services.TryAddSingleton();
- services.TryAddTransient();
-
-#if ANDROID
- services.AddClientAndroidServices();
-#elif iOS
- services.AddClientiOSServices();
-#elif Mac
- services.AddClientMacServices();
-#elif Windows
- services.AddClientWindowsServices();
-#endif
-
- services.AddClientSharedServices();
-
- return services;
- }
-}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.Services.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.Services.cs
new file mode 100644
index 0000000..5a611c5
--- /dev/null
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.Services.cs
@@ -0,0 +1,50 @@
+using Bit.TemplatePlayground.Client.Maui.Services;
+
+namespace Bit.TemplatePlayground.Client.Maui;
+
+public static partial class MauiProgram
+{
+ public static void ConfigureServices(this MauiAppBuilder builder)
+ {
+ // Services being registered here can get injected in Maui (Android, iOS, macOS, Windows)
+
+ var services = builder.Services;
+ var configuration = builder.Configuration;
+
+ services.AddMauiBlazorWebView();
+
+ if (BuildConfiguration.IsDebug())
+ {
+ services.AddBlazorWebViewDeveloperTools();
+ }
+
+ Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.Absolute, out var apiServerAddress);
+
+ services.TryAddTransient(sp =>
+ {
+ var handler = sp.GetRequiredKeyedService("DefaultMessageHandler");
+ HttpClient httpClient = new(handler)
+ {
+ BaseAddress = apiServerAddress
+ };
+ return httpClient;
+ });
+
+ services.TryAddTransient();
+ services.TryAddTransient();
+ services.TryAddSingleton();
+ services.TryAddTransient();
+
+#if ANDROID
+ services.AddClientMauiProjectAndroidServices();
+#elif iOS
+ services.AddClientMauiProjectIosServices();
+#elif Mac
+ services.AddClientMauiProjectMacCatalystServices();
+#elif Windows
+ services.AddClientMauiProjectWindowsServices();
+#endif
+
+ services.AddClientCoreProjectServices();
+ }
+}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.cs
index ee8eb2a..d3f244a 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/MauiProgram.cs
@@ -3,7 +3,7 @@
namespace Bit.TemplatePlayground.Client.Maui;
-public static class MauiProgram
+public static partial class MauiProgram
{
public static MauiApp CreateMauiApp()
{
@@ -15,28 +15,7 @@ public static MauiApp CreateMauiApp()
.UseMauiApp()
.Configuration.AddClientConfigurations();
- var services = builder.Services;
-
- services.AddMauiBlazorWebView();
-
- if (BuildConfiguration.IsDebug())
- {
- services.AddBlazorWebViewDeveloperTools();
- }
-
- Uri.TryCreate(builder.Configuration.GetApiServerAddress(), UriKind.Absolute, out var apiServerAddress);
-
- services.AddTransient(sp =>
- {
- var handler = sp.GetRequiredKeyedService("DefaultMessageHandler");
- HttpClient httpClient = new(handler)
- {
- BaseAddress = apiServerAddress
- };
- return httpClient;
- });
-
- services.AddClientMauiServices();
+ builder.ConfigureServices();
builder.ConfigureLifecycleEvents(lifecycle =>
{
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Android/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Android/Extensions/IServiceCollectionExtensions.cs
index c669f4f..1a06246 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Android/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Android/Extensions/IServiceCollectionExtensions.cs
@@ -1,10 +1,10 @@
namespace Microsoft.Extensions.DependencyInjection;
-public static class IAndroidServiceCollectionExtensions
+public static partial class IServiceCollectionExtensions
{
- public static IServiceCollection AddClientAndroidServices(this IServiceCollection services)
+ public static IServiceCollection AddClientMauiProjectAndroidServices(this IServiceCollection services)
{
- // Services registered in this class can be injected in Android.
+ // Services being registered here can get injected in Maui/Android.
return services;
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/MacCatalyst/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/MacCatalyst/Extensions/IServiceCollectionExtensions.cs
index 0cc5d4f..fc1bd2f 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/MacCatalyst/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/MacCatalyst/Extensions/IServiceCollectionExtensions.cs
@@ -1,10 +1,10 @@
namespace Microsoft.Extensions.DependencyInjection;
-public static class IMacServiceCollectionExtensions
+public static partial class IServiceCollectionExtensions
{
- public static IServiceCollection AddClientMacServices(this IServiceCollection services)
+ public static IServiceCollection AddClientMauiProjectMacCatalystServices(this IServiceCollection services)
{
- // Services registered in this class can be injected in macOS.
+ // Services being registered here can get injected in Maui/macOS.
return services;
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Windows/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Windows/Extensions/IServiceCollectionExtensions.cs
index 27419a2..6c9c355 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Windows/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/Windows/Extensions/IServiceCollectionExtensions.cs
@@ -1,10 +1,10 @@
namespace Microsoft.Extensions.DependencyInjection;
-public static class IWindowsServiceCollectionExtensions
+public static partial class IServiceCollectionExtensions
{
- public static IServiceCollection AddClientWindowsServices(this IServiceCollection services)
+ public static IServiceCollection AddClientMauiProjectWindowsServices(this IServiceCollection services)
{
- // Services registered in this class can be injected in Windows.
+ // Services being registered here can get injected in Maui/windows.
return services;
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/iOS/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/iOS/Extensions/IServiceCollectionExtensions.cs
index 1688624..6be4e37 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/iOS/Extensions/IServiceCollectionExtensions.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Maui/Platforms/iOS/Extensions/IServiceCollectionExtensions.cs
@@ -1,8 +1,8 @@
namespace Microsoft.Extensions.DependencyInjection;
-public static class IiOSServiceCollectionExtensions
+public static partial class IServiceCollectionExtensions
{
- public static IServiceCollection AddClientiOSServices(this IServiceCollection services)
+ public static IServiceCollection AddClientMauiProjectIosServices(this IServiceCollection services)
{
// Services registered in this class can be injected in iOS.
diff --git a/src/Client/Bit.TemplatePlayground.Client.Web/Bit.TemplatePlayground.Client.Web.csproj b/src/Client/Bit.TemplatePlayground.Client.Web/Bit.TemplatePlayground.Client.Web.csproj
index 88dc2aa..8ae4833 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Web/Bit.TemplatePlayground.Client.Web.csproj
+++ b/src/Client/Bit.TemplatePlayground.Client.Web/Bit.TemplatePlayground.Client.Web.csproj
@@ -15,6 +15,7 @@
true
Default
true
+ true
@@ -29,15 +30,16 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/Client/Bit.TemplatePlayground.Client.Web/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Web/Extensions/IServiceCollectionExtensions.cs
deleted file mode 100644
index e4d970a..0000000
--- a/src/Client/Bit.TemplatePlayground.Client.Web/Extensions/IServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Bit.TemplatePlayground.Client.Web.Services;
-
-namespace Microsoft.Extensions.DependencyInjection;
-
-public static class IServiceCollectionExtensions
-{
- public static IServiceCollection AddClientWebServices(this IServiceCollection services)
- {
- // Services being registered here can get injected in web (blazor web assembly & blazor server)
-
- services.AddTransient();
- services.AddTransient();
-
- services.AddClientSharedServices();
-
- return services;
- }
-}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Web/Program.Services.cs b/src/Client/Bit.TemplatePlayground.Client.Web/Program.Services.cs
new file mode 100644
index 0000000..2c2737f
--- /dev/null
+++ b/src/Client/Bit.TemplatePlayground.Client.Web/Program.Services.cs
@@ -0,0 +1,38 @@
+using Bit.TemplatePlayground.Client.Web.Services;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+
+namespace Bit.TemplatePlayground.Client.Web;
+
+public static partial class Program
+{
+ public static void ConfigureServices(this WebAssemblyHostBuilder builder)
+ {
+ // Services being registered here can get injected in web project only.
+
+ var services = builder.Services;
+ var configuration = builder.Configuration;
+
+ configuration.AddClientConfigurations();
+
+ Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.RelativeOrAbsolute, out var apiServerAddress);
+
+ if (apiServerAddress!.IsAbsoluteUri is false)
+ {
+ apiServerAddress = new Uri(new Uri(builder.HostEnvironment.BaseAddress), apiServerAddress);
+ }
+
+ services.TryAddTransient(sp => new HttpClient(sp.GetRequiredKeyedService("DefaultMessageHandler")) { BaseAddress = apiServerAddress });
+
+ services.AddClientWebProjectServices();
+ }
+
+ public static void AddClientWebProjectServices(this IServiceCollection services)
+ {
+ // Services being registered here can get injected in both web project and server (during prerendering).
+
+ services.TryAddTransient();
+ services.TryAddTransient();
+
+ services.AddClientCoreProjectServices();
+ }
+}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Web/Program.cs b/src/Client/Bit.TemplatePlayground.Client.Web/Program.cs
index 5fa7cf7..c1c14fe 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Web/Program.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Web/Program.cs
@@ -1,34 +1,41 @@
-#if BlazorWebAssemblyStandalone
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+#if BlazorWebAssemblyStandalone
using Microsoft.AspNetCore.Components.Web;
#endif
-using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
-var builder = WebAssemblyHostBuilder.CreateDefault(args);
+namespace Bit.TemplatePlayground.Client.Web;
+
+public static partial class Program
+{
+ public static async Task Main(string[] args)
+ {
+ var builder = WebAssemblyHostBuilder.CreateDefault(args);
#if BlazorWebAssemblyStandalone
builder.RootComponents.Add("#app-container");
builder.RootComponents.Add("head::after");
#endif
-builder.Configuration.AddClientConfigurations();
+ builder.ConfigureServices();
-Uri.TryCreate(builder.Configuration.GetApiServerAddress(), UriKind.RelativeOrAbsolute, out var apiServerAddress);
-
-if (apiServerAddress!.IsAbsoluteUri is false)
-{
- apiServerAddress = new Uri(new Uri(builder.HostEnvironment.BaseAddress), apiServerAddress);
-}
+ var host = builder.Build();
-builder.Services.AddTransient(sp => new HttpClient(sp.GetRequiredKeyedService("DefaultMessageHandler")) { BaseAddress = apiServerAddress });
+ if (AppRenderMode.MultilingualEnabled)
+ {
+ var culture = await host.Services.GetRequiredService().GetItem("Culture");
+ host.Services.GetRequiredService().SetCurrentCulture(culture);
+ }
-builder.Services.AddClientWebServices();
-
-var host = builder.Build();
+ try
+ {
+ await host.RunAsync();
+ }
+ catch (JSException exp) when (exp.Message is "Error: Could not find any element matching selector '#app-container'.")
+ {
+#if BlazorWebAssemblyStandalone
+await Console.Error.WriteLineAsync("Either run/publish Client.Web project or set BlazorWebAssemblyStandalone to false.");
+#endif
+ }
-if (AppRenderMode.MultilingualEnabled)
-{
- var culture = await host.Services.GetRequiredService().GetItem("Culture");
- host.Services.GetRequiredService().SetCurrentCulture(culture);
+ }
}
-
-await host.RunAsync();
diff --git a/src/Client/Bit.TemplatePlayground.Client.Web/wwwroot/service-worker.js b/src/Client/Bit.TemplatePlayground.Client.Web/wwwroot/service-worker.js
index afc966f..6f0a2b7 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Web/wwwroot/service-worker.js
+++ b/src/Client/Bit.TemplatePlayground.Client.Web/wwwroot/service-worker.js
@@ -1,4 +1,4 @@
-// bit version: 8.7.4-pre-01
+// bit version: 8.7.5-pre-01
// https://github.com/bitfoundation/bitplatform/tree/develop/src/Bswup
self.assetsInclude = [];
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/.config/dotnet-tools.json b/src/Client/Bit.TemplatePlayground.Client.Windows/.config/dotnet-tools.json
index c32f6b1..a6c91e9 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/.config/dotnet-tools.json
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"vpk": {
- "version": "0.0.212",
+ "version": "0.0.276",
"commands": [
"vpk"
]
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml b/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml
index 4e2e47d..79f0717 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml
@@ -1,4 +1,8 @@
-
+
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml.cs b/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml.cs
index 16e3f64..2cccbdc 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/App.xaml.cs
@@ -33,5 +33,16 @@ private void App_Exit(object sender, ExitEventArgs e)
using StreamWriter writer = new StreamWriter(stream);
writer.Write(JsonSerializer.Serialize(Properties));
}
+
+ private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
+ {
+ try
+ {
+ ((MainWindow)MainWindow).BlazorWebView.Services.GetRequiredService().Handle(e.Exception);
+ }
+ catch { }
+
+ e.Handled = true;
+ }
}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/Bit.TemplatePlayground.Client.Windows.csproj b/src/Client/Bit.TemplatePlayground.Client.Windows/Bit.TemplatePlayground.Client.Windows.csproj
index 5e7d5a3..334b85c 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/Bit.TemplatePlayground.Client.Windows.csproj
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/Bit.TemplatePlayground.Client.Windows.csproj
@@ -17,17 +17,17 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
PreserveNewest
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/Extensions/IServiceCollectionExtensions.cs b/src/Client/Bit.TemplatePlayground.Client.Windows/Extensions/IServiceCollectionExtensions.cs
deleted file mode 100644
index 3ecd92a..0000000
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/Extensions/IServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Bit.TemplatePlayground.Client.Windows.Services;
-
-namespace Microsoft.Extensions.DependencyInjection;
-
-public static class IServiceCollectionExtensions
-{
- public static void AddWindowsServices(this IServiceCollection services)
- {
- services.TryAddTransient();
- services.AddTransient();
- services.AddTransient();
-
- services.AddClientSharedServices();
- }
-}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml b/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml
index 2ea0217..0b0db7e 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml
@@ -1,16 +1,22 @@
-
+
-
+
-
+
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml.cs b/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml.cs
index b3f3345..2ffead4 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/MainWindow.xaml.cs
@@ -8,26 +8,7 @@ public MainWindow()
{
AppRenderMode.IsBlazorHybrid = true;
var services = new ServiceCollection();
- ConfigurationBuilder configurationBuilder = new();
- configurationBuilder.AddClientConfigurations();
- var configuration = configurationBuilder.Build();
- services.AddTransient(sp => configuration);
- Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.Absolute, out var apiServerAddress);
- services.AddTransient(sp =>
- {
- var handler = sp.GetRequiredKeyedService("DefaultMessageHandler");
- HttpClient httpClient = new(handler)
- {
- BaseAddress = apiServerAddress
- };
- return httpClient;
- });
- services.AddWpfBlazorWebView();
- if (BuildConfiguration.IsDebug())
- {
- services.AddBlazorWebViewDeveloperTools();
- }
- services.AddWindowsServices();
+ services.ConfigureServices();
InitializeComponent();
BlazorWebView.Services = services.BuildServiceProvider();
BlazorWebView.Services.GetRequiredService().SetCurrentCulture(App.Current.Properties["Culture"]?.ToString());
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/Program.Services.cs b/src/Client/Bit.TemplatePlayground.Client.Windows/Program.Services.cs
new file mode 100644
index 0000000..63a6f69
--- /dev/null
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/Program.Services.cs
@@ -0,0 +1,40 @@
+using System.Net.Http;
+using Bit.TemplatePlayground.Client.Windows.Services;
+
+namespace Bit.TemplatePlayground.Client.Windows;
+
+public static partial class Program
+{
+ public static void ConfigureServices(this IServiceCollection services)
+ {
+ // Services being registered here can get injected in windows project only.
+
+ ConfigurationBuilder configurationBuilder = new();
+ configurationBuilder.AddClientConfigurations();
+ var configuration = configurationBuilder.Build();
+ services.TryAddTransient(sp => configuration);
+
+ Uri.TryCreate(configuration.GetApiServerAddress(), UriKind.Absolute, out var apiServerAddress);
+ services.TryAddTransient(sp =>
+ {
+ var handler = sp.GetRequiredKeyedService("DefaultMessageHandler");
+ HttpClient httpClient = new(handler)
+ {
+ BaseAddress = apiServerAddress
+ };
+ return httpClient;
+ });
+
+ services.AddWpfBlazorWebView();
+ if (BuildConfiguration.IsDebug())
+ {
+ services.AddBlazorWebViewDeveloperTools();
+ }
+
+ services.TryAddTransient();
+ services.TryAddTransient();
+ services.TryAddTransient();
+
+ services.AddClientCoreProjectServices();
+ }
+}
diff --git a/src/Client/Bit.TemplatePlayground.Client.Windows/Program.cs b/src/Client/Bit.TemplatePlayground.Client.Windows/Program.cs
index 7a52259..f658fae 100644
--- a/src/Client/Bit.TemplatePlayground.Client.Windows/Program.cs
+++ b/src/Client/Bit.TemplatePlayground.Client.Windows/Program.cs
@@ -3,7 +3,7 @@
namespace Bit.TemplatePlayground.Client.Windows;
-public class Program
+public partial class Program
{
[STAThread]
public static void Main(string[] args)
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index f9bcf57..eeb56c9 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,4 +1,4 @@
-
+
12.0
@@ -8,6 +8,9 @@
$(NoWarn);CS1998;CS1591
$(WarningsAsErrors);CS0114
+
+
+
en-US
true
false