Empezar.BlazorWebAPI 1.9.3-alpha

This is a prerelease version of Empezar.BlazorWebAPI.
dotnet add package Empezar.BlazorWebAPI --version 1.9.3-alpha                
NuGet\Install-Package Empezar.BlazorWebAPI -Version 1.9.3-alpha                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Empezar.BlazorWebAPI" Version="1.9.3-alpha" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Empezar.BlazorWebAPI --version 1.9.3-alpha                
#r "nuget: Empezar.BlazorWebAPI, 1.9.3-alpha"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Empezar.BlazorWebAPI as a Cake Addin
#addin nuget:?package=Empezar.BlazorWebAPI&version=1.9.3-alpha&prerelease

// Install Empezar.BlazorWebAPI as a Cake Tool
#tool nuget:?package=Empezar.BlazorWebAPI&version=1.9.3-alpha&prerelease                

Empezar.BlazorWebAPI

Ease development and maintenance of a blazor web app server project, serving WASM as well as prerendering razor components.

Aim is to consolidate all the common services that all API projects serving WASM or MAUI applications need in secure and robust manner.

Installation

Install the nuget package for Empezar.BlazorWebAPI in your API Project.

You can also use Manager Nuget Packages to install the same.

dotnet add package Empezar.BlazorWebAPI

Usage

Register the service in Program.cs of API project.

Please note that this service already encapsulates Empezar.Cloud service. Hence, if using this package, you do not need to register Empezar.Cloud service.

var builder = WebApplication.CreateBuilder(args);

builder
//Register core services
.AddEZRBlazorWebAPI()

//Register authentication services using AzureAD and Google providers for a single tenant setup, for multi tenant setup, use AddAuthenticationMultiTenant. Register your claims.
.AddAuthenticationSingleTenant<Users, UsersReadParams>((user, claims) =>
{
    claims.Add(new(ClaimTypes.Name, user.uname));
})

.AddScoped<IFormFactor, FormFactor>()

//Register UI services to prerender razor components in your SharedUI
.AddSharedUIServices(builder.Configuration[BaseConfigConstants.KestralLoopBackAdress]!, AppPlatform.Web)

//Multi-culture support 
.Configure<RequestLocalizationOptions>(options => options.SetDefaultCulture(Constants.SupportedCultures.First())
.AddSupportedCultures(Constants.SupportedCultures).AddSupportedUICultures(Constants.SupportedCultures))

//Register API project to render Razor components in InteractiveWebAssembly mode
.AddRazorComponents().AddInteractiveWebAssemblyComponents()

//Share authentication state between server and client
.AddAuthenticationStateSerialization(options => options.SerializeAllClaims = true);

var app = builder.Build();

if (app.Environment.IsDevelopment()) app.UseWebAssemblyDebugging();

app.UseRequestLocalization();

//Use services and API's provided by the package. ErrorDisplay is a razor component to gracefully handle errors during prerender and show freindly error page. You will need to pass your models library to register your models with postgres for JSONB datatypes
app.UseEZRBlazorWebAPI<ErrorDisplay>(typeof(Users).Assembly)

//Use File upload API (Cloud agnostic using Empezar.Cloud)
.UseEZRFileUpload(UploadValidator)

//Use System Parameters API
.UseEZRSysParams()

//Use Injest Logs API
.UseIngestLog()

//Register your App.razor
.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()

//Register razor pages from other assemblies
.AddAdditionalAssemblies(typeof(BaseComponent).Assembly,
							typeof(OneHaul.SharedUI._Imports).Assembly, typeof(OneHaul.WebClient._Imports).Assembly);

app.Run();


Configurations can be done in appSettings.config or environment valriables as shown below

"EZRBlazorWebAPI": {
	"KestralLoopBackAdress": "https://localhost:5001/",
	"MaxRequestBodySizeInMB": "20",
	"ZeptoMailKey": "Your Zepto Account Key, which will enable sending emails",
	"GGWinClientId":"Google Client Id for Windows app (if using MAUI)",
	"GGWinClientSecret": "Google Client Secret for Windows app (if using MAUI)",
	"GGWinCallbackPath": "Google callback path on Windows (if using MAUI)",
	"GGAndClientId": "Google Client Id for Android app (if using MAUI)",
	"GGAndClientSecret": "Google Client Secret for Android app (if using MAUI)",
	"GGAndCallbackPath": "Google callback path on Android (if using MAUI)",
	"GGMacClientId": "Google Client Id for iOS & Mac app (if using MAUI)",
	"GGMacClientSecret": "Google Client Secret for iOS & Mac app (if using MAUI)",
	"GGMacCallbackPath": "Google callback path for iOS & Mac app (if using MAUI)",
	"JWTIss": "Your JWT Issuer",
	"JWTAud": "Your JWT Audience",
	"JWTKey": "Your JWT Key",
	"AgGridLicenseKey": "Your AG-Grid component license key",
	"Cloud": {
		"Type": "AWS",
		"BucketName": "your-bucket-name",
		"AzureBlobConnection":"your-azure-connectionstring",
		"AllowedFileTypes": [ "docx", "xlsx", "csv", "pdf", "jpeg", "jpg", "png", "zip" ]
	},
	"Authentications": {
		"JWT": {
			"Expiry": 4320
		},
		"Microsoft": {
			"ClientId": "Your Microsoft Client Id",
			"ClientSecret": "Your Microsoft Client Secret",
			"Instance": "https://login.microsoftonline.com/",
			"Domain": "common",
			"TenantId": "common",
			"CallbackPath": "/login-callback",
			"CallbackPath_Win": "http://localhost",
			"CallbackPath_Android": "com.empezardigital.onehaul://auth",
			"CallbackPath_Mac": "http://localhost"
		},
		"Google": {
			"ClientId": "Your Google Client Id",
			"ClientSecret": "Your Google Client Secret",
		}
	},
	"Scalar": {
		"DocumentName": "DocumentName",
		"Title": "Application Title",
		"Theme": "Default",
		"Endpoint": "apidocs"
	},
	"PGConnections": {
		"EnableAuditLog": true
	},
	"RateLimiter": {
		"AuthUserPermitLimit": 50,
		"AuthUserWindowInSec": 10,
		"UnAuthUserPermitLimit": 20,
		"UnAuthUserWindowInSec": 60
	},
	"EZRAPIs": {
		"File": {
			"UploadRoles": [ "admin" ],
			"DownloadRoles": [ "admin" ],
			"DeleteRoles": [ "admin" ]
		},
		"SysParams": {
			"CreateRoles": [ "admin" ],
			"ReadRoles": [ "admin" ],
			"UpdateRoles": [ "admin" ]
		},
		"Auth": {
			"SignInFromSingleLocation": false,
			"GetOTP": {
				"BlockDomainOTP": true,
				"ForDomain": "adani.com"
			}
		}
	},
	"CSPHeaders": {
		"default-src": "'self'",
		"script-src": "'self' 'wasm-unsafe-eval' 'nonce-{nonce}' 'sha256-QW7zjkPzGiErwRHGBPrB5pIBLJwqu7+aHJOafqD0Wfo=' ",
		"style-src": "'self' 'unsafe-inline' fonts.googleapis.com",
		"img-src": "'self' data: fonts.googleapis.com",
		"connect-src": "'self'",
		"font-src": "'self' data: fonts.googleapis.com fonts.scalar.com fonts.gstatic.com",
		"object-src": "'none'",
		"base-uri": "'self'",
		"form-action": "'self'",
		"frame-ancestors": "'none'",
		"block-all-mixed-content": "",
		"upgrade-insecure-requests": ""
	}
}

OR

export EZRBlazorWebAPI__Cloud__Type="AWS"
export EZRBlazorWebAPI__Cloud__AzureBlobConnection="YourConnectionString"

Points to note

1. The nuget requires you to develop using Postgres database
2. All API's will be registered under /api endpoint and WASM application will be served on root
3. The nuget registers Scalar to display api documentation path can be configured in appsettings Scalar:Endpoint
4. Exception Handling and CSP registration has been added to pipeline.
5. Exception logs from WASM and MAUI are synced to API
6. [DapperJson] attribute is used on classes and properties which needs to be stored in postgres as JSONB datatype

Base API's Provided

AppSettings Service:

To share AppSettings between API and MAUI application

Authentication Services:

Below services are available for Single / Multi Tenant: Login via Microsoft / Google. Validate & exchange Microsoft / Google token for application JWT Token Generate token with APIKey Other API's for OTP Authentication and Authenticator Code Logout

File Services:

File Upload / Download & Delete

System Parameter Services:

Use this as a master where data on a key is stored / retrieved / upserted as Json[]. Upserting single record on uin is available.

Ingest Log Service:

Sync exception Logs > Warning from WASM and MAUI to Web

Check Scalar endpoint for all the API's generated by the nuget along with your created API's

Tools for Development

Fluid API

Make your class related to your functionality implementing IEndpointDefinition.

With endpoints object you will now get NewEndPoint method where you can define a new endpoint and register CRUD / Custom API's easily. It uses

public class SampleAPI : IEndpointDefinition
{
	public void MapEndpoints(WebApplication endpoints) =>
		endpoints.NewEndPoint(BaseAPI.SysParams)
			.CreateAPI<SysParams<object>, long>(
				createBuilder: (m, c) => SimpleBuilder.Create(@$"WITH find as (
									SELECT 	ARRAY['value', (index-1)::TEXT] as path, (contact->>'uin')::INT uin
									FROM 	master.sysparams, jsonb_array_elements(sysvalue->'value') with ordinality arr(contact, index)
									WHERE 	((({m.sysvalue}::JSONB)->>'uin')::INT IS NOT NULL) AND systype = {m.systype} AND ((contact->>'uin')::INT = (({m.sysvalue}::JSONB)->>'uin')::INT)
									LIMIT 1
								), genuin as (
									SELECT 	path, COALESCE(uin, nextval('sysparamsuin')) uin
									FROM 	(SELECT path, uin, 1 ord FROM find UNION ALL 
											SELECT null, null, 2 ORDER BY ord LIMIT 1) a
								), jpath as (
									SELECT 	COALESCE(path, ARRAY['value', uin::TEXT]) as path, uin
									FROM 	genuin
								), updtbl as (
									UPDATE 	master.sysparams
									SET 	sysvalue = JSONB_SET(sysvalue, jp.path, JSONB_SET({m.sysvalue}::JSONB, '{{uin}}'::TEXT[], to_jsonb(jp.uin), true), true)
									FROM 	jpath jp
									WHERE 	systype = {m.systype}
								)
								SELECT uin FROM jpath;"),
				roles: app.Configuration.GetSection("EZRBlazorWebAPI:EZRAPIs:SysParams:CreateRoles").Get<string[]>()!,
				validateModel: upsertValidator,
				onSuccess: onUpsertSuccess)
			.ReadAPI<SysParamsReadParams, SysParams<object>>(
				createBuilder: (r, c) => SimpleBuilder.Create($"SELECT * FROM master.sysparams sp WHERE systype = {r.systype}"),
				roles: app.Configuration.GetSection("EZRBlazorWebAPI:EZRAPIs:SysParams:ReadRoles").Get<string[]>()!,
				validateModel: readValidator,
				onAfterRead: onAfterRead,
				onAfterReadAsync: onAfterReadAsync)
			.UpdateAPI<SysParams<object>>(
				createBuilder: (m, c) => SimpleBuilder.Create($"UPDATE master.sysparams SET sysvalue = {m.sysvalue}::JSONB WHERE systype = {m.systype}"),
				roles: app.Configuration.GetSection("EZRBlazorWebAPI:EZRAPIs:SysParams:UpdateRoles").Get<string[]>()!,
				validateModel: updateValidator,
				onSuccess: onUpdateSuccess)
			.CustomAPI(
				pattern: "/", 
				method: HttpMethod.Get, 
				customDelegate: (ClaimsPrincipal caller) => "Hi", 
				routebuilder: r => r.RequireRole("admin").WithDescription("This is real test"));
}

Libraries compatible in API / WASM and MAUI

IQurl

Manage httpClient calls easily

var response = await qurl.OnPath(Url.Path(BaseAPI.AppSettings).Query("platform", BaseConstants.CurrentPlatform.ToString()), HttpMethod.Get, logger)
.ReadAsStringAsync(CancellationToken.None);

ICrypt

Inject ICrypt for Encryption and Decryption accross all platforms. Encrypt / Decrypt model or string cross platform.

async Task<IResult> (AppPlatform platform, IConfiguration config, ICrypt crypt) =>
				{
					string? microsoftCallbackURLSection = null, googleClientId = null, googleClientSecret = null, googleCallbackURL = null;

					switch (platform)
					{
						case AppPlatform.Windows:
							microsoftCallbackURLSection = "EZRBlazorWebAPI:Authentications:Microsoft:CallbackPath_Win";
							googleClientId = endpoints.Configuration[BaseConfigConstants.GGWinClientId];
							googleClientSecret = endpoints.Configuration[BaseConfigConstants.GGWinClientSecret];
							googleCallbackURL = endpoints.Configuration[BaseConfigConstants.GGWinCallbackPath];
							break;
						case AppPlatform.Android:
							microsoftCallbackURLSection = "EZRBlazorWebAPI:Authentications:Microsoft:CallbackPath_Android";
							googleClientId = endpoints.Configuration[BaseConfigConstants.GGAndClientId];
							googleClientSecret = endpoints.Configuration[BaseConfigConstants.GGAndClientSecret];
							googleCallbackURL = endpoints.Configuration[BaseConfigConstants.GGAndCallbackPath];
							break;
						case AppPlatform.IOS or AppPlatform.MacCatalyst:
							microsoftCallbackURLSection = "EZRBlazorWebAPI:Authentications:Microsoft:CallbackPath_Mac";
							googleClientId = endpoints.Configuration[BaseConfigConstants.GGMacClientId];
							googleClientSecret = endpoints.Configuration[BaseConfigConstants.GGMacClientSecret];
							googleCallbackURL = endpoints.Configuration[BaseConfigConstants.GGMacCallbackPath];
							break;
						case AppPlatform.WASM:
							microsoftCallbackURLSection = "EZRBlazorWebAPI:Authentications:Microsoft:CallbackPath";
							googleClientId = string.Empty;
							googleClientSecret = string.Empty;
							googleCallbackURL = string.Empty;
							break;
						default:
							throw new Exception($"Invalid Platform {platform}");
					}

					string? environment = null;
					return Results.Text(await crypt.EncryptAsync(new AppConfig(
									Environment: string.IsNullOrEmpty(environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")) ? "Production" : environment,
									Microsoft:
										new(Authority: $"{endpoints.Configuration["EZRBlazorWebAPI:Authentications:Microsoft:Instance"]!}{endpoints.Configuration["EZRBlazorWebAPI:Authentications:Microsoft:TenantId"]!}/v2.0",
											ClientId: endpoints.Configuration["EZRBlazorWebAPI:Authentications:Microsoft:ClientId"]!,
											CallbackURL: endpoints.Configuration[microsoftCallbackURLSection]!),
									Google:
										new(ClientId: googleClientId!,
											ClientSecret: googleClientSecret!,
											CallbackURL: googleCallbackURL!),
									AgGridLicenseKey: endpoints.Configuration["EZRBlazorWebAPI:AgGridLicenseKey"]
								), BaseConstants.AppConfigEncryptionKey));
				}

IMini

Compress / Decompress bytes, strings, files

//Compress
await iMini.CompressAsync(byteArray);

//Decompress
await iMini.DecompressAsync(byteArray);

Import / Export

Import and Export excel files or razor components as PDF's or extract HTML to send as email

Chron

Create your class and inherit from Chron , implement ExecuteBackgroundTask to register a background process. Time can be registered in the constructor.

Chron takes care that the background service should run on only 1 container in case if multiple have scaled up

AsyncChron

Chron job in MAUI and WASM

BaseAppCache

Lazy Caching services on client side

Fily

File service for WASM and MAUI

Port of Handson Table and AG-Grid

Use components <HandsonTable /> or <AGGrid />

DBFactory

use DBFactory.CreateDbConnection to create connection objects. It accepts a boolean isReadOnly param to determine which connection to return (Read Only or Read/Write)

Please explore for many more such features that can't be documented

License

MIT

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.9.3-alpha 40 2/21/2025
1.9.2-alpha 40 2/14/2025
1.9.1-alpha 38 2/14/2025
1.9.0-alpha 43 2/14/2025