Chatilay.MongoContext
0.1.0-preview.1
dotnet add package Chatilay.MongoContext --version 0.1.0-preview.1
NuGet\Install-Package Chatilay.MongoContext -Version 0.1.0-preview.1
<PackageReference Include="Chatilay.MongoContext" Version="0.1.0-preview.1" />
<PackageVersion Include="Chatilay.MongoContext" Version="0.1.0-preview.1" />
<PackageReference Include="Chatilay.MongoContext" />
paket add Chatilay.MongoContext --version 0.1.0-preview.1
#r "nuget: Chatilay.MongoContext, 0.1.0-preview.1"
#:package Chatilay.MongoContext@0.1.0-preview.1
#addin nuget:?package=Chatilay.MongoContext&version=0.1.0-preview.1&prerelease
#tool nuget:?package=Chatilay.MongoContext&version=0.1.0-preview.1&prerelease
Chatilay.MongoContext
A typed MongoDB context for services that keep their own database. A derived context declares collections as properties; the package owns the client, the database, collection naming, index setup, the audit and soft-delete conventions applied on every write, and a readiness check.
The client is built once per context type and collections are cached, so reading a collection property on every request does not allocate a new connection pool — the failure mode this package exists to prevent.
Installation
dotnet add package Chatilay.MongoContext
Defining a context
public sealed class MessageDocContext(MongoContextDependencies dependencies)
: MongoDocContext(dependencies)
{
public IMongoCollection<MessageDoc> Messages => GetCollection<MessageDoc>();
}
Client and Database are exposed on the context for the cases the properties do not cover
(transactions, RunCommand, aggregation over a raw collection).
Registration
builder.Services.AddMongoDocContext<MessageDocContext>(builder.Configuration);
Reads the MongoDbSettings section by default:
{
"MongoDbSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "chatilay_msg_mongo"
}
}
A different section, and any client tuning:
builder.Services.AddMongoDocContext<MessageDocContext>(
builder.Configuration,
sectionName: "MessageMongo",
configure: options =>
{
options.ApplicationName = "message-api";
options.ConfigureClient = settings =>
{
settings.MaxConnectionPoolSize = 200;
settings.ServerSelectionTimeout = TimeSpan.FromSeconds(5);
};
});
Without configuration binding — useful in tests:
services.AddMongoDocContext<MessageDocContext>(options =>
{
options.ConnectionString = "mongodb://localhost:27017";
options.DatabaseName = "messages_test";
});
Registration is idempotent. Each context type gets its own client, so two contexts in one process may point at different clusters; two contexts sharing a connection string still get one client each. The client and database are also resolvable as keyed services:
var client = provider.GetRequiredKeyedService<IMongoClient>(typeof(MessageDocContext));
A missing setting fails at resolution with a message naming the context and the section that was searched, rather than a driver-level error later on.
Collection naming
The default strategy appends s to the type name — MessageDoc lands in MessageDocs.
Pin a name explicitly when the type name should not leak into the database:
[MongoCollection("messages")]
public class MessageDoc : MongoDocument;
Or replace the strategy for the whole context:
options.NamingStrategy = new DelegateCollectionNamingStrategy(type => type.Name.ToLowerInvariant());
Documents
public class MessageDoc : MongoDocument // Id: ObjectId
{
public string Content { get; set; } = null!;
public string ChannelId { get; set; } = null!;
}
public class AuditEvent : MongoDocument<Guid> { } // Id: Guid
IMongoDocument<TKey> requires nothing but Id, and IMongoDocument is the shorthand for
an ObjectId key. Audit and soft-delete fields are not part of the contract: declare the
ones a document actually needs and let the conventions below maintain them. A document that
needs none of them stays a plain class.
Conventions
Conventions name the properties the package maintains on write. All three are off until a
name is set; UseDefaults() turns them on with the names CreatedAt, UpdatedAt and
IsDeleted:
builder.Services.AddMongoDocContext<MessageDocContext>(builder.Configuration, configure: options =>
{
options.UseDefaultConventions(); // same as options.Conventions.UseDefaults()
// options.Conventions.UpdatedAtProperty = null; // turn just this one off
// options.Conventions.SoftDeleteProperty = "Deleted"; // or use your own name
});
| Convention | Applied on | Behaviour |
|---|---|---|
CreatedAtProperty |
insert | Stamped with the current time when the caller left it unset |
UpdatedAtProperty |
update, replace | Stamped unless the update already writes that field |
SoftDeleteProperty |
NotDeleted, FindActive, soft-delete helpers |
The boolean flag those helpers read and write |
Stamping happens inside the collection the context hands out, so it covers the driver's own
API — InsertOneAsync, UpdateOneAsync, ReplaceOneAsync, BulkWriteAsync and their
session overloads. There is no separate write API to remember:
await context.Messages.InsertOneAsync(doc, cancellationToken: ct); // CreatedAt stamped
await context.Messages.UpdateOneAsync(filter, update, cancellationToken: ct); // UpdatedAt added
Date properties may be DateTimeOffset or DateTime, nullable or not; the soft-delete
property must be a bool. A property a document does not declare — or declares with a type
that cannot be maintained — is skipped with one warning per document type. Reads are skipped
the same way rather than filtering on a field that does not exist, which would silently match
nothing.
OfType<TDerived>() returns the driver's own filtered collection, so conventions do not
follow it; WithReadPreference, WithReadConcern and WithWriteConcern keep them.
Validating the names
A property name is a string, so a typo cannot be caught by the compiler. Check it at startup:
var report = app.Services.ValidateMongoConventions();
if (!report.IsValid)
{
app.Logger.LogWarning("Mongo conventions: {Issues}", report);
}
It resolves every registered context and lists the conventions that did not apply.
Soft delete
await context.Messages.SoftDeleteOneAsync(id, ct); // by Id, whatever the key type is
await context.Messages.SoftDeleteManyAsync(filter, ct);
var page = await context.Messages.FindActive(m => m.ChannelId == channelId).ToListAsync(ct);
var filter = context.Messages.NotDeleted() & Builders<MessageDoc>.Filter.Eq(m => m.ChannelId, id);
Soft-deleting a document type that has no soft-delete property throws — doing nothing would
look like a successful delete. NotDeleted() on such a type returns an empty filter instead.
Indexes
Declare them next to the documents they belong to:
public sealed class NotificationIndexes : IMongoIndexProvider<NotificationMongoContext, NotificationDoc>
{
public IMongoCollection<NotificationDoc> GetCollection(NotificationMongoContext context)
=> context.Notifications;
public IEnumerable<CreateIndexModel<NotificationDoc>> GetIndexes() =>
[
new(Builders<NotificationDoc>.IndexKeys
.Ascending(n => n.TargetUserId)
.Ascending(n => n.IsRead)
.Descending(n => n.CreatedAt),
new CreateIndexOptions { Background = true, Name = "idx_target_user_read_created" })
];
public IEnumerable<string> ObsoleteIndexNames => ["idx_target_user"];
}
builder.Services.AddMongoIndexProviders(typeof(NotificationIndexes).Assembly);
...
await app.Services.EnsureMongoIndexesAsync();
Creating and dropping are both idempotent, so it is safe on every boot; a name in
ObsoleteIndexNames that no longer exists is ignored.
Health check
builder.Services.AddHealthChecks()
.AddMongoContextCheck<MessageDocContext>(tags: ["ready", "db"]);
Runs ping against the context's database.
DateTimeOffset storage
The driver stores DateTimeOffset as a document ({ DateTime, Ticks, Offset }), not as a BSON
date. TTL indexes do not work on such a field, and range queries and sorts fall back to
document comparison. To store dates as BSON dates instead:
MongoSerializationDefaults.UseUtcDateTimeOffset(); // before the first document is serialized
This changes the on-disk format: documents already written in the document form will fail to deserialize, sub-millisecond precision is lost and the value comes back as UTC. Migrate the collection first — enable it on a new collection, or rewrite the existing one.
MongoSerializationDefaults.UseStandardGuids() is the same kind of opt-in for Guid values.
Driver 3.x refuses to serialize a Guid until a representation is chosen, so a document with
a Guid key needs this call (or a per-property [BsonGuidRepresentation]).
API surface
| Type | Purpose |
|---|---|
MongoDocContext |
Base context — GetCollection<TDocument>(), Client, Database |
MongoContextDependencies |
Client, database, naming and conventions handed to a context by the container |
IMongoDocument<TKey> / IMongoDocument |
Id, with any key type or with ObjectId |
MongoDocument<TKey> / MongoDocument |
Base classes for the above |
AddMongoDocContext<TContext> |
Registration, from configuration or explicit options |
MongoContextOptions |
Connection overrides, application name, lifetime, naming, client tuning, conventions |
MongoDocumentConventions |
CreatedAtProperty, UpdatedAtProperty, SoftDeleteProperty, UseDefaults() |
ValidateMongoConventions / MongoConventionReport |
Startup check for convention names that did not resolve |
IMongoCollectionNamingStrategy |
SuffixCollectionNamingStrategy, DelegateCollectionNamingStrategy, [MongoCollection] |
IMongoIndexProvider<TContext, TDocument> |
Index declarations, applied by EnsureMongoIndexesAsync |
AddMongoContextCheck<TContext> |
Readiness check |
| Query helpers | NotDeleted, FindActive, SoftDeleteOneAsync, SoftDeleteManyAsync |
MongoSerializationDefaults |
Opt-in DateTimeOffset and Guid representations |
Requirements
.NET 8.0 or later, MongoDB.Driver 3.x.
Building from source
dotnet test -c Release
dotnet pack -c Release
All build output lands under artifacts/ at the repository root.
License
MIT — see LICENSE.
| Product | Versions 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 was computed. 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 was computed. 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. |
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.30)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- MongoDB.Driver (>= 3.11.1)
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 |
|---|---|---|
| 0.1.0-preview.1 | 138 | 9/4/2026 |