Ejemplo n.º 1
0
        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            output.CopyHtmlAttribute(SrcAttributeName, context);
            ProcessUrlAttribute(SrcAttributeName, output);

            if (AppendVersion)
            {
                EnsureFileVersionProvider();

                // Retrieve the TagHelperOutput variation of the "src" attribute in case other TagHelpers in the
                // pipeline have touched the value. If the value is already encoded this ImageTagHelper may
                // not function properly.
                Src = output.Attributes[SrcAttributeName].Value as string;

                output.Attributes.SetAttribute(SrcAttributeName, _fileVersionProvider.AddFileVersionToPath(Src));
            }
        }
    public static string AddFileVersionToPath(this IRazorPage page, string path)
    {
        var          context            = page.ViewContext.HttpContext;
        IMemoryCache cache              = context.RequestServices.GetRequiredService <IMemoryCache>();
        var          hostingEnvironment = context.RequestServices.GetRequiredService <IHostingEnvironment>();
        var          versionProvider    = new FileVersionProvider(hostingEnvironment.WebRootFileProvider, cache, context.Request.Path);

        return(versionProvider.AddFileVersionToPath(path));
    }
Ejemplo n.º 3
0
        /// <summary>
        /// Appends a version hash querystring parameter to the end
        /// of the file path, e.g. 'myfile.js?v=examplecomputedhash'
        /// </summary>
        /// <param name="applicationRelativePath">
        /// The static resource file path, which must be the full application
        /// relative path.
        /// </param>
        /// <returns>
        /// If the file is found, the path is returned with the version
        /// appended, otherwise the unmodified path is returned.
        /// </returns>
        public string AppendVersion(string applicationRelativePath)
        {
            // Only support site relative urls, so no need to pass requestPathBase;
            var versionProvider = new FileVersionProvider(_staticResourceFileProvider, _memoryCache, null);

            var path = versionProvider.AddFileVersionToPath(applicationRelativePath);

            return(path);
        }
Ejemplo n.º 4
0
        private string GetVersionedSrc(string srcValue)
        {
            if (AppendVersion == true)
            {
                srcValue = FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, srcValue);
            }

            return(srcValue);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Adds version query string to the file
        /// </summary>
        /// <param name="path">The relative path. Should not start with "~/"</param>
        /// <returns></returns>
        private string AddFileVersionToPath(string path)
        {
            var context         = this.HttpContext;
            var pathBase        = context.Request.PathBase;
            var versionProvider = new FileVersionProvider(_env.WebRootFileProvider, _cache, pathBase);

            //var globingUrlBuilder = new GlobbingUrlBuilder(_environment.WebRootFileProvider, _memCache, pathBase);
            //var globingUrl = globingUrlBuilder.BuildUrlList(null, path, null);
            return(versionProvider.AddFileVersionToPath(path));
        }
        private string GetVersionedSrc(string srcValue)
        {
            EnsureFileVersionProvider();

            if (_options.AppendVersion)
            {
                srcValue = _fileVersionProvider.AddFileVersionToPath(srcValue);
            }

            return(srcValue);
        }
Ejemplo n.º 7
0
        private void BuildFallbackBlock(TagHelperContent builder)
        {
            EnsureGlobbingUrlBuilder();
            var fallbackHrefs =
                GlobbingUrlBuilder.BuildUrlList(FallbackHref, FallbackHrefInclude, FallbackHrefExclude).ToArray();

            if (fallbackHrefs.Length > 0)
            {
                if (FileVersion == true)
                {
                    for (var i = 0; i < fallbackHrefs.Length; i++)
                    {
                        // fallbackHrefs come from bound attributes and globbing. Must always be non-null.
                        Debug.Assert(fallbackHrefs[i] != null);

                        fallbackHrefs[i] = _fileVersionProvider.AddFileVersionToPath(fallbackHrefs[i]);
                    }
                }

                builder.Append(Environment.NewLine);

                // Build the <meta /> tag that's used to test for the presence of the stylesheet
                builder.Append(string.Format(
                                   CultureInfo.InvariantCulture,
                                   "<meta name=\"x-stylesheet-fallback-test\" class=\"{0}\" />",
                                   HtmlEncoder.HtmlEncode(FallbackTestClass)));

                // Build the <script /> tag that checks the effective style of <meta /> tag above and renders the extra
                // <link /> tag to load the fallback stylesheet if the test CSS property value is found to be false,
                // indicating that the primary stylesheet failed to load.
                builder
                .Append("<script>")
                .Append(string.Format(
                            CultureInfo.InvariantCulture,
                            JavaScriptResources.GetEmbeddedJavaScript(FallbackJavaScriptResourceName),
                            JavaScriptEncoder.JavaScriptStringEncode(FallbackTestProperty),
                            JavaScriptEncoder.JavaScriptStringEncode(FallbackTestValue),
                            JavaScriptStringArrayEncoder.Encode(JavaScriptEncoder, fallbackHrefs)))
                .Append("</script>");
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Generates a has of the file.
        /// </summary>
        protected string AddFileVersionToPath(string fileName, IAsset asset)
        {
            if (_fileProvider == null)
            {
                _fileProvider = new FileVersionProvider(
                    asset.GetFileProvider(HostingEnvironment),
                    Cache,
                    ViewContext.HttpContext.Request.PathBase);
            }

            return(_fileProvider.AddFileVersionToPath(fileName));
        }
Ejemplo n.º 9
0
 /// <inheritdoc />
 public override void Process(TagHelperContext context, TagHelperOutput output)
 {
     if (AppendVersion)
     {
         EnsureFileVersionProvider();
         output.Attributes[SrcAttributeName] = _fileVersionProvider.AddFileVersionToPath(Src);
     }
     else
     {
         // Pass through attribute that is also a well-known HTML attribute.
         output.CopyHtmlAttribute(SrcAttributeName, context);
     }
 }
Ejemplo n.º 10
0
        private void AppendVersionedHref(string hrefName, string hrefValue, TagHelperContent builder)
        {
            if (AppendVersion == true)
            {
                hrefValue = FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, hrefValue);
            }

            builder
            .AppendHtml(hrefName)
            .AppendHtml("=\"")
            .Append(hrefValue)
            .AppendHtml("\" ");
        }
Ejemplo n.º 11
0
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            output.CopyHtmlAttribute(SrcAttributeName, context);
            ProcessUrlAttribute(SrcAttributeName, output);

            if (AppendVersion)
            {
                EnsureFileVersionProvider();

                Src = output.Attributes[SrcAttributeName].Value as string;

                // Check if the CDN Base URI is set and add to the src attribute
                if (!string.IsNullOrWhiteSpace(CdnUri))
                {
                    output.Attributes.SetAttribute(SrcAttributeName, string.Format("{0}{1}", CdnUri, _fileVersionProvider.AddFileVersionToPath(Src)));
                }
            }

            //Retrieve any existing onerror handler code
            var onError = output.Attributes[OnErrorAttributeName]?.Value as string;

            //Check if there's a fallback source and no onerror handler
            if (!string.IsNullOrWhiteSpace(FallbackSrc) && string.IsNullOrWhiteSpace(onError))
            {
                string resolvedUrl;
                if (TryResolveUrl(FallbackSrc, out resolvedUrl))
                {
                    FallbackSrc = resolvedUrl;
                }

                if (AppendVersion)
                {
                    FallbackSrc = _fileVersionProvider.AddFileVersionToPath(FallbackSrc);
                }

                //Apply fallback handler code
                onError = $"this.src='{FallbackSrc}';console.log('{Src} NOT FOUND.')";
                output.Attributes.SetAttribute(OnErrorAttributeName, onError);
            }
        }
Ejemplo n.º 12
0
        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (AppendVersion)
            {
                EnsureFileVersionProvider();

                string resolvedUrl;
                if (TryResolveUrl(Src, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
                {
                    Src = resolvedUrl;
                }
                output.Attributes[SrcAttributeName] = _fileVersionProvider.AddFileVersionToPath(Src);
            }
            else
            {
                // Pass through attribute that is also a well-known HTML attribute.
                output.CopyHtmlAttribute(SrcAttributeName, context);
                ProcessUrlAttribute(SrcAttributeName, output);
            }
        }
Ejemplo n.º 13
0
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            output.CopyHtmlAttribute(SrcAttributeName, context);
            ProcessUrlAttribute(SrcAttributeName, output);

            var bareUrl = Src.Substring(0, Src.LastIndexOf('.'));

            if (AppendVersion)
            {
                EnsureFileVersionProvider();
            }

            foreach (var format in SourceFormats)
            {
                var formatUrl = $"{bareUrl}.{format}";
                var finalUrl  = AppendVersion
                    ? FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, formatUrl)
                    : formatUrl;
                output.Content.AppendHtml($@"<source type=""image/{format}"" srcset=""{finalUrl}"" />");
            }

            var url = AppendVersion
                ? FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, Src)
                : Src;

            output.Content.AppendHtml(!Eager
                ? $@"<img src=""{url}"" alt=""{Alt}"" width=""{Width}"" height=""{Height}"">"
                : $@"<img src=""{url}"" alt=""{Alt}"" width=""{Width}"" height=""{Height}"" loading=""lazy"">");

            output.Attributes.Clear();
        }
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (AppendIntegrity == true)
            {
                EnsureFileVersionProvider();

                // Pass through attribute that is also a well-known HTML attribute.
                if (Href != null)
                {
                    output.CopyHtmlAttribute(HrefAttributeName, context);
                }

                // process
                ProcessUrlAttribute(HrefAttributeName, output);

                // get the tag helper version of the Href
                Href = output.Attributes[HrefAttributeName]?.Value as string;

                if (Href != null)
                {
                    // get the pre-versioned path or generate it ourselves
                    var versionedPath = AppendVersion == true ? Href : _fileVersionProvider.AddFileVersionToPath(Href);

                    int queryIndex;
                    if ((queryIndex = versionedPath.IndexOf(QueryFragment)) > -1)
                    {
                        var query = QueryHelpers.ParseQuery(versionedPath.Substring(queryIndex));

                        if (query != null && query.ContainsKey(VersionKey))
                        {
                            var version = query[VersionKey];

                            output.Attributes.Add(IntegrityAttributeName, $"{IntegrityHashName}-{version}");
                            output.Attributes.Add(CrossOriginAttributeName, CrossOriginAnonymousName);
                        }
                    }
                }
            }
        }
Ejemplo n.º 15
0
        private void AppendFallbackHrefs(TagHelperContent builder, IReadOnlyList <string> fallbackHrefs)
        {
            builder.AppendHtml("[");
            var firstAdded = false;

            // Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
            var fallbackHrefsCount = fallbackHrefs.Count;

            for (var i = 0; i < fallbackHrefsCount; i++)
            {
                if (firstAdded)
                {
                    builder.AppendHtml(",\"");
                }
                else
                {
                    builder.AppendHtml("\"");
                    firstAdded = true;
                }

                // fallbackHrefs come from bound attributes (a C# context) and globbing. Must always be non-null.
                Debug.Assert(fallbackHrefs[i] != null);

                var valueToWrite = fallbackHrefs[i];
                if (AppendVersion == true)
                {
                    valueToWrite = FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, fallbackHrefs[i]);
                }

                // Must HTML-encode the href attribute value to ensure the written <link/> element is valid. Must also
                // JavaScript-encode that value to ensure the doc.write() statement is valid.
                valueToWrite = HtmlEncoder.Encode(valueToWrite);
                valueToWrite = JavaScriptEncoder.Encode(valueToWrite);

                builder.AppendHtml(valueToWrite);
                builder.AppendHtml("\"");
            }
            builder.AppendHtml("]");
        }
Ejemplo n.º 16
0
        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            // Pass through attribute that is also a well-known HTML attribute.
            if (Src != null)
            {
                output.CopyHtmlAttribute(SrcAttributeName, context);
            }

            // If there's no "src" attribute in output.Attributes this will noop.
            ProcessUrlAttribute(SrcAttributeName, output);

            // Retrieve the TagHelperOutput variation of the "src" attribute in case other TagHelpers in the
            // pipeline have touched the value. If the value is already encoded this ScriptTagHelper may
            // not function properly.
            Src = output.Attributes[SrcAttributeName]?.Value as string;

            if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out var mode))
            {
                // No attributes matched so we have nothing to do
                return;
            }

            if (AppendVersion == true)
            {
                EnsureFileVersionProvider();

                if (Src != null)
                {
                    var index             = output.Attributes.IndexOfName(SrcAttributeName);
                    var existingAttribute = output.Attributes[index];
                    output.Attributes[index] = new TagHelperAttribute(
                        existingAttribute.Name,
                        FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, Src),
                        existingAttribute.ValueStyle);
                }
            }

            var builder = output.PostElement;

            builder.Clear();

            if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
            {
                BuildGlobbedScriptTags(output.Attributes, builder);
                if (string.IsNullOrEmpty(Src))
                {
                    // Only SrcInclude is specified. Don't render the original tag.
                    output.TagName = null;
                    output.Content.SetContent(string.Empty);
                }
            }

            if (mode == Mode.Fallback)
            {
                if (TryResolveUrl(FallbackSrc, resolvedUrl: out string resolvedUrl))
                {
                    FallbackSrc = resolvedUrl;
                }

                BuildFallbackBlock(output.Attributes, builder);
            }
        }
Ejemplo n.º 17
0
        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            // Pass through attribute that is also a well-known HTML attribute.
            if (Href != null)
            {
                output.CopyHtmlAttribute(HrefAttributeName, context);
            }

            // If there's no "href" attribute in output.Attributes this will noop.
            ProcessUrlAttribute(HrefAttributeName, output);

            // Retrieve the TagHelperOutput variation of the "href" attribute in case other TagHelpers in the
            // pipeline have touched the value. If the value is already encoded this LinkTagHelper may
            // not function properly.
            Href = output.Attributes[HrefAttributeName]?.Value as string;

            Mode mode;

            if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out mode))
            {
                // No attributes matched so we have nothing to do
                return;
            }

            // NOTE: Values in TagHelperOutput.Attributes may already be HTML-encoded.
            var attributes = new TagHelperAttributeList(output.Attributes);

            if (AppendVersion == true)
            {
                EnsureFileVersionProvider();

                if (Href != null)
                {
                    output.Attributes[HrefAttributeName].Value = _fileVersionProvider.AddFileVersionToPath(Href);
                }
            }

            var builder = new DefaultTagHelperContent();

            if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
            {
                BuildGlobbedLinkTags(attributes, builder);
                if (string.IsNullOrEmpty(Href))
                {
                    // Only HrefInclude is specified. Don't render the original tag.
                    output.TagName = null;
                    output.Content.SetContent(HtmlString.Empty);
                }
            }

            if (mode == Mode.Fallback)
            {
                string resolvedUrl;
                if (TryResolveUrl(FallbackHref, resolvedUrl: out resolvedUrl))
                {
                    FallbackHref = resolvedUrl;
                }

                BuildFallbackBlock(builder);
            }

            output.PostElement.SetContent(builder);
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Processes the tag helper.
        /// </summary>
        /// <param name="context">The <see cref="TagHelperContext"/> instance.</param>
        /// <param name="output">The <see cref="TagHelperOutput"/> instance.</param>
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            // Don't load from CDN in development
            if (_env.IsDevelopment())
            {
                return;
            }

            var cdnUri = (CdnUri.EndsWith("/")) ? CdnUri.TrimEnd('/') : CdnUri;

            if (String.IsNullOrWhiteSpace(cdnUri))
            {
                return;
            }

            if (!pathAttribues.ContainsKey(output.TagName))
            {
                return;
            }

            var pathAttributeName = pathAttribues[output.TagName];

            if (!output.Attributes.ContainsName(pathAttributeName))
            {
                return;
            }

            ProcessUrlAttribute(pathAttributeName, output);

            var path = output.Attributes[pathAttributeName].Value?.ToString();

            if (String.IsNullOrWhiteSpace(path))
            {
                return;
            }

            var index = path.IndexOf('?');

            if (index > 0)
            {
                path = path.Substring(index);
            }

            // Don't change path if it's absolute
            Uri uri;

            if (Uri.TryCreate(path, UriKind.Absolute, out uri))
            {
                return;
            }

            if (AppendVersion)
            {
                EnsureFileVersionProvider();

                path = _fileVersionProvider.AddFileVersionToPath(path);
            }

            if (path.StartsWith("~/"))
            {
                path = path.Substring(1);
            }

            if (!path.StartsWith("/"))
            {
                path = "/" + path;
            }

            var cdnPath = cdnUri + path;

            output.Attributes.SetAttribute(pathAttributeName, cdnPath);
        }
Ejemplo n.º 19
0
        private void BuildFallbackBlock(TagHelperAttributeList attributes, DefaultTagHelperContent builder)
        {
            EnsureGlobbingUrlBuilder();
            EnsureFileVersionProvider();

            var fallbackSrcs = GlobbingUrlBuilder.BuildUrlList(FallbackSrc, FallbackSrcInclude, FallbackSrcExclude);

            if (fallbackSrcs.Any())
            {
                // Build the <script> tag that checks the test method and if it fails, renders the extra script.
                builder.Append(Environment.NewLine)
                .Append("<script>(")
                .Append(FallbackTestExpression)
                .Append("||document.write(\"");

                // May have no "src" attribute in the dictionary e.g. if Src and SrcInclude were not bound.
                if (!attributes.ContainsName(SrcAttributeName))
                {
                    // Need this entry to place each fallback source.
                    attributes.Add(new TagHelperAttribute(SrcAttributeName, value: null));
                }

                foreach (var src in fallbackSrcs)
                {
                    // Fallback "src" values come from bound attributes and globbing. Must always be non-null.
                    Debug.Assert(src != null);

                    builder.Append("<script");

                    foreach (var attribute in attributes)
                    {
                        if (!attribute.Name.Equals(SrcAttributeName, StringComparison.OrdinalIgnoreCase))
                        {
                            var encodedKey     = JavaScriptEncoder.JavaScriptStringEncode(attribute.Name);
                            var attributeValue = attribute.Value.ToString();
                            var encodedValue   = JavaScriptEncoder.JavaScriptStringEncode(attributeValue);

                            AppendAttribute(builder, encodedKey, encodedValue, escapeQuotes: true);
                        }
                        else
                        {
                            // Ignore attribute.Value; use src instead.
                            var attributeValue = src;
                            if (AppendVersion == true)
                            {
                                attributeValue = _fileVersionProvider.AddFileVersionToPath(attributeValue);
                            }

                            // attribute.Key ("src") does not need to be JavaScript-encoded.
                            var encodedValue = JavaScriptEncoder.JavaScriptStringEncode(attributeValue);

                            AppendAttribute(builder, attribute.Name, encodedValue, escapeQuotes: true);
                        }
                    }

                    builder.Append("><\\/script>");
                }

                builder.Append("\"));</script>");
            }
        }
Ejemplo n.º 20
0
        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            string resolvedUrl;

            // Pass through attribute that is also a well-known HTML attribute.
            if (Src != null)
            {
                output.CopyHtmlAttribute(SrcAttributeName, context);

                if (TryResolveUrl(Src, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
                {
                    Src = resolvedUrl;
                }

                ProcessUrlAttribute(SrcAttributeName, output);
            }

            var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);

            modeResult.LogDetails(Logger, this, context.UniqueId, ViewContext.View.Path);

            if (!modeResult.FullMatches.Any())
            {
                // No attributes matched so we have nothing to do
                return;
            }

            // NOTE: Values in TagHelperOutput.Attributes may already be HTML-encoded.
            var attributes = new TagHelperAttributeList(output.Attributes);

            if (AppendVersion == true)
            {
                EnsureFileVersionProvider();

                var attributeStringValue = output.Attributes[SrcAttributeName]?.Value as string;
                if (attributeStringValue != null)
                {
                    output.Attributes[SrcAttributeName].Value =
                        _fileVersionProvider.AddFileVersionToPath(attributeStringValue);
                }
            }

            var builder = new DefaultTagHelperContent();

            // Get the highest matched mode
            var mode = modeResult.FullMatches.Select(match => match.Mode).Max();

            if (mode == Mode.GlobbedSrc || mode == Mode.Fallback && !string.IsNullOrEmpty(SrcInclude))
            {
                BuildGlobbedScriptTags(attributes, builder);
                if (string.IsNullOrEmpty(Src))
                {
                    // Only SrcInclude is specified. Don't render the original tag.
                    output.TagName = null;
                    output.Content.SetContent(string.Empty);
                }
            }

            if (mode == Mode.Fallback)
            {
                if (TryResolveUrl(FallbackSrc, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
                {
                    FallbackSrc = resolvedUrl;
                }

                BuildFallbackBlock(attributes, builder);
            }

            output.PostElement.SetContent(builder);
        }
Ejemplo n.º 21
0
        /// <inheritdoc />
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            string resolvedUrl;

            // Pass through attribute that is also a well-known HTML attribute.
            if (Href != null)
            {
                output.CopyHtmlAttribute(HrefAttributeName, context);
            }

            // If there's no "href" attribute in output.Attributes this will noop.
            ProcessUrlAttribute(HrefAttributeName, output);

            // Retrieve the TagHelperOutput variation of the "href" attribute in case other TagHelpers in the
            // pipeline have touched the value. If the value is already encoded this LinkTagHelper may
            // not function properly.
            Href = output.Attributes[HrefAttributeName]?.Value as string;

            var modeResult = AttributeMatcher.DetermineMode(context, ModeDetails);

            modeResult.LogDetails(Logger, this, context.UniqueId, ViewContext.View.Path);

            if (!modeResult.FullMatches.Any())
            {
                // No attributes matched so we have nothing to do
                return;
            }

            // NOTE: Values in TagHelperOutput.Attributes may already be HTML-encoded.
            var attributes = new TagHelperAttributeList(output.Attributes);

            if (AppendVersion == true)
            {
                EnsureFileVersionProvider();

                if (Href != null)
                {
                    output.Attributes[HrefAttributeName].Value = _fileVersionProvider.AddFileVersionToPath(Href);
                }
            }

            var builder = new DefaultTagHelperContent();

            // Get the highest matched mode
            var mode = modeResult.FullMatches.Select(match => match.Mode).Max();

            if (mode == Mode.GlobbedHref || mode == Mode.Fallback && !string.IsNullOrEmpty(HrefInclude))
            {
                BuildGlobbedLinkTags(attributes, builder);
                if (string.IsNullOrEmpty(Href))
                {
                    // Only HrefInclude is specified. Don't render the original tag.
                    output.TagName = null;
                    output.Content.SetContent(HtmlString.Empty);
                }
            }

            if (mode == Mode.Fallback)
            {
                if (TryResolveUrl(FallbackHref, encodeWebRoot: false, resolvedUrl: out resolvedUrl))
                {
                    FallbackHref = resolvedUrl;
                }

                BuildFallbackBlock(builder);
            }

            output.PostElement.SetContent(builder);
        }