577 lines
24 KiB
C#
577 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|
using Microsoft.AspNetCore.Mvc.Routing;
|
|
using Nop.Core;
|
|
using Nop.Core.Domain.Blogs;
|
|
using Nop.Core.Domain.Catalog;
|
|
using Nop.Core.Domain.Common;
|
|
using Nop.Core.Domain.Forums;
|
|
using Nop.Core.Domain.Localization;
|
|
using Nop.Core.Domain.News;
|
|
using Nop.Core.Domain.Seo;
|
|
using Nop.Core.Events;
|
|
using Nop.Services.Blogs;
|
|
using Nop.Services.Catalog;
|
|
using Nop.Services.Localization;
|
|
using Nop.Services.News;
|
|
using Nop.Services.Topics;
|
|
|
|
namespace Nop.Services.Seo
|
|
{
|
|
/// <summary>
|
|
/// Represents a sitemap generator
|
|
/// </summary>
|
|
public partial class SitemapGenerator : ISitemapGenerator
|
|
{
|
|
#region Fields
|
|
|
|
private readonly BlogSettings _blogSettings;
|
|
private readonly ForumSettings _forumSettings;
|
|
private readonly IActionContextAccessor _actionContextAccessor;
|
|
private readonly IBlogService _blogService;
|
|
private readonly ICategoryService _categoryService;
|
|
private readonly IEventPublisher _eventPublisher;
|
|
private readonly ILanguageService _languageService;
|
|
private readonly IManufacturerService _manufacturerService;
|
|
private readonly INewsService _newsService;
|
|
private readonly IProductService _productService;
|
|
private readonly IProductTagService _productTagService;
|
|
private readonly IStoreContext _storeContext;
|
|
private readonly ITopicService _topicService;
|
|
private readonly IUrlHelperFactory _urlHelperFactory;
|
|
private readonly IUrlRecordService _urlRecordService;
|
|
private readonly IWebHelper _webHelper;
|
|
private readonly LocalizationSettings _localizationSettings;
|
|
private readonly NewsSettings _newsSettings;
|
|
private readonly SitemapXmlSettings _sitemapXmlSettings;
|
|
|
|
#endregion
|
|
|
|
#region Ctor
|
|
|
|
public SitemapGenerator(BlogSettings blogSettings,
|
|
ForumSettings forumSettings,
|
|
IActionContextAccessor actionContextAccessor,
|
|
IBlogService blogService,
|
|
ICategoryService categoryService,
|
|
IEventPublisher eventPublisher,
|
|
ILanguageService languageService,
|
|
IManufacturerService manufacturerService,
|
|
INewsService newsService,
|
|
IProductService productService,
|
|
IProductTagService productTagService,
|
|
IStoreContext storeContext,
|
|
ITopicService topicService,
|
|
IUrlHelperFactory urlHelperFactory,
|
|
IUrlRecordService urlRecordService,
|
|
IWebHelper webHelper,
|
|
LocalizationSettings localizationSettings,
|
|
NewsSettings newsSettings,
|
|
SitemapXmlSettings sitemapSettings)
|
|
{
|
|
_blogSettings = blogSettings;
|
|
_forumSettings = forumSettings;
|
|
_actionContextAccessor = actionContextAccessor;
|
|
_blogService = blogService;
|
|
_categoryService = categoryService;
|
|
_eventPublisher = eventPublisher;
|
|
_languageService = languageService;
|
|
_manufacturerService = manufacturerService;
|
|
_newsService = newsService;
|
|
_productService = productService;
|
|
_productTagService = productTagService;
|
|
_storeContext = storeContext;
|
|
_topicService = topicService;
|
|
_urlHelperFactory = urlHelperFactory;
|
|
_urlRecordService = urlRecordService;
|
|
_webHelper = webHelper;
|
|
_localizationSettings = localizationSettings;
|
|
_newsSettings = newsSettings;
|
|
_sitemapXmlSettings = sitemapSettings;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
/// <summary>
|
|
/// Get UrlHelper
|
|
/// </summary>
|
|
/// <returns>UrlHelper</returns>
|
|
protected virtual IUrlHelper GetUrlHelper()
|
|
{
|
|
return _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get HTTP protocol
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the protocol name as string
|
|
/// </returns>
|
|
protected virtual async Task<string> GetHttpProtocolAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return store.SslEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generate URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the list of sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IList<SitemapUrl>> GenerateUrlsAsync()
|
|
{
|
|
var sitemapUrls = new List<SitemapUrl>
|
|
{
|
|
//home page
|
|
await GetLocalizedSitemapUrlAsync("Homepage"),
|
|
|
|
//search products
|
|
await GetLocalizedSitemapUrlAsync("ProductSearch"),
|
|
|
|
//contact us
|
|
await GetLocalizedSitemapUrlAsync("ContactUs")
|
|
};
|
|
|
|
//news
|
|
if (_newsSettings.Enabled)
|
|
sitemapUrls.Add(await GetLocalizedSitemapUrlAsync("NewsArchive"));
|
|
|
|
//blog
|
|
if (_blogSettings.Enabled)
|
|
sitemapUrls.Add(await GetLocalizedSitemapUrlAsync("Blog"));
|
|
|
|
//forum
|
|
if (_forumSettings.ForumsEnabled)
|
|
sitemapUrls.Add(await GetLocalizedSitemapUrlAsync("Boards"));
|
|
|
|
//categories
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeCategories)
|
|
sitemapUrls.AddRange(await GetCategoryUrlsAsync());
|
|
|
|
//manufacturers
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeManufacturers)
|
|
sitemapUrls.AddRange(await GetManufacturerUrlsAsync());
|
|
|
|
//products
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeProducts)
|
|
sitemapUrls.AddRange(await GetProductUrlsAsync());
|
|
|
|
//product tags
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeProductTags)
|
|
sitemapUrls.AddRange(await GetProductTagUrlsAsync());
|
|
|
|
//news
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeNews && _newsSettings.Enabled)
|
|
sitemapUrls.AddRange(await GetNewsItemUrlsAsync());
|
|
|
|
//blog posts
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeBlogPosts && _blogSettings.Enabled)
|
|
sitemapUrls.AddRange(await GetBlogPostUrlsAsync());
|
|
|
|
//topics
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeTopics)
|
|
sitemapUrls.AddRange(await GetTopicUrlsAsync());
|
|
|
|
//custom URLs
|
|
if (_sitemapXmlSettings.SitemapXmlIncludeCustomUrls)
|
|
sitemapUrls.AddRange(GetCustomUrls());
|
|
|
|
//event notification
|
|
await _eventPublisher.PublishAsync(new SitemapCreatedEvent(sitemapUrls));
|
|
|
|
return sitemapUrls;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get news item URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetNewsItemUrlsAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return await (await _newsService.GetAllNewsAsync(storeId: store.Id))
|
|
.SelectAwait(async news => await GetLocalizedSitemapUrlAsync("NewsItem",
|
|
async lang => new { SeName = await _urlRecordService.GetSeNameAsync(news, news.LanguageId, ensureTwoPublishedLanguages: false) },
|
|
news.CreatedOnUtc)).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get category URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetCategoryUrlsAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return await (await _categoryService.GetAllCategoriesAsync(storeId: store.Id))
|
|
.SelectAwait(async category => await GetLocalizedSitemapUrlAsync("Category", GetSeoRouteParamsAwait(category), category.UpdatedOnUtc)).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get manufacturer URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetManufacturerUrlsAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return await (await _manufacturerService.GetAllManufacturersAsync(storeId: store.Id))
|
|
.SelectAwait(async manufacturer => await GetLocalizedSitemapUrlAsync("Manufacturer", GetSeoRouteParamsAwait(manufacturer), manufacturer.UpdatedOnUtc)).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get product URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetProductUrlsAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return await (await _productService.SearchProductsAsync(0, storeId: store.Id,
|
|
visibleIndividuallyOnly: true, orderBy: ProductSortingEnum.CreatedOn))
|
|
.SelectAwait(async product => await GetLocalizedSitemapUrlAsync("Product", GetSeoRouteParamsAwait(product), product.UpdatedOnUtc)).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get product tag URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetProductTagUrlsAsync()
|
|
{
|
|
return await (await _productTagService.GetAllProductTagsAsync())
|
|
.SelectAwait(async productTag => await GetLocalizedSitemapUrlAsync("ProductsByTag", GetSeoRouteParamsAwait(productTag))).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get topic URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetTopicUrlsAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return await (await _topicService.GetAllTopicsAsync(store.Id)).Where(t => t.IncludeInSitemap)
|
|
.SelectAwait(async topic => await GetLocalizedSitemapUrlAsync("Topic", GetSeoRouteParamsAwait(topic))).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get blog post URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap URLs
|
|
/// </returns>
|
|
protected virtual async Task<IEnumerable<SitemapUrl>> GetBlogPostUrlsAsync()
|
|
{
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
|
|
return await (await _blogService.GetAllBlogPostsAsync(store.Id))
|
|
.Where(p => p.IncludeInSitemap)
|
|
.SelectAwait(async post => await GetLocalizedSitemapUrlAsync("BlogPost",
|
|
async lang => new { SeName = await _urlRecordService.GetSeNameAsync(post, post.LanguageId, ensureTwoPublishedLanguages: false) },
|
|
post.CreatedOnUtc)).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get custom URLs for the sitemap
|
|
/// </summary>
|
|
/// <returns>Sitemap URLs</returns>
|
|
protected virtual IEnumerable<SitemapUrl> GetCustomUrls()
|
|
{
|
|
var storeLocation = _webHelper.GetStoreLocation();
|
|
|
|
return _sitemapXmlSettings.SitemapCustomUrls.Select(customUrl =>
|
|
new SitemapUrl(string.Concat(storeLocation, customUrl), new List<string>(), UpdateFrequency.Weekly, DateTime.UtcNow));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get route params for URL localization
|
|
/// </summary>
|
|
/// <typeparam name="T">Model type</typeparam>
|
|
/// <param name="model">Model</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the lambda for route params
|
|
/// </returns>
|
|
protected virtual Func<int?, Task<object>> GetSeoRouteParamsAwait<T>(T model)
|
|
where T : BaseEntity, ISlugSupported
|
|
{
|
|
return async lang => new { SeName = await _urlRecordService.GetSeNameAsync(model, lang) };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write sitemap index file into the stream
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <param name="sitemapNumber">The number of sitemaps</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task WriteSitemapIndexAsync(Stream stream, int sitemapNumber)
|
|
{
|
|
var urlHelper = GetUrlHelper();
|
|
|
|
await using var writer = new XmlTextWriter(stream, Encoding.UTF8)
|
|
{
|
|
Formatting = Formatting.Indented
|
|
};
|
|
writer.WriteStartDocument();
|
|
writer.WriteStartElement("sitemapindex");
|
|
writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");
|
|
writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
|
|
writer.WriteAttributeString("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
|
|
writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd");
|
|
|
|
//write URLs of all available sitemaps
|
|
for (var id = 1; id <= sitemapNumber; id++)
|
|
{
|
|
var url = urlHelper.RouteUrl("sitemap-indexed.xml", new { Id = id }, await GetHttpProtocolAsync());
|
|
var location = await XmlHelper.XmlEncodeAsync(url);
|
|
|
|
writer.WriteStartElement("sitemap");
|
|
writer.WriteElementString("loc", location);
|
|
writer.WriteElementString("lastmod", DateTime.UtcNow.ToString(NopSeoDefaults.SitemapDateFormat));
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write sitemap file into the stream
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <param name="sitemapUrls">List of sitemap URLs</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task WriteSitemapAsync(Stream stream, IList<SitemapUrl> sitemapUrls)
|
|
{
|
|
await using var writer = new XmlTextWriter(stream, Encoding.UTF8)
|
|
{
|
|
Formatting = Formatting.Indented
|
|
};
|
|
writer.WriteStartDocument();
|
|
writer.WriteStartElement("urlset");
|
|
writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");
|
|
writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
|
|
writer.WriteAttributeString("xmlns:xhtml", "http://www.w3.org/1999/xhtml");
|
|
writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd");
|
|
|
|
//write URLs from list to the sitemap
|
|
foreach (var sitemapUrl in sitemapUrls)
|
|
{
|
|
//write base url
|
|
await WriteSitemapUrlAsync(writer, sitemapUrl);
|
|
|
|
//write all alternate url if exists
|
|
foreach (var alternate in sitemapUrl.AlternateLocations
|
|
.Where(p => !p.Equals(sitemapUrl.Location, StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
await WriteSitemapUrlAsync(writer, new SitemapUrl(alternate, sitemapUrl));
|
|
}
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write sitemap
|
|
/// </summary>
|
|
/// <param name="writer">XML stream writer</param>
|
|
/// <param name="sitemapUrl">Sitemap URL</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task WriteSitemapUrlAsync(XmlTextWriter writer, SitemapUrl sitemapUrl)
|
|
{
|
|
if (string.IsNullOrEmpty(sitemapUrl.Location))
|
|
return;
|
|
|
|
writer.WriteStartElement("url");
|
|
|
|
var loc = await XmlHelper.XmlEncodeAsync(sitemapUrl.Location);
|
|
writer.WriteElementString("loc", loc);
|
|
|
|
//write all related url
|
|
foreach (var alternate in sitemapUrl.AlternateLocations)
|
|
{
|
|
if (string.IsNullOrEmpty(alternate))
|
|
continue;
|
|
|
|
//extract seo code
|
|
var altLoc = await XmlHelper.XmlEncodeAsync(alternate);
|
|
var altLocPath = new Uri(altLoc).PathAndQuery;
|
|
var (_, lang) = await altLocPath.IsLocalizedUrlAsync(_actionContextAccessor.ActionContext.HttpContext.Request.PathBase, true);
|
|
|
|
if (string.IsNullOrEmpty(lang?.UniqueSeoCode))
|
|
continue;
|
|
|
|
writer.WriteStartElement("xhtml:link");
|
|
writer.WriteAttributeString("rel", "alternate");
|
|
writer.WriteAttributeString("hreflang", lang.UniqueSeoCode);
|
|
writer.WriteAttributeString("href", altLoc);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteElementString("changefreq", sitemapUrl.UpdateFrequency.ToString().ToLowerInvariant());
|
|
writer.WriteElementString("lastmod", sitemapUrl.UpdatedOn.ToString(NopSeoDefaults.SitemapDateFormat, CultureInfo.InvariantCulture));
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will build an XML sitemap for better index with search engines.
|
|
/// See http://en.wikipedia.org/wiki/Sitemaps for more information.
|
|
/// </summary>
|
|
/// <param name="id">Sitemap identifier</param>
|
|
/// <param name="stream">Stream of sitemap.</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task GenerateAsync(Stream stream, int? id)
|
|
{
|
|
//generate all URLs for the sitemap
|
|
var sitemapUrls = await GenerateUrlsAsync();
|
|
|
|
//split URLs into separate lists based on the max size
|
|
var sitemaps = sitemapUrls
|
|
.Select((url, index) => new { Index = index, Value = url })
|
|
.GroupBy(group => group.Index / NopSeoDefaults.SitemapMaxUrlNumber)
|
|
.Select(group => group
|
|
.Select(url => url.Value)
|
|
.ToList()).ToList();
|
|
|
|
if (!sitemaps.Any())
|
|
return;
|
|
|
|
if (id.HasValue)
|
|
{
|
|
//requested sitemap does not exist
|
|
if (id.Value == 0 || id.Value > sitemaps.Count)
|
|
return;
|
|
|
|
//otherwise write a certain numbered sitemap file into the stream
|
|
await WriteSitemapAsync(stream, sitemaps.ElementAt(id.Value - 1));
|
|
}
|
|
else
|
|
{
|
|
//URLs more than the maximum allowable, so generate a sitemap index file
|
|
if (sitemapUrls.Count >= NopSeoDefaults.SitemapMaxUrlNumber)
|
|
{
|
|
//write a sitemap index file into the stream
|
|
await WriteSitemapIndexAsync(stream, sitemaps.Count);
|
|
}
|
|
else
|
|
{
|
|
//otherwise generate a standard sitemap
|
|
await WriteSitemapAsync(stream, sitemaps.First());
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// This will build an XML sitemap for better index with search engines.
|
|
/// See http://en.wikipedia.org/wiki/Sitemaps for more information.
|
|
/// </summary>
|
|
/// <param name="id">Sitemap identifier</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the sitemap.xml as string
|
|
/// </returns>
|
|
public virtual async Task<string> GenerateAsync(int? id)
|
|
{
|
|
await using var stream = new MemoryStream();
|
|
await GenerateAsync(stream, id);
|
|
|
|
return Encoding.UTF8.GetString(stream.ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return localized urls
|
|
/// </summary>
|
|
/// <param name="routeName">Route name</param>
|
|
/// <param name="getRouteParamsAwait">Lambda for route params object</param>
|
|
/// <param name="dateTimeUpdatedOn">A time when URL was updated last time</param>
|
|
/// <param name="updateFreq">How often to update url</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task<SitemapUrl> GetLocalizedSitemapUrlAsync(string routeName,
|
|
Func<int?, Task<object>> getRouteParamsAwait = null,
|
|
DateTime? dateTimeUpdatedOn = null,
|
|
UpdateFrequency updateFreq = UpdateFrequency.Weekly)
|
|
{
|
|
var urlHelper = GetUrlHelper();
|
|
|
|
//url for current language
|
|
var url = urlHelper.RouteUrl(routeName,
|
|
getRouteParamsAwait != null ? await getRouteParamsAwait(null) : null,
|
|
await GetHttpProtocolAsync());
|
|
|
|
var updatedOn = dateTimeUpdatedOn ?? DateTime.UtcNow;
|
|
var languages = _localizationSettings.SeoFriendlyUrlsForLanguagesEnabled
|
|
? await _languageService.GetAllLanguagesAsync()
|
|
: null;
|
|
|
|
if (languages == null)
|
|
return new SitemapUrl(url, new List<string>(), updateFreq, updatedOn);
|
|
|
|
var pathBase = _actionContextAccessor.ActionContext.HttpContext.Request.PathBase;
|
|
//return list of localized urls
|
|
var localizedUrls = await languages
|
|
.SelectAwait(async lang =>
|
|
{
|
|
var currentUrl = urlHelper.RouteUrl(routeName,
|
|
getRouteParamsAwait != null ? await getRouteParamsAwait(lang.Id) : null,
|
|
await GetHttpProtocolAsync());
|
|
|
|
if (string.IsNullOrEmpty(currentUrl))
|
|
return null;
|
|
|
|
//Extract server and path from url
|
|
var scheme = new Uri(currentUrl).GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
|
|
var path = new Uri(currentUrl).PathAndQuery;
|
|
|
|
//Replace seo code
|
|
var localizedPath = path
|
|
.RemoveLanguageSeoCodeFromUrl(pathBase, true)
|
|
.AddLanguageSeoCodeToUrl(pathBase, true, lang);
|
|
|
|
return new Uri(new Uri(scheme), localizedPath).ToString();
|
|
})
|
|
.Where(value => !string.IsNullOrEmpty(value))
|
|
.ToListAsync();
|
|
|
|
return new SitemapUrl(url, localizedUrls, updateFreq, updatedOn);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |