Backend Seeding
LightNap provides a comprehensive seeding system that runs automatically on application startup. This system ensures that essential data like roles, users, and application content is present in the database before the application becomes available.
How Seeding Works
The seeding process is triggered during application startup in Program.cs, immediately after database migrations are applied:
- Database Migration - Entity Framework migrations are applied (if configured)
- Seeding Execution - The
Seederclass runs through its seeding pipeline - Application Start - The application becomes available to handle requests
The seeding process runs every time the application starts, making it idempotent—it safely checks for existing data and only creates what’s missing.
The Seeding Pipeline
The Seeder class executes the following steps in order:
1. Seed Roles (SeedRolesAsync)
Creates all application roles defined in ApplicationRoles.All and removes any roles not defined in the application. This ensures the role set matches your application’s configuration.
2. Seed Users (SeedUsersAsync)
Creates user accounts based on the SeededUsers configuration in appsettings.json and assigns them to their designated roles. See Seeding Users for detailed configuration options.
3. Seed Static Content (SeedStaticContentAsync)
Loads static content from the file system (StaticContent directory) into the database. This includes:
- Zones - Content areas within pages
- Pages - Full page content
Content is organized by:
- Type - Zone or Page
- Access Level - Public, Authenticated, or Explicit
- Key - Kebab-case identifier
- Language - Two-letter language code (e.g.,
en,es) - Format - HTML, Markdown, or Plain Text
4. Seed Application Content (SeedApplicationContentAsync)
Provides a hook for custom application-wide seeding logic that should run in all environments. This method is intentionally left empty by default.
private Task SeedApplicationContentAsync()
{
// TODO: Add any seeding code you want run every time
// the app loads in any environment.
return Task.CompletedTask;
}
Use this for seeding data that is essential to your application regardless of whether it’s running in development, staging, or production.
5. Seed Environment Content (SeedEnvironmentContentAsync)
Calls the optional SeedEnvironmentContent() partial method, which can be implemented in environment-specific files like Seeder.Development.cs.
Environment-Specific Seeding
One of the key features of LightNap’s seeding system is support for environment-specific seeding through partial classes. This allows you to include development data without risking it being deployed to production.
How It Works
The Seeder class is declared as partial and includes an optional partial method:
partial void SeedEnvironmentContent();
You can implement this method in a separate file with a conditional build symbol:
Seeder.Development.cs
namespace LightNap.WebApi.Configuration
{
public partial class Seeder
{
private ApplicationDbContext _db = serviceProvider.GetRequiredService<ApplicationDbContext>();
partial void SeedEnvironmentContent()
{
this.SeedEnvironmentContentInternalAsync().Wait();
}
private async Task SeedEnvironmentContentInternalAsync()
{
// Seed sample data for development/testing
if (!_db.SomeEntities.Any())
{
_db.SomeEntities.Add(new SomeEntity { Name = "Test Data" });
await _db.SaveChangesAsync();
}
}
}
}
Environment File Examples
You can create different seeder files for each environment:
- Seeder.Development.cs - Test users, sample data, mock content
- Seeder.Staging.cs - Staging-specific data that mimics production
- Seeder.Production.cs - Production-specific initialization (use sparingly)
Only include the appropriate seeder file in your build configuration for each environment. Never deploy development seeders to production.
System User Context
During seeding, the application uses a special SystemUserContext instead of the normal IUserContext. This is necessary because:
- No authenticated user exists during application startup
- Elevated privileges are required to create roles and users
- Authorization checks would otherwise fail
The SystemUserContext:
- Always reports as authenticated and an administrator
- Has all roles and claims
- Returns
"system"as the user ID - Bypasses all authorization checks
This context is configured in Program.cs:
// Replace IUserContext with SystemUserContext for seeding
var seederServiceCollection = new ServiceCollection();
foreach (var descriptor in builder.Services.Where(descriptor => descriptor.ServiceType != typeof(IUserContext)))
{
seederServiceCollection.Add(descriptor);
}
seederServiceCollection.AddScoped<IUserContext, SystemUserContext>();
seederServiceCollection.AddScoped<Seeder>();
using var seederServiceProvider = seederServiceCollection.BuildServiceProvider();
var seeder = seederServiceProvider.GetRequiredService<Seeder>();
await seeder.SeedAsync();
Accessing Services in Environment Seeders
The Seeder class uses constructor injection to access dependencies. In your environment-specific seeder, you can access services through the serviceProvider parameter:
public partial class Seeder
{
private ApplicationDbContext _db = serviceProvider.GetRequiredService<ApplicationDbContext>();
partial void SeedEnvironmentContent()
{
// Access other services as needed
var notificationService = serviceProvider.GetRequiredService<INotificationService>();
// Your seeding logic
}
}
Best Practices
Do’s
- Keep it idempotent - Check if data exists before creating it
- Use transactions - Wrap complex seeding in database transactions
- Log your actions - Use the injected
ILoggerto track seeding progress - Version your seed data - Consider adding version checks for complex migrations
- Test thoroughly - Run your seeders multiple times to ensure idempotency
Don’ts
- Don’t assume order - Roles are processed alphabetically, not in definition order
- Don’t seed sensitive data - Use configuration for passwords and secrets
- Don’t deploy development seeders - Use build conditions to exclude them
- Don’t skip error handling - Seeding failures should stop application startup
- Don’t create too much data - Large seed operations slow down startup
Example: Seeding Related Entities
Here’s an example of seeding complex related data in a development environment:
private async Task SeedEnvironmentContentInternalAsync()
{
// Reference existing seeded users by email
// Note: Use appsettings.json SeededUsers configuration to create test users
// rather than calling GetOrCreateUserAsync() here. See the Seeding Users guide.
var admin = await _db.Users.FirstOrDefaultAsync(u => u.Email == "admin@admin.com");
var user1 = await _db.Users.FirstOrDefaultAsync(u => u.Email == "user1@site.com");
if (admin == null || user1 == null)
{
logger.LogWarning("Test users not found. Configure them in appsettings.json SeededUsers.");
return;
}
// Seed sample blog posts
if (!_db.BlogPosts.Any())
{
_db.BlogPosts.AddRange(
new BlogPost
{
Title = "Welcome Post",
Content = "Welcome to our site!",
AuthorId = admin.Id,
CreatedAt = DateTime.UtcNow
},
new BlogPost
{
Title = "User Post",
Content = "My first post!",
AuthorId = user1.Id,
CreatedAt = DateTime.UtcNow.AddHours(-1)
}
);
await _db.SaveChangesAsync();
}
// Seed sample application-specific data
if (!_db.Categories.Any())
{
_db.Categories.AddRange(
new Category { Name = "Technology", Slug = "technology" },
new Category { Name = "Science", Slug = "science" }
);
await _db.SaveChangesAsync();
}
}
User Seeding Best Practice: Prefer using the SeededUsers configuration in appsettings.json to create test users, including administrators and users for specific roles. The environment seeder should reference existing users rather than creating them. Additionally, note that LightNap automatically notifies administrators when new users register through the normal registration process, so you don’t need to manually create these notifications in your seeder.
Troubleshooting
Seeding Fails Silently
Check the application logs during startup. Seeding errors are logged at the Error level and will cause the application to throw an exception.
Data Not Appearing
Ensure that:
- The seeding code is actually being executed (add log statements)
- Database transactions are being committed (
SaveChangesAsync()) - The idempotency checks aren’t preventing creation
Environment Seeder Not Running
Verify that:
- The file is included in the build configuration
- The partial method signature matches exactly
- Build conditions are correct for your environment
Performance Issues
If seeding takes too long:
- Reduce the amount of data being seeded
- Use
AddRange()instead of multipleAdd()calls - Consider lazy initialization instead of startup seeding
- Disable automatic migrations if not needed
Related Topics
- Seeding Users - Detailed user seeding configuration
- Application Configuration - Overall configuration reference
- Database Providers - Database setup and migrations
- Adding Entities - Creating new database entities