Skip to content

Commit 4181997

Browse files
committed
Brand new concept of Web extension
- takes into account features of AspNetCore - passes instances of session/transaction scope directly to Controller/Action/RazorPage depending on user configures it. User have full control of session and transaction. - fair amount of castomization in Filter and Middleware. User can define his own behavior through override of certain virtual methods
1 parent 51f71c0 commit 4181997

File tree

8 files changed

+477
-30
lines changed

8 files changed

+477
-30
lines changed

Extensions/Xtensive.Orm.Web/ApplicationBuilderExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (C) 2019-2020 Xtensive LLC.
1+
// Copyright (C) 2019-2020 Xtensive LLC.
22
// This code is distributed under MIT license terms.
33
// See the License.txt file in the project root for more information.
44

@@ -18,6 +18,7 @@ public static class ApplicationBuilderExtensions
1818
/// </summary>
1919
/// <param name="builder"><see cref="IApplicationBuilder"/> instance.</param>
2020
/// <returns><paramref name="builder"/> with <see cref="SessionManager"/>.</returns>
21+
[Obsolete]
2122
public static IApplicationBuilder UseSessionManager(this IApplicationBuilder builder)
2223
{
2324
return builder.UseMiddleware<SessionManager>();
@@ -29,6 +30,7 @@ public static IApplicationBuilder UseSessionManager(this IApplicationBuilder bui
2930
/// <param name="builder"><see cref="IApplicationBuilder"/> instance.</param>
3031
/// <param name="sessionProvider">User-defined session provider which will be used instead of built-in provider.</param>
3132
/// <returns><paramref name="builder"/> with <see cref="SessionManager"/>.</returns>
33+
[Obsolete]
3234
public static IApplicationBuilder UseSessionManager(this IApplicationBuilder builder, Func<Pair<Session, System.IDisposable>> sessionProvider)
3335
{
3436
SessionManager.SessionProvider = sessionProvider;

Extensions/Xtensive.Orm.Web/DomainBuilder.cs

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright (C) 2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using Microsoft.AspNetCore.Http;
7+
using System.Threading.Tasks;
8+
using System.Runtime.ExceptionServices;
9+
10+
namespace Xtensive.Orm.Web
11+
{
12+
13+
/// <summary>
14+
/// DataObjects.Net Session providing middleware.
15+
/// </summary>
16+
public class OpenSessionMiddlewere
17+
{
18+
private readonly RequestDelegate next;
19+
20+
/// <summary>
21+
/// Opens session and puts it to registered <see cref="SessionAccessor"/> service before
22+
/// next middleware execution and releases resources when execution returns.
23+
/// </summary>
24+
/// <param name="context">The <see cref="HttpContext"/>.</param>
25+
/// <returns>Task perfroming this operation.</returns>
26+
public async Task Invoke(HttpContext context)
27+
{
28+
var sessionAccessor = (SessionAccessor) context.RequestServices.GetService(WellKnownTypes.SessionAccessorType);
29+
if (sessionAccessor != null) {
30+
var domain = GetDomainFromServices(context);
31+
var session = await OpenSessionAsync(domain, context);
32+
var transaction = await OpenTransactionAsync(session, context);
33+
BindResourcesToContext(session, transaction, context);
34+
35+
using (sessionAccessor.BindHttpContext(context)) {
36+
try {
37+
await next.Invoke(context);
38+
}
39+
catch(Exception exception) {
40+
// if we caught exception here then
41+
// 1) it is unhendeled exception
42+
// or
43+
// 2) it was thrown intentionally
44+
if(CompleteTransactionOnException(exception, context)) {
45+
transaction.Complete();
46+
}
47+
if(RethrowException(exception, context)) {
48+
ExceptionDispatchInfo.Throw(exception);
49+
}
50+
}
51+
finally {
52+
RemoveResourcesFromContext(context);
53+
await transaction.DisposeAsync();
54+
await session.DisposeAsync();
55+
}
56+
}
57+
}
58+
else {
59+
await next.Invoke(context);
60+
}
61+
}
62+
63+
/// <summary>
64+
/// Opens session asynchronously.
65+
/// </summary>
66+
/// <param name="domain">Domain instance</param>
67+
/// <param name="context">Action executing context</param>
68+
/// <returns>Instance of <see cref="Session"/>.</returns>
69+
protected virtual Task<Session> OpenSessionAsync(Domain domain, HttpContext context) => domain.OpenSessionAsync();
70+
71+
/// <summary>
72+
/// Opens transaction scope asynchronously.
73+
/// </summary>
74+
/// <param name="session">Domain instance.</param>
75+
/// <param name="context">Action executing context.</param>
76+
/// <returns>Instance of <see cref="TransactionScope"/>.</returns>
77+
protected virtual async Task<TransactionScope> OpenTransactionAsync(Session session, HttpContext context) =>
78+
await session.OpenTransactionAsync();
79+
80+
/// <summary>
81+
/// Determines whether transaction should be completed even though exception appeared.
82+
/// </summary>
83+
/// <param name="thrownException">Thrown exception instance.</param>
84+
/// <param name="context">The <see cref="HttpContext"/>.</param>
85+
/// <returns><see langword="true"/>if transaction should be rollbacked, otherwise, <see langword="false"/>.</returns>
86+
protected virtual bool CompleteTransactionOnException(Exception thrownException, HttpContext context) => false;
87+
88+
/// <summary>
89+
/// Determines whether exception should be re-thrown.
90+
/// </summary>
91+
/// <param name="thrownException">Thrown exception instance.</param>
92+
/// <param name="context">The <see cref="HttpContext"/>.</param>
93+
/// <returns><see langword="true"/>if the exception should be re-thrown, otherwise, <see langword="false"/>.</returns>
94+
protected virtual bool RethrowException(Exception thrownException, HttpContext context) => true;
95+
96+
private static void BindResourcesToContext(Session session, TransactionScope transactionScope, HttpContext context)
97+
{
98+
context.Items[SessionAccessor.SessionIdentifier] = session;
99+
context.Items[SessionAccessor.ScopeIdentifier] = transactionScope;
100+
}
101+
102+
private static void RemoveResourcesFromContext(HttpContext context)
103+
{
104+
_ = context.Items.Remove(SessionAccessor.ScopeIdentifier);
105+
_ = context.Items.Remove(SessionAccessor.SessionIdentifier);
106+
}
107+
108+
private static Domain GetDomainFromServices(HttpContext context)
109+
{
110+
var domain = (Domain) context.RequestServices.GetService(WellKnownTypes.DomainType);
111+
return domain == null
112+
? throw new InvalidOperationException("Domain is not found among registered services.")
113+
: domain;
114+
}
115+
116+
/// <summary>
117+
/// Creates an instance of <see cref="OpenSessionMiddlewere"/>
118+
/// </summary>
119+
/// <param name="next"></param>
120+
public OpenSessionMiddlewere(RequestDelegate next)
121+
{
122+
this.next = next;
123+
}
124+
}
125+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (C) 2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using Xtensive.Core;
7+
using Microsoft.AspNetCore.Http;
8+
9+
namespace Xtensive.Orm.Web
10+
{
11+
/// <summary>
12+
/// A wrapper that provides access <see cref="Xtensive.Orm.Session">Session</see>
13+
/// and <see cref="Xtensive.Orm.TransactionScope">TransactionScope</see>.
14+
/// </summary>
15+
public sealed class SessionAccessor
16+
{
17+
internal static readonly object SessionIdentifier = new object();
18+
internal static readonly object ScopeIdentifier = new object();
19+
20+
private HttpContext context;
21+
22+
/// <summary>
23+
/// Provides session from bound to <see cref="HttpContext"/>.
24+
/// </summary>
25+
public Session Session =>
26+
context != null && context.Items.TryGetValue(SessionIdentifier, out var instance)
27+
? (Session) instance
28+
: null;
29+
30+
/// <summary>
31+
/// Provides opened transaction scope bound to <see cref="HttpContext"/>.
32+
/// </summary>
33+
public TransactionScope TransactionScope =>
34+
context != null && context.Items.TryGetValue(TransactionScope, out var instance)
35+
? (TransactionScope) instance
36+
: null;
37+
38+
internal bool ContextIsBound => context != null;
39+
40+
internal IDisposable BindHttpContext(HttpContext context)
41+
{
42+
this.context = context;
43+
return new Disposable(NullTheContext);
44+
}
45+
46+
private void NullTheContext(bool disposing) => context = null;
47+
}
48+
}

Extensions/Xtensive.Orm.Web/SessionManager.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
// See the License.txt file in the project root for more information.
44

55
using System;
6+
using System.Linq;
67
using Xtensive.Core;
78
using Microsoft.AspNetCore.Http;
89
using Microsoft.AspNetCore.Builder;
910
using System.Threading.Tasks;
1011
using System.Threading;
12+
using Microsoft.AspNetCore.Mvc.Filters;
1113

1214
namespace Xtensive.Orm.Web
1315
{
1416
/// <summary>
1517
/// Provides access to current <see cref="Session"/>.
1618
/// </summary>
19+
[Obsolete]
1720
public class SessionManager
1821
{
1922
private static SessionManager current;
@@ -105,6 +108,22 @@ public bool HasErrors
105108
/// <returns>Task</returns>
106109
public async Task Invoke(HttpContext context)
107110
{
111+
var accessor = (SessionAccessor)context.RequestServices.GetService(typeof(SessionAccessor));
112+
using (accessor.BindHttpContext(context)) {
113+
BeforeActions(context);
114+
try {
115+
await nextMiddlewareRunner.Invoke(context);
116+
}
117+
catch (Exception) {
118+
var
119+
HasErrors = true;
120+
throw;
121+
}
122+
finally {
123+
AfterActions(context);
124+
}
125+
126+
}
108127
BeforeActions(context);
109128

110129
try
@@ -208,4 +227,26 @@ public SessionManager(RequestDelegate next)
208227
current = this;
209228
}
210229
}
230+
231+
232+
public class CustomActionFilter : IActionFilter
233+
{
234+
235+
236+
public void OnActionExecuted(ActionExecutedContext context)
237+
{
238+
239+
}
240+
241+
public void OnActionExecuting(ActionExecutingContext context)
242+
{
243+
var sessionParameter = context.ActionDescriptor.Parameters.FirstOrDefault(p => p.ParameterType == typeof(Session));
244+
context.ActionArguments[sessionParameter.Name] = OpenSession();
245+
}
246+
247+
private Session OpenSession()
248+
{
249+
return null
250+
}
251+
}
211252
}

0 commit comments

Comments
 (0)