-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy pathConfig.cs
355 lines (311 loc) · 15.3 KB
/
Config.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
349
350
351
352
353
354
355
using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Web.Configuration;
using System.Web.Hosting;
using Microsoft.CodeAnalysis.CSharp;
using Umbraco.Core;
namespace Umbraco.ModelsBuilder.Configuration
{
/// <summary>
/// Represents the models builder configuration.
/// </summary>
public class Config
{
internal const string DefaultStaticMixinGetterPattern = "Get{0}";
internal const LanguageVersion DefaultLanguageVersion = LanguageVersion.CSharp7_3;
internal const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
internal const ClrNameSource DefaultClrNameSource = ClrNameSource.Alias; // for legacy reasons
internal const string DefaultModelsDirectory = "~/App_Data/Models";
/// <summary>
/// Initializes a new instance of the <see cref="Config"/> class.
/// </summary>
public Config()
{
const string prefix = "Umbraco.ModelsBuilder.";
// giant kill switch, default: false
// must be explicitely set to true for anything else to happen
Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true";
// ensure defaults are initialized for tests
StaticMixinGetterPattern = DefaultStaticMixinGetterPattern;
LanguageVersion = DefaultLanguageVersion;
ModelsNamespace = DefaultModelsNamespace;
ClrNameSource = DefaultClrNameSource;
ModelsDirectory = HostingEnvironment.IsHosted
? HostingEnvironment.MapPath(DefaultModelsDirectory)
: DefaultModelsDirectory.TrimStart("~/");
DebugLevel = 0;
// stop here, everything is false
if (!Enable) return;
// mode
var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"];
if (!string.IsNullOrWhiteSpace(modelsMode))
{
switch (modelsMode)
{
case nameof(ModelsMode.Nothing):
ModelsMode = ModelsMode.Nothing;
break;
case nameof(ModelsMode.PureLive):
ModelsMode = ModelsMode.PureLive;
break;
case nameof(ModelsMode.Dll):
ModelsMode = ModelsMode.Dll;
break;
case nameof(ModelsMode.LiveDll):
ModelsMode = ModelsMode.LiveDll;
break;
case nameof(ModelsMode.AppData):
ModelsMode = ModelsMode.AppData;
break;
case nameof(ModelsMode.LiveAppData):
ModelsMode = ModelsMode.LiveAppData;
break;
default:
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode."
+ " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
}
}
// default: false
EnableApi = ConfigurationManager.AppSettings[prefix + "EnableApi"].InvariantEquals("true");
RenderLanguageFallback = ConfigurationManager.AppSettings[prefix + "RenderLanguageFallback"].InvariantEquals("true");
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
// default: true
EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false");
StaticMixinGetters = !ConfigurationManager.AppSettings[prefix + "StaticMixinGetters"].InvariantEquals("false");
FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false");
// default: initialized above with DefaultModelsNamespace const
var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"];
if (!string.IsNullOrWhiteSpace(value))
ModelsNamespace = value;
// default: initialized above with DefaultStaticMixinGetterPattern const
value = ConfigurationManager.AppSettings[prefix + "StaticMixinGetterPattern"];
if (!string.IsNullOrWhiteSpace(value))
StaticMixinGetterPattern = value;
// default: initialized above with DefaultLanguageVersion const
value = ConfigurationManager.AppSettings[prefix + "LanguageVersion"];
if (!string.IsNullOrWhiteSpace(value))
{
LanguageVersion lv;
if (!Enum.TryParse(value, true, out lv))
throw new ConfigurationErrorsException($"Invalid language version \"{value}\".");
LanguageVersion = lv;
}
// default: initialized above with DefaultClrNameSource const
value = ConfigurationManager.AppSettings[prefix + "ClrNameSource"];
if (!string.IsNullOrWhiteSpace(value))
{
switch (value)
{
case nameof(ClrNameSource.Nothing):
ClrNameSource = ClrNameSource.Nothing;
break;
case nameof(ClrNameSource.Alias):
ClrNameSource = ClrNameSource.Alias;
break;
case nameof(ClrNameSource.RawAlias):
ClrNameSource = ClrNameSource.RawAlias;
break;
case nameof(ClrNameSource.Name):
ClrNameSource = ClrNameSource.Name;
break;
default:
throw new ConfigurationErrorsException($"ClrNameSource \"{value}\" is not a valid source."
+ " Note that sources are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ClrNameSource))));
}
}
// default: initialized above with DefaultModelsDirectory const
value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"];
if (!string.IsNullOrWhiteSpace(value))
{
var root = HostingEnvironment.IsHosted
? HostingEnvironment.MapPath("~/")
: Directory.GetCurrentDirectory();
if (root == null)
throw new ConfigurationErrorsException("Could not determine root directory.");
// GetModelsDirectory will ensure that the path is safe
ModelsDirectory = GetModelsDirectory(root, value, AcceptUnsafeModelsDirectory);
}
// default: 0
value = ConfigurationManager.AppSettings[prefix + "DebugLevel"];
if (!string.IsNullOrWhiteSpace(value))
{
int debugLevel;
if (!int.TryParse(value, out debugLevel))
throw new ConfigurationErrorsException($"Invalid debug level \"{value}\".");
DebugLevel = debugLevel;
}
// not flagging if not generating, or live (incl. pure)
if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive())
FlagOutOfDateModels = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="Config"/> class.
/// </summary>
public Config(
bool enable = false,
ModelsMode modelsMode = ModelsMode.Nothing,
bool enableApi = true,
bool renderLanguageFallback = false,
string modelsNamespace = null,
bool enableFactory = true,
LanguageVersion languageVersion = DefaultLanguageVersion,
bool staticMixinGetters = true,
string staticMixinGetterPattern = null,
bool flagOutOfDateModels = true,
ClrNameSource clrNameSource = DefaultClrNameSource,
string modelsDirectory = null,
bool acceptUnsafeModelsDirectory = false,
int debugLevel = 0)
{
Enable = enable;
ModelsMode = modelsMode;
EnableApi = enableApi;
RenderLanguageFallback = renderLanguageFallback;
ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace;
EnableFactory = enableFactory;
LanguageVersion = languageVersion;
StaticMixinGetters = staticMixinGetters;
StaticMixinGetterPattern = string.IsNullOrWhiteSpace(staticMixinGetterPattern) ? DefaultStaticMixinGetterPattern : staticMixinGetterPattern;
FlagOutOfDateModels = flagOutOfDateModels;
ClrNameSource = clrNameSource;
ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory;
AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory;
DebugLevel = debugLevel;
}
// internal for tests
internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe)
{
// making sure it is safe, ie under the website root,
// unless AcceptUnsafeModelsDirectory and then everything is OK.
if (!Path.IsPathRooted(root))
throw new ConfigurationErrorsException($"Root is not rooted \"{root}\".");
if (config.StartsWith("~/"))
{
var dir = Path.Combine(root, config.TrimStart("~/"));
// sanitize - GetFullPath will take care of any relative
// segments in path, eg '../../foo.tmp' - it may throw a SecurityException
// if the combined path reaches illegal parts of the filesystem
dir = Path.GetFullPath(dir);
root = Path.GetFullPath(root);
if (!dir.StartsWith(root) && !acceptUnsafe)
throw new ConfigurationErrorsException($"Invalid models directory \"{config}\".");
return dir;
}
if (acceptUnsafe)
return Path.GetFullPath(config);
throw new ConfigurationErrorsException($"Invalid models directory \"{config}\".");
}
/// <summary>
/// Gets a value indicating whether the whole models experience is enabled.
/// </summary>
/// <remarks>
/// <para>If this is false then absolutely nothing happens.</para>
/// <para>Default value is <c>false</c> which means that unless we have this setting, nothing happens.</para>
/// </remarks>
public bool Enable { get; }
/// <summary>
/// Gets the models mode.
/// </summary>
public ModelsMode ModelsMode { get; }
/// <summary>
/// Gets a value indicating whether to serve the API.
/// </summary>
public bool ApiServer => EnableApi && ApiInstalled && IsDebug;
/// <summary>
/// Gets a value indicating whether to enable the API.
/// </summary>
/// <remarks>
/// <para>Default value is <c>true</c>.</para>
/// <para>The API is used by the Visual Studio extension and the console tool to talk to Umbraco
/// and retrieve the content types. It needs to be enabled so the extension & tool can work.</para>
/// </remarks>
public bool EnableApi { get; }
/// <summary>
/// Gets a value indicating whether the generated code adds language fallback on variant properties.
/// </summary>
/// <remarks>
/// <para>Default value is <c>false</c>.</para>
/// </remarks>
public bool RenderLanguageFallback { get; }
/// <summary>
/// Gets a value indicating whether the API is installed.
/// </summary>
public bool ApiInstalled => _apiInstalled.Value;
private readonly Lazy<bool> _apiInstalled = new Lazy<bool>(() =>
{
try
{
return Assembly.Load("Umbraco.ModelsBuilder.Api") != null;
}
catch (FileNotFoundException)
{
return false;
}
});
/// <summary>
/// Gets a value indicating whether system.web/compilation/@debug is true.
/// </summary>
public bool IsDebug
{
get
{
var section = (CompilationSection) ConfigurationManager.GetSection("system.web/compilation");
return section != null && section.Debug;
}
}
/// <summary>
/// Gets the models namespace.
/// </summary>
/// <remarks>That value could be overriden by other (attribute in user's code...). Return default if no value was supplied.</remarks>
public string ModelsNamespace { get; }
/// <summary>
/// Gets a value indicating whether we should enable the models factory.
/// </summary>
/// <remarks>Default value is <c>true</c> because no factory is enabled by default in Umbraco.</remarks>
public bool EnableFactory { get; }
/// <summary>
/// Gets the Roslyn parser language version.
/// </summary>
/// <remarks>Default value is <c>CSharp6</c>.</remarks>
public LanguageVersion LanguageVersion { get; }
/// <summary>
/// Gets a value indicating whether to generate static mixin getters.
/// </summary>
/// <remarks>Default value is <c>false</c> for backward compat reaons.</remarks>
public bool StaticMixinGetters { get; }
/// <summary>
/// Gets the string pattern for mixin properties static getter name.
/// </summary>
/// <remarks>Default value is "GetXxx". Standard string format.</remarks>
public string StaticMixinGetterPattern { get; }
/// <summary>
/// Gets a value indicating whether we should flag out-of-date models.
/// </summary>
/// <remarks>Models become out-of-date when data types or content types are updated. When this
/// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are
/// generated through the dashboard, the files is cleared. Default value is <c>false</c>.</remarks>
public bool FlagOutOfDateModels { get; }
/// <summary>
/// Gets the CLR name source.
/// </summary>
public ClrNameSource ClrNameSource { get; }
/// <summary>
/// Gets the models directory.
/// </summary>
/// <remarks>Default is ~/App_Data/Models but that can be changed.</remarks>
public string ModelsDirectory { get; }
/// <summary>
/// Gets a value indicating whether to accept an unsafe value for ModelsDirectory.
/// </summary>
/// <remarks>An unsafe value is an absolute path, or a relative path pointing outside
/// of the website root.</remarks>
public bool AcceptUnsafeModelsDirectory { get; }
/// <summary>
/// Gets a value indicating the debug log level.
/// </summary>
/// <remarks>0 means minimal (safe on live site), anything else means more and more details (maybe not safe).</remarks>
public int DebugLevel { get; }
}
}