-
Notifications
You must be signed in to change notification settings - Fork 17
Added design time support for ViewComponent TagHelpers #98
Conversation
🆙 📅 |
serviceCollection.AddSingleton<ITagHelperDescriptorFactory>(_tagHelperDescriptorFactory); | ||
|
||
// Populate the service collection | ||
_populateMethod.Invoke(_serviceCollectionProviderClassInstance, new object[] { serviceCollection, assemblyName }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this happen more than once in the lifetime of the process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This runs for every assembly that we resolve descriptors from.
Example, in the following command, it runs twice.
dotnet razor-tooling resolve-taghelpers .\project.json ViewComponentTagHelper.Web Microsoft.AspNetCore.Mvc.TagHelpers
|
||
try | ||
{ | ||
var assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var type = Type.GetType("Microsoft.AspNetCore.Mvc.DesignTimeMvcServiceCollectionProvider, Microsoft.AspNetCore.Mvc");
if (type != null)
{
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also const strings
LoadMvcAssembly(); | ||
} | ||
|
||
if (_hasMvcAssembly) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we check if _populateMethod
is not null instead of having a separate variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do that but I feel the code looks more readable this way.
throwOnError: true); | ||
|
||
_serviceCollectionProviderClassInstance = Activator.CreateInstance(providerClass); | ||
_populateMethod = providerClass.GetMethod( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do something similar to this - https://github.com/aspnet/HttpAbstractions/blob/e3207557342f514deea4618f595a049bfbd7a024/src/Microsoft.AspNetCore.Http.Extensions/HeaderDictionaryTypeExtensions.cs#L223-L234 - to ensure we're getting the right overload of PopulateServiceCollection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really sad that we can't just call into AddMvc
😢 .
|
||
public AssemblyTagHelperDescriptorResolver() | ||
: this(new TagHelperTypeResolver()) | ||
: this(tagHelperTypeResolver: new TagHelperTypeResolver()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for named params
|
||
if (_hasMvcAssembly) | ||
{ | ||
var serviceCollection = new ServiceCollection(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to talk to @Eilon about having DI in RazorTooling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we decided this is fine.
var services = serviceCollection.BuildServiceProvider(); | ||
var tagHelperDescriptorResolver = services.GetRequiredService<ITagHelperDescriptorResolver>(); | ||
|
||
var directiveDescriptors = new TagHelperDirectiveDescriptor[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new[]
@@ -61,6 +52,47 @@ public IEnumerable<TagHelperDescriptor> Resolve(string assemblyName, ErrorSink e | |||
DesignResources.InvalidProtocolValue, | |||
typeof(TagHelperDescriptor).FullName, ProtocolVersion)); | |||
} | |||
|
|||
if (!_loadedAssembly) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this ever get hit more than once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The Resolve
method is called every time you resolve an assembly. https://github.com/aspnet/RazorTooling/blob/dev/src/Microsoft.AspNetCore.Razor.Design/ResolveTagHelpersRunCommand.cs#L42
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which case, could we create a delegate from populateMethod
(https://docs.microsoft.com/en-us/dotnet/core/api/system.reflection.methodinfo#System_Reflection_MethodInfo_CreateDelegate_System_Type_)
var services = serviceCollection.BuildServiceProvider(); | ||
var tagHelperDescriptorResolver = services.GetRequiredService<ITagHelperDescriptorResolver>(); | ||
|
||
var directiveDescriptors = new TagHelperDirectiveDescriptor[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could share the logic from here on down with the falling back to manually resolving descriptors.
Aka, if MVC isn't in the project:
TagHelperDescriptorResolver tagHelperDescriptorResolver;
if (_hasMvcAssembly)
{
// Do work to get tagHelperDescriptorResolver from MVC
}
else
{
tagHelperDescriptorResolver = new TagHelperDescriptorResolver(_tagHelperTypeResolver, _tagHelperDescriptorFactory);
}
// Build resolution context and resolve
// Win
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⌚
throwOnError: true); | ||
|
||
_serviceCollectionProviderClassInstance = Activator.CreateInstance(providerClass); | ||
_populateMethod = providerClass.GetMethod( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really sad that we can't just call into AddMvc
😢 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BUgz
🆙 📅 Really sucks that Github doesn't hide outdated comments. |
fd9875c
to
69f01f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
private void LoadMvcAssembly() | ||
{ | ||
const string namespaceName = "Microsoft.AspNetCore.Mvc"; | ||
const string typeName = "DesignTimeMvcServiceCollectionProvider"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fullTypeName
; can combine this with the above.
🆙 📅 |
@@ -61,6 +52,47 @@ public IEnumerable<TagHelperDescriptor> Resolve(string assemblyName, ErrorSink e | |||
DesignResources.InvalidProtocolValue, | |||
typeof(TagHelperDescriptor).FullName, ProtocolVersion)); | |||
} | |||
|
|||
if (!_loadedAssembly) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which case, could we create a delegate from populateMethod
(https://docs.microsoft.com/en-us/dotnet/core/api/system.reflection.methodinfo#System_Reflection_MethodInfo_CreateDelegate_System_Type_)
const string assemblyName = "Microsoft.AspNetCore.Mvc"; | ||
const string methodName = "PopulateServiceCollection"; | ||
|
||
_loadedAssembly = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set this after you've loaded the assembly.
|
||
try | ||
{ | ||
var providerClass = Type.GetType($"{typeFullName}, {assemblyName}", throwOnError: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why throwOnError? It returns a null
if it can't find it by default.
.FirstOrDefault(methodInfo => | ||
{ | ||
if (string.Equals(methodName, methodInfo.Name, StringComparison.Ordinal) | ||
&& methodInfo.ReturnParameter.ParameterType.Equals(typeof(void))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably don't need to check for return type since overloads can only vary by parameters.
return false; | ||
}); | ||
|
||
_hasMvcAssembly = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we use the existence of the method to know if Mvc is present?
|
||
private void LoadMvcAssembly() | ||
{ | ||
const string typeFullName = "Microsoft.AspNetCore.Mvc.DesignTimeMvcServiceCollectionProvider"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private const string ... at the top of the class.
{ | ||
var methodParams = methodInfo.GetParameters(); | ||
return methodParams.Length == 2 | ||
&& methodParams[0].ParameterType.Equals(typeof(IServiceCollection)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&&
on the previous line.
private bool _hasAssembly; | ||
private Type _providerClass; | ||
private MethodInfo _populateMethod; | ||
//private Type _serviceCollectionExtensionsClass; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
@@ -0,0 +1,160 @@ | |||
// Copyright (c) .NET Foundation. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you still need this type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidental check-in. Removed.
|
||
try | ||
{ | ||
var providerClass = Type.GetType($"{typeFullName}, {assemblyName}", throwOnError: true); | ||
var providerClass = Type.GetType($"{TypeFullName}, {MvcAssemblyName}", throwOnError: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throwOnError: false
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry didn't fully update yet
c618ddf
to
fe3fcf2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there 👍
_serviceCollectionProviderClassInstance = Activator.CreateInstance(providerClass); | ||
|
||
// Get the method from the type | ||
_populateMethod = providerClass.GetMethods(BindingFlags.Public | BindingFlags.Static) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var populateMethod = ...;
_populateMethodDelegate = populateMethod.CreateDelegate(typeof(Action<IServiceCollection, string>));
@@ -56,6 +56,44 @@ public IEnumerable<TagHelperDescriptor> Resolve(string assemblyName, ErrorSink e | |||
DesignResources.InvalidProtocolValue, | |||
typeof(TagHelperDescriptor).FullName, ProtocolVersion)); | |||
} | |||
|
|||
if (!_isInitialized) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could move the if check in to the method. I really couldn't find a suitable example in AspNetCore so something from the old world (https://github.com/aspnet/AspNetWebStack/blob/1a987f82d648a95aabed7d35e4c2bee17185c44d/src/System.Web.Http/Routing/RouteCollectionRoute.cs#L41)
🆙 📅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serviceCollection.AddSingleton<ITagHelperDescriptorFactory>(_tagHelperDescriptorFactory); | ||
|
||
// Populate the service collection | ||
_populateMethodDelegate.DynamicInvoke(serviceCollection, assemblyName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_populateMethodDelegate(serviceCollection, assemblyName);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
x2
private readonly TagHelperTypeResolver _tagHelperTypeResolver; | ||
|
||
private bool _isInitialized; | ||
private Delegate _populateMethodDelegate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private Action<IServiceCollection, string> _populateMethodDelegate;
return false; | ||
}); | ||
|
||
_populateMethodDelegate = populateMethod?.CreateDelegate(typeof(Action<IServiceCollection, string>)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd have to cast here.
5b4fc0e
to
bf3a26f
Compare
bf3a26f
to
24a9c54
Compare
Issue - aspnet/Mvc#1051
Related PR: aspnet/Mvc#5304
@NTaylorMullen @pranavkm