SlugKit.Dapper 1.0.0

dotnet add package SlugKit.Dapper --version 1.0.0
                    
NuGet\Install-Package SlugKit.Dapper -Version 1.0.0
                    
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="SlugKit.Dapper" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SlugKit.Dapper" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SlugKit.Dapper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SlugKit.Dapper --version 1.0.0
                    
#r "nuget: SlugKit.Dapper, 1.0.0"
                    
#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.
#:package SlugKit.Dapper@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SlugKit.Dapper&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SlugKit.Dapper&version=1.0.0
                    
Install as a Cake Tool

SlugKit

Automatic URL-safe slug generation for EF Core and Dapper entities with scoped uniqueness, transliteration, and regeneration control. Inspired by spatie/laravel-sluggable.

NuGet CI License: MIT


Table of Contents


Packages

Package NuGet ID Description
Core SlugKit Interfaces, attributes, generator, options
ASP.NET Core SlugKit.AspNetCore DI registration helpers
EF Core SlugKit.EntityFrameworkCore SaveChanges interceptor, query extensions
Dapper SlugKit.Dapper IDbConnection lookup extensions

Quick Start

1. Install packages

dotnet add package SlugKit
dotnet add package SlugKit.EntityFrameworkCore   # for EF Core
dotnet add package SlugKit.Dapper                # for Dapper

2. Mark your entity

public class Article : IHasSlug
{
    public int Id { get; set; }
    public string Title { get; set; }

    [Slug(From = nameof(Title), Unique = true, MaxLength = 80)]
    public string Slug { get; set; }
}

3. Register services

// Program.cs
builder.Services.AddSlugKit(options =>
{
    options.DefaultSeparator = "-";
    options.DefaultMaxLength = 100;
    options.DefaultOnUpdate = SlugBehavior.NeverRegenerate;
    options.ReservedSlugs = ["admin", "api", "settings", "new", "edit", "delete"];
})
.UseEntityFrameworkCore<AppDbContext>()
.UseDapper();

4. Register the EF Core interceptor

// In your DbContext OnConfiguring, or when registering DbContext:
optionsBuilder.AddInterceptors(serviceProvider.GetRequiredService<SlugGenerationInterceptor>());

5. Save your entity — slug is generated automatically

var article = new Article { Title = "Hello World" };
context.Articles.Add(article);
await context.SaveChangesAsync();

Console.WriteLine(article.Slug); // "hello-world"

Core Concepts

IHasSlug Interface

All slug-bearing entities must implement IHasSlug:

public interface IHasSlug
{
    string Slug { get; set; }
}

SlugAttribute

Decorate the Slug property with [Slug] to configure generation:

[AttributeUsage(AttributeTargets.Property)]
public class SlugAttribute : Attribute
{
    public string From { get; set; }           // Source property name (required)
    public string? Scope { get; set; }         // Scoping property name (optional)
    public bool Unique { get; set; } = true;
    public int MaxLength { get; set; } = 100;
    public SlugBehavior OnUpdate { get; set; } = SlugBehavior.NeverRegenerate;
}

SlugBehavior

Value Description
NeverRegenerate Slug is set once on insert and never changed (default). Stable, bookmarkable URLs.
AlwaysRegenerate Slug is regenerated from source on every save, even if source did not change.
RegenerateOnChange Slug is regenerated only when the source property value has changed.

Slug Generation Pipeline

Given the input "Héllo, Wörld!" with MaxLength = 100:

Step Operation Result
1 Transliterate "Hello, World!"
2 Lowercase "hello, world!"
3 Replace non-alphanumeric with separator "hello-world-"
4 Collapse consecutive separators "hello-world-"
5 Trim separators from start/end "hello-world"
6 Truncate to MaxLength "hello-world" (no truncation needed)
7 Uniqueness check + suffix "hello-world" (unique, no suffix needed)

If "hello-world" already exists, the result would be "hello-world-2", then "hello-world-3", etc.


Configuration

Global Options

services.AddSlugKit(options =>
{
    options.DefaultSeparator = "-";           // Word separator character
    options.DefaultMaxLength = 100;           // Max slug length
    options.DefaultOnUpdate = SlugBehavior.NeverRegenerate;
    options.ReservedSlugs = ["admin", "api", "settings"];
});

Individual [Slug] attribute settings always override global options.

Reserved Slugs

Slugs that match a reserved value are automatically suffixed:

// "admin" is reserved → generated slug becomes "admin-2"
var entity = new SomeEntity { Name = "Admin" };
// slug → "admin-2"

Default reserved slugs: admin, api, settings, new, edit, delete.

Custom Transliterator

Replace the default NFD-based transliterator with your own for non-Latin scripts:

public class CyrillicTransliterator : ISlugTransliterator
{
    private static readonly Dictionary<char, string> Map = new()
    {
        ['а'] = "a", ['б'] = "b", ['в'] = "v", // ...
    };

    public string Transliterate(string input)
        => string.Concat(input.Select(c => Map.TryGetValue(c, out var v) ? v : c.ToString()));
}

// Registration:
services.AddSlugKit().UseTransliterator<CyrillicTransliterator>();

EF Core Integration

Setup

Install the package:

dotnet add package SlugKit.EntityFrameworkCore

Register services and wire the interceptor into your DbContext:

// Program.cs
builder.Services.AddSlugKit(/* options */)
    .UseEntityFrameworkCore<AppDbContext>();

builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
    options.UseSqlServer(connectionString);
    options.AddInterceptors(sp.GetRequiredService<SlugGenerationInterceptor>());
});

Scoped Uniqueness

Use Scope to make slugs unique within a parent entity:

public class TicketTag : IHasSlug
{
    public string Name { get; set; }
    public Guid ProjectId { get; set; }

    [Slug(From = nameof(Name), Scope = nameof(ProjectId), Unique = true)]
    public string Slug { get; set; }
}

Two TicketTag rows with the same Name in different projects can share the same slug. Two rows in the same project will get "tag-name" and "tag-name-2".

Update Behaviour

// Never regenerate (default) — stable URLs
public class Page : IHasSlug
{
    public string Title { get; set; }

    [Slug(From = nameof(Title), OnUpdate = SlugBehavior.NeverRegenerate)]
    public string Slug { get; set; }
}

// Always regenerate — slug tracks title exactly
public class Article : IHasSlug
{
    public string Title { get; set; }

    [Slug(From = nameof(Title), OnUpdate = SlugBehavior.AlwaysRegenerate)]
    public string Slug { get; set; }
}

// Regenerate only when title changes
public class Post : IHasSlug
{
    public string Title { get; set; }

    [Slug(From = nameof(Title), OnUpdate = SlugBehavior.RegenerateOnChange)]
    public string Slug { get; set; }
}

Query Extensions

using SlugKit.EntityFrameworkCore.Extensions;

// Filter by slug
var article = await context.Articles.WhereSlug("hello-world").FirstOrDefaultAsync();

// Convenience single-result lookup
var article = await context.Articles.FindBySlugAsync("hello-world");

Dapper Integration

Setup

dotnet add package SlugKit.Dapper
services.AddSlugKit().UseDapper();

FindBySlugAsync

using SlugKit.Dapper.Extensions;

// Simple lookup by slug
var project = await connection.FindBySlugAsync<Project>("my-project");

// Scoped lookup (unique within project)
var tag = await connection.FindBySlugAsync<TicketTag>(
    slug: "backend",
    scopeId: projectId,
    scopeColumn: "project_id");

The table name is resolved automatically:

  • If the entity has [Table("table_name")], that name is used.
  • Otherwise, the type name is converted to snake_case and pluralised (e.g. TicketTagticket_tags).

GetExistingSlugsAsync

Use this to populate the existingSlugs list before calling ISlugGenerator.Generate:

var existingSlugs = await connection.GetExistingSlugsAsync<TicketTag>(
    scopeId: projectId,
    scopeColumn: "project_id");

var newSlug = slugGenerator.Generate("Backend", existingSlugs);
// "backend-2" if "backend" is already taken in this project

ASP.NET Core Integration

dotnet add package SlugKit.AspNetCore
using SlugKit.AspNetCore.DependencyInjection;

services.AddSlugKit().AddAspNetCore();

Advanced Scenarios

Custom Slug Generator

Replace the default generator entirely:

public class MySlugGenerator : ISlugGenerator
{
    public string Generate(string source, IReadOnlyList<string>? existingSlugs = null, int maxLength = 0)
    {
        // your implementation
    }
}

services.AddSlugKit();
services.AddSingleton<ISlugGenerator, MySlugGenerator>();

Unicode and Non-Latin Scripts

The default transliterator handles Western European accents via Unicode NFD decomposition:

Input Output
café cafe
naïve naive
Zürich zurich
crème brûlée creme-brulee
Año Nuevo ano-nuevo

For Cyrillic, CJK, Arabic, or other scripts, register a custom ISlugTransliterator.

Table Name Resolution (Dapper)

Entity Type Resolved Table Name
[Table("articles")] articles
Project (no attribute) projects
TicketTag (no attribute) ticket_tags
BlogPost (no attribute) blog_posts

Entity Examples

Simple Entity (global uniqueness)

public class Project : IHasSlug
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Slug(From = nameof(Name), Unique = true, MaxLength = 60)]
    public string Slug { get; set; }
}

Scoped Entity (unique per parent)

public class TicketTag : IHasSlug
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Guid ProjectId { get; set; }

    [Slug(From = nameof(Name), Scope = nameof(ProjectId), Unique = true)]
    public string Slug { get; set; }
}

Always-fresh slug (tracks source changes)

public class WikiPage : IHasSlug
{
    public int Id { get; set; }
    public string Title { get; set; }

    [Slug(From = nameof(Title), OnUpdate = SlugBehavior.RegenerateOnChange, MaxLength = 120)]
    public string Slug { get; set; }
}

Supported Frameworks

All packages multi-target:

Framework Supported
.NET 8 Yes
.NET 9 Yes
.NET 10 Yes

SlugKit.AspNetCore targets net8.0 and net10.0 (ASP.NET Core LTS cadence).


License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  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.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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.0.0 111 3/26/2026