/
RequestContext.cs
172 lines (150 loc) · 5.61 KB
/
RequestContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
#if !NETCORE
using System.Runtime.Remoting.Messaging;
#else
using System.Threading;
#endif
namespace ServiceStack;
/// <summary>
/// Abstraction to provide a context per request.
/// in aspnet.web its equivalent to System.Web.HttpContext.Current.Items falls back to CallContext
/// </summary>
public class RequestContext
{
public static readonly RequestContext Instance = new RequestContext();
#if !NETCORE
/// <summary>
/// Tell ServiceStack to use ThreadStatic Items Collection for RequestScoped items.
/// Warning: ThreadStatic Items aren't pinned to the same request in async services which callback on different threads.
/// </summary>
public static bool UseThreadStatic;
[ThreadStatic]
public static IDictionary RequestItems;
#else
public static AsyncLocal<IDictionary> AsyncRequestItems = new AsyncLocal<IDictionary>();
#endif
/// <summary>
/// Start a new Request context, everything deeper in Async pipeline will get this new RequestContext dictionary.
/// </summary>
public void StartRequestContext()
{
// This fixes problems if the RequestContext.Instance.Items was touched on startup or outside of request context.
// It would turn it into a static dictionary instead flooding request with each-others values.
// This can already happen if I register a Funq.Container Request Scope type and Resolve it on startup.
CreateItems();
}
/// <summary>
/// Gets a list of items for this request.
/// </summary>
/// <remarks>This list will be cleared on every request and is specific to the original thread that is handling the request.
/// If a handler uses additional threads, this data will not be available on those threads.
/// </remarks>
public virtual IDictionary Items
{
get => GetItems() ?? CreateItems();
set => CreateItems(value);
}
private const string _key = "__Request.Items";
private IDictionary GetItems()
{
#if !NETCORE
try
{
if (UseThreadStatic)
return RequestItems;
//Don't init CallContext on Main Thread which inits copies in Request threads
if (!ServiceStackHost.IsReady())
return new Dictionary<object, object>();
if (System.Web.HttpContext.Current != null)
return System.Web.HttpContext.Current.Items;
return CallContext.LogicalGetData(_key) as IDictionary;
}
catch (NotImplementedException)
{
//Fixed in Mono master: https://github.com/mono/mono/pull/817
return CallContext.GetData(_key) as IDictionary;
}
#else
return AsyncRequestItems.Value;
#endif
}
private IDictionary CreateItems(IDictionary items = null)
{
#if !NETCORE
try
{
if (UseThreadStatic)
{
RequestItems = items ??= new Dictionary<object, object>();
}
else
{
CallContext.LogicalSetData(_key, items ??= new ConcurrentDictionary<object, object>());
}
}
catch (NotImplementedException)
{
//Fixed in Mono master: https://github.com/mono/mono/pull/817
CallContext.SetData(_key, items ??= new ConcurrentDictionary<object, object>());
}
return items;
#else
return AsyncRequestItems.Value = items ?? new Dictionary<object, object>();
#endif
}
public T GetOrCreate<T>(Func<T> createFn)
{
if (Items.Contains(typeof(T).Name))
return (T)Items[typeof(T).Name];
return (T)(Items[typeof(T).Name] = createFn());
}
public void EndRequest()
{
#if !NETCORE
if (UseThreadStatic)
Items = null;
else
CallContext.FreeNamedDataSlot(_key);
#else
//setting to AsyncLocal.Value to null does not really null it
//possible bug in .NET Core
AsyncRequestItems.Value?.Clear();
#endif
}
/// <summary>
/// Track any IDisposable's to dispose of at the end of the request in IAppHost.OnEndRequest()
/// </summary>
/// <param name="instance"></param>
public void TrackDisposable(IDisposable instance)
{
if (!ServiceStackHost.IsReady()) return;
if (instance == null) return;
if (instance is IService) return; //IService's are already disposed right after they've been executed
DisposableTracker disposableTracker = null;
if (!Items.Contains(DisposableTracker.HashId))
Items[DisposableTracker.HashId] = disposableTracker = new DisposableTracker();
if (disposableTracker == null)
disposableTracker = (DisposableTracker)Items[DisposableTracker.HashId];
disposableTracker.Add(instance);
}
/// <summary>
/// Release currently registered dependencies for this request
/// </summary>
/// <returns>true if any dependencies were released</returns>
public bool ReleaseDisposables()
{
if (!ServiceStackHost.IsReady()) return false;
if (!ServiceStackHost.Instance.Config.DisposeDependenciesAfterUse) return false;
var ctxItems = Instance.Items;
if (ctxItems[DisposableTracker.HashId] is DisposableTracker disposables)
{
disposables.Dispose();
ctxItems.Remove(DisposableTracker.HashId);
return true;
}
return false;
}
}