Providers

Unlike services, providers can have multiple implementations. In this tutorial we will introduce you to the most common provider, the Site Map Expander Provider.

Site Map Expander

The site map expander provider allows us to specify the tokens available for a particular site map node.

Let's create the following migration file (called "Migration13.cs") to add some tokens for our product:

using FluentMigrator;

namespace DemoShop.Shop.Migrations;

[Migration(201908141852)]
public class Migration13 : AutoReversingMigration {
    public override void Up() {
        Insert.IntoTable("TokenTypes").Row(new { Id = "Product", Name = "Product" });

        Insert.IntoTable("Tokens")
            .Row(new { TypeId = "Product", Name = "Name" })
            .Row(new { TypeId = "Product", Name = "Brand" })
            .Row(new { TypeId = "Product", Name = "Price" });
    }
}

Note: The tokens are grouped together under a token type.

Now we'll create our site map provider by adding a file called "DetailsSiteMapExpander.cs" to the "Providers" folder:

using System.Collections.Generic;
using System.Threading.Tasks;
using Kit.SiteMap;
using Kit.Tokens;

namespace DemoShop.Shop.Providers;

public class DetailsSiteMapExpander : ISiteMapExpander {
    private readonly ITokenManager _tokenManager;

    public DetailsSiteMapExpander(ITokenManager tokenManager) {
        _tokenManager = tokenManager;
    }

    #region IToken Members

    public virtual async Task<IEnumerable<IToken>> GetTokensAsync() {
        return await _tokenManager.GetTokensAsync("Product");
    }

    #endregion
}

Note: “Product” is the name of the token type we specified in our migration file above.

Now we just have to register it. This is done by adding the following SiteMapExpander attribute to the “Home” controller's “Details” method (adding any missing namespaces):

[SiteMapExpander<DetailsSiteMapExpander>(Override = true)]

So far we've specified which tokens are available, but we haven't actually passed the values to the site map node. We can do this similarly to how we overrode the site map node's page title and metadata by implementing the ISiteMapResolve's Tokens property. Open the "ViewModels/Home/ProductViewModel.cs" file and add the following property:

public virtual IDictionary<string, object?> Tokens => new Dictionary<string, object?>() {
    { "Name", Name },
    { "Brand", BrandName },
    { "Price", Price.ToString("c") }
};

Note: The name of the token must match the one specified in the migration file above.

When you modify the "View Product" site map node in the admin panel you'll see the available tokens against the page title and meta fields. You could stop here, however we're going to make a small modification so that the New Product and Edit Product pages also show the available tokens.

To start open the "ViewModels/Admin/ProductFormViewModel.cs" file make the following changes:

Add the following property:

public IEnumerable<IToken> Tokens { get; set; } = new List<IToken>();

Now add the following argument to the end of the constructor and “Initialize” method (adding any missing namespaces):

IEnumerable<IToken> tokens

Note: Make sure you pass the tokens to the “Initialize” method when you call it.

Finally make sure you set the tokens inside the “Initialize” method's body:

Tokens = tokens;

Now open the "AdminController.cs" file make the following modifications:

First inject the "ITokenManager" into the constructor and store it in the following field:

private readonly ITokenManager _tokenManager;

Next we need to feed in the following when instantiating the “ProductFormViewModel” class and calling the “Initialize” method:

await _tokenManager.GetTokensAsync("Product")

Note: There should be 4 occurances of this.

Finally replace the head and body tabs in both the "NewProduct.cshtml" and "EditProduct.cshtml" view files with the following:

<section id="head" class="tab-pane">
    <editor for="Head" view-data-tokens="Model.Tokens" />
</section>
<section id="body" class="tab-pane">
    <editor for="Body" view-data-pagelayouts="Model.PageLayouts" view-data-tokens="Model.Tokens" />
</section>

You can also specify that a site map node can serve multiple pages. For example our product details page belongs to a single action (with a single site map node), however it actually serves multiple pages for each product.

To enable this add the following to the "DetailsSiteMapExpander.cs" file:

#region ISiteMapNode Members

public virtual async Task<IEnumerable<ISiteMapNode>> GetNodesAsync(ISiteMapNode parentNode) {
    var node = await _siteMapManager.GetNodeAsync("Details", "Home", "DemoShop.Shop");

    return (await _shopService.GetProductsAsync()).Select(p => node.Copy(p.Name, new { id = p.Id })).ToList();
}

#endregion

Note: You will also need to inject "ISiteMapManager” and "IShopService" into the constructor the same way as the "ITokenManager".

Now go the admin panel  and set the “View Product” site map node to show in the menu. Now when selecting a URL, if you select the “Shop" node you'll have the option to select the product.

Events »