KNM.Core 0.0.1-alpha

KNM.Core

KNM.Core is a shared NuGet infrastructure library for KoNiMa enterprise .NET applications. It eliminates code duplication across projects by providing battle-tested, configurable core services.

Features

Feature Description
Email Sending SMTP email with 4 responsive HTML templates + plain-text fallback
In-Memory Cache Prefixed cache keys, absolute + sliding expiration
Structured Logging Database, File, and Application Insights providers
Template Rendering $$PLACEHOLDER$$ substitution engine for HTML/text templates
Result Pattern Result<T> for error handling without exceptions
Extension Methods String, Number, Date, Enum helpers

Installation

dotnet add package KNM.Core

Quick Start

// Program.cs
builder.Services.AddKnmCore()
    .WithCache(cache => {
        cache.Prefix = "MYAPP_";
        cache.DurationInMinutes = 60;
    })
    .WithEmail(email => {
        email.SmtpHost = "smtp.example.com";
        email.SmtpPort = 587;
        email.SmtpUser = "user@example.com";
        email.SmtpPassword = "password";
        email.SmtpDisplayName = "My App";
        email.SmtpDisplayEmail = "noreply@example.com";
        email.EnableSsl = true;
    })
    .WithLogger(logger => {
        logger.Level = KnmLogLevel.Info;
        logger.Provider = KnmLogProvider.File;
        logger.FilePath = "logs/app.log";
    });

Or via appsettings.json inline configuration:

builder.Services.AddKnmCore(options => {
    options.Cache.Prefix = "MYAPP_";
    options.Email.SmtpHost = "smtp.example.com";
});

Email Service

Send a simple email

public class MyService(IEmailSenderService emailSender)
{
    public async Task SendWelcome(string userEmail, string userName)
    {
        var dto = new SendMailDto
        {
            Company = "MyApp",
            LogoUrl = "https://myapp.com/logo.png",
            Template = EmailTemplate.General,
            Recipients = [
                new UserEmailSettings
                {
                    Language = "en",
                    Subject = $"Welcome, {userName}!",
                    DisplayName = userName,
                    EmailAddress = userEmail,
                    Message = new MailMessage
                    {
                        Preview = "Welcome to MyApp",
                        Title = "WELCOME!",
                        Salutation = $"Dear {userName}",
                        Message = "Thank you for joining our platform.",
                        Goodbye = "The MyApp Team"
                    }
                }
            ]
        };

        var result = await emailSender.SendAsync(dto);
        if (!result.IsSuccess)
            Console.WriteLine($"Send failed: {result.Error}");
    }
}

Available Templates

Template Use case
EmailTemplate.General Basic informational email
EmailTemplate.WithButton Email with a call-to-action button
EmailTemplate.WithDetailedDetails Email with icon + highlighted details block
EmailTemplate.WithCode Email with a prominently displayed OTP/verification code

Custom Template Override

Supply per-project HTML/TXT files via EmailOptions.TemplateOverrides. When a key is present the renderer loads from disk; otherwise it falls back to the embedded template.

builder.Services.AddKnmCore()
    .WithEmail(email => {
        email.TemplateOverrides[EmailTemplate.General] =
            ("wwwroot/emails/General.html", "wwwroot/emails/General.txt");
    });

Paths are absolute or relative to the working directory. The file content is cached in memory after the first load, identical to embedded templates.

Cache Service

public class MyService(IAppCache cache)
{
    public async Task<User?> GetUser(int id)
    {
        return await cache.GetOrSetAsync(
            key: $"user:{id}",
            factory: async () => await db.Users.FindAsync(id),
            duration: TimeSpan.FromMinutes(15)
        );
    }
}

Logger Service

IKnmLogger is a structured application logger with pluggable providers. Configure via WithLogger().

Providers

Provider Description
KnmLogProvider.File Daily-rotated log files (e.g. app.2026-03-11.log)
KnmLogProvider.Database Writes to a DbContext via a configurable factory + SaveLogEntry reflection call
KnmLogProvider.ApplicationInsights Sends TraceTelemetry / ExceptionTelemetry to Azure Application Insights

Configuration

// File provider
.WithLogger(logger => {
    logger.Provider  = KnmLogProvider.File;
    logger.Level     = KnmLogLevel.Info;
    logger.FilePath  = "logs/app.log";      // date suffix added automatically
    logger.LogCulture = "en-US";            // language for log messages
});

// Database provider
.WithLogger(logger => {
    logger.Provider         = KnmLogProvider.Database;
    logger.Level            = KnmLogLevel.Warning;
    logger.DbContextFactory = sp => sp.GetRequiredService<MyAppDbContext>();
    // DbContext must expose a SaveLogEntry(string level, string message, ...) method
});

// Application Insights provider
.WithLogger(logger => {
    logger.Provider              = KnmLogProvider.ApplicationInsights;
    logger.AppInsightsConnString = "InstrumentationKey=...";
});

Usage

public class MyService(IKnmLogger logger)
{
    public void DoWork()
    {
        logger.LogInfo("Operation started", action: "DoWork", entity: "MyService");

        try { /* ... */ }
        catch (Exception ex)
        {
            logger.LogError("Operation failed", exception: ex, action: "DoWork");
        }
    }
}

Log methods

Method Level
LogInfo(message, ...) Information
LogWarning(message, ...) Warning
LogError(message, exception?, ...) Error
LogCritical(message, exception?, ...) Critical

Result Pattern

// Return Result<T> from your methods
public Result<User> GetUser(int id)
{
    var user = db.Users.Find(id);
    return user is null
        ? Result<User>.Failure("User not found", "USER_404")
        : Result<User>.Success(user);
}

// Consume with chaining
var result = GetUser(42)
    .Map(user => user.Email)
    .Map(email => email.ToLower());

if (result.IsSuccess)
    Console.WriteLine(result.Value);
else
    Console.WriteLine($"Error [{result.ErrorCode}]: {result.Error}");

HttpClient Extensions

HttpClientExtensions wraps any HttpClient with automatic Polly retry and a configurable per-request timeout. All methods return Result<T> or Result.

Method Description
GetWithRetryAsync<T>(url, retries, timeout) GET → deserialize JSON to T
PostWithRetryAsync<T>(url, body, retries, timeout) POST JSON body → deserialize response
PutWithRetryAsync<T>(url, body, retries, timeout) PUT JSON body → deserialize response
DeleteWithRetryAsync(url, retries, timeout) DELETE → Result

Retry policy: exponential backoff with jitter, triggers on HttpRequestException and HTTP 500/502/503/504/408.

var result = await httpClient.GetWithRetryAsync<WeatherData>(
    url: "https://api.example.com/weather/rome",
    retries: 3,
    timeout: TimeSpan.FromSeconds(10));

if (result.IsSuccess)
    Console.WriteLine(result.Value!.Temperature);
else
    Console.WriteLine($"[{result.ErrorCode}] {result.Error}");

Extension Methods

String

"hello world".Truncate(8);             // "hello..."
"User@Example.COM".NormalizeEmail();   // "user@example.com"
"<b>Hello</b>".ToCleanText();          // "Hello"
myObject.ToJson(indent: true);         // pretty JSON
"{\"name\":\"Alice\"}".FromJson<User>(); // User object

Number

1234.56m.ToCurrency("it-IT");          // "€ 1.234,56"
1234.5678m.RoundBankers(2);            // 1234.57
1_500_000L.ToHumanReadable();          // "1.5M"

Date

DateTime.Now.ToShortDate();                     // "11/03/2026" (default: it-IT)
DateTime.Now.ToShortDate("en-US");              // "3/11/2026"
DateTime.Now.ToFormattedDate("yyyy-MM-dd");     // "2026-03-11"
DateTime.Now.ToIso8601();                       // "2026-03-11T10:00:00.000Z"
new DateTime(1990, 5, 15).GetAge();             // 35
DateTime.Now.ToUnixTimestamp();                 // Unix epoch seconds
DateTime.Now.IsWeekend();                       // false
DateTime.Now.StartOfMonth();                    // 2026-03-01
DateTime.Now.EndOfMonth();                      // 2026-03-31

Enum

MyEnum.SomeValue.GetDescription();     // reads [Description] attribute
"Active".TryParse<StatusEnum>();       // StatusEnum? (null-safe)

Architecture

KNM.Core/
├── Models/          DTOs, enums, CoreOptions, Result<T>
├── Services/
│   ├── Interfaces/  IEmailSenderService, IAppCache, ITemplateRenderer, IKnmLogger
│   └── ...          Concrete implementations
├── Extensions/      StringExtensions, NumberExtensions, DateExtensions, EnumExtensions
├── Templates/       Embedded HTML + TXT email templates
├── Resources/       Localized strings (en-US default, it-IT, es-ES, fr-FR, de-DE)
└── Internal/        EmailInternal (not public API)

Requirements

  • .NET 10+
  • Microsoft.Extensions.Caching.Memory
  • Microsoft.Extensions.DependencyInjection.Abstractions
  • Microsoft.Extensions.Logging.Abstractions

License

MIT — KoNiMa S.r.l.

Showing the top 20 packages that depend on KNM.Core.

Packages Downloads
KNM.Reporting
Unified reporting abstraction supporting SSRS, FastReport, QuestPDF, and ClosedXML with fluent DI configuration
1
KNM.Reporting
Unified reporting abstraction supporting SSRS, FastReport, QuestPDF, and ClosedXML with fluent DI configuration
2
KNM.Reporting.Designer
Plug and play Blazor admin panel for report generation, management, and monitoring in the KoNiMa ecosystem
0

Version Downloads Last updated
1.5.0 3 30/03/2026
1.4.0 3 29/03/2026
1.3.1 4 26/03/2026
1.3.0 0 26/03/2026
1.2.0 4 25/03/2026
0.0.2-alpha 2 12/03/2026
0.0.1-alpha 1 11/03/2026