<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Sunylcumar blog</title> <link>https://world.optimizely.com/blogs/sunylcumar-blog/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Render ContentArea without  wrapping them in surrounding div</title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/render-contentarea-without--wrapping-them-in-surrounding-div/</link>            <description>&lt;p&gt;CustomContentAreaRenderer is a specialized class that overrides the default ContentAreaRenderer.&amp;nbsp;It customizes the rendering behavior for content area items, specifically by rendering content items&amp;nbsp;without wrapping them in a surrounding &amp;lt;div&amp;gt; element, which is typically used in the default implementation.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class CustomContentAreaRenderer : ContentAreaRenderer
{
    protected override void RenderContentAreaItem(
    IHtmlHelper htmlHelper,
    ContentAreaItem contentAreaItem,
    string templateTag,
    string htmlTag,
    string cssClass)
    {
        if (contentAreaItem == null)
        {
            return;
        }

        var content = contentAreaItem.LoadContent();
        if (content == null)
        {
            return;
        }

        // Directly render the content item without surrounding div
        htmlHelper.RenderContentData(content, false);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/render-contentarea-without--wrapping-them-in-surrounding-div/</guid>            <pubDate>Sun, 18 May 2025 14:31:41 GMT</pubDate>           <category>Blog post</category></item><item> <title>Indexing a content item programatically</title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/indexing-a-content-item-programatically/</link>            <description>&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public bool IndexContent(int contentId, bool contentOnly, bool childrenOnly, string language)
{
    // Retrieve the content
    var contentReference = new ContentReference(contentId);
    var contentItems = new List&amp;lt;IContent&amp;gt;();

    if (contentReference != null &amp;amp;&amp;amp; contentReference != ContentReference.EmptyReference)
    {
        if (contentOnly)
        {
            var contentItem = _contentRepository.Get&amp;lt;IContent&amp;gt;(contentReference);
            try
            {
                // Index the content
                _contentIndexer.Index(contentItem);
                return true;
            }
            catch (Exception ex)
            {
                _logger.Error($&quot;Error in IndexContent, exception is {ex}&quot;);
                return false;
            }
        }
        else
        {
            if (childrenOnly)
            {
                //Get the children of the content
                var children = _contentRepository.GetChildren&amp;lt;IContent&amp;gt;(contentReference);
                if (children != null &amp;amp;&amp;amp; children.Any())
                    contentItems = children.ToList();
            }
            else
            {
                //Get the descendants of the content
                var descendants = _contentRepository.GetDescendents(contentReference).Select(_contentRepository.Get&amp;lt;IContent&amp;gt;);
                if (descendants != null &amp;amp;&amp;amp; descendants.Any())
                    contentItems = descendants.ToList();
            }
            // Save the updated content
            if (contentItems != null &amp;amp;&amp;amp; contentItems.Count &amp;gt; 0)
            {
                foreach (var contentItem in contentItems)
                {
                    try
                    {
                        // Index the content
                        _contentIndexer.Index(contentItem);
                    }
                    catch (Exception ex)
                    {
                        _logger.Error($&quot;Error in IndexContent, exception is {ex}&quot;);
                    }
                }
                return true;
            }
        }
    }
    return false;
}
&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/indexing-a-content-item-programatically/</guid>            <pubDate>Sun, 18 May 2025 14:26:27 GMT</pubDate>           <category>Blog post</category></item><item> <title>Add a new menu item to the Admin Menu in Optimizely CMS</title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/add-a-new-menu-item-to-the-admin-menu-in-optimizely-cms/</link>            <description>&lt;p&gt;Create a new Controller called CustomMenuController and decorate with [Authorize(Roles =&quot;CMSAdmins&quot;)] so that it will be accessed by admins only&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace AdminMenu.Controllers
{
    [Route(&quot;episerver/CustomMenu&quot;)]
    [Authorize(Roles =&quot;CMSAdmins&quot;)]
    public class CustomMenuController : Controller
    {
        [HttpGet(&quot;&quot;)]
        public IActionResult Index()
        {
            return View(&quot;~/Views/CustomMenu /Index.cshtml&quot;);
        }     
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a view under Views/CustomMenu/Index.cshtml for the controller&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;@{
Layout = null;
}
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;    
    &amp;lt;title&amp;gt;Custom Menu&amp;lt;/title&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
    &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=Edge&quot; /&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;link rel=&quot;shortcut icon&quot; href=&quot;/Util/images/favicon.ico&quot; type=&quot;image/x-icon&quot; /&amp;gt;
    &amp;lt;link href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot;&amp;gt;
&amp;lt;/head&amp;gt;

 &amp;lt;body&amp;gt;
    @Html.AntiForgeryToken()
    @Html.Raw(Html.CreatePlatformNavigationMenu())
    &amp;lt;div class=&quot;margin-100&quot;&amp;gt;
        &amp;lt;div class=&quot;container mt-5&quot;&amp;gt;
          &amp;lt;h1&amp;gt;This is a custom menu example&amp;lt;/h1&amp;gt;
          &amp;lt;p&amp;gt;Custom menu implementation&amp;lt;/p&amp;gt;
       &amp;lt;/div&amp;gt; 
     &amp;lt;/div&amp;gt;  
    &amp;lt;required-client-resources area=&quot;Footer&quot; /&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a new class called MenuItem that inherits IMenuProvider&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace FEO.CMS.Global.Business.MenuProviders
{
    [MenuProvider]
    public class CustomMenuProvider : IMenuProvider
    {
        public IEnumerable&amp;lt;MenuItem&amp;gt; GetMenuItems()
        {
            var menuItems = new List&amp;lt;MenuItem&amp;gt;();

            menuItems.Add(new UrlMenuItem(&quot;Custom Admin Menu&quot;,
              &quot;/global/cms/CustomAdminMenu&quot;,
             &quot;/EPiServer/CustomMenu&quot;)
            {
                SortIndex = SortIndex.First + 25,
                AuthorizationPolicy = CmsPolicyNames.CmsAdmin
            });
            return menuItems;
        }
       
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thats all, run the application and you can find a new menu item under the admin menu&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/add-a-new-menu-item-to-the-admin-menu-in-optimizely-cms/</guid>            <pubDate>Sun, 18 May 2025 14:10:36 GMT</pubDate>           <category>Blog post</category></item><item> <title>Helper method to encode query string properly </title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/helper-method-to-encode-query-string-properly-/</link>            <description>&lt;p&gt;When using Url.ContentUrl() in Optimizely 12, encodes spaces as + in the query string. If you want to encode the spaces as %20, use the below helper method.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt; public static string GetEncodedUrl(string href)
 {
     var decodedHref = System.Web.HttpUtility.UrlDecode(href);
     int questionMarkIndex = decodedHref.IndexOf(&#39;?&#39;);
     string path = questionMarkIndex &amp;gt;= 0 ? decodedHref.Substring(0, questionMarkIndex) : decodedHref;
     var relativeUrl = UrlResolver.GetUrl(path);
     string query = questionMarkIndex &amp;gt;= 0 ? decodedHref.Substring(questionMarkIndex + 1).Replace(&quot;&amp;amp;amp;&quot;, &quot;&amp;amp;&quot;) : string.Empty;     
     var encodedQuery = !string.IsNullOrEmpty(query) ? $&quot;?{Uri.EscapeUriString(query)}&quot; : query;
     return $&quot;{relativeUrl}{encodedQuery}&quot;;
 }&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/helper-method-to-encode-query-string-properly-/</guid>            <pubDate>Tue, 13 May 2025 09:26:19 GMT</pubDate>           <category>Blog post</category></item><item> <title>Get ContentReference from GUID</title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/get-contentreference-from-guid/</link>            <description>&lt;p&gt;Optimizely CMS 12 provides a Utility function to retrieve Content Reference from a Guid by ussing the below static method.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var contentReference = PermanentLinkUtility.FindContentReference(new Guid(&quot;9812C961-4A51-4C8C-9580-0DED7764C1B2&quot;));&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/get-contentreference-from-guid/</guid>            <pubDate>Tue, 13 May 2025 09:23:02 GMT</pubDate>           <category>Blog post</category></item><item> <title>Creating a Dropdownlist in Optimizely CMS: Populate ISelectionFactory with values from another block&#39;s properties</title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/populate-iselectionfactory-with-values-from-another-blocks-properties/</link>            <description>&lt;p&gt;&lt;strong&gt;Create a Block to hold selection options&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Cms.Shell.UI.ObjectEditing.EditorDescriptors;
using EPiServer.PlugIn;
using EPiServer.Shell.ObjectEditing;
using System.ComponentModel.DataAnnotations;
namespace MyApp.Models.Blocks
{
    [ContentType(DisplayName = &quot;Select Options Block&quot;, Description = &quot;&quot;)]
    public class SelectOptionsBlock : BlockData
    {
        [CultureSpecific]     
        [Display(Name = &quot;Selection Options&quot;, Description = &quot;List of options for the selection list.&quot;,  GroupName = SystemTabNames.Content, Order = 100)]
        [EditorDescriptor(EditorDescriptorType = typeof(CollectionEditorDescriptor&amp;lt;SelectionOption&amp;gt;))]
        public virtual IList&amp;lt;SelectionOption&amp;gt; SelectionOptions { get; set; }
    }

    public class SelectionOption
    {
        public string Text { get; set; }
        public string Value { get; set; }
    }

    [PropertyDefinitionTypePlugIn]
    public class SelectionOptionProperty : PropertyList&amp;lt;SelectionOption&amp;gt; { }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Create a page where you want to have the selection list and add the following 2 properties&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace MyApp.Models.Pages
{
    [ContentType(DisplayName = &quot;SelectionList Page&quot;, Description = &quot;Page with a selection list populated from a block.&quot;, GroupName = SiteGroupNames.Specialized)]
    public class SelectionListPage : PageData
    {
        [CultureSpecific]
        [Display(Name = &quot;Select Options Block&quot;, Description = &quot;&quot;, GroupName = SystemTabNames.Content, Order = 1)]
        [AllowedTypes(typeof(SelectOptionsBlock))]
        public virtual ContentReference SelectOptionsBlockContent { get; set; }

        [CultureSpecific]
        [Display(Name = &quot;Select List&quot;, Description = &quot;&quot;,  GroupName = SystemTabNames.Content,  Order = 1)]
        [SelectOne(SelectionFactoryType = typeof(ItemSelectionFactory))]
        public virtual string SelectList { get; set; }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Now create a selection list factory&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.ServiceLocation;
using EPiServer.Shell.ObjectEditing;
using SVP.Models.Blocks;
namespace MyApp.Business.SelectionFactories
{
    public class ItemSelectionFactory : ISelectionFactory
    {
        public IEnumerable&amp;lt;ISelectItem&amp;gt; GetSelections(ExtendedMetadata metadata)
        {
            var loader = ServiceLocator.Current.GetInstance&amp;lt;IContentLoader&amp;gt;();
            var list = new List&amp;lt;SelectItem&amp;gt;();  
            dynamic contentMetadata = metadata;
            var ownerContent = contentMetadata.OwnerContent as IContent;            
            //create a dynamic object of current content type
            dynamic parentItem = ownerContent;
            //check if the block containing the select options is added to the current content (page or block)
            if (parentItem.SelectOptionsBlockContent != null)
            {
                //Get the block containing select options
                var optionsBlock = loader.Get&amp;lt;SelectOptionsBlock&amp;gt; parentItem.SelectOptionsBlockContent);
                //check if the optionsBlock has any items
                if (optionsBlock.SelectionOptions != null &amp;amp;&amp;amp; optionsBlock.SelectionOptions.Count &amp;gt; 0)
                {
                    foreach (var listItem in optionsBlock.SelectionOptions)
                    {
                        list.Add(new SelectItem() { Text = listItem.Text, Value = listItem.Value });
                    }
                }
            }
            return list;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the CMS, create a new block of type SelectOptionsBlock and add the necessary select options and publish it.&lt;/p&gt;
&lt;p&gt;Select this newly created block in your page that you have created to have the selection list and publish the page.&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/5/populate-iselectionfactory-with-values-from-another-blocks-properties/</guid>            <pubDate>Mon, 12 May 2025 14:28:46 GMT</pubDate>           <category>Blog post</category></item><item> <title>Custom Sitemap.xml generator in Optimizely CMS 12</title>            <link>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/1/create-custom-sitemap-xml-in-optimizely-cms-12/</link>            <description>&lt;p&gt;This is a step by step guide to generate sitemap.xml file for all the sites in optimizely cms 12. The sitemaps are generated under wwwroot/sitemaps with the name of the site configured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&amp;nbsp;&lt;/strong&gt;Add a new field in Home Page to store the name of the sitemap file&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Create an interface for Sitemap generator service&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Implement the interface with sitemap generation logic&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Register the Service in startup.cs&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Create a scheduled job that will run the sitemap generator service&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 6:&lt;/strong&gt;&amp;nbsp; Create a Controlller that will serve sitemap&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 7:&lt;/strong&gt; Access the sitemap using the the url for example https://localhost:5000/seo-sitemap.xml&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Home.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace CustomSitemap.Web.Pages
{
    [ContentType(DisplayName = &quot;Home Page&quot;, GroupName = &quot;Corporate&quot;, GUID = &quot;8F0896B1-E3F2-4E8C-81AC-4FE52C519402&quot;)]
    public class HomePage : PageData
    {        
        [Display(Name = &quot;SEO sitemap.xml file name&quot;,
            Description = &quot;Provide meaning full name for SEO sitemap like site-sitemap.xml&quot;,
          GroupName = SystemTabNames.Settings,
          Order = 550)]
        public virtual string SitemapFileName { get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ISitemapGeneratorService.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace CustomSitemap.Web.Business.Sitemap
{
    public interface ISitemapGeneratorService
    {
        IEnumerable&amp;lt;SiteDefinition&amp;gt; GetAllSites();
        bool GenerateSitemaps();
        void DeleteSitemaps();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SitemapGeneratorService.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Filters;
using EPiServer.Web;
using EPiServer.Web.Routing;
using EPiServer.Logging;
using System.Globalization;
using CustomSitemap.Web.Business.Extensions;

namespace CustomSitemap.Web.Business.Sitemap
{
 /// &amp;lt;summary&amp;gt;
/// Get the site names from configuration and generate sitemap with the sitename and save to wwwroot/sitemaps
/// &amp;lt;/summary&amp;gt;    
public class SitemapGeneratorService : ISitemapGeneratorService
    {
        private readonly IContentLoader _contentLoader;
        private readonly IContentRepository _contentRepository;
        private readonly UrlResolver _urlResolver;
        private readonly ISiteDefinitionRepository _siteDefinitionRepository;
        private readonly ILanguageBranchRepository _languageBranchRepository;
        private readonly IWebHostEnvironment _webHostingEnvironment;
        private readonly EPiServer.Logging.ILogger _logger;

        /// &amp;lt;summary&amp;gt;
        /// constructor
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;contentLoader&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;urlResolver&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;siteDefinitionRepository&quot;&amp;gt;&amp;lt;/param&amp;gt;
        public SitemapGeneratorService(
            IContentLoader contentLoader,
            IContentRepository contentRepository,
            UrlResolver urlResolver,
            ISiteDefinitionRepository siteDefinitionRepository,
            ILanguageBranchRepository languageBranchRepository,
            IWebHostEnvironment webHostingEnvironment) 
        {
            _contentLoader = contentLoader;
            _contentRepository = contentRepository;
            _urlResolver = urlResolver;
            _siteDefinitionRepository = siteDefinitionRepository;
            _languageBranchRepository = languageBranchRepository;
            _webHostingEnvironment = webHostingEnvironment;
            _logger = LogManager.GetLogger(typeof(SitemapGeneratorService));
        }

        /// &amp;lt;summary&amp;gt;
        /// List all the sites created in optimizely cms
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public IEnumerable&amp;lt;SiteDefinition&amp;gt; GetAllSites()
        {
            return _siteDefinitionRepository.List();
        }
        
        public void DeleteSitemaps()
        {
            // Get the path to the wwwroot folder
            var wwwrootPath = _webHostingEnvironment.WebRootPath;
            
            // Check if the directory exists
            if (!Directory.Exists(wwwrootPath))
                return;

            // Get the path for sitemap files wwwroot/sitemaps
            var sitemapsPath = Path.Combine(_webHostingEnvironment.WebRootPath, &quot;sitemaps&quot;);            
            var sitemapFiles = Directory.GetFiles(sitemapsPath, &quot;*sitemap.xml&quot;, SearchOption.TopDirectoryOnly);

            foreach (var file in sitemapFiles)
            {
                try
                {
                    // Delete each file
                    File.Delete(file);
                    _logger.Warning($&quot;Deleted: {file}&quot;);
                }
                catch (Exception ex)
                {
                    // Log the error or handle it as needed
                    _logger.Error($&quot;Error deleting file {file}: {ex.Message}&quot;);
                }
            }
        }

        public bool GenerateSitemaps()
        {
            try
            {
                //Delete already created sitemap files
                DeleteSitemaps();

                // Iterate over all sites and generate sitemaps
                foreach (var siteDefinition in GetAllSites())
                {
                    //get the start page for the current site
                    var startPage = _contentLoader.Get&amp;lt;PageData&amp;gt;(siteDefinition.StartPage);                    
                    
                    //get the url of the host
                    var primaryHostUrl = siteDefinition.Hosts.FirstOrDefault(host =&amp;gt; host.Type == HostDefinitionType.Primary).Url;

                    //generate sitemap
                    var sitemapContent = GenerateSitemapForSite(startPage, primaryHostUrl);

                    // Save the generated sitemap.xml to a specified location
                    var sitemapPath = Path.Combine(_webHostingEnvironment.WebRootPath, &quot;sitemaps&quot;, siteDefinition.Name.Replace(&quot; &quot;, &quot;-&quot;).ToLower() + &quot;-sitemap.xml&quot;);
                    File.WriteAllText(sitemapPath, sitemapContent);
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        private string GenerateSitemapForSite(PageData startPage, Uri primaryHostUrl)
        {
            var publishedFilter = new FilterPublished();
            var accessFilter = new FilterAccess();
            var sitemapEntries = new List&amp;lt;string&amp;gt;();

            //var availablePageLanguages = FilterForVisitor.Filter(pageLanguagesBranches).OfType&amp;lt;PageData&amp;gt;();
            //var currentPageLanguages = availablePageLanguages.Select(page =&amp;gt; page.Language.Name).ToList();

           //Get the available languages for the start page of the site
            var pageLanguagesBranches = _contentRepository.GetLanguageBranches&amp;lt;PageData&amp;gt;(startPage.ContentLink).ToList();
            
            //var hostname = startPage.
            foreach (var availablePage in pageLanguagesBranches)
            {
                var culture = new CultureInfo(availablePage.Language.Name);                
                
                //Add home page to the sitemap
                var homePageUrl = _urlResolver.GetUrl(availablePage.ContentLink, availablePage.Language.Name);
                sitemapEntries.Add($&quot;&amp;lt;url&amp;gt;&amp;lt;loc&amp;gt;{homePageUrl}&amp;lt;/loc&amp;gt;&amp;lt;lastmod&amp;gt;{Convert.ToDateTime(availablePage.GetLastPublishDate()).ToString(&quot;yyyy-MM-ddTHH:mm:sszzz&quot;)}&amp;lt;/lastmod&amp;gt;&amp;lt;priority&amp;gt;0.80&amp;lt;/priority&amp;gt;&amp;lt;/url&amp;gt;&quot;);

                //get the children of the home page
                var pages = _contentLoader.GetDescendents(availablePage.ContentLink);

                foreach (var pageLink in pages)
                {
                    if (_contentLoader.TryGet(pageLink, culture, out PageData page))
                    {
                        //check if the page is set to exclude from sitemap 
                        bool excludeFromSitemap = bool.TryParse(page[&quot;ExcludeFromSitemap&quot;] as string, out bool result);

                        //if the page is published, is allowed in the sitemap and is not a shortcut
                        if (!publishedFilter.ShouldFilter(page) &amp;amp;&amp;amp; !accessFilter.ShouldFilter(page) &amp;amp;&amp;amp; !excludeFromSitemap &amp;amp;&amp;amp; page.LinkType != PageShortcutType.Shortcut)
                        {
                            var url = _urlResolver.GetUrl(page.ContentLink, availablePage.Language.Name);
                            if (!string.IsNullOrEmpty(url))
                            {
                                sitemapEntries.Add($&quot;&amp;lt;url&amp;gt;&amp;lt;loc&amp;gt;{url}&amp;lt;/loc&amp;gt;&amp;lt;lastmod&amp;gt;{Convert.ToDateTime(page.GetLastPublishDate()).ToString(&quot;yyyy-MM-ddTHH:mm:sszzz&quot;)}&amp;lt;/lastmod&amp;gt;&amp;lt;priority&amp;gt;0.64&amp;lt;/priority&amp;gt;&amp;lt;/url&amp;gt;&quot;);
                            }
                        }
                    }
                }
            }

            return $&quot;&amp;lt;urlset xmlns=\&quot;http://www.sitemaps.org/schemas/sitemap/0.9\&quot; xmlns:xsi=\&quot;http://www.w3.org/2001/XMLSchema-instance\&quot; xsi:schemaLocation=\&quot;http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\&quot;&amp;gt;{string.Join(&quot;&quot;, sitemapEntries)}&amp;lt;/urlset&amp;gt;&quot;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Startup.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
	services.AddTransient&amp;lt;ISitemapGeneratorService, SitemapGeneratorService&amp;gt;();
	services.AddCms();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SitemapGeneratorJob.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace CustomSitemap.Web.Business.ScheduledJobs
{
    /// &amp;lt;summary&amp;gt;
    /// Scheduled job for generating SEO sitemaps.
    /// &amp;lt;/summary&amp;gt;
    [ScheduledPlugIn(DisplayName = &quot;Generate SEO Sitemaps&quot;, Description = &quot;Generates sitemap for each site&quot;, GUID = &quot;5C3DDEC2-0CD5-4734-B8EE-FDA8BE08C46E&quot;)]
    public class SitemapGeneratorJob : ScheduledJobBase
    {
        private readonly EPiServer.Logging.ILogger _logger;        
        private ISitemapGeneratorService _sitemapGeneratorService;
        private readonly IWebHostEnvironment _environment;
        private bool _stopSignaled;
        /// &amp;lt;summary&amp;gt;
        /// Default constructor
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;siteConfigService&quot;&amp;gt;&amp;lt;/param&amp;gt;
        public SitemapGeneratorJob(
            ISitemapGeneratorService sitemapGeneratorService,
            IWebHostEnvironment environment)
        {
            _sitemapGeneratorService = sitemapGeneratorService;
            _logger = LogManager.GetLogger(typeof(SitemapGeneratorJob));
            IsStoppable = true;
            _environment = environment;
        }

        /// &amp;lt;summary&amp;gt;
        /// stop
        /// &amp;lt;/summary&amp;gt;
        public override void Stop()
        {
            _stopSignaled = true;
        }

        /// &amp;lt;summary&amp;gt;
        /// Set the primary hosts
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public override string Execute()
        {
            OnStatusChanged(&quot;Starting execution of SitemapGeneratorJob&quot;);

            _logger.Information(&quot;Starting the job : SitemapGeneratorJob&quot;);

            //Call the method to Generate Sitemaps

            if (_environment.IsDevelopment())
            {
                _logger.Information($&quot;Current environment is {_environment.EnvironmentName}&quot;);
                _logger.Warning($&quot;This job is not intended to run on the environment {_environment.EnvironmentName}&quot;);
                return &quot;SitemapGeneratorJob will not run in development environment&quot;;
            }
            var status = _sitemapGeneratorService.GenerateSitemaps();

            if (!status)
                return &quot;SitemapGeneratorJob did not run successfully. Please check the logs for more information&quot;;

            if (_stopSignaled)
            {                
                return &quot;SitemapGeneratorJob was stopped&quot;;
            }

            return &quot;SitemapGeneratorJob completed successfully&quot;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SitemapController.cs&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;namespace CustomSitemap.Web.Controllers
{
    /// &amp;lt;summary&amp;gt;
    /// controller to get SEO sitemap or regular xml file.
    /// &amp;lt;/summary&amp;gt;
    [Route(&quot;{sitemapName}.xml&quot;)]
    public class SitemapController : Controller
    {
        private readonly IWebHostEnvironment _webHostEnvironment;
        private readonly ISiteDefinitionResolver _siteDefinitionResolver;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IContentLoader _contentLoader;
        
        public SitemapController(IWebHostEnvironment webHostEnvironment,
            ISiteDefinitionResolver siteDefinitionResolver,
            IHttpContextAccessor httpContextAccessor,
            IContentLoader contentLoader)
        {
            _webHostEnvironment = webHostEnvironment;
            _siteDefinitionResolver = siteDefinitionResolver;
            _httpContextAccessor = httpContextAccessor;
            _contentLoader = contentLoader;
        }

        /// &amp;lt;summary&amp;gt;
        /// action method to serve sitemap or any xml file
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;sitemapName&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        [HttpGet]
        public IActionResult Index(string sitemapName)
        {
            if (!string.IsNullOrEmpty(sitemapName) &amp;amp;&amp;amp; !sitemapName.ToLower().EndsWith(&quot;.xml&quot;))
            {
                sitemapName = $&quot;{sitemapName}.xml&quot;;
            }

            //get the start page for the site
            var startPage = _contentLoader.Get&amp;lt;PageData&amp;gt;(ContentReference.StartPage);
            //read the sitemap file name from the home page
            var sitemapFileName = startPage[&quot;SitemapFileName&quot;] as string;            

            //if the requested file name is same as sitemap file name configured in home page or if it is not configured but it is sitemap.xml
            if ((!string.IsNullOrEmpty(sitemapFileName) &amp;amp;&amp;amp; sitemapFileName.Equals(sitemapName, StringComparison.OrdinalIgnoreCase)) || (string.IsNullOrEmpty(sitemapFileName) &amp;amp;&amp;amp; sitemapName.ToLower().Equals(&quot;sitemap.xml&quot;)))
            {
                var httpContext = _httpContextAccessor.HttpContext;
                if (httpContext == null)
                {
                    return NotFound();
                }

                // Attempt to resolve the site by host
                var currentSite = _siteDefinitionResolver.GetByHostname(httpContext.Request.Host.Host, true);

                // Fallback to hostname with port
                if (currentSite == null)
                {
                    var hostWithPort = httpContext.Request.Host.Value; // Host with port, e.g., &quot;localhost:5000&quot;
                    currentSite = _siteDefinitionResolver.GetByHostname(hostWithPort, true);
                }

                //get the sitemap from wwwroot/sitemaps
                var filePath = Path.Combine(_webHostEnvironment.WebRootPath, &quot;sitemaps&quot;, currentSite.Name.Replace(&quot; &quot;, &quot;-&quot;).ToLower() + &quot;-sitemap.xml&quot;);
                if (!System.IO.File.Exists(filePath))
                {
                    return NotFound();
                }

                //read the contents of the sitemap file and return
                var fileContent = System.IO.File.ReadAllText(filePath);
                return Content(fileContent, &quot;application/xml&quot;);
            }
            else
            {
                //this is not a sitemap file, so try getting the requested file from wwwroot
                var filePath = Path.Combine(_webHostEnvironment.WebRootPath, sitemapName);
                if (!System.IO.File.Exists(filePath))
                {
                    return NotFound();
                }
                
                //return the requested file
                var fileContent = System.IO.File.ReadAllText(filePath);
                return Content(fileContent, &quot;application/xml&quot;);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/sunylcumar-blog/dates/2025/1/create-custom-sitemap-xml-in-optimizely-cms-12/</guid>            <pubDate>Tue, 07 Jan 2025 13:07:15 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>