Giter Club home page Giter Club logo

toolbelt.blazor.headelement's Introduction

Blazor Head Element Helper NuGet Package

Summary

This components and services allows you to change the title of document, "meta" elements such as OGP, and "link" elements such as canonical url, favicon etc. on your Blazor app.

This package supports both seiver-side Blazor and client-side Blazor WebAssembly app.

And also supports server-side pre-rendering on your server-side Blazor app.

See also following the live demonstration sites.

Notice

Blazor on .NET 6 (since preview 7) or later has started to provide <PageTitle> and <HeadContent> components.

"ASP.NET Core updates in .NET 6 Preview 7 - Modify HTML <head> content from Blazor components" | ASP.NET Blog

However, I'm going to continue to provide this package and keep maintaining, because this library still has a few advantages in some edge cases.

Please see the following table to know the differences between this library and <PageTitle>/<HeadContent> components in .NET 6 or later.

Feature This library .NET6 or later
Overriding pre-rendered meta or link πŸ‘ Can handle it properly. 😒 Just append it. it may cause duplication.
Canceling <meta "http-equiv"="refresh" /> πŸ‘ Works well. 😒 There is no support.
Using it as a service, not components πŸ‘ Supported. 😒 Maybe not Supported.
Modify <head> content from many places πŸ‘ Can do. 😒 Only one instance of the <HeadContent> component can effect.
Server-side pre-rendering πŸ‘ Just add one line inside the server code. 😒 You have to split the fallback page into two .cshtml files for resolving complicated rendering order problems.
Controlling the order of elements inside a <head> 😒 hard πŸ‘ Can control perfectly
Support 😒 Just personal hobby πŸ‘ Official support from the ASP.NET Team

How to use

Installation

  1. Add package to your project like this.
dotnet add package Toolbelt.Blazor.HeadElement
  1. Register "Head Element Helper" service at your Blazor app's Startup.
using Toolbelt.Blazor.Extensions.DependencyInjection; // <- Add this, and...

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddHeadElementHelper(); // <- Add this.
    ...

If the project is Blazor WebAssembly App v.3.2+, you should do it in Program class, instead.

using Toolbelt.Blazor.Extensions.DependencyInjection; // <- Add this, and...
...
public class Program
{
  public static async Task Main(string[] args)
  {
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    ...
    builder.Services.AddHeadElementHelper(); // <- Add this.
    ...
  1. Open Toolbelt.Blazor.HeadElement namespace in _Imports.razor file.
@* This is "_Imports.razor" *@
...
@using Toolbelt.Blazor.HeadElement

A. Change the title of the document

  1. Markup <Title> component in your .razor file.
@* This is "Pages/Counter.razor" *@
@page "/counter"

<Title>Counter(@currentCount) - Server Side App</Title>

The title of document will be changed.

fig1

B. Change "meta" elements

You can also add or override "meta" elements at runtime dynamically using <Meta> component like this.

@* This is "Pages/Counter.razor" *@
@page "/counter"

<Meta Property="ogp:title" Content="Counter" />

Note: You can also use native attribute names (lower and kebab case. ex: "http-equiv") instead of Razor component parameter names (pascal case).

C. Change "link" elements

You can add or override "link" elements at runtime dynamically using <Link> component like this.

@* This is "Pages/Counter.razor" *@
@page "/counter"

<Link Rel="icon" Href="@($"/favicons/{GetFaviconName()}")" />

fig3

Note: You can also use native attribute names (lower and kebab case) instead of Razor component parameter names (pascal case).

D. IHeadElementHelper

You can do these tasks by using IHeadElementHelper service instead of using <Title>, <Meta>, and <Link> components.

You can get the IHeadElementHelper service instnace by "Dependency Injection" mechanism.

@inject IHeadElementHelper HeadElementHelper
@using static Toolbelt.Blazor.HeadElement.MetaElement
...
@code {
  protected override async Task OnInitializedAsync()
  {
    await HeadElementHelper.SetTitleAsync("Wow!");
    await HeadElementHelper.SetMetaElementsAsync(
      ByName("description", "Foo bar..."),
      ByProp("og:title", "WoW!")
    );
    await HeadElementHelper.SetLinkElementsAsync(
      new LinkElement("canonical", "https://foo.com/bar")
    );
    ...

Server-side pre-rendering support

If you want to get srever-side pre-rendering support, do this.

  1. Add Toolbelt.Blazor.HeadElement.ServerPrerendering package to your project like this.
dotnet add package Toolbelt.Blazor.HeadElement.ServerPrerendering
  1. Register "Head Element Server Prerendering" middleware at your server-side Blazor app's Startup, before app.UseStaticFiles().
using Toolbelt.Blazor.Extensions.DependencyInjection; // <- Add this, and...
...
public class Startup
{
  ...
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddHeadElementHelper(); // <!- Don't miss this line, and...
    ...

  public void Configure(IApplicationBuilder app)
  {
    app.UseHeadElementServerPrerendering(); // <- Add this.
    ...
    app.UseStaticFiles()
    ...

fig2

License

Mozilla Public License Version 2.0

toolbelt.blazor.headelement's People

Contributors

jsakamoto avatar pacas00 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

toolbelt.blazor.headelement's Issues

Title breaks on URI Fragment "domain.com/test#foo"

Upon page load with the following code, the page is correctly rendered as having the title "Test Title". However, when the user clicks on the link, that title disappears and, in this case, is replaced by the default title of the Blazor App. This code is on a brand new .Net 5 Blazor project with default settings. The issue appears in both Pre-Rendering and in Server Rendering.

@page "/TitleTest"
@using Toolbelt.Blazor.HeadElement

<Title>Test Title</Title>

<a href="/TitleTest#">example</a>

Unhandled exception in circuit

Hi, I have some problem with the application.
There is probably something looping in the nginx configuration that caused the application to get ddos/flood.

I got error 500 status probably because of this error.

It could be caused by something else but I figured I'd report the issue.

Json error:

{"Timestamp":"2022-12-10T09:22:18.5782563+01:00","Level":"Error","MessageTemplate":"Unhandled exception in circuit '{CircuitId}'.","RenderedMessage":"Unhandled exception in circuit '\"UPcusR1_t3zFRYb5laCQXsZwm6hJzBtpeFrz52Zy4MU\"'.","Exception":"Microsoft.JSInterop.JSDisconnectedException: JavaScript interop calls cannot be issued at this time. This is because the circuit has disconnected and is being disposed.\n   at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(Int64 asyncHandle, String identifier, String argsJson, JSCallResultType resultType, Int64 targetInstanceId)\n   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, CancellationToken cancellationToken, Object[] args)\n   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)\n   at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)\n   at Microsoft.JSInterop.Implementation.JSObjectReference.DisposeAsync()\n   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.EnsureScriptEnabledAsync[T]()\n   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.InvokeJSAsync[T](String identifier, Object[] args)\n   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultTitleAsync()\n   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultsAsync()\n   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.ResetIfNeededAsync()\n   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.SetTitleAsync(String title)\n   at Stand.Libraries.Website.AppState.Head.SetTitleAsync(String title) in C:\\Git\\Stand\\Source\\Libraries\\Stand.Libraries.Website\\AppState\\Head.cs:line 24\n   at Grajse.Areas.Web.Pages.Index.OnInitializedAsync() in C:\\Git\\Stand\\Source\\Projects\\Grajse\\Areas\\Web\\Pages\\Index.razor:line 56\n   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()\n   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)","Properties":{"CircuitId":"UPcusR1_t3zFRYb5laCQXsZwm6hJzBtpeFrz52Zy4MU","EventId":{"Id":111,"Name":"CircuitUnhandledException"},"SourceContext":"Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost","TransportConnectionId":"a53Zn0DqAw0MWHbf5vYLrw","RequestId":"0HMMQGRTPT3J1:00000001","RequestPath":"/_blazor","ConnectionId":"0HMMQGRTPT3J1"}}

Parse error:

{
"Timestamp":"2022-12-10T09:22:18.5782563+01:00",
"Level":"Error",
"MessageTemplate":"Unhandled exception in circuit '{CircuitId}'.",
"RenderedMessage":"Unhandled exception in circuit '\"UPcusR1_t3zFRYb5laCQXsZwm6hJzBtpeFrz52Zy4MU\"'.",
"Exception":"Microsoft.JSInterop.JSDisconnectedException: JavaScript interop calls cannot be issued at this time. This is because the circuit has disconnected and is being disposed.\n at 
Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(Int64 asyncHandle, String identifier, String argsJson, JSCallResultType resultType, Int64 targetInstanceId)\n at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, CancellationToken cancellationToken, Object[] args)\n at 
Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)\n at 
Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)\n at 
Microsoft.JSInterop.Implementation.JSObjectReference.DisposeAsync()\n at 
Toolbelt.Blazor.HeadElement.HeadElementHelperService.EnsureScriptEnabledAsync[T]()\n at 
Toolbelt.Blazor.HeadElement.HeadElementHelperService.InvokeJSAsync[T](String identifier, Object[] args)\n at 
Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultTitleAsync()\n at 
Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultsAsync()\n at 
Toolbelt.Blazor.HeadElement.HeadElementHelperService.ResetIfNeededAsync()\n at 
Toolbelt.Blazor.HeadElement.HeadElementHelperService.SetTitleAsync(String title)\n at 
Stand.Libraries.Website.AppState.Head.SetTitleAsync(String title) in 
C:\\Git\\Stand\\Source\\Libraries\\Stand.Libraries.Website\\AppState\\Head.cs:line 24\n at 
Grajse.Areas.Web.Pages.Index.OnInitializedAsync() in 
C:\\Git\\Stand\\Source\\Projects\\Grajse\\Areas\\Web\\Pages\\Index.razor:line 56\n at 
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()\n at 
Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState 
owningComponentState)",

"Properties":{
"CircuitId":"UPcusR1_t3zFRYb5laCQXsZwm6hJzBtpeFrz52Zy4MU",
"EventId":{
"Id":111,
"Name":"CircuitUnhandledException"
},
"SourceContext":"Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost",
"TransportConnectionId":"a53Zn0DqAw0MWHbf5vYLrw",
"RequestId":"0HMMQGRTPT3J1:00000001",
"RequestPath":"/_blazor",
"ConnectionId":"0HMMQGRTPT3J1"
}
}

my provider:

namespace Stand.Libraries.Website.AppState;

public class Head
{
    public readonly IHeadElementHelper _headElementHelper;

    public List<MetaElement> MetaElements = new();
    public List<LinkElement> LinkElements = new();

    public static string? TitlePrefix { get; set; }

    public Head(IHeadElementHelper HeadElementHelper)
        => _headElementHelper = HeadElementHelper;

    public async Task SetTitleAsync(string title)
        => await _headElementHelper.SetTitleAsync($"{TitlePrefix}{title}");

    public async Task SetDescriptionAsync(string description)
        => await _headElementHelper.SetMetaElementsAsync(
            ByName("description", description)
        );

    public async Task SetMetaAsync(string property, string content)
        => await _headElementHelper.SetMetaElementsAsync(
            ByProp(property, content)
        );

    public async Task SetLinkAsync(List<Link> links)
    {
        links.ForEach(x => LinkElements.Add(new LinkElement(x.Rel, x.Href)));   
        await _headElementHelper.SetLinkElementsAsync(LinkElements.ToArray());
    }
        
    
    public async Task SetMetaAsync(List<Meta> metas)
    {
        var element = new List<MetaElement>();
        metas.ForEach(x => element.Add(ByProp(x.Property, x.Content)));
        await _headElementHelper.SetMetaElementsAsync(element.ToArray());
    }

    public class Meta
    {
        public string Property { get; set; } = string.Empty;
        public string Content { get; set; } = string.Empty;
    }

    public class Link
    {
        public string Rel { get; set; } = string.Empty;
        public string Href { get; set; } = string.Empty;
    }

}

Index:

    protected override async Task OnInitializedAsync()
        => await Head.SetTitleAsync(Culture.Translate("Home Page"));

Details of my problem described elsewhere:
dotnet/aspnetcore#45522 (comment)

Performance issue

Hi, I noticed that with more meta tags it creates a performance issue and high latency.

I use C# Core 7 rc1 Blazor server side

I created my own provider:

public class Head
{
    public readonly IHeadElementHelper _headElementHelper;

    public Head(IHeadElementHelper HeadElementHelper) 
        => _headElementHelper = HeadElementHelper;

    public async Task SetTitleAsync(string title)
        => await _headElementHelper.SetTitleAsync(title);

    public async Task SetDescriptionAsync(string description)
        => await _headElementHelper.SetMetaElementsAsync(
            ByName("description", description)
        );

    public async Task SetMetaAsync(string property, string content)
        => await _headElementHelper.SetMetaElementsAsync(
            ByProp(property, content)
        );

    public async Task SetMetaAsync(List<Meta> metas)
    {
        var element = new List<MetaElement>();
        metas.ForEach(x => element.Add(ByProp(x.Property, x.Content)));
        await _headElementHelper.SetMetaElementsAsync(element.ToArray());
    }

    public class Meta
    {
        public string Property { get; set; } = string.Empty;
        public string Content { get; set; } = string.Empty;
    }

}

Then I unleashed it:

    protected override async Task OnParametersSetAsync()
    {
        await Head.SetTitleAsync("Testuje");
        await Head.SetDescriptionAsync("test");
        await Head.SetMetaAsync(new() {
            new() { Property = "og:url", Content = "" },
            new() { Property = "og:type", Content = "article" },
            new() { Property = "og:title", Content = "" },
            new() { Property = "og:description", Content = "" },
            
            new() { Property = "og:image", Content = "" },
            new() { Property = "og:image:width", Content = "1200" },
            new() { Property = "og:image:height", Content = "630" },
            new() { Property = "og:image:alt", Content = "" },
            new() { Property = "og:image:type", Content = "image/jpeg" },
            new() { Property = "og:site_name", Content = "" },
            new() { Property = "og:locale", Content = "" },

            new() { Property = "article:published_time", Content = "" },
            new() { Property = "article:modified_time", Content = "" },
            new() { Property = "article:section", Content = "" },
            new() { Property = "article:tag", Content = "" },
            new() { Property = "article:author", Content = "" },
            new() { Property = "article:publisher", Content = "" },
            new() { Property = "article:published_first", Content = "" },
            new() { Property = "article:published_last", Content = "" },
            new() { Property = "article:expiration_time", Content = "" },
            new() { Property = "article:modified_time", Content = "" },

            new() { Property = "twitter:card", Content = "summary_large_image" },
            new() { Property = "twitter:title", Content = "" },
            new() { Property = "twitter:description", Content = "" },
            new() { Property = "twitter:url", Content = "" },
            new() { Property = "twitter:image", Content = "" },
            new() { Property = "twitter:image:alt", Content = "" },
            new() { Property = "twitter:site", Content = "" },
            new() { Property = "twitter:creator", Content = "" },
        });
    }

Unfortunately, with such a large number of tags, the page loading time jumped from 0.3 seconds to 1.6 seconds.
After turning off the library, the page loads in 0.3 seconds.

I count my time in generating HTML dom.

Now it runs tests on the network.
Without HeadElement
image

from HeadElement
image

There is a problem somewhere, we need to create a benchmark.

The problem also occurs in the clean version of the project.

SEO

Hi there,

are there any findings as to how Blazor Server in general and this solution in particular behave with regard to search engine optimization?

Are the bots able to crawl such SPA-pages and index all of them?

Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).

I have this problem, I'm using Core 6 preview Blazor server

[13:37:22 WRN] Unhandled exception rendering component: Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).
Error: Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).
    at https://localhost:5001/_framework/blazor.server.js:1:288
    at Array.forEach (<anonymous>)
    at r.findFunction (https://localhost:5001/_framework/blazor.server.js:1:256)
    at v (https://localhost:5001/_framework/blazor.server.js:1:1882)
    at https://localhost:5001/_framework/blazor.server.js:1:2662
    at new Promise (<anonymous>)
    at et.beginInvokeJSFromDotNet (https://localhost:5001/_framework/blazor.server.js:1:2643)
    at https://localhost:5001/_framework/blazor.server.js:1:62750
    at Array.forEach (<anonymous>)
    at et._invokeClientMethod (https://localhost:5001/_framework/blazor.server.js:1:62736)
Microsoft.JSInterop.JSException: Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).
Error: Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).
    at https://localhost:5001/_framework/blazor.server.js:1:288
    at Array.forEach (<anonymous>)
    at r.findFunction (https://localhost:5001/_framework/blazor.server.js:1:256)
    at v (https://localhost:5001/_framework/blazor.server.js:1:1882)
    at https://localhost:5001/_framework/blazor.server.js:1:2662
    at new Promise (<anonymous>)
    at et.beginInvokeJSFromDotNet (https://localhost:5001/_framework/blazor.server.js:1:2643)
    at https://localhost:5001/_framework/blazor.server.js:1:62750
    at Array.forEach (<anonymous>)
    at et._invokeClientMethod (https://localhost:5001/_framework/blazor.server.js:1:62736)
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args) in Microsoft.JSInterop.dll:token 0x6000039+0x18b
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.InvokeJSAsync[T](String identifier, Object[] args) in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000004+0x0
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultTitleAsync() in Toolbelt.Blazor.HeadElement.Services.dll:token 0x600000d+0x8f
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultsAsync() in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000007+0x77
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.ResetIfNeededAsync() in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000006+0xe8
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.SetTitleAsync(String title) in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000009+0xdb
   at Toolbelt.Blazor.HeadElement.Title.OnParametersSetAsync() in Toolbelt.Blazor.HeadElement.dll:token 0x6000030+0xf1
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) in Microsoft.AspNetCore.Components.dll:token 0x60000a2+0x65
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() in Microsoft.AspNetCore.Components.dll:token 0x60000a0+0x154
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) in Microsoft.AspNetCore.Components.dll:token 0x600023e+0xb9
[13:37:22 ERR] Unhandled exception in circuit 'vMPYy120JDyAqnqG6k6T7qo-bQJrEf2pq1dbe2n2sGU'.
Microsoft.JSInterop.JSException: Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).
Error: Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined).
    at https://localhost:5001/_framework/blazor.server.js:1:288
    at Array.forEach (<anonymous>)
    at r.findFunction (https://localhost:5001/_framework/blazor.server.js:1:256)
    at v (https://localhost:5001/_framework/blazor.server.js:1:1882)
    at https://localhost:5001/_framework/blazor.server.js:1:2662
    at new Promise (<anonymous>)
    at et.beginInvokeJSFromDotNet (https://localhost:5001/_framework/blazor.server.js:1:2643)
    at https://localhost:5001/_framework/blazor.server.js:1:62750
    at Array.forEach (<anonymous>)
    at et._invokeClientMethod (https://localhost:5001/_framework/blazor.server.js:1:62736)
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args) in Microsoft.JSInterop.dll:token 0x6000039+0x18b
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.InvokeJSAsync[T](String identifier, Object[] args) in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000004+0x0
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultTitleAsync() in Toolbelt.Blazor.HeadElement.Services.dll:token 0x600000d+0x8f
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultsAsync() in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000007+0x77
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.ResetIfNeededAsync() in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000006+0xe8
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.SetTitleAsync(String title) in Toolbelt.Blazor.HeadElement.Services.dll:token 0x6000009+0xdb
   at Toolbelt.Blazor.HeadElement.Title.OnParametersSetAsync() in Toolbelt.Blazor.HeadElement.dll:token 0x6000030+0xf1
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) in Microsoft.AspNetCore.Components.dll:token 0x60000a2+0x65
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() in Microsoft.AspNetCore.Components.dll:token 0x60000a0+0x154
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState) in Microsoft.AspNetCore.Components.dll:token 0x600023e+0xb9
[13:37:22 INF] Executed endpoint '/_blazor'

System.ObjectDisposedException showing up in Production Logs

I'm seeing the following error show up in my Logs.

I'm unable to replicate it manually but I'm seeing it on my production site. The best I can guess, it's a timing issue where the client has navigated away from the page while OnParameterSetAsync() is still executing. I suspect this might be caused by a crawler bot quickly navigating the links on the site?

This is Blazor Server running on .NET 6 and HeadElement 6.0.2 hosted in Azure AppService.

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'JSObjectReference'.
   at Microsoft.JSInterop.Implementation.JSObjectReference.ThrowIfDisposed()
   at Microsoft.JSInterop.Implementation.JSObjectReference.InvokeAsync[TValue](String identifier, Object[] args)
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.InvokeJSAsync[T](String identifier, Object[] args)
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultTitleAsync()
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultsAsync()
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.ResetIfNeededAsync()
   at Toolbelt.Blazor.HeadElement.HeadElementHelperService.SetLinkElementsAsync(LinkElement[] elements)
   at Toolbelt.Blazor.HeadElement.Link.OnParametersSetAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

[Bug] Problem with polish chars eg. Δ…,Ε›,Δ‡,Δ™

Hi, thank you for the great lib.
When I create Title with for example polish chars eg. Δ…,Ε›,Δ‡,Δ™ - title render blank

<Title>Darmowe zdjecia</Title> works correct but <Title>Darmowe zdjΔ™cia</Title> render <title></title> in html

With Meta everything works ok.

Best regards.

Cannot process pending renders after the renderer has been disposed.

Hi,

I got an error : Cannot process pending renders after the renderer has been disposed.

It is when I start the application.

I noticed that if I only use the "Title" tag, it works.

Stack trace

at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
at MyApp.Web.Shared.Application.ApplicationBar.d__21.MoveNext() in C:\MyApp\Web\Shared\Application\ApplicationBar.razor:line 486

Some code

Really nothing special... but

context.Services.AddHeadElementHelper();
app.UseHeadElementServerPrerendering();
<Meta Property="ogp:url" Content="https://www.socloze.com"/>
<Meta Property="ogp:type" Content="website"/>
<Meta Property="ogp:title" Content="SoCloze"/>
<Meta Property="ogp:image" Content="https://www.socloze.com/images/logo!40.png"/>
<Meta Property="og:site_name" Content="SoCloze"/>
<Meta Name="robots" Content="noimageindex, noarchive"/>
<Meta Name="apple-mobile-web-app-status-bar-style" Content="default"/>
<Meta Name="mobile-web-app-capable" Content="yes"/>
<Meta Name="theme-color" Content="#ffffff"/>
<Meta Property="application-name" Name="SoCloze"/>
<Meta Property="description" Name="description" Content="Find products nearby your location and ideas on SoCloze" data-app="true"/>
<Meta Property="og:description" Name="og:description" Content="Find products nearby your location and ideas on SoCloze" data-app="true"/>

Head elements output in random order

First off, thank you for this!

In troubleshooting some meta properties I noticed that they are rendered to the DOM in at least a semi-random order. Is there perhaps an option that I'm missing to output the order of elements in the order that they are declared in my razor pages?

If this isn't part of the feature-set and it is possible to do this, I would be glad to submit a pull request for it!

Thank you again,
TheShrug

Is there any solution to changing the CSS class of the <body> element?

Hi jsakamoto!

Thank you for this excellent nuget package.
I realize that your package is designed for manipulating the element but I would like the ability to manipulate the <body> element and I guess your code is very close already. Would you consider extending the functionality of this package to support attributes on the <body> tag? Or maybe you have another package available that can do that? Or can suggest an alternative please?

Many thanks for your advice.

Benjamin

Crashes on Linux .NET 5

I had been using this library on .NET 3.1 on Ubuntu for a while, but after upgrading to .NET 5,0, I get this crash. Removing Toolbelt.Blazor.HeadElement from the project allows it to run. It also was running fine on my Windows development machine.

SyntaxError: Unexpected token , in JSON at position 30
Unhandled exception rendering component: Unexpected token , in JSON at position 30
warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
""at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
""at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
""at Toolbelt.Blazor.HeadElement.Link.OnParametersSetAsync()
""at Toolbelt.Blazor.HeadElement.HeadElementHelperService.SetLinkElementsAsync(LinkElement[] elements)
""at Toolbelt.Blazor.HeadElement.HeadElementHelperService.GetDefaultLinkElementsAsync()
""at Toolbelt.Blazor.HeadElement.HeadElementHelperService.InvokeJSAsync[T](String identifier, Object[] args)
""at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
"" at e.connection.onreceive (https://store.spiritualselftransformation.com/_framework/blazor.server.js:1:17296)
"" at e.processIncomingData (https://store.spiritualselftransformation.com/_framework/blazor.server.js:1:24211)
"" at e.invokeClientMethod (https://store.spiritualselftransformation.com/_framework/blazor.server.js:1:26273)
"" at Array.forEach (<anonymous>)
"" at https://store.spiritualselftransformation.com/_framework/blazor.server.js:1:26303
"" at e.beginInvokeJSFromDotNet (https://store.spiritualselftransformation.com/_framework/blazor.server.js:1:70021)
"" at new Promise (<anonymous>)
"" at https://store.spiritualselftransformation.com/_framework/blazor.server.js:1:70055
"" at Object.query (https://store.spiritualselftransformation.com/_content/Toolbelt.Blazor.HeadElement.Services/script.min.js:1:1674)
"" at JSON.parse (<anonymous>)

[Feature Request] Discriminate <Meta> overrides base on other attributes too

As of now it does seem that the Name property is the only one being used for search and override so if you have some other extra attribute distinguising the <meta>s it does only override the first match by Name, ex.:

<meta name="theme-color" media="(prefers-color-scheme: light)" content="hsl(25,84%,88%)">
<meta name="background-color" media="(prefers-color-scheme: light)" content="hsl(25,84%,88%)">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="hsl(25,16%,12%)">
<meta name="background-color" media="(prefers-color-scheme: dark)" content="hsl(25,16%,12%)">

as you can see there's the media query that can enable different values based on the color-scheme of the OS but the <Meta> component goes and replaces only the first two values leaving the next two untouched.

Is there a way to have implemented search match based also on other attributes?

Using Blazor Static Rendering Does Not Work

Dotnet core 5
Blazor Server

When I use Blazor Static Rendering with this library, certain parts of my HTML break, Namely the <pre> and <code> tags, if I try to change the title while statically rendering with Blazor these tags behave very strangely.

Switching to Server Prerender Does however work.

If i simple remove the call to the library weather that be as a service or components, I get this same behavior.

[Feature Request]

Currently the Color property is not supported, meaning that Safari pinned tabs are not supported:

<Link Rel="mask-icon" Href="@($"/images/{clientName}/safari-pinned-tab.svg")" Color="#82aabb" />

Is there anything that can be done? It is not a deal breaker, but would certainly be nice to have.

Could not find 'Toolbelt.Head.Title.query' ('Toolbelt' was undefined) but on Blazor WASM and DisableClientScriptAutoInjection = true

Hi, I saw the other issue which was about the server side type of project. Here I have a Blazor WASM project and I need offline functionality to work(so no dynamic loading of js).

How am I supposed to accomplish such setup?
As of now I tried with that option disabled and included

<script src="_content/Toolbelt.Blazor.HeadElement.Services/script.min.js"></script>

in the index.html but it doesn't seem to do the trick, what should I do to make it work properly?

as-attribute on link-elements

Hello,

I would like to create different preloadings based on the active page. But I cannot set the as-attibute on a LinkElement... would it be possible to extend this?

Thank you very much

[Feature Request] Options to supply Type, Hreflang, Title and other options in Link Element

@jsakamoto, Many thanks for the wonderful package. This works awesome solving many problems.

I'm trying <Link> element and I'm not able to specify type="application/xml", hreflang="en", title="some title" for it.

Example,

<Link rel="sitemap" type="application/xml" title="Sitemap" Href="@($"{BaseUrl}sitemap.xml")" />
<Link rel="alternate" type="application/rss+xml" Href="@($"{BaseUrl}atom.xml")" />
<Link rel="alternate" Href="@($"{BaseUrl}blogs/{Slug}/")" hreflang="en" />
<Link rel="alternate" Href="@($"{BaseUrl}blogs/{Slug}/")" hreflang="x-default" />
<Link rel="canonical" Href="@($"{BaseUrl}blogs/{Slug}/")" />
<Link rel="index" title="@Title - Base Title" Href="@($"{BaseUrl}blogs/{Slug}/")" />

This throws script error.

Please can you add an option to supply Type, Title, Hreflang to Link Element?

Provide the ability to set a fixed suffix/prefix for the title

It happens fairly often where you want the titles across all the pages in your website to have a shared pattern.
For instance, no matter what page you're on in Stack Overflow, the title always ends with " - Stack Overflow".

It would be nice if this library provides a feature that allows users to achieve this, it would make it even more superior than it already is to Microsoft's built-in <Title> component.

I propose a property gets added to HeadElementHelperServiceOptions, so that you could do the following:

builder.Services.AddHeadElementHelper(op =>
{
    op.TitleSuffix = " | Tesla Motors";
});

Or something similar.
Without this, we currently have to repeat the suffix on every page:
(Products.razor)

<Title>Products | Tesla Motors</Title>

That way, this would suffice:

<Title>Products</Title>

Nicer, and cleaner.

There could also be a parameter for the <Title> component with which you can specify not to use the suffix, this is useful when for example you have a page whose title you want to be unique:

<Title UseSuffix="false">Tesla Motors Home Page</Title>

Change Html base tag

Is it possible to change html base tag?

<base href="/" />

The dev and production environment have different href value. I would like to change the value dynamically instead of manually.

Can only include one HrefLang tag (cannot implement recommended pattern)

It is recommended to include the current language of a page in the hreflang as well as a link for each and every other language version of a page.

With the current implementation the Hreflang Link elements are overwriting itself.

I think it's because of the code below:

const linkComparer = {
           canonical: () => true,
           prev: () => true,
           next: () => true,
           icon: (m, a) => ('' + m.sizes) === a.s,
           alternate: (m, a) => m.type === a.p && m.media === a.m,
           preload: (m, a) => getAttr(m, href) === a.h && m.media === a.m,
       };

Shouldn't the HrefLang also be part of the link comparison?

After update to 6.0.0 the tab text will not automatically update

After update to 6.0.0 the tab text will not automatically update.
If I go to a new page, the tab text remains the same.
Hit refresh, and it changes.
I am also using the server prerendering.

It worked fine the day before the 6.0.0 update. No other changes were made.

.NET 5.0 support

Starting with .NET 5 RC1, there's a binary-breaking change in RenderTreeFrame that will affect this library because of how it uses the internal RenderTreeFrame APIs. For this library to work with .NET 5 RC1 and later, you'll need to recompile. And because you're using the Microsoft.AspNetCore.Components.RenderTree.* APIs directly you'll have to multitarget netstandard2.0 and net5.0 if you want to retain compatibility with both.

I think this line here would need to be changed to:

<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>

... and then instead of depending purely on Microsoft.AspNetCore.Components.Web 3.0.0 directly, you'll need to depend on either the 3.x or 5.x versions depending on target framework. For example, replace this line here with something like:

<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="3.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.0-rc.1.*" Condition="'$(TargetFramework)' != 'netstandard2.0'" />

ObjectDisposedException: Cannot access a closed Stream

I use "Toolbelt.Blazor.HeadElement.ServerPrerendering" error:
An unhandled exception occurred while processing the request.
ObjectDisposedException: Cannot access a closed Stream.
System.IO.MemoryStream.Seek(long offset, SeekOrigin loc)

Stack Query Cookies Headers Routing
ObjectDisposedException: Cannot access a closed Stream.
System.IO.MemoryStream.Seek(long offset, SeekOrigin loc)
Toolbelt.Blazor.HeadElement.Middlewares.HeadElementServerPrerenderingMiddleware.InvokeAsync(HttpContext context, IHeadElementHelperStore store)
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Hope the author helps to fix this error! Thanks,

Consider falling back on setAttribute when HTMLMetaElement.prototype.media doesn't exist

Hello from the web compatiblity team at Mozilla, and thank you for your hard work! While diagnosing this webcompat.com report, I noticed that in issue #30, code was added to check meta elements' media property, which Firefox does not yet support.

I was wondering if you would be willing to adjust the code to use getAttribute to fetch the media value when HTMLMetaElement.prototype.media is not yet supported? Otherwise as meta-tags are rewritten, they will end up losing their media attribute, and theme engines relying on it will not function as well (as reported in the webcompat issue).

Should I reapply <Meta> tags on each page of the app?

Currently I have a top layer component called <CascadingState> where I provide for common state for every part of the webapp, and there do reside all the <Meta> tags(so I don't need to repeat them for each page) that seem to work fine when a page is loaded with forceReload or it's customized within that same page, updating those tags values in real time.

But the common navigation seem to reset the state of <Meta> tags to the default values(of the original index.html).
Then obviously each page has <Title> that setups the state of the title alone.
So my final guess is that you can't really separate the states in the way that I did, and the latest activated component does override everything, so I guess I'm gonna write a component to manage in a tight ways such repetitive state and let you all know.

Allowing multiple og:image elements

Defining multiple tags with the same keys can be useful. In the case of "og:image" Facebook sharing (and prehaps other SEO parsers) will take all the elements and attach all the images to the post.

Currently a code like this:

<Meta Property="og:image" Content="https://img1" />
<Meta Property="og:image" Content="https://img2" />

Will leave the header with only a single - using just the value from the latest declaration (in this case with value "https://img2". Adding the option to have multiple items of the same sort would be useful.

<Link> creates forece reload

Using this method causes a heavy reload of the page:

@using Toolbelt.Blazor.HeadElement
<Link Rel="canonical" Href="https://google.pl" />

The problem is described here:
dotnet/aspnetcore#15317

What's the problem?
In the case of server side rendering, blazor loads the page content and then the async elements are reloaded.
This gives us the effect of double page loading.

Only Link has a problem, meta tags work well.

Demo:
https://gyazo.com/b36e3ea01ebeefb56e60b524b3b24532

If I don't use or _headElementHelper.SetLinkElementsAsync then the problem disappears.

Changelogs not found

Hi, I was just wondering if you have any place where I could look up the changelog of each new release of the library, there's such place?
Otherwise I just wonder what was done behind the scenes when I upgrade such library.

Link support and title constant

Hi, what do you think to do to support:
<link rel ...> or for <style>?
Quite often we need to load a specific style only for a specific subpage, so why load it when the user does not enter there?

Alternatively, you can also add <script> so that it loads in the footer, sometimes we want to quickly add a script or a ready-made library, e.g. "datatables"

Another idea is to add a constant variable to title, sometimes there are cases where we want to make "page name - site name". Site name is constant on every page so it would be nice if it was automatically added.

These are just my suggestions, you are doing a really good job.

HeadElement working properly after updates on the week 03/12/2021 ?

Hello,
I'm not sure if I am doing something wrong, but on a SEO check, my page descriptions stopped working.
This was working perfectly in my Blazor server side app. It uses the Blazor.HeadElement code to take care of the tittle and the meta tags. I also ran it on a newly created built in Microsoft code sample with the counter, and published it. It seems to work the same with or without adding the Blazor.HeadElement. This code was working on the production system, even after going to Net 5.
Here are the details.
Microsoft Visual Studio Professional 2019 Version 16.9.1 (fully patched and updated as of 03/12/2021) running on a fully updated and patched Win10 pc. All VS extensions are updated to the current versions, And the HeadElement is at v5.4.1. Compiled for .NET 5.0 framework.
This can be tested using: freetools.seobility.net

Let me know if this works, or if I'm confused.
Thanks, Ty

CSS in the head is being called twice

Hi jsakamoto!

I'm using the server side prerendering for my site Blazor Server App.

Everything works Ok until I do the following, let me explain the situation

The error happens when I update the page in the browser, then I go to the next screen and the title and description change ok but it also reloads the css again and the css route changes.

For example:
the route of my css is www.siteBlazor.com/subdomain/css/main.min.css (By default in normal situation)

So, I refresh the page and it continues showing the previous route ("www.siteBlazor.com/subdomain/css/main.min.css"),
but when I go to the next page everything that is on the head changes to the tags being updated but at the same time it also changes the css route to "www.siteBlazor.com/subdomain/subdomain/css/main.min.css"

This generates an error because that route does not exist.

Can you help me?

Thank You,
Regards,
Esteban

Add Meta Element without overwriting the existing one

In some cases it would be nice to add the same meta tag multiple times eg the og:image

In my case: I want to display more images or create a carousel if the link is shared on facebook. For this it is necessary to add the same tag multiple times.

BR Markus

Unable to render title from within components with OnAfterRenderAsync

Hello. I'm trying to create component that would display current page title. Problem is, it doesn't work when used with OnAfterRenderAsync, probably because it's fired before the title is set as any later call does work.

<h1>@title</h1> @* null *@
@code {
  string title;
  protected override async Task OnAfterRenderAsync(bool firstRender)
  {
    if (firstRender)
    {
      title = await HeadElementHelper.GetTitleAsync();
    }
  }
}

Perhaps event would be of help here? Something like

protected override void OnInitialized()
{
    HeadElementHelper.TitleChanged += OnTitleChanged;    
}

private void OnTitleChanged(object sender, TitleChangedEventArgs args)
{
    title = args.Title;
}

Thoughts?

[Bug] 7.1.0 - <meta> content attribute value becomes empty and gets replaced by media attribute which gives unexpected results in browser

After upgrading to 7.1.0, content attribute of normal <meta> element in index.html gets replaced by media attribute and the content attribute value becomes empty in blazor wasm apps and this produces unexpected results in browser.

Example,

This,

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

Becomes,

<meta name="viewport" media="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" content="" />

This breaks the responsiveness of the site.

All the <meta> tags in index.html gets affected.

<meta> tags in index.html with content attribute

<meta name="author" content="Abdul Rahman Shabeek Mohamed">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="copyright" content="I Love DotNet">
<meta name="language" content="EN">
<meta name="robots" content="index,follow" />
<meta name="owner" content="Abdul Rahman Shabeek Mohamed">
<meta http-equiv="Content-Security-Policy"
      content="base-uri 'self';
           block-all-mixed-content;
           img-src data: 'self';
           object-src 'none';
           script-src 'self'
                      https://gist.github.com
                      'unsafe-eval'
                      'unsafe-inline'
                      data:;
           style-src 'self'
                     https://github.githubassets.com
                     'unsafe-inline';
           upgrade-insecure-requests;">

<meta> tags in index.html with content attribute replaced by media attribute and content attribute value becomes empty

<meta name="author" media="Abdul Rahman Shabeek Mohamed" content="">
<meta name="viewport" media="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" content="">
<meta name="copyright" media="I Love DotNet" content="">
<meta name="language" media="EN" content="">
<meta name="robots" media="index,follow" content="">
<meta name="owner" media="Abdul Rahman Shabeek Mohamed" content="">
<meta http-equiv="Content-Security-Policy" media="base-uri 'self';
               block-all-mixed-content;
               img-src data: 'self';
               object-src 'none';
               script-src 'self'
                          https://gist.github.com
                          'unsafe-eval'
                          'unsafe-inline'
                          data:;
               style-src 'self'
                         https://github.githubassets.com
                         'unsafe-inline';
               upgrade-insecure-requests;" content="">

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.