BrandGhost
MongoClient in ASP.NET Core: Lifetime and Resource Ownership

MongoClient in ASP.NET Core: Lifetime and Resource Ownership

MongoClient in ASP.NET Core raises two questions that look similar but are not the same: how long should the application reuse the client, and who owns the resources behind it? A singleton registration answers the first question. It does not automatically explain every part of shutdown, especially when the driver's cluster infrastructure can be shared.

For a C# application developer, the useful starting point is a clear composition boundary. Configure a client for a known deployment, make it available to application services, and keep request-specific state out of that shared object. Then examine disposal using the behavior of the driver version actually selected.

Current-information check: October 10, 2026. The official documentation links to driver API 3.12.0. Examples below use that baseline and were not executed. Check the current connection documentation and package compatibility before integrating them into an application.

Separate MongoClient lifetime from request lifetime

The MongoClient connection guide recommends a singleton lifetime. In an application with one MongoDB configuration, that means many requests can use the same application-owned client rather than creating a new client each time they need a collection.

That statement concerns the client, not every dependency that happens to call it. A request-scoped service may need an authenticated user's context, validation state, or other request data. It can borrow the singleton client without becoming a singleton itself. Making the entire call graph singleton would be a separate and potentially incorrect decision.

Similarly, a singleton client should not become the storage location for a current user's ID. The URI and driver settings describe deployment access. Application authorization describes which data a caller may access. Sharing a configured client does not justify sharing mutable request state across callers.

The broader principle is covered in dependency-injection lifetime guidance. Dependencies can have different lifetimes when their relationship is safe. The lifetime of the external resource does not force every service that uses it into the same scope.

Configure MongoClient in ASP.NET Core explicitly

The URI is the input to MongoClient, as shown by the official guide. An ASP.NET Core application can obtain it from configuration and refuse to start its composition successfully when the required value is absent. Avoid a silent localhost fallback that makes an invalid production configuration look like a valid application setup.

The following is a complete minimal Program.cs illustration for an ASP.NET Core project that references MongoDB.Driver 3.12.0. It uses the configuration keys Mongo:ConnectionString and Mongo:Database. Supply them through the application's normal configuration providers; do not place real credentials in the article or source control.

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
using MongoDB.Driver;

var builder = WebApplication.CreateBuilder(args);
var uri = builder.Configuration["Mongo:ConnectionString"]
    ?? throw new InvalidOperationException(
        "Mongo:ConnectionString is required.");
var databaseName = builder.Configuration["Mongo:Database"]
    ?? throw new InvalidOperationException(
        "Mongo:Database is required.");

builder.Services.AddSingleton<IMongoClient>(_ => new MongoClient(uri));
builder.Services.AddSingleton<IMongoDatabase>(services =>
    services.GetRequiredService<IMongoClient>().GetDatabase(databaseName));

var app = builder.Build();

app.MapGet("/orders/count", async (IMongoDatabase database) =>
{
    var orders = database.GetCollection<BsonDocument>("orders");
    var count = await orders.CountDocumentsAsync(
        Builders<BsonDocument>.Filter.Empty);
    return new { Count = count };
});

app.Run();

The example demonstrates registration and a read operation, not a public production endpoint design. Authorization, tenant restrictions, and the suitability of returning a collection count are application concerns. If run, the handler queries the configured collection; no successful count or timing is claimed here.

The factory registration also expresses an owner: the application container creates the client. That is different from receiving an externally created client instance whose owner lives elsewhere. Microsoft documents the distinction in its dependency-injection disposal guidance.

Do not infer from the registration that every cluster connection will be disposed when the container disposes the client. Container ownership and driver cluster ownership are different layers. That distinction is especially important when translating a familiar DI pattern into resource-cleanup promises.

Understand database and collection handles

The 3.12.0 client source constructs database handles with the client's cluster and operation executor. GetDatabase is therefore not analogous to creating a separate request-owned SQL connection and opening it before use.

A collection handle identifies a collection and its document type. Reusing that handle can make a service's dependencies clearer, but obtaining it is not proof of network access. A count, find, or another server operation is the boundary that can produce database-operation evidence.

This explains why checking that GetDatabase returned an object is not a readiness test. It checks object construction. It does not check whether credentials are valid for the intended operation, whether the target deployment is available, or whether stored documents can deserialize into an application model.

The existing MongoDB filtering tutorial can supply the operation-level predicate discussion. Client lifetime should remain understandable without embedding a complete filtering tutorial into the composition code.

Keep request services as borrowers

A request service can receive IMongoClient, choose its configured database and collection, and expose a narrow operation. The service does not become the owner of the client simply because it holds a reference to it.

Here is a separate complete minimal application showing a scoped reader. It intentionally uses BsonDocument so the example remains about lifetime rather than constructor mapping. A production typed model would need its own explicit BSON contract.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
using MongoDB.Driver;

var builder = WebApplication.CreateBuilder(args);
var uri = builder.Configuration["Mongo:ConnectionString"]
    ?? throw new InvalidOperationException(
        "Mongo:ConnectionString is required.");

builder.Services.AddSingleton<IMongoClient>(_ => new MongoClient(uri));
builder.Services.AddScoped<OrderReader>();

var app = builder.Build();
app.MapGet("/orders/{id}", async (
    string id,
    OrderReader reader,
    CancellationToken cancellationToken) =>
{
    var order = await reader.FindAsync(id, cancellationToken);
    return order is null
        ? Microsoft.AspNetCore.Http.Results.NotFound()
        : Microsoft.AspNetCore.Http.Results.Text(
            order.ToJson(), "application/json");
});

app.Run();

public sealed class OrderReader
{
    private readonly IMongoCollection<BsonDocument> _orders;

    public OrderReader(IMongoClient client)
    {
        _orders = client.GetDatabase("application")
            .GetCollection<BsonDocument>("orders");
    }

    public Task<BsonDocument?> FindAsync(
        string id,
        CancellationToken cancellationToken)
    {
        var filter = Builders<BsonDocument>.Filter.Eq("_id", id);
        return _orders.Find(filter)
            .FirstOrDefaultAsync(cancellationToken);
    }
}

This example explicitly assumes string _id values. It does not convert a route string into an ObjectId or imply that the two BSON types are equivalent. A document with an ObjectId _id would need a differently typed predicate.

The driver find guide documents FirstOrDefaultAsync returning no document when there is no match. The handler distinguishes that case from a returned document. It does not catch database exceptions and disguise them as an empty result.

Neither the handler nor OrderReader disposes the shared client. Request cancellation is passed to the operation instead. Ending one request and ending the application's resource owner are different events; the code keeps them different.

Read disposal claims against the selected version

MongoClient in ASP.NET Core deserves version-specific wording because disposal changed across driver generations. The current guide says MongoClient implements IDisposable in v3.0 and later. That is evidence for a method being available; it is not evidence that every resource behind the method is exclusively owned by that object.

The guide explicitly states that disposing MongoClient does not dispose the cluster and underlying connections. It points to ClusterRegistry.UnregisterAndDisposeCluster for those resources. A source-reviewed article should preserve that distinction rather than flatten it into “DI cleans everything up.”

The pinned MongoClient.Dispose(bool) implementation disposes its operation executor, returns the cluster through _settings.ClusterSource.Return(_cluster), disposes its encryption controller if present, and marks the client disposed. The constructor gets its cluster through the configured cluster source. These implementation details show why ownership must be examined at more than the client level.

The registry implementation has a dictionary of clusters keyed by configuration and a GetOrCreateCluster method. Its unregister method removes the matching registry entry and disposes the cluster. Calling that method is therefore materially different from merely ending a borrowed reference.

This article does not prescribe an unconditional shutdown callback that unregisters a cluster for every application. Such a callback would require knowing that no other client still depends on the cluster. An ownership policy should establish that fact rather than assume it because the application has only one visible registration.

Why shared clusters complicate cleanup

Imagine an application component creates a client while another library also creates one with equivalent relevant configuration. If infrastructure is shared, the visible client in your service collection is not the entire ownership picture. Manually disposing shared infrastructure could interfere with the other borrower.

The source establishes the existence of keyed registry reuse, not a guarantee that any two URI strings always share a cluster. Configuration details and the selected cluster source matter. Avoid promising “same URI means same pool” without verifying the effective settings and the version's cluster-key behavior.

The opposite mistake is assuming all resources are independent because two MongoClient objects were constructed. Object identity at the application layer does not prove independent infrastructure ownership. These are separate things to inspect.

Similar ownership questions appear when loading plugins into isolated contexts. A component's lifetime can end while something it used still belongs to a broader owner. The MongoDB-specific rule still comes from the driver's documentation and source, not from an analogy to plugins.

Configuration changes are not request state

A useful configuration boundary treats deployment settings as deliberate application inputs. Changing credentials, endpoints, or other client settings deserves an explicit replacement and ownership plan. It should not be implemented by mutating a shared object based on a request parameter.

The pinned client constructor uses a frozen copy of the supplied settings. That means a service should not assume changing the original settings instance later will reconfigure an already created client. Configuration reload and resource replacement need their own behavior, not an accidental dependency on mutable configuration objects.

This is one reason a factory can be helpful at the application composition boundary without turning every request into a client factory call. The boundary creates the configured resource once for its intended lifetime. A handler uses it to perform work.

The published singleton and dependency-injection explanation provides background on sharing a dependency. Its historical example is not a source for current driver disposal behavior; keep those evidence roles distinct.

What to verify without confusing the layers

For future verification, distinguish configuration parsing, dependency resolution, server access, and shutdown behavior. They are not interchangeable tests. Successfully resolving OrderReader would show that DI can create the service. It would not show that a query succeeds or that clusters are released safely.

A server-operation check should use the application's intended environment and permissions. An ownership check should include every component that creates a client, especially libraries or test fixtures. A shutdown check should examine the chosen driver behavior rather than assuming the presence of IDisposable settles it.

The MongoDB Testcontainers companion remains a separate resource for temporary database setup. No Docker environment, server operation, or shutdown experiment was run for this article.

Identify the owner before describing shutdown

The following distinctions summarize the documented ownership boundaries. They are not an additional cleanup mechanism. Their purpose is to make a shutdown explanation reviewable before any application introduces manual resource disposal.

Object or relationship What it establishes What it does not establish
Container-created singleton client Application-level reuse and container-managed client disposal Exclusive ownership of every cluster connection
Request-scoped reader A shorter-lived borrower of the shared client Permission to dispose that borrowed client
Database handle A configured database namespace using client infrastructure A successful server access check
Collection handle A collection namespace and document contract Evidence that stored documents satisfy the contract
Registered cluster Infrastructure that may be reused by keyed configuration A guarantee that only one client depends on it

For MongoClient in ASP.NET Core, this vocabulary helps keep operational explanations honest. “The host owns the client” and “the host exclusively owns the cluster” are different statements. The first can be visible in a factory registration. The second requires examining the components that create clients and the selected driver's sharing behavior.

For MongoClient in ASP.NET Core, an eventual shutdown review should therefore name both the owner and the borrowers. If that picture is incomplete, preserve the uncertainty rather than adding an unconditional unregister callback. A narrower documented statement is more useful than a convenient cleanup promise that has not been established.

Frequently asked questions

These answers summarize lifetime and ownership separately. They do not replace examining the concrete application's client registrations.

Should MongoClient in ASP.NET Core be a singleton?

The current driver documentation recommends singleton reuse. For an application with one configured deployment identity, that is the illustrated composition. Multiple configurations and distinct security boundaries may require different clients, each with explicit ownership.

Can a scoped service depend on a singleton client?

Yes, a shorter-lived service can borrow a longer-lived client. Keep request-specific state in the request service, and do not dispose the shared client when the request service ends. The borrowing relationship is the key distinction.

Does GetDatabase open a request connection?

The pinned source constructs a database handle using the client's infrastructure. Obtaining the handle is not a server-operation success check. Verify actual access with an appropriate operation in an authorized environment.

Does disposing MongoClient close every connection?

The current guide explicitly says client disposal does not dispose the cluster and underlying connections. The source returns the cluster through the configured cluster source. Do not describe the method as a universal closure guarantee.

Should application shutdown always unregister the cluster?

Not without establishing ownership. The registry method disposes infrastructure that might be shared. An application needs evidence about other clients and its selected cluster source before prescribing manual cluster cleanup.

Were the registration and shutdown examples exercised?

No. The examples are source-reviewed and the article makes no runtime success claim. Server access, dependency behavior in a concrete project, and resource cleanup remain items for separately authorized validation.

Keep the ownership explanation explicit

MongoClient in ASP.NET Core is not just a lifetime label. It is a configured application dependency with borrowers and version-sensitive infrastructure ownership. A singleton registration can express reuse clearly while still leaving cluster cleanup as a separate question.

Understanding that separation helps prevent both request-level disposal bugs and overconfident shutdown guarantees. The goal is not one universal architecture, but an ownership explanation that matches the selected driver and the application that actually uses it.

xUnit in ASP.NET Core - What You Need to Know to Start

Learn how to leverage xUnit in ASP.NET Core tests with simple code examples. See how to write easy-to-understand test code and cover your ASP.NET Core apps!

Autofac In ASP NET Core - How To Avoid This Debugging Nightmare

Learn how to use Autofac in ASP NET Core applications with only a couple of lines of code! Avoid this common problem when setting up dependency injection.

AutofacServiceProviderFactory in ASP.NET Core - Problems With Dependency Injection (Part 1)

Learn how to use AutofacServiceProviderFactory in ASP.NET Core for dependency injection! This article explains how to set it up along with the pros and cons.

An error has occurred. This application may no longer respond until reloaded. Reload