-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInlineScriptConcatenatorTagHelper.cs
More file actions
148 lines (137 loc) · 6.16 KB
/
InlineScriptConcatenatorTagHelper.cs
File metadata and controls
148 lines (137 loc) · 6.16 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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Razor.TagHelpers;
namespace ScriptManagerPlus
{
/// <summary>
/// Tag Helper for view scripts that should be ordered, deduped and rendered at the script tag with the script-render attribute.
/// </summary>
[HtmlTargetElement("script", Attributes = AddAttributeName)]
[HtmlTargetElement("script", Attributes = DependsOnAttributeName)]
[HtmlTargetElement("script", Attributes = AliasAttributeName)]
[HtmlTargetElement("script", Attributes = IsDependencyAttributeName)]
public class InlineScriptConcatenatorTagHelper : TagHelper
{
private const string AddAttributeName = "script-name";
private const string DependsOnAttributeName = "script-depends-on";
private const string AliasAttributeName = "script-alias";
private const string SrcAttributeName = "src";
private const string IsDependencyAttributeName = "IsDependency";
public const string ViewDataKey = "NamedScriptInfos";
static readonly Regex _namePatern = new Regex(@"^[^\s|;,]+$");
static readonly Regex _listPatern = new Regex(@"^([^\s|;,]+[\s|;,]+)*[^\s|;,]+$");
private readonly IHttpContextAccessor _httpContextAccessor;
private string[] _aliases;
private string[] _dependsOn;
public InlineScriptConcatenatorTagHelper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Gets or sets if this script should be omitted if no other scripts depend on it.
/// </summary>
/// <value>
/// <c>true</c> if this instance is not needed if not depended on; otherwise, <c>false</c> will cause it to always render.
/// </value>
[HtmlAttributeName(IsDependencyAttributeName)]
public bool IsDependency { get; set; }
/// <summary>
/// Gets or sets the script name.
/// </summary>
/// <value>
/// The unique name for de-duplication.
/// </value>
[HtmlAttributeName(AddAttributeName)]
public string Name { get; set; }
/// <summary>
/// Gets or sets a list of scripts that must be loaded before execution. List can be delimited by spaces, commas, pipes or semi-colons.
/// </summary>
/// <value>
/// The name or aliases of all dependent on scripts
/// </value>
[HtmlAttributeName(DependsOnAttributeName)]
public string DependsOn
{
get { return null == _dependsOn ? "" : string.Join(" ", _dependsOn); }
set
{
if (_listPatern.IsMatch(value))
_dependsOn = value.Split(" \r\n\t,|;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
else throw new ArgumentOutOfRangeException(nameof(DependsOn), "Invalid format");
}
}
/// <summary>
/// Gets or sets a list of aliases this script can be referenced by. List can be delimited by spaces, commas, pipes or semi-colons.
/// </summary>
/// <value>
/// The aliases
/// </value>
[HtmlAttributeName(AliasAttributeName)]
public string Aliases
{
get { return null == _aliases ? "" : string.Join(" ", _aliases); }
set
{
if (_listPatern.IsMatch(value))
_aliases = value.Split(" \r\n\t,|;".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
else throw new ArgumentOutOfRangeException(nameof(Aliases), "Invalid format");
}
}
/// <summary>
/// Address of the external script to use.
/// </summary>
/// <remarks>
/// Passed through to the generated HTML in all cases.
/// </remarks>
[HtmlAttributeName(SrcAttributeName)]
public string Src { get; set; }
/// <summary>
/// Asynchronously removes the script from the render pipeline and stores it into the HTML context to be rendered later.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="output">The output.</param>
/// <returns></returns>
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
//Validate inputs
var hasName = null != Name && _namePatern.IsMatch(Name);
var hasSrc = !string.IsNullOrWhiteSpace(Src);
if (!hasName && !hasSrc)
throw new ArgumentException("Name is required. It must be a single string without whitespace, commas, pipes or semi-colons.", nameof(Name));
var namedScript = new NamedScriptInfo { Name = Name ?? Src, Src = Src, Dependencies = _dependsOn, Aliases = _aliases };
if (hasSrc)
{
if (!Src.EndsWith(".min.js"))
{
//TODO: Consider automatically looking at a minified source cache
}
}
else
{
//Get the script contents
var contents = await output.GetChildContentAsync(true);
var scriptContent = contents.GetContent();
namedScript.Script = scriptContent;
}
//Save them into the http Context
if (_httpContextAccessor.HttpContext.Items.ContainsKey(ViewDataKey))
{
var scripts = (IDictionary<string, NamedScriptInfo>)_httpContextAccessor.HttpContext.Items[ViewDataKey];
if (scripts.ContainsKey(namedScript.Name))
Debug.WriteLine("Duplicate script ignored");
else
scripts.Add(namedScript.Name, namedScript);
}
else
_httpContextAccessor.HttpContext.Items[ViewDataKey] = new Dictionary<string, NamedScriptInfo> { { namedScript.Name, namedScript } };
//suppress any output
output.SuppressOutput();
}
}
}