4
4
using System ;
5
5
using System . Collections . Generic ;
6
6
using System . IO ;
7
+ using System . Linq ;
8
+ using System . Threading . Tasks ;
7
9
using Microsoft . AspNet . FileSystems ;
8
- using Microsoft . CodeAnalysis . CSharp ;
10
+ using Microsoft . CodeAnalysis ;
11
+ using Microsoft . Framework . Cache . Memory ;
9
12
using Microsoft . Framework . DependencyInjection ;
10
13
using Microsoft . Framework . OptionsModel ;
11
14
using Microsoft . Framework . Runtime ;
@@ -17,37 +20,42 @@ public class RazorPreCompiler
17
20
{
18
21
private readonly IServiceProvider _serviceProvider ;
19
22
private readonly IFileSystem _fileSystem ;
20
- private readonly IMvcRazorHost _host ;
21
23
22
24
public RazorPreCompiler ( [ NotNull ] IServiceProvider designTimeServiceProvider ,
25
+ [ NotNull ] IMemoryCache precompilationCache ,
23
26
[ NotNull ] CompilationSettings compilationSettings ) :
24
27
this ( designTimeServiceProvider ,
25
- designTimeServiceProvider . GetRequiredService < IMvcRazorHost > ( ) ,
26
28
designTimeServiceProvider . GetRequiredService < IOptions < RazorViewEngineOptions > > ( ) ,
29
+ precompilationCache ,
27
30
compilationSettings )
28
31
{
29
32
}
30
33
31
34
public RazorPreCompiler ( [ NotNull ] IServiceProvider designTimeServiceProvider ,
32
- [ NotNull ] IMvcRazorHost host ,
33
35
[ NotNull ] IOptions < RazorViewEngineOptions > optionsAccessor ,
36
+ [ NotNull ] IMemoryCache precompilationCache ,
34
37
[ NotNull ] CompilationSettings compilationSettings )
35
38
{
36
39
_serviceProvider = designTimeServiceProvider ;
37
- _host = host ;
38
40
_fileSystem = optionsAccessor . Options . FileSystem ;
39
41
CompilationSettings = compilationSettings ;
42
+ PreCompilationCache = precompilationCache ;
40
43
}
41
44
42
45
protected CompilationSettings CompilationSettings { get ; }
43
46
47
+ protected IMemoryCache PreCompilationCache { get ; }
48
+
44
49
protected virtual string FileExtension { get ; } = ".cshtml" ;
45
50
51
+ protected virtual int MaxDegreesOfParallelism { get ; } = Environment . ProcessorCount ;
52
+
53
+
46
54
public virtual void CompileViews ( [ NotNull ] IBeforeCompileContext context )
47
55
{
48
56
var descriptors = CreateCompilationDescriptors ( context ) ;
49
57
50
- if ( descriptors . Count > 0 )
58
+ if ( descriptors . Any ( ) )
51
59
{
52
60
var collectionGenerator = new RazorFileInfoCollectionGenerator (
53
61
descriptors ,
@@ -58,93 +66,127 @@ public virtual void CompileViews([NotNull] IBeforeCompileContext context)
58
66
}
59
67
}
60
68
61
- protected virtual IReadOnlyList < RazorFileInfo > CreateCompilationDescriptors (
62
- [ NotNull ] IBeforeCompileContext context )
69
+ protected virtual IEnumerable < RazorFileInfo > CreateCompilationDescriptors (
70
+ [ NotNull ] IBeforeCompileContext context )
63
71
{
64
- var list = new List < RazorFileInfo > ( ) ;
72
+ var filesToProcess = new List < RelativeFileInfo > ( ) ;
73
+ GetFileInfosRecursive ( root : string . Empty , razorFiles : filesToProcess ) ;
65
74
66
- foreach ( var info in GetFileInfosRecursive ( string . Empty ) )
67
- {
68
- var descriptor = ParseView ( info , context ) ;
75
+ var razorFiles = new RazorFileInfo [ filesToProcess . Count ] ;
76
+ var syntaxTrees = new SyntaxTree [ filesToProcess . Count ] ;
77
+ var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism } ;
78
+ var diagnosticsLock = new object ( ) ;
69
79
70
- if ( descriptor != null )
80
+ Parallel . For ( 0 , filesToProcess . Count , parallelOptions , index =>
81
+ {
82
+ var file = filesToProcess [ index ] ;
83
+ var cacheEntry = PreCompilationCache . GetOrSet ( file . RelativePath ,
84
+ file ,
85
+ OnCacheMiss ) ;
86
+ if ( cacheEntry != null )
71
87
{
72
- list . Add ( descriptor ) ;
88
+ if ( cacheEntry . Success )
89
+ {
90
+ syntaxTrees [ index ] = cacheEntry . SyntaxTree ;
91
+ razorFiles [ index ] = cacheEntry . FileInfo ;
92
+ }
93
+ else
94
+ {
95
+ lock ( diagnosticsLock )
96
+ {
97
+ foreach ( var diagnostic in cacheEntry . Diagnostics )
98
+ {
99
+ context . Diagnostics . Add ( diagnostic ) ;
100
+ }
101
+ }
102
+ }
73
103
}
74
- }
104
+ } ) ;
105
+
106
+ context . CSharpCompilation = context . CSharpCompilation
107
+ . AddSyntaxTrees ( syntaxTrees . Where ( tree => tree != null ) ) ;
108
+ return razorFiles . Where ( file => file != null ) ;
109
+ }
75
110
76
- return list ;
111
+ protected IMvcRazorHost GetRazorHost ( )
112
+ {
113
+ return _serviceProvider . GetRequiredService < IMvcRazorHost > ( ) ;
77
114
}
78
115
79
- private IEnumerable < RelativeFileInfo > GetFileInfosRecursive ( string currentPath )
116
+ private PrecompilationCacheEntry OnCacheMiss ( ICacheSetContext cacheSetContext )
80
117
{
81
- var path = currentPath ;
118
+ var fileInfo = ( RelativeFileInfo ) cacheSetContext . State ;
119
+ var entry = GetCacheEntry ( fileInfo ) ;
82
120
83
- var fileInfos = _fileSystem . GetDirectoryContents ( path ) ;
84
- if ( ! fileInfos . Exists )
121
+ if ( entry != null )
85
122
{
86
- yield break ;
123
+ cacheSetContext . AddExpirationTrigger ( _fileSystem . Watch ( fileInfo . RelativePath ) ) ;
124
+ foreach ( var viewStartPath in ViewStartUtility . GetViewStartLocations ( fileInfo . RelativePath ) )
125
+ {
126
+ cacheSetContext . AddExpirationTrigger ( _fileSystem . Watch ( viewStartPath ) ) ;
127
+ }
87
128
}
88
129
130
+ return entry ;
131
+ }
132
+
133
+ private void GetFileInfosRecursive ( string root , List < RelativeFileInfo > razorFiles )
134
+ {
135
+ var fileInfos = _fileSystem . GetDirectoryContents ( root ) ;
136
+
89
137
foreach ( var fileInfo in fileInfos )
90
138
{
91
139
if ( fileInfo . IsDirectory )
92
140
{
93
- var subPath = Path . Combine ( path , fileInfo . Name ) ;
94
-
95
- foreach ( var info in GetFileInfosRecursive ( subPath ) )
96
- {
97
- yield return info ;
98
- }
141
+ var subPath = Path . Combine ( root , fileInfo . Name ) ;
142
+ GetFileInfosRecursive ( subPath , razorFiles ) ;
99
143
}
100
144
else if ( Path . GetExtension ( fileInfo . Name )
101
145
. Equals ( FileExtension , StringComparison . OrdinalIgnoreCase ) )
102
146
{
103
- var relativePath = Path . Combine ( currentPath , fileInfo . Name ) ;
147
+ var relativePath = Path . Combine ( root , fileInfo . Name ) ;
104
148
var info = new RelativeFileInfo ( fileInfo , relativePath ) ;
105
- yield return info ;
149
+ razorFiles . Add ( info ) ;
106
150
}
107
151
}
108
152
}
109
153
110
- protected virtual RazorFileInfo ParseView ( [ NotNull ] RelativeFileInfo fileInfo ,
111
- [ NotNull ] IBeforeCompileContext context )
154
+ protected virtual PrecompilationCacheEntry GetCacheEntry ( [ NotNull ] RelativeFileInfo fileInfo )
112
155
{
113
156
using ( var stream = fileInfo . FileInfo . CreateReadStream ( ) )
114
157
{
115
- var results = _host . GenerateCode ( fileInfo . RelativePath , stream ) ;
116
-
117
- foreach ( var parserError in results . ParserErrors )
118
- {
119
- var diagnostic = parserError . ToDiagnostics ( fileInfo . FileInfo . PhysicalPath ) ;
120
- context . Diagnostics . Add ( diagnostic ) ;
121
- }
122
-
123
- var generatedCode = results . GeneratedCode ;
158
+ var host = GetRazorHost ( ) ;
159
+ var results = host . GenerateCode ( fileInfo . RelativePath , stream ) ;
124
160
125
- if ( generatedCode != null )
161
+ if ( results . Success )
126
162
{
127
- var syntaxTree = SyntaxTreeGenerator . Generate ( generatedCode ,
163
+ var syntaxTree = SyntaxTreeGenerator . Generate ( results . GeneratedCode ,
128
164
fileInfo . FileInfo . PhysicalPath ,
129
165
CompilationSettings ) ;
130
- var fullTypeName = results . GetMainClassName ( _host , syntaxTree ) ;
166
+ var fullTypeName = results . GetMainClassName ( host , syntaxTree ) ;
131
167
132
168
if ( fullTypeName != null )
133
169
{
134
- context . CSharpCompilation = context . CSharpCompilation . AddSyntaxTrees ( syntaxTree ) ;
135
-
136
170
var hash = RazorFileHash . GetHash ( fileInfo . FileInfo ) ;
137
-
138
- return new RazorFileInfo ( )
171
+ var razorFileInfo = new RazorFileInfo
139
172
{
140
- FullTypeName = fullTypeName ,
141
173
RelativePath = fileInfo . RelativePath ,
142
174
LastModified = fileInfo . FileInfo . LastModified ,
143
175
Length = fileInfo . FileInfo . Length ,
144
- Hash = hash ,
176
+ FullTypeName = fullTypeName
145
177
} ;
178
+
179
+ return new PrecompilationCacheEntry ( razorFileInfo , syntaxTree ) ;
146
180
}
147
181
}
182
+ else
183
+ {
184
+ var diagnostics = results . ParserErrors
185
+ . Select ( error => error . ToDiagnostics ( fileInfo . FileInfo . PhysicalPath ) )
186
+ . ToList ( ) ;
187
+
188
+ return new PrecompilationCacheEntry ( diagnostics ) ;
189
+ }
148
190
}
149
191
150
192
return null ;
0 commit comments