-
-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathRazorTemplateBase.cs
More file actions
564 lines (495 loc) · 20.3 KB
/
RazorTemplateBase.cs
File metadata and controls
564 lines (495 loc) · 20.3 KB
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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
#pragma warning disable CS1591
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
namespace Westwind.RazorHosting
{
/// <summary>
/// Base class used for Razor Page Templates - Razor generates
/// a class from the parsed Razor markup and this class is the
/// base class. Class must implement an Execute() method that
/// is overridden by the parser and contains the code that generates
/// the markup. Write() and WriteLiteral() must be implemented
/// to handle output generation inside of the Execute() generated
/// code.
///
/// This class can be subclassed to provide custom functionality.
/// One common feature likely will be to provide Context style properties
/// that are application specific (ie. HelpBuilderContext) and strongly
/// typed and easily accesible in Razor markup code.
/// </summary>
public class RazorTemplateBase<TModel> : RazorTemplateBase
where TModel : class, new()
{
/// <summary>
/// Create a strongly typed model
/// </summary>
public new TModel Model { get; set; }
/// <summary>
/// This method is called upon instantiation
/// and allows passing custom configuration
/// data to the template from the Engine.
///
/// This method can then be overridden
/// </summary>
/// <param name="model"></param>
/// <param name="configurationData"></param>
public override void InitializeTemplate(object model = null, object configurationData = null)
{
Html = new HtmlHelper();
if (model is TModel)
Model = model as TModel;
}
}
/// <summary>
/// Base class used for Razor Page Templates - Razor generates
/// a class from the parsed Razor markup and this class is the
/// base class. Class must implement an Execute() method that
/// is overridden by the parser and contains the code that generates
/// the markup. Write() and WriteLiteral() must be implemented
/// to handle output generation inside of the Execute() generated
/// code.
///
/// This class can be subclassed to provide custom functionality.
/// One common feature likely will be to provide Context style properties
/// that are application specific (ie. HelpBuilderContext) and strongly
/// typed and easily accesible in Razor markup code.
/// </summary>
public class RazorTemplateBase : MarshalByRefObject, IDisposable
{
/// <summary>
/// You can pass in a generic context object
/// to use in your template code
/// </summary>
public dynamic Model { get; set; }
/// <summary>
/// Simplistic Html Helper implementation
/// </summary>
public HtmlHelper Html { get; set; }
/// <summary>
/// An optional result property that can receive a
/// a processing result that can be passed back to the
/// the caller.
/// </summary>
public dynamic ResultData { get; set; }
/// <summary>
/// Class that generates output. Currently ultra simple
/// with only Response.Write() implementation.
/// </summary>
public RazorResponse Response { get; set; }
/// <summary>
/// Class that provides request specific information.
/// May or may not have its member data set.
/// </summary>
public RazorRequest Request { get; set; }
/// <summary>
/// Instance of the HostContainer that is hosting
/// this Engine instance. Note that this may be null
/// if no HostContainer is used.
///
/// Note this object needs to be cast to the
/// the appropriate Host Container
/// </summary>
public object HostContainer { get; set; }
/// <summary>
/// Instance of the RazorEngine object.
/// </summary>
public object Engine { get; set; }
public RazorTemplateConfiguration TemplateConfigData
{
get
{
if (_templateConfigData != null)
return _templateConfigData;
var engine = Engine as RazorEngine;
var config = engine?.TemplatePerRequestConfigurationData as RazorTemplateConfiguration;
_templateConfigData = config;
return config;
}
}
private RazorTemplateConfiguration _templateConfigData;
/// <summary>
/// This method is called upon instantiation
/// and allows passing custom configuration
/// data to the template from the Engine.
///
/// This method can then be overridden
/// </summary>
/// <param name="model">Model to to render with - optional</param>
/// <param name="configurationData">Configuration data you want to send to the template</param>
public virtual void InitializeTemplate(object model = null, object configurationData = null)
{
Html = new HtmlHelper();
Model = model;
}
public RazorTemplateBase()
{
Response = new RazorResponse();
Request = new RazorRequest();
}
/// <summary>
/// Writes a literal string. Used to write generic text
/// from the page markup (ie. non-expression text)
/// </summary>
/// <param name="value"></param>
public virtual void WriteLiteral(object value)
{
if (value == null)
return;
Response.Write(value);
}
/// <summary>
/// Writes an expression value. This value is HtmlEncoded always
/// </summary>
/// <param name="value"></param>
public virtual void Write(object value = null)
{
if (value == null)
return;
if (value is RawString || value is IHtmlString)
{
// Write as raw string without encoding
WriteLiteral(value.ToString());
}
else
{
// For HTML output we'd probably want to HTMLEncode everything
// But not for plain text templating
WriteLiteral(Utilities.HtmlEncode(value));
}
}
public virtual void WriteTo(TextWriter writer, object value)
{
if (value == null)
return;
if (value is RawString | value is IHtmlString)
{
writer.Write(value);
}
else
{
writer.Write(HtmlEncode(value));
}
}
/// <summary>
/// Writes the provided <paramref name="value"/>, as a literal, to the provided <paramref name="writer"/>.
/// </summary>
/// <param name="writer">The <see cref="TextWriter"/> that should be written to.</param>
/// <param name="value">The value that should be written as a literal.</param>
public virtual void WriteLiteralTo(TextWriter writer, object value)
{
if (value == null)
return;
writer.Write(value);
}
/// <summary>
/// WriteAttribute implementation lifted from ANurse's MicroRazor Implementation
/// and the AspWebStack source.
/// </summary>
/// <param name="name"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <param name="values"></param>
public virtual void WriteAttribute(string name, PositionTagged<string> prefix, PositionTagged<string> suffix, params AttributeValue[] values)
{
WriteAttributeTo(Response.Writer, name, prefix, suffix, values);
}
/// <summary>
/// WriteAttributeTo implementation lifted from ANurse's MicroRazor Implementation
/// and the AspWebStack source.
/// </summary>
/// <param name="writer"></param>
/// <param name="name"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <param name="values"></param>
public virtual void WriteAttributeTo(TextWriter writer, string name, PositionTagged<string> prefix, PositionTagged<string> suffix, params AttributeValue[] values)
{
bool first = true;
bool wroteSomething = false;
if (values.Length == 0)
{
// Explicitly empty attribute, so write the prefix and suffix
WritePositionTaggedLiteral(writer, prefix);
WritePositionTaggedLiteral(writer, suffix);
}
else
{
for (int i = 0; i < values.Length; i++)
{
AttributeValue attrVal = values[i];
PositionTagged<object> val = attrVal.Value;
PositionTagged<string> next = i == values.Length - 1 ?
suffix : // End of the list, grab the suffix
values[i + 1].Prefix; // Still in the list, grab the next prefix
bool? boolVal = null;
if (val.Value is bool)
{
boolVal = (bool)val.Value;
}
if (val.Value != null && (boolVal == null || boolVal.Value))
{
string valStr = val.Value as string;
if (valStr == null)
{
valStr = val.Value.ToString();
}
if (boolVal != null)
{
Debug.Assert(boolVal.Value);
valStr = name;
}
if (first)
{
WritePositionTaggedLiteral(writer, prefix);
first = false;
}
else
{
WritePositionTaggedLiteral(writer, attrVal.Prefix);
}
// Calculate length of the source span by the position of the next value (or suffix)
int sourceLength = next.Position - attrVal.Value.Position;
if (attrVal.Literal)
{
WriteLiteralTo(writer, valStr);
}
else
{
WriteTo(writer, valStr); // Write value
}
wroteSomething = true;
}
}
if (wroteSomething)
{
WritePositionTaggedLiteral(writer, suffix);
}
}
}
private void WritePositionTaggedLiteral(TextWriter writer, string value, int position)
{
WriteLiteralTo(writer, value);
}
private void WritePositionTaggedLiteral(TextWriter writer, PositionTagged<string> value)
{
WritePositionTaggedLiteral(writer, value.Value, value.Position);
}
/// <summary>
/// Encodes HTML to safe html
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public virtual string HtmlEncode(string input)
{
return WebUtility.HtmlEncode(input);
}
/// <summary>
/// Encodes HTML to safe HTML
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public virtual string HtmlEncode(object input)
{
if (input == null)
return "";
return WebUtility.HtmlEncode(input.ToString());
}
/// <summary>
/// Produces raw unencoded output in razor
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public virtual RawString Raw(string text)
{
return new RawString(text);
}
/// <summary>
/// Produces raw unencoded output in Razor code
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public virtual RawString Raw(RawString text)
{
return text;
}
/// <summary>
/// Allows rendering a dynamic template from within the
/// running template. The template passed must be a string
/// and you can pass a model for rendering.
///
/// This is useful to support nested templating for allowing
/// rendered values to contain embedded Razor template expressions
/// which is useful where user generated content may contain
/// Razor template logic.
/// </summary>
/// <param name="template"></param>
/// <param name="model"></param>
/// <returns></returns>
public virtual string RenderTemplate(string template, object model)
{
if (template == null)
return string.Empty;
if (!template.Contains("@"))
return template;
// use dynamic to get around generic type casting
dynamic engine = Engine;
string result = engine.RenderTemplate(template, model);
if (result == null)
throw new ApplicationException("RenderTemplate failed: " + engine.ErrorMessage);
return result;
}
#region Path Helpers
/// <summary>
/// Resolves a ~ Url by removing ~ and using `/`
/// path.
///
/// This is not recommended for direct linked files in
/// HTML (scripts, images, css etc.) as these links may
/// not work properly.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public virtual string ResolveUrl(string url)
{
if (string.IsNullOrEmpty(url))
return url;
if (url.StartsWith("~"))
{
// only makes sense for folder based containers
var host = this.HostContainer as RazorFolderHostContainer;
if (host != null)
{
// create a full path for the URL, then create a relative URL to the current template
url = url.Replace("~/", "").Replace("~", "");
var fullPath = Path.Combine(host.TemplatePath, url);
fullPath = GetRelativePath(fullPath, Path.GetDirectoryName(Request.TemplatePath));
if (fullPath.Contains(":\\"))
fullPath = "file:///" + fullPath;
else
fullPath = fullPath.Replace("\\", "/");
return fullPath;
}
}
return url;
}
/// <summary>
/// Returns a relative path string from a full path based on a base path
/// provided.
/// </summary>
/// <param name="fullPath">The path to convert. Can be either a file or a directory</param>
/// <param name="basePath">The base path on which relative processing is based. Should be a directory.</param>
/// <returns>
/// String of the relative path.
///
/// Examples of returned values:
/// test.txt, ..\test.txt, ..\..\..\test.txt, ., .., subdir\test.txt
/// </returns>
public static string GetRelativePath(string fullPath, string basePath)
{
// ForceBasePath to a path
if (!basePath.EndsWith(value: "\\"))
basePath += "\\";
#pragma warning disable CS0618
Uri baseUri = new Uri(uriString: basePath, dontEscape: true);
Uri fullUri = new Uri(uriString: fullPath, dontEscape: true);
#pragma warning restore CS0618
Uri relativeUri = baseUri.MakeRelativeUri(uri: fullUri);
// Uri's use forward slashes so convert back to backward slahes
return relativeUri.ToString().Replace(oldValue: "/", newValue: "\\");
}
#endregion
/// <summary>
/// Razor Parser overrides this method, but this method is effectively
/// never called - it's just a placeholder in order to allow
/// invoking the template.
/// </summary>
public virtual void Execute()
{
}
public virtual void Dispose()
{
if (Response != null)
{
Response.Dispose();
Response = null;
}
}
/// <summary>
/// Force this host to stay alive indefinitely
/// </summary>
/// <returns></returns>
public override object InitializeLifetimeService()
{
return null;
}
#region Old_WriteAttribute_Implementations
#if false
/// <summary>
/// This method is used to write out attribute values using
/// some funky nested tuple storage.
///
/// Handles situations like href="@Model.Entry.Id"
///
/// This call comes in from the Razor runtime parser
/// </summary>
/// <param name="attr"></param>
/// <param name="tokens"></param>
public virtual void WriteAttribute(string attr,
//params object[] parms)
Tuple<string, int> token1,
Tuple<string, int> token2,
Tuple<Tuple<string, int>,
Tuple<object, int>, bool> token3)
{
object value = null;
if (token3 != null)
value = token3.Item2.Item1;
else
value = string.Empty;
var output = token1.Item1 + value.ToString() + token2.Item1;
Response.Write(output);
}
/// <summary>
/// This method is used to write out attribute values using
/// some funky nested tuple storage.
///
/// Handles situations like href="@(Model.Url)?parm1=1"
/// where text and expressions mix in the attribute
///
/// This call comes in from the Razor runtime parser
/// </summary>
/// <param name="attr"></param>
/// <param name="tokens"></param>
public virtual void WriteAttribute(string attr,
//params object[] parms)
Tuple<string, int> token1,
Tuple<string, int> token2,
Tuple<Tuple<string, int>,
Tuple<object, int>, bool> token3,
Tuple<Tuple<string, int>,
Tuple<string, int>, bool> token4)
{
// WriteAttribute("href",
// Tuple.Create(" href=\"", 395),
// Tuple.Create("\"", 452),
// Tuple.Create(Tuple.Create("", 402), Tuple.Create<System.Object, System.Int32>("Value", 402), false),
// Tuple.Create(Tuple.Create("", 439), Tuple.Create("?action=login", 439), true)
object value = null;
object textval = null;
if (token3 != null)
value = token3.Item2.Item1;
else
value = string.Empty;
if (token4 != null)
textval = token4.Item2.Item1;
else
textval = string.Empty;
var output = token1.Item1 + value.ToString() + textval.ToString() + token2.Item1;
Response.Write(output);
}
#endif
#endregion
}
}