This repository has been archived by the owner on Jan 24, 2021. It is now read-only.
/
NancyModule.cs
348 lines (309 loc) · 14.1 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
343
344
345
346
347
348
namespace Nancy
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Routing;
using Nancy.Session;
using Nancy.Validation;
using Nancy.ViewEngines;
/// <summary>
/// Contains the functionality for defining routes and actions in Nancy.
/// </summary>
/// <value>This is the core type in the entire framework and changes to this class should not be very frequent because it represents a change to the core API of the framework.</value>
public abstract class NancyModule : 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>
/// <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>
/// </summary>
public AfterPipeline After { get; protected 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>
/// </summary>
public BeforePipeline Before { get; protected 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>
/// </summary>
public ErrorPipeline OnError { get; protected set; }
/// <summary>
/// Gets or sets the current Nancy context
/// </summary>
/// <value>A <see cref="NancyContext"/> instance.</value>
public NancyContext Context { get; set; }
/// <summary>
/// Non-model specific data for rendering in the response
/// </summary>
public dynamic ViewBag
{
get
{
return this.Context == null ? null : this.Context.ViewBag;
}
}
/// <summary>
/// Gets or sets the model validation result
/// </summary>
public ModelValidationResult ModelValidationResult
{
get { return this.Context == null ? null : this.Context.ModelValidationResult; }
set
{
if (this.Context != null)
{
this.Context.ModelValidationResult = value;
}
}
}
/// <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>
/// <remarks>These actions will also be used when a HEAD request is recieved.</remarks>
public RouteBuilder Get
{
get { return new RouteBuilder("GET", this); }
}
/// <summary>
/// Get the root path of the routes in the current module.
/// </summary>
/// <value>A <see cref="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; private set; }
/// <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>
/// Gets or sets an <see cref="Request"/> instance that represents the current request.
/// </summary>
/// <value>An <see cref="Request"/> instance.</value>
public Request Request
{
get { return this.Context.Request; }
set { this.Context.Request = value; }
}
/// <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>
public IEnumerable<Route> Routes
{
get { return this.routes.AsReadOnly(); }
}
/// <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="Response"/> or one of the types that can implicitly be types into a <see cref="Response"/>.</remarks>
public IResponseFormatter Response { get; set; }
/// <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 determin which view that should be rendered.</value>
public ViewRenderer View
{
get { return new ViewRenderer(this); }
}
public Negotiator Negotiate
{
get { return new Negotiator(this.Context); }
}
/// <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>
/// Gets or sets the model binder locator
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IModelBinderLocator ModelBinderLocator { get; set; }
/// <summary>
/// Gets or sets the validator locator.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IModelValidatorLocator ValidatorLocator { get; set; }
/// <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="NancyModule"/> that the route is being configured for.</param>
public RouteBuilder(string method, NancyModule parentModule)
{
this.method = method;
this.parentModule = parentModule;
}
/// <summary>
/// Defines a Nancy route for the specified <paramref name="path"/>.
/// </summary>
/// <value>A delegate that is used to invoke the route.</value>
public Func<dynamic, dynamic> this[string path]
{
set { this.AddRoute(path, null, value); }
}
/// <summary>
/// Defines a Nancy route for the specified <paramref name="path"/> and <paramref name="condition"/>.
/// </summary>
/// <value>A delegate that is used to invoke the route.</value>
public Func<dynamic, dynamic> this[string path, Func<NancyContext, bool> condition]
{
set { this.AddRoute(path, condition, value); }
}
private void AddRoute(string path, Func<NancyContext, bool> condition, Func<dynamic, dynamic> value)
{
var fullPath = String.Concat(this.parentModule.ModulePath, path);
this.parentModule.routes.Add(new Route(this.method, fullPath, condition, value));
}
}
/// <summary>
/// Helper class for rendering a view from a route handler.
/// </summary>
public class ViewRenderer : IHideObjectMembers
{
private readonly NancyModule module;
/// <summary>
/// Initializes a new instance of the <see cref="ViewRenderer"/> class.
/// </summary>
/// <param name="module">The <see cref="NancyModule"/> instance that is rendering the view.</param>
public ViewRenderer(NancyModule module)
{
this.module = module;
}
/// <summary>
/// Renders the view with its name resolved from the model type, and model defined by the <paramref name="model"/> parameter.
/// </summary>
/// <param name="model">The model that should be passed into the view.</param>
/// <returns>A delegate that can be invoked with the <see cref="Stream"/> that the view should be rendered to.</returns>
/// <remarks>The view name is model.GetType().Name with any Model suffix removed.</remarks>
public Negotiator this[dynamic model]
{
get { return this.GetNegotiator(null, model); }
}
/// <summary>
/// Renders the view with the name defined by the <paramref name="viewName"/> parameter.
/// </summary>
/// <param name="viewName">The name of the view to render.</param>
/// <returns>A delegate that can be invoked with the <see cref="Stream"/> that the view should be rendered to.</returns>
/// <remarks>The extension in the view name is optional. If it is omitted, then Nancy will try to resolve which of the available engines that should be used to render the view.</remarks>
public Negotiator this[string viewName]
{
get { return this.GetNegotiator(viewName, null); }
}
/// <summary>
/// Renders the view with the name and model defined by the <paramref name="viewName"/> and <paramref name="model"/> parameters.
/// </summary>
/// <param name="viewName">The name of the view to render.</param>
/// <param name="model">The model that should be passed into the view.</param>
/// <returns>A delegate that can be invoked with the <see cref="Stream"/> that the view should be rendered to.</returns>
/// <remarks>The extension in the view name is optional. If it is omitted, then Nancy will try to resolve which of the available engines that should be used to render the view.</remarks>
public Negotiator this[string viewName, dynamic model]
{
get { return this.GetNegotiator(viewName, model); }
}
private Negotiator GetNegotiator(string viewName, object model)
{
var negotiationContext = this.module.Context.NegotiationContext;
negotiationContext.ViewName = viewName;
negotiationContext.DefaultModel = model;
negotiationContext.PermissableMediaRanges.Clear();
negotiationContext.PermissableMediaRanges.Add("text/html");
return new Negotiator(this.module.Context);
}
}
}
}