Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Support IsIndexer for ViewComponentTagHelpers #5207

Closed
cjqian opened this issue Aug 31, 2016 · 12 comments
Closed

Support IsIndexer for ViewComponentTagHelpers #5207

cjqian opened this issue Aug 31, 2016 · 12 comments
Assignees
Milestone

Comments

@cjqian
Copy link
Contributor

cjqian commented Aug 31, 2016

After #5189 is merged, view component descriptors can be changed to tag helper descriptors. However, the tag helper descriptors will have IsIndexer set to false.

@NTaylorMullen
Copy link
Contributor

For some context: This will enable users to more easily set Dictionary parameters passed to ViewComponents.

Example:

public class SomeViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(Dictionary<string, int> values)
    {
        return View()
    }
}

With what we have today you can only do the following to invoke SomeViewComponent in TagHelper form:

@{
    var ages = new Dictionary<string, int>
    {
        { "age", 123 }
    };
}
<vc:some values="ages" />

After this issue is completed you will be able to do:

<vc:some values-age="123" />

@Eilon
Copy link
Member

Eilon commented Nov 2, 2016

Moving to 1.2.

@dazinator
Copy link

Can you actually do this now (as of 1.1.0) - i.e render a view component in a razor page using taghelper syntax:

<vc:some values="ages" />

Because I just gave that a go but no joy - also couldn't see any docs on this.

@NTaylorMullen
Copy link
Contributor

@dazinator yes you can. Ensure you have @addTagHelper TheAssemblyWithViewComponents

@dazinator
Copy link

dazinator commented Dec 5, 2016

Hmm... I can't appear to get this working - but I am wondering - could this be effected by me using a custom TagHelperTypeResolver ?

Basically in my ViewImports.cshtml file I have:

@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Gluon.Module.*"

My VC is in an assembly `Gluon.Module.SomeModule.dll so it get's matched by my addTagHelper directive.

As standard, addTagHelper directives don't let you use glob pattern matching on the assembly name portion of the directive, so to enable that I had to write this custom ITagHelperTypeResolver

    public class AssemblyNameGlobbingTagHelperTypeResolver : ITagHelperTypeResolver
    {
       
        private static readonly System.Reflection.TypeInfo ITagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();

        protected TagHelperFeature Feature { get; }

        public AssemblyNameGlobbingTagHelperTypeResolver(ApplicationPartManager manager)
        {
            if (manager == null)
            {
                throw new ArgumentNullException(nameof(manager));
            }

            Feature = new TagHelperFeature();
            manager.PopulateFeature(Feature);

            // _manager = manager;

        }

        /// <inheritdoc />
        public IEnumerable<Type> Resolve(
            string name,
            SourceLocation documentLocation,
            ErrorSink errorSink)
        {
            if (errorSink == null)
            {
                throw new ArgumentNullException(nameof(errorSink));
            }

            if (string.IsNullOrEmpty(name))
            {
                var errorLength = name == null ? 1 : Math.Max(name.Length, 1);
                errorSink.OnError(
                    documentLocation,
                    "Tag Helper Assembly Name Cannot Be Empty Or Null",
                    errorLength);

                return Type.EmptyTypes;
            }


            IEnumerable<TypeInfo> libraryTypes;
            try
            {
                libraryTypes = GetExportedTypes(name);
            }
            catch (Exception ex)
            {
                errorSink.OnError(
                    documentLocation,
                    $"Cannot Resolve Tag Helper Assembly: {name}, {ex.Message}",
                    name.Length);

                return Type.EmptyTypes;
            }

            return libraryTypes;

        }


        /// <inheritdoc />
        protected IEnumerable<System.Reflection.TypeInfo> GetExportedTypes(string assemblyNamePattern)
        {
            if (assemblyNamePattern == null)
            {
                throw new ArgumentNullException(nameof(assemblyNamePattern));
            }

            var results = new List<System.Reflection.TypeInfo>();

            for (var i = 0; i < Feature.TagHelpers.Count; i++)
            {
                var tagHelperAssemblyName = Feature.TagHelpers[i].Assembly.GetName();

                if (assemblyNamePattern.Contains("*")) // is it actually a pattern?
                {
                    if (tagHelperAssemblyName.Name.Like(assemblyNamePattern))
                    {
                        results.Add(Feature.TagHelpers[i]);
                        continue;
                    }
                }

                // not a pattern so treat as normal assembly name.
                var assyName = new AssemblyName(assemblyNamePattern);
                if (AssemblyNameComparer.OrdinalIgnoreCase.Equals(tagHelperAssemblyName, assyName))
                {
                    results.Add(Feature.TagHelpers[i]);
                    continue;
                }
            }

            return results;
        }

        private class AssemblyNameComparer : IEqualityComparer<AssemblyName>
        {
            public static readonly IEqualityComparer<AssemblyName> OrdinalIgnoreCase = new AssemblyNameComparer();

            private AssemblyNameComparer()
            {
            }

            public bool Equals(AssemblyName x, AssemblyName y)
            {
                // Ignore case because that's what Assembly.Load does.
                return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) &&
                       string.Equals(x.CultureName ?? string.Empty, y.CultureName ?? string.Empty, StringComparison.Ordinal);
            }

            public int GetHashCode(AssemblyName obj)
            {
                var hashCode = 0;
                if (obj.Name != null)
                {
                    hashCode ^= obj.Name.GetHashCode();
                }

                hashCode ^= (obj.CultureName ?? string.Empty).GetHashCode();
                return hashCode;
            }
        }
    }

There is a Like method in there that evaluates the glob pattern against an assembly name.

I register this on startup:

    services.AddSingleton<ITagHelperTypeResolver, AssemblyNameGlobbingTagHelperTypeResolver>();

This ITagHelperTypeResolver works fine, in that I am able to invoke the VC the old fashioned way on my page.

I'll give it a go in a vanilla project to see if I can work out what the issue might be!

@NTaylorMullen
Copy link
Contributor

Ya your custom TagHelperTypeResolver will not work for tooling. It is something we're looking to extend in future Razor releases though. For more info check out: aspnet/Razor#836

@dazinator
Copy link

Doh!

@johanskoldekrans
Copy link

johanskoldekrans commented Feb 9, 2017

I am trying to use the ViewComponents I have as tag helpers and get the intellisense working just fine but it isnt rendered when I preview running from VS2015. Any changes you need to make to get them working as taghelpers?!?

I am running 1.1.0 and have the @addTagHelper in _Viewimports.

Just noticed that optional parameters is not supported yet and I have those in most of my viewcomponents so I have to wait I guess. Sorry for not checking all outstanding issues before commenting.

@Eilon
Copy link
Member

Eilon commented Feb 9, 2017

@johanskoldekrans if you're still having an issue please file a new issue and we will take a look. Thanks!

@johanskoldekrans
Copy link

@Eilon isnt optional parameters an open issue with 1.1?

@Eilon
Copy link
Member

Eilon commented Feb 9, 2017

@johanskoldekrans that's something we're looking to support in 2.0, please see this issue: #5541

@johanskoldekrans
Copy link

@Eilon thats why I updated my initial comment. 😊 Keep up the good work

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants