MongoDB C# Driver mit .NET Dependency Injection aufsetzen
Drei Komponenten stehen im Zentrum, wenn man mit C# auf MongoDB-Datenbanken zugreifen will:
- der Client in Form einer
IMongoClient
-Instanz, - die Datenbank, die die Schnittstelle
IMongoDatabase
verwendet und - je Collection eine
IMongoCollection<T>
-Instanz.
Hierbei dient der Client als Factory für die IMongoDatabase
-Instanz und diese wiederum als Factory für MongoCollection<T>
-Objekte.
Unser Post nimmt hierbei ein Szenario an, bei dem eine ASP.NET Web API als Micro Service auf eine einzelne MongoDB-Datenbank zugreift, so dass Client und Datenbank innerhalb des Projekts geteilt werden können.
Bootstrapping
Solange wir noch mit MongoDB C# Driver 2.x arbeiten, ist es eine gute Idee, die Guid-Representation explizit beim Anwendungsstart festzulegen:
#pragma warning disable CS0618 // Type or member is obsolete
BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3;
#pragma warning restore CS0618 // Type or member is obsolete
Die Property ist bereits als obsolet markiert, weil ab Version 3 des MongoDB C# Drivers GuidRepresentationMode.V3
der Standard sein wird.
Konfiguration
Um von verschiedenen Stellen einfach auf Verbindungseinstellungen zugreifen zu können, ist es eine deutliche Vereinfachung, die Einstellungen für MongoDB in einer eigenen Klasse zusammen zu fassen:
public class MongoConfiguration
{
public string ConnectionString { get; set; } = "mongodb://localhost:27017";
public string DatabaseName { get; set; } = "mongo_api";
public bool LogStatements { get; set; } = false;
}
Es bleibt natürlich Ihnen überlassen, ob Sie an dieser Stelle sinnvolle Default-Werte vorgeben können, oder ob das in Ihrem Szenario nicht möglich ist. Die Werte können selbstverständlich auch über die User Secrets gepflegt werden.
Die Konfiguration wird nach dem Options-Muster im Inversion-of-control-Container registriert, damit wir im Folgenden einfach darauf zugreifen können:
builder.Services.Configure<MongoConfiguration>(builder.Configuration.GetSection("Mongo"));
Registrierung des MongoClients
Der Client ist thread-safe und hat einen Connection-Pool integriert, so dass Verbindungen mit gleichen Einstellungen über die gleiche Connection laufen können. Das macht IMongoClient
zu einem idealen Kandidaten für ein Singleton:
builder.Services.AddSingleton<IMongoClient>(prov =>
{
var config = prov.GetRequiredService<IOptions<MongoConfiguration>>().Value;
var settings = MongoClientSettings.FromConnectionString(config.ConnectionString);
settings.LinqProvider = MongoDB.Driver.Linq.LinqProvider.V3;
return new MongoClient(settings);
});
Obiger Code erstellt den Client in einer Factory-Methode. So können die Konfigurationseinstellungen verwendet werden. Außerdem bietet die Verwendung der MongoClientSettings
die Möglichkeit, die Einstellungen weiter anzupassen. Unbedingt angeraten ist an der Stelle die Auswahl des neuen LINQ-Providers.
Registrierung der Datenbank
Anschließend wird die Datenbank ebenfalls als Singleton registriert; diese setzt auf dem Client und der Konfiguration auf:
builder.Services.AddSingleton(prov =>
{
var client = prov.GetRequiredService<IMongoClient>();
var config = prov.GetRequiredService<IOptions<MongoConfiguration>>().Value;
return client.GetDatabase(config.DatabaseName);
});
Erstellung der Collections
Auch die Collections können im IoC-Container registriert werden; grundsätzlich kann man diese aber auch erst in einer Repository-Klasse erzeugen. Da wir in unserem Beispiel bisher stark auf den IoC gesetzt haben, fahren wir auf diese Art fort:
builder.Services.AddSingleton(prov =>
{
var db = prov.GetRequiredService<IMongoDatabase>();
return db.GetCollection<MyDocument>("my_documents");
});
Dadurch können Controller, die Daten der Collection verarbeiten, diese einfach verwenden:
[ApiController]
[Route("[controller]")]
public class MyDocumentsController : ControllerBase
{
private readonly ILogger<MyDocumentsController> _logger;
private readonly IMongoCollection<MyDocument> _coll;
public MyDocumentsController(
ILogger<MyDocumentsController> logger,
IMongoCollection<MyDocument> coll)
{
_logger = logger;
_coll = coll;
}
[HttpGet]
public async Task<IEnumerable<MyDocument>> GetAsync()
{
var options = new FindOptions<MyDocument, MyDocument>()
{
Limit = 100,
};
return (await _coll.FindAsync(FilterDefinition<MyDocument>.Empty, options))
.ToEnumerable();
}
}
Fazit
Da die zentralen Klassen, die zum Aufbau einer Verbindung zu MongoDB verwendet werden, thread-safe sind, können diese als Singletons registriert werden. Legt man darüber hinaus wichtige Verbindungsinformationen in der Konfiguration ab, steht dem Deployment fast schon nichts mehr im Wege.
Übrigens: das vollständige Sample ist auf github zu finden.