Cedita Essence

Our extensions and helpers for building projects of all types, a .NET Standard Library.

Cedita Essence is our "base library", or a set of helpers and extensions to help build .NET applications from the ground up. With extensions for Dependency Injection, ASP.NET Core, and Entity Framework Core - it is a comprehensive set of useful tools for rapid development.

It is comprised of 4 main components:

Full documentation is being compiled by our development team and will be available shortly.

Licensed under the Apache License, Version 2.0.

Core

The Cedita.Essence Core comprises of type extensions, asynchronous process holder, standardised operation results and reflection helpers.

A highlight of functionality is available below.

Type Extensions

A few examples of the most common extensions are below.

"012abc".IsNullOrWhiteSpace(); // false
"012abc".IsAlphaNumeric(); // true
"012abc".IsNumeric(); // false
"012abc".IsAlpha(); // false
"abc".EncodeBase64(); // "YWJj"
"YWJj".DecodeBase64(); // "abc"
myEnum.GetDisplayName(); // Pulls from [DisplayName] attribute commonly used in ASP.NET
myEnum.Includes(MyEnum.MustHave); // Checks if a Flags enum contains the specified flag
new[] { 1, 2, 3, 4, 5 }.Slice(0, 2); // [1,2]
queryable.Paginate(2, 15, out int totalResults); // 15 items, skipping first 15
user.ValuesFrom(model, nameof(model.Title)); // user has Title property set from model

Operation Results

Standardising Operation Results across your application or API has benefits for all consumers, especially where an Error result may be different, and an Exception is not appropriate.

async Task<OperationResult<Output, Error>> PerformTask()
{
    // do something
    if(success) {
        return OperationResult.Success(myOutput);
    } else {
        return OperationResult.Failure(myError); 
    }
}

var opResult = await PerformTask();
if (opResult.Succeeded) {
    // Consume opResult.Result
} else {
    // Handle opResult.Error
}

ASP.NET Core

Cedita.Essence.AspNetCore provides Tag Helpers for Razor, and an Authentication Handler for accepting API Keys in your Web APIs where deemed appropriate.

Tag Helpers

// Startup.cs - for Bootstrap navigation
services.Configure<EssenceTagHelperOptions>(o =>
{
    o.DefaultClassIfClass = "active";
});

// _ViewImports.cshtml

// _Layout.cshtml
<li class-if-page="Index"><a asp-page="/Index">Homed</a></li>
// Enhanced matching can be performed on multiple expressions using ;
// Query Parameter support is also available using [qs=qsv]
class-if-controller="Reporting[type=Clients];Clients"

API Key Authentication

// Startup.cs
services.AddScoped<IApiKeyProvider, MyApiKeyProvider>();
services.AddAuthentication()
    .AddApiKeyAuthentication(o => {
        o.EnableHeaderAuthentication = true; // Accepts over X-Api-Key by default, see ApiKeyHeader
        o.EnableQueryStringAuthentication = true; // Accepts via ?apiKey by default, see ApiKeyQueryString
    });

// MyApiKey
public class MyApiKey : IApiKey
{
    public string Key { get; set; }
    public string UserId { get; set; }
    public IEnumerable<Claim> AdditionalClaims { get; set; }
}

// MyApiKeyProvider.cs
public class ApiKeyProvider : IApiKeyProvider
{
    public async Task<IApiKey> GetApiKeyAsync(string key)
    {
        // Look up API Key in database and return any additional claims etc
        return new MyApiKey {
            UserId = dbApiKey.UserId,
            Key = dbApiKey.Key,
            AdditionalClaims = new []
            {
                new Claim("sub", dbApiKey.UserId),
                new Claim("scope", "api-only"),
            },
        };
    }
}

Dependency Injection

Automate the wiring up of your dependencies using Essence's AutoInject attribute.

// Startup.cs (default is CallingAssembly)
services.AutoInjectForAssemblies(typeof(Cedita.NewProject.Root).Assembly);
// Filter injected types with a filter
services.AutoInjectForAssemblies(typeof(Cedita.NewProject.Root).Assembly, t => !t.IsSealed);

// Service classes (default AutoInject is Transient)
[AutoInject(ServiceLifetime.Scoped)]
public class MyService : IMyService
// Control what types are implemented by the class with explicit declaration
[AutoInject(implements: typeof(IMyOtherService)]
public class MyOtherService : IMyOtherService, IAnotherService

Entity Framework Core

Cedita.Essence.EntityFrameworkCore provides automated audit logging, date audits, and sensible defaults.

Automated Date Auditing

// Model classes
public MyAuditableClass : IAuditDates {
    // ..
    public DateTimeOffset DateCreated { get; set; }
    public DateTimeOffset DateModified { get; set; }
    public DateTimeOffset? DateDeleted { get; set; }

// DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
    // ..
    modelBuilder.ConfigureAuditableEntities();
    // ..

// Migration (bottom of Up())
var triggers = DateModifiedTriggerGenerator.GetTriggersSql();
// Control pluralisation / bespoke table names with explicit definitions
// Default pluralisation is ending 's' += "es", 'y' name[0..^1] + "ies", others += "s"
var triggers = DateModifiedTriggerGenerator.GetTriggersSql(new Dictionary<Type, string> {
    { typeof(MyBespokeTableEntity), "BespokeNames" }
});
foreach(var trigger in triggers)
{
    migrationBuilder.Sql(trigger);
}

Global Defaults

// DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
    // ..
    modelBuilder.ConfigureDecimalPrecision(); // Default 19, 4
    modelBuilder.ConfigureDefaultTypeToColumn(typeof(DateTime), "datetime2");
    // Depending on database design
    modelBuilder.ConfigureDeleteBehaviour(DeleteBehaviour.Restrict);
    // Or for your own schema, ignoring Identity where used
    modelBuilder.ConfigureDeleteBehaviourExcludingIdentity<ApplicationUser>(DeleteBehavior.Restrict);
    // Deny filters (Useful in a scoped DbContext)
    modelBuilder.ConfigureDenyFilter();

Full Auditing / Comments / Logs

// Startup.cs
services.AddScoped<IAuditService, AuditService<ApplicationDbContext, EntityAudit, int>>(); 

// DbContext
public DbSet<EntityAudit> Audits { get; set; }

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Table>().HasKey(e => e.Name);

// Migration (bottom of Up() after 'Tables' table added)
migrationBuilder.Sql(StaticSql.TableTable);

// Usage
// Inject IAuditService, change myEntity
auditService.ContextAddAuditChanges(dbContext.Entry(myEntity));
// Log (for system events)
auditService.ContextAddLog(dbContext.Entry(myEntity), "Log entry");
// Comment (for user notes)
auditService.ContextAddComment(dbContext.Entry(myEntity), "Comment entry");
// Changes are added to the context, and so need to be saved
await dbContext.SaveChangesAsync();

We use only essential cookies to help deliver content and keep everything working. No tracking here.