ASP.Net Core Razor - Get All Page Titles

887 Views Asked by At

I'm using ASP.Net Core 6.0 with Razor pages. I set each page's title with ViewData["Title"] = "Home" in the the .schtml files. I also set the metadata description with <meta name="description" content="Home page." />.

I would like to display a list of the current pages on my home page. This list would include the page title and meta description along with the Url so I can link to the page. I can get the Urls from the EndpointDataSource.

I could add the information into a database and update it every time I add a new page. I don't like the idea of creating two places for the same data without some way to keep them in sync.

Is there a way to get this information from the existing pages?

I've tried putting the page title in a base class that inherits from PageModel. I was not able to access the PageModel of another page from C#. I tried using the page Urls to load the pages. I don't know how to do that either.

I searched for a solution and I didn't find any. I could ask an AI bot but I don't know what to ask.

3

There are 3 best solutions below

5
Md Farid Uddin Kiron On

I searched for a solution and I didn't find any. I could ask an AI bot but I don't know what to ask.

Seems you are using Razor page. If you investigate you would seen endpointDataSource.Endpoints is type of EndpointDataSource which contains list of Endpoints where we can get information related to endpoint/page or route metadata.

enter image description here

enter image description here

As you can see Endpoints is a type of IReadOnlyList and it's parent class EndpointDataSource is a abstruct class so yes we cannot directly instantiate it but of course we can call it anywhere. Due to its abstruction level we need to inroduce constructor to invoke it.

Is there a way to get this information from the existing pages?

Yes, we do have the better in fact, eligant way to invoke it to get the all page name therefore, its meta-data as well. Here is the complete demo for you.

Asp.net core Razor Page For Getting All Containing Page Name:

public class ListAllRazorPagesModel : PageModel
    {
        private readonly IEnumerable<EndpointDataSource> _endpointSources;
        public ListAllRazorPagesModel(IEnumerable<EndpointDataSource> endpointDataSources)
        {
            _endpointSources = endpointDataSources;
        }
       
        public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
        public void OnGet()
        {

            EndpointSources = _endpointSources
                        .SelectMany(es => es.Endpoints)
                        .OfType<RouteEndpoint>();
        }
    }

Note: I have injected EndpointDataSource using ListAllRazorPagesModel constructor to invoke Endpoints over it.

View:

@page
@model ListAllRazorPagesModel
<table class="table">
    <thead>
        <tr>
            <th>Display Name
            <th>URL
            <th>Route Pattern
           
        </tr>
    </thead>
    <tbody>
        @foreach (var pageName in @Model.EndpointSources)
        {
            <tr>
                <td>@pageName.DisplayName?.TrimStart('/')</td>
                <td>@pageName</td>
                <td>@pageName.RoutePattern.RawText</td>
            </tr>
        }
    </tbody>
</table>

Output:

enter image description here

Note: If you would like to know more details on EndpointDataSource you could check our official document here.

1
Dave B On

A possible solution is to move all of your strings from the page model and Razor pages to a resource file. That way strings can be retrieved centrally from each Razor page or the home page where the title, description, and meta content can be listed along with the EndpointDataSource @Md describes in his answer.

  1. Store each page title and <meta> tag attribute strings in a resource file. This removed the hard-coded strings from Razor pages.

  2. Prefix the Name of each entry in the resource file with a modified version of the unique route string. For example: "/news-update/today" can be converted to "newsupdatetoday" and "/news-update/yesterday" can be "newsupdateyesterday". The string resource Name can be set as newsupdatetodayPageTitle", "newsupdatetodayMetaDescription" and "newsupdatetodayMetaContent". Using a unique prefix for each route allows the resource entries to be queried for multiple values: E.g.

    var result = resourceList.FindAll(x => x.Name.StartsWith("newsupdatetoday"));

  3. In the OnGet() method of each Razor page, retrieve the string resources for the page and add it to the page model. E.g.

    public string PageTitle { get; set; }

    public string MetaDescription { get; set; }

    public string MetaContent { get; set; }

  4. Embed the strings from the page model in the Razor page:

    <meta name="@Model.MetaDescription" content="@Model.MetaContent" />

  5. In the home page OnGet() method, use the EndpointDataSource solution described by @Md to iterate each endpoint. Filter out the home page route. Convert the endpoint URL to a prefix string and retrieve all of the resources that start with the URL prefix.

  6. Fill the Razor page with the data for each page.

The solution:

enter image description here

enter image description here

ListAllRazorPages.cshtml.cs

using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Resources;

namespace WebApplication1.Pages
{
    public class ListAllRazorPagesModel : PageModel
    {
        private readonly IEnumerable<EndpointDataSource> _endpointSources;
        public ListAllRazorPagesModel(
            IEnumerable<EndpointDataSource> endpointDataSources)
        {
            _endpointSources = endpointDataSources;
        }

        public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
        public List<PageData> PageDataList { get; set; }

        public void OnGet()
        {
            EndpointSources = _endpointSources
                        .SelectMany(es => es.Endpoints)
                        .OfType<RouteEndpoint>();

            ResourceManager rm = Resource.ResourceManager;

            PageDataList = new List<PageData>();

            foreach (RouteEndpoint routeEndpoint in EndpointSources)
            {
                // If the route pattern is an empty string then
                // it represents the home page. The home page is
                // represented by the route pattern '/Index'.
                // To avoid listing the home page twice then
                // the route pattern with the empty string is skipped
                // in the 'foreach' iterator.
                if (string.IsNullOrWhiteSpace(routeEndpoint.RoutePattern.RawText)) { continue; }
            
                string url = routeEndpoint.ToString();

                // E.g. url = "/Error"
                if (url.Contains("ListAllRazorPages")) { continue; }

                // E.g. "error"
                string lowercaseUrl = url.ToLower()?.TrimStart('/');

                try
                {
                    PageData pageData = new()
                    {
                        Title = rm.GetString($"{lowercaseUrl}Title"),
                        MetaDescription = rm.GetString($"{lowercaseUrl}MetaDescription"),
                        MetaContent = rm.GetString($"{lowercaseUrl}MetaContent"),
                        URL = url
                    };
                    PageDataList.Add(pageData);
                }
                catch (Exception ex)
                {
                }
            }
        }
    }
}

public class PageData
{
    public string Title { get; set; }
    public string MetaDescription { get; set; }
    public string MetaContent { get; set; }
    public string URL { get; set; }
}

ListAllRazorPages.cshtml

@page
@model WebApplication1.Pages.ListAllRazorPagesModel
@{
}
<table class="table">
    <thead>
        <tr>
            <th>Page Title</th>
            <th>Meta Description</th>
            <th>Meta Content</th>
        </tr>
    </thead>
    <tbody>
        @foreach (PageData pageData in @Model.PageDataList)
        {
            <tr>
                <td><a href="@pageData.URL">@pageData.Title</a></td>
                <td>@pageData.MetaDescription</td>
                <td>@pageData.MetaContent</td>
            </tr>
        }
    </tbody>
</table>

Privacy.cshtml.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebApplication1.Pages
{
    public class PrivacyModel : PageModel
    {
        private readonly ILogger<PrivacyModel> _logger;

        public string PageTitle = Resource.ResourceManager.GetString("privacyTitle");

        public PrivacyModel(ILogger<PrivacyModel> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
        }
    }
}

Privacy.cshtml

@page
@model PrivacyModel
@{
    ViewData["Title"] = Model.PageTitle;
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's privacy policy.</p>

enter image description here

0
Mike Martin On

I ended up doing an HTML scrape of the article pages and putting the data into a CSV file. Now I can use the CSV to display the latest articles on my homepage. It's clean, easy, and doesn't need to run often. I still need to put the scraper method into a partial page so it doesn't slow down the homepage loading time.

Here is the code:

using CsvHelper;
using HtmlAgilityPack;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Stream_City.Models;
using System.Globalization;

namespace Stream_City.Pages
{
    public class IndexModel : PageModel
    {
        private readonly IEnumerable<EndpointDataSource> _endpointSources;

        public IndexModel(IEnumerable<EndpointDataSource> endpointSources)
        {
            _endpointSources = endpointSources;
        }

        public void OnGet()
        {
            UpdateArticleList(_endpointSources);
        }

        static void UpdateArticleList(IEnumerable<EndpointDataSource> _endpointSources)
        {
            var lastModified = System.IO.File.GetLastWriteTime(Constants.ArticlesFilename);
            bool outOfDate = DateTime.Now > lastModified.AddDays(1);

            if (outOfDate)
            {
                IEnumerable<RouteEndpoint> endpointSources = _endpointSources.SelectMany(es => es.Endpoints).OfType<RouteEndpoint>();
                HtmlWeb web = new();
                var pageAttributes = new HashSet<PageAttributeRow>();

                foreach (var source in endpointSources)
                {
                    string? articleUrl = GetArticleUrl(source);

                    if (articleUrl != null)
                    {
                        HtmlDocument doc = web.Load(articleUrl);
                        var node = doc.DocumentNode.SelectSingleNode("html/head/title");
                        string? title = node?.InnerText;

                        node = doc.DocumentNode.SelectSingleNode("//meta[@name='description']");
                        string? description = node?.GetAttributeValue("content", "");
                        string? imageSrc = null;
                        string? imageAlt = null;

                        //get main image
                        node = doc.DocumentNode.SelectSingleNode("//main//img");

                        if (node != null)
                        {
                            imageSrc = node.Attributes["src"].Value;
                            imageAlt = node.Attributes["alt"].Value;
                        }

                        pageAttributes.Add(new PageAttributeRow
                        {
                            Url = articleUrl,
                            Title = title,
                            Description = description,
                            ImageSrc = imageSrc,
                            ImageAlt = imageAlt
                        });
                    }
                }

                using (var writer = new StreamWriter(Constants.ArticlesFilename))
                using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
                {
                    csv.WriteRecords(pageAttributes);
                }
            }
        }

        static string? GetArticleUrl(RouteEndpoint source)
        {
            string? articleUrl = null;

            if (source.DisplayName != null)
            {
                if (source.DisplayName.StartsWith(Constants.ArticleFolder))
                    articleUrl = String.Format("{0}{1}", Constants.SiteUrl, source.DisplayName);
            }

            return articleUrl;
        }

        private class PageAttributeRow
        {
            public string? Url { get; set; }
            public string? Title { get; set; }
            public string? Description { get; set; }
            public string? ImageSrc { get; set; }
            public string? ImageAlt { get; set; }
        }
    }
}