# Using TransactionalSession with Entity Framework and ASP.NET Core > [!WARNING] > NServiceBus integrates with ASP.NET Core 3.x [using `GenericHost`](/nservicebus/hosting/extensions-hosting.md). For older versions of ASP.NET Core use the community package [Community.NServiceBus.WebHost](https://github.com/timbussmann/Community.NServiceBus.WebHost). ASP.NET Core 2.x has a race condition and the community package implements a workaround. It's recommended to upgrade to ASP.NET Core 3.0 and use NServiceBus.Extensions.Hosting package. This sample shows how to send messages and modify data in a database atomically within the scope of a web request using the `NServiceBus.TransactionalSession` package with ASP.NET Core. The ASP.NET Core application hosts a [send-only endpoint](/nservicebus/endpoints/index.md#send-only). The operations are triggered by an incoming HTTP request to ASP.NET Core that will manage the `ITransactionalSession` lifetime using a request middleware. > [!NOTE] > Starting in version 8.2.0, `NServiceBus.Persistence.Sql.TransactionalSession` is supported in send-only endpoints. Refer to the [documentation](/nservicebus/transactional-session/index.md#remote-processor) for more details. ## Prerequisites Ensure an instance of SQL Server (Version 2012 or above) is installed and accessible on `localhost` and port `1433`. Alternatively, change the connection string to point to a different SQL Server instance. At startup, each endpoint will create the required SQL assets including databases, tables, and schemas. ## Running the solution When the solution is run, a new browser window/tab opens, as well as a console application. The browser will navigate to `http://localhost:58118/`. An async [WebAPI](https://dotnet.microsoft.com/apps/aspnet/apis) controller handles the request. It stores a new document using Entity Framework and sends an NServiceBus message to the endpoint hosted in the console application. The message is processed by the NServiceBus message handler in the `Sample.Receiver` project and results in `"Message received at endpoint"` printed to the console. In addition, the handler will update the previously created entity. To query all the stored entities, navigate to `http://localhost:58118/all`. To apply a complex object hierarchy using the transactional session on an endpoint, navigate to `http://localhost:58118/service`. ## Configuration The endpoint is configured using the `UseNServiceBus` extension method: ```cs var endpointConfiguration = new EndpointConfiguration("Samples.ASPNETCore.Sender"); endpointConfiguration.UseSerialization(); endpointConfiguration.EnableInstallers(); endpointConfiguration.UseTransport(new LearningTransport { TransportTransactionMode = TransportTransactionMode.ReceiveOnly }).RouteToEndpoint(typeof(MyMessage), "Sample.Receiver"); var persistence = endpointConfiguration.UsePersistence(); persistence.SqlDialect(); persistence.ConnectionBuilder(() => new SqlConnection(ConnectionString)); var transactionalSessionOptions = new TransactionalSessionOptions { ProcessorEndpoint = "TransactionalSessionProcessor" }; persistence.EnableTransactionalSession(transactionalSessionOptions); endpointConfiguration.EnableOutbox(); endpointConfiguration.SendOnly(); hostBuilder.Services.AddNServiceBusEndpoint(endpointConfiguration); ``` The transactional session is enabled via the `endpointConfiguration.EnableTransactionalSession()` method call. Note that the transactional session feature requires [the outbox](/nservicebus/outbox/index.md) to be configured to ensure that operations across the storage and the message broker are atomic. See the documentation on [transaction consistency](/nservicebus/transactional-session/index.md#transaction-consistency) for more details. When used together with the outbox in a send-only endpoint, the transactional session must be configured with a remote endpoint(processor endpoint) that will manage the outbox on behalf of the send-only endpoint. The remote endpoint can be specified using the `TransactionalSessionOptions`. ```cs var transactionalSessionOptions = new TransactionalSessionOptions { ProcessorEndpoint = "TransactionalSessionProcessor" }; persistence.EnableTransactionalSession(transactionalSessionOptions); ``` The processor endpoint must have both the outbox and the transactional session enabled. ```cs persistence.EnableTransactionalSession(); endpointConfiguration.EnableOutbox(); ``` ASP.NET Core uses `ConfigureWebHostDefaults` for configuration and a custom result filter is registered for the `ITransactionalSession` lifetime management: ```cs hostBuilder.Services.AddScoped(); hostBuilder.Services.AddControllers(o => o.Filters.AddService()); ``` Entity Framework support is configured by registering the `DbContext`: ```cs // Configure Entity Framework to attach to the synchronized storage session when required hostBuilder.Services.AddScoped(b => { if (b.GetService() is ISqlStorageSession { Connection: not null } session) { var context = new MyDataContext(new DbContextOptionsBuilder() .UseSqlServer(session.Connection) .Options); //Use the same underlying ADO.NET transaction context.Database.UseTransaction(session.Transaction); //Ensure context is flushed before the transaction is committed session.OnSaveChanges((s, cancellationToken) => context.SaveChangesAsync(cancellationToken)); return context; } else { var context = new MyDataContext(new DbContextOptionsBuilder() .UseSqlServer(ConnectionString) .Options); return context; } }); ``` The registration ensures that the `MyDataContext` type is built using the same session and transaction that is used by the `ITransactionalSession`. Once the transactional session is committed, it notifies the Entity Framework context to call `SaveChangesAsync`. When the transactional session is not used, a data context with a dedicated connection is returned. ## Using the session The message session is injected into `SendMessageController` via method injection. Message operations executed on the `ITransactionalSession` API are transactionally consistent with the database operations performed on the `MyDataContext`. ```cs [HttpGet] public async Task Get([FromServices] ITransactionalSession messageSession) { var id = Guid.NewGuid().ToString(); await dataContext.MyEntities.AddAsync(new MyEntity { Id = id, Processed = false }); var message = new MyMessage { EntityId = id }; await messageSession.Send(message); return $"Message with entity ID '{id}' sent to endpoint"; } ``` The lifecycle of the session is managed by the `MessageSessionFilter` which hooks into the [result filter](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0#iresultfilter-and-iasyncresultfilter) of the ASP.NET pipeline. When a controller action with an `ITransactionalSession` parameter is called, the filter opens the session, performs the next action, and then commits the session: ```cs public class MessageSessionFilter : IAsyncResourceFilter { public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { if (context.ActionDescriptor.Parameters.Any(p => p.ParameterType == typeof(ITransactionalSession))) { var session = context.HttpContext.RequestServices.GetRequiredService(); await session.Open(new SqlPersistenceOpenSessionOptions()); var result = await next(); if (result.Exception is null) { await session.Commit(); } } else { await next(); } } } ``` > [!NOTE] > The resource filter could be extended to return problem details (for example, with `context.Result = new ObjectResult(new ProblemDetails())`) in cases where the transactional session cannot be committed. This is omitted from the sample. For controller actions that do not have `ITransactionalSession` parameter, navigate to `http://localhost:58118/all`, a data context with a dedicated connection is used. ```cs [HttpGet("/all")] public async Task> GetAll() { return await dataContext.MyEntities.ToListAsync(); } ``` > [!NOTE] > The sample uses method injection as an opinionated way of expressing the need for having the transactional boundaries managed by the infrastructure. If it is preferred to express the transactional boundaries with an attribute to make sure even complex dependency chains get access to the transactional session, without needing to inject that into the controller action, an [action attribute](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?#action-filters) must be used to annotate controllers or actions. For example, navgiate to `http://localhost:58118/service` which injects a service that depends on `ITransactionalSession`. ```cs [HttpGet("/service")] [RequiresTransactionalSession] public async Task Get([FromServices] ServiceUsingTransactionalSession service) { var id = await service.Execute(); return $"Message with entity ID '{id}' sent to endpoint"; } ``` The `[RequiresTransactionalSession]` attribute makes sure the session is opened and committed. ```cs public sealed class RequiresTransactionalSessionAttribute() : TypeFilterAttribute(typeof(TransactionalSessionFilter)) { class TransactionalSessionFilter(ITransactionalSession transactionalSession) : IAsyncResourceFilter { public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { await transactionalSession.Open(new SqlPersistenceOpenSessionOptions()); var result = await next(); if (result.Exception is null) { await transactionalSession.Commit(); } } } } ``` as long as the attribute is registered in the configuration ```cs hostBuilder.Services.AddScoped(); hostBuilder.Services.AddScoped(); ``` This diagram visualizes the interaction between the resource filter, `ITransactionalSession`, and the Web API controller: ```mermaid sequenceDiagram autonumber User->>Filter: Http Request activate Filter Filter->>TransactionalSession: Open activate TransactionalSession TransactionalSession-->>Filter: Reply Filter->>Controller: next() activate Controller Controller->>TransactionalSession: Send/Publish... Controller->>TransactionalSession: Use SynchronizedStorageSession deactivate Controller Filter->>TransactionalSession: Commit deactivate TransactionalSession Filter-->>User: Reply deactivate Filter ``` ## Handling the message The `MyHandler` handles the message sent by the WebAPI and accesses the previously committed data stored by the controller: ```cs public class MyHandler(MyDataContext dataContext, ILogger logger) : IHandleMessages { public async Task Handle(MyMessage message, IMessageHandlerContext context) { logger.LogInformation("Message received at endpoint"); var entity = await dataContext.MyEntities.Where(e => e.Id == message.EntityId) .FirstAsync(cancellationToken: context.CancellationToken); entity.Processed = true; } } ```