Skip to content
This repository has been archived by the owner on Feb 23, 2021. It is now read-only.

Added design time support for ViewComponent TagHelpers #98

Merged
merged 1 commit into from
Sep 24, 2016

Conversation

ajaybhargavb
Copy link
Contributor

@ajaybhargavb
Copy link
Contributor Author

🆙 📅

serviceCollection.AddSingleton<ITagHelperDescriptorFactory>(_tagHelperDescriptorFactory);

// Populate the service collection
_populateMethod.Invoke(_serviceCollectionProviderClassInstance, new object[] { serviceCollection, assemblyName });
Copy link
Contributor

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?

Copy link
Contributor Author

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"));
Copy link
Contributor

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)
{
   ...
}

Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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())
Copy link
Contributor

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();
Copy link
Contributor

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.

Copy link
Member

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[]
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var services = serviceCollection.BuildServiceProvider();
var tagHelperDescriptorResolver = services.GetRequiredService<ITagHelperDescriptorResolver>();

var directiveDescriptors = new TagHelperDirectiveDescriptor[]
Copy link
Contributor

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

Copy link
Contributor

@NTaylorMullen NTaylorMullen left a 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(
Copy link
Contributor

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 😢 .

Copy link
Contributor

@NTaylorMullen NTaylorMullen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BUgz

@ajaybhargavb
Copy link
Contributor Author

🆙 📅 Really sucks that Github doesn't hide outdated comments.

@ajaybhargavb ajaybhargavb force-pushed the ajbaaska/vcth-designtime branch from fd9875c to 69f01f1 Compare September 22, 2016 23:57
Copy link
Contributor

@NTaylorMullen NTaylorMullen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. :shipit:

private void LoadMvcAssembly()
{
const string namespaceName = "Microsoft.AspNetCore.Mvc";
const string typeName = "DesignTimeMvcServiceCollectionProvider";
Copy link
Contributor

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.

@ajaybhargavb
Copy link
Contributor Author

🆙 📅

@@ -61,6 +52,47 @@ public IEnumerable<TagHelperDescriptor> Resolve(string assemblyName, ErrorSink e
DesignResources.InvalidProtocolValue,
typeof(TagHelperDescriptor).FullName, ProtocolVersion));
}

if (!_loadedAssembly)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const string assemblyName = "Microsoft.AspNetCore.Mvc";
const string methodName = "PopulateServiceCollection";

_loadedAssembly = true;
Copy link
Contributor

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);
Copy link
Contributor

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)))
Copy link
Contributor

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;
Copy link
Contributor

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";
Copy link
Contributor

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))
Copy link
Contributor

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;
Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwOnError: false

Copy link
Contributor Author

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

@ajaybhargavb ajaybhargavb force-pushed the ajbaaska/vcth-designtime branch from c618ddf to fe3fcf2 Compare September 23, 2016 22:12
Copy link
Contributor

@pranavkm pranavkm left a 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)
Copy link
Contributor

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)
Copy link
Contributor

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)

@ajaybhargavb
Copy link
Contributor Author

🆙 📅

Copy link
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

serviceCollection.AddSingleton<ITagHelperDescriptorFactory>(_tagHelperDescriptorFactory);

// Populate the service collection
_populateMethodDelegate.DynamicInvoke(serviceCollection, assemblyName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_populateMethodDelegate(serviceCollection, assemblyName);

Copy link
Contributor

@pranavkm pranavkm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit: x2

private readonly TagHelperTypeResolver _tagHelperTypeResolver;

private bool _isInitialized;
private Delegate _populateMethodDelegate;
Copy link
Contributor

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>));
Copy link
Contributor

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.

@ajaybhargavb ajaybhargavb force-pushed the ajbaaska/vcth-designtime branch 2 times, most recently from 5b4fc0e to bf3a26f Compare September 24, 2016 00:14
@ajaybhargavb ajaybhargavb force-pushed the ajbaaska/vcth-designtime branch from bf3a26f to 24a9c54 Compare September 24, 2016 00:54
@ajaybhargavb ajaybhargavb merged commit 24a9c54 into dev Sep 24, 2016
@ajaybhargavb ajaybhargavb deleted the ajbaaska/vcth-designtime branch September 24, 2016 00:54
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants