forked from NancyFx/Nancy
-
Notifications
You must be signed in to change notification settings - Fork 9
/
NancyModule.cs
342 lines (302 loc) · 13.3 KB
/
NancyModule.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
namespace Nancy
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using Nancy.Configuration;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Routing;
using Nancy.Session;
using Nancy.Validation;
using Nancy.ViewEngines;
/// <summary>
/// Basic class containing the functionality for defining routes and actions in Nancy.
/// </summary>
public abstract class NancyModule : INancyModule, IHideObjectMembers
{
private readonly List<Route> routes;
/// <summary>
/// Initializes a new instance of the <see cref="NancyModule"/> class.
/// </summary>
protected NancyModule()
: this(String.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NancyModule"/> class.
/// </summary>
/// <param name="modulePath">A <see cref="string"/> containing the root relative path that all paths in the module will be a subset of.</param>
protected NancyModule(string modulePath)
{
this.After = new AfterPipeline();
this.Before = new BeforePipeline();
this.OnError = new ErrorPipeline();
this.ModulePath = modulePath;
this.routes = new List<Route>();
}
/// <summary>
/// Non-model specific data for rendering in the response
/// </summary>
public dynamic ViewBag
{
get
{
return this.Context == null ? null : this.Context.ViewBag;
}
}
public dynamic Text
{
get { return this.Context.Text; }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for DELETE requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Delete
{
get { return new RouteBuilder("DELETE", this); }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for GET requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Get
{
get { return new RouteBuilder("GET", this); }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for HEAD requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Head
{
get { return new RouteBuilder("HEAD", this); }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for OPTIONS requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Options
{
get { return new RouteBuilder("OPTIONS", this); }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for PATCH requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Patch
{
get { return new RouteBuilder("PATCH", this); }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for POST requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Post
{
get { return new RouteBuilder("POST", this); }
}
/// <summary>
/// Gets <see cref="RouteBuilder"/> for declaring actions for PUT requests.
/// </summary>
/// <value>A <see cref="RouteBuilder"/> instance.</value>
public RouteBuilder Put
{
get { return new RouteBuilder("PUT", this); }
}
/// <summary>
/// Get the root path of the routes in the current module.
/// </summary>
/// <value>
/// A <see cref="T:System.String" /> containing the root path of the module or <see langword="null" />
/// if no root path should be used.</value><remarks>All routes will be relative to this root path.
/// </remarks>
public string ModulePath { get; protected set; }
/// <summary>
/// Gets all declared routes by the module.
/// </summary>
/// <value>A <see cref="IEnumerable{T}"/> instance, containing all <see cref="Route"/> instances declared by the module.</value>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual IEnumerable<Route> Routes
{
get { return this.routes.AsReadOnly(); }
}
/// <summary>
/// Gets the current session.
/// </summary>
public ISession Session
{
get { return this.Request.Session; }
}
/// <summary>
/// Renders a view from inside a route handler.
/// </summary>
/// <value>A <see cref="ViewRenderer"/> instance that is used to determine which view that should be rendered.</value>
public ViewRenderer View
{
get { return new ViewRenderer(this); }
}
/// <summary>
/// Used to negotiate the content returned based on Accepts header.
/// </summary>
/// <value>A <see cref="Negotiator"/> instance that is used to negotiate the content returned.</value>
public Negotiator Negotiate
{
get { return new Negotiator(this.Context); }
}
/// <summary>
/// Gets or sets the validator locator.
/// </summary>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public IModelValidatorLocator ValidatorLocator { get; set; }
/// <summary>
/// Gets or sets an <see cref="Request"/> instance that represents the current request.
/// </summary>
/// <value>An <see cref="Request"/> instance.</value>
public virtual Request Request
{
get { return this.Context.Request; }
set { this.Context.Request = value; }
}
/// <summary>
/// The extension point for accessing the view engines in Nancy.
/// </summary><value>An <see cref="IViewFactory" /> instance.</value>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public IViewFactory ViewFactory { get; set; }
/// <summary><para>
/// The post-request hook
/// </para><para>
/// The post-request hook is called after the response is created by the route execution.
/// It can be used to rewrite the response or add/remove items from the context.
/// </para>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
/// </summary>
public AfterPipeline After { get; set; }
/// <summary>
/// <para>
/// The pre-request hook
/// </para>
/// <para>
/// The PreRequest hook is called prior to executing a route. If any item in the
/// pre-request pipeline returns a response then the route is not executed and the
/// response is returned.
/// </para>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
/// </summary>
public BeforePipeline Before { get; set; }
/// <summary>
/// <para>
/// The error hook
/// </para>
/// <para>
/// The error hook is called if an exception is thrown at any time during executing
/// the PreRequest hook, a route and the PostRequest hook. It can be used to set
/// the response and/or finish any ongoing tasks (close database session, etc).
/// </para>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
/// </summary>
public ErrorPipeline OnError { get; set; }
/// <summary>
/// Gets or sets the current Nancy context
/// </summary>
/// <value>A <see cref="NancyContext" /> instance.</value>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
public NancyContext Context { get; set; }
/// <summary>
/// An extension point for adding support for formatting response contents.
/// </summary><value>This property will always return <see langword="null" /> because it acts as an extension point.</value><remarks>Extension methods to this property should always return <see cref="P:Nancy.NancyModuleBase.Response" /> or one of the types that can implicitly be types into a <see cref="P:Nancy.NancyModuleBase.Response" />.</remarks>
public IResponseFormatter Response { get; set; }
/// <summary>
/// Gets or sets the model binder locator
/// </summary>
/// <remarks>This is automatically set by Nancy at runtime.</remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public IModelBinderLocator ModelBinderLocator { get; set; }
/// <summary>
/// Gets or sets the model validation result
/// </summary>
/// <remarks>This is automatically set by Nancy at runtime when you run validation.</remarks>
public virtual ModelValidationResult ModelValidationResult
{
get { return this.Context == null ? null : this.Context.ModelValidationResult; }
set
{
if (this.Context != null)
{
this.Context.ModelValidationResult = value;
}
}
}
/// <summary>
/// Helper class for configuring a route handler in a module.
/// </summary>
public class RouteBuilder : IHideObjectMembers
{
private readonly string method;
private readonly NancyModule parentModule;
/// <summary>
/// Initializes a new instance of the <see cref="RouteBuilder"/> class.
/// </summary>
/// <param name="method">The HTTP request method that the route should be available for.</param>
/// <param name="parentModule">The <see cref="INancyModule"/> that the route is being configured for.</param>
public RouteBuilder(string method, NancyModule parentModule)
{
this.method = method;
this.parentModule = parentModule;
}
/// <summary>
/// Defines an async route for the specified <paramref name="path"/>
/// </summary>
public Func<dynamic, CancellationToken, Task<dynamic>> this[string path]
{
set { this.AddRoute(string.Empty, path, null, value); }
}
/// <summary>
/// Defines an async route for the specified <paramref name="path"/> and <paramref name="condition"/>.
/// </summary>
public Func<dynamic, CancellationToken, Task<dynamic>> this[string path, Func<NancyContext, bool> condition]
{
set { this.AddRoute(string.Empty, path, condition, value); }
}
/// <summary>
/// Defines an async route for the specified <paramref name="path"/> and <paramref name="name"/>
/// </summary>
public Func<dynamic, CancellationToken, Task<dynamic>> this[string name, string path]
{
set { this.AddRoute(name, path, null, value); }
}
/// <summary>
/// Defines an async route for the specified <paramref name="path"/>, <paramref name="condition"/> and <paramref name="name"/>
/// </summary>
public Func<dynamic, CancellationToken, Task<dynamic>> this[string name, string path, Func<NancyContext, bool> condition]
{
set { this.AddRoute(name, path, condition, value); }
}
protected void AddRoute(string name, string path, Func<NancyContext, bool> condition, Func<dynamic, CancellationToken, Task<dynamic>> value)
{
var fullPath = GetFullPath(path);
this.parentModule.routes.Add(new Route(name, this.method, fullPath, condition, value));
}
private string GetFullPath(string path)
{
var relativePath = (path ?? string.Empty).Trim('/');
var parentPath = (this.parentModule.ModulePath ?? string.Empty).Trim('/');
if (string.IsNullOrEmpty(parentPath))
{
return string.Concat("/", relativePath);
}
if (string.IsNullOrEmpty(relativePath))
{
return string.Concat("/", parentPath);
}
return string.Concat("/", parentPath, "/", relativePath);
}
}
}
}