Try your search with a different keyword or use * as a wildcard.
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Hosting;
using Nop.Core;
using Nop.Core.Configuration;
using Nop.Core.Domain.Seo;
using Nop.Services.Localization;
using Nop.Web.Framework.Mvc.Routing;
using Nop.Web.Framework.WebOptimizer;
namespace Nop.Web.Framework.UI;
///
/// Represents the HTML helper implementation
///
public partial class NopHtmlHelper : INopHtmlHelper
{
#region Fields
protected readonly AppSettings _appSettings;
protected readonly HtmlEncoder _htmlEncoder;
protected readonly IActionContextAccessor _actionContextAccessor;
protected readonly INopAssetHelper _bundleHelper;
protected readonly Lazy _localizationService;
protected readonly IStoreContext _storeContext;
protected readonly IUrlHelperFactory _urlHelperFactory;
protected readonly IWebHostEnvironment _webHostEnvironment;
protected readonly SeoSettings _seoSettings;
protected readonly Dictionary> _scriptParts = new();
protected readonly Dictionary> _inlineScriptParts = new();
protected readonly List _cssParts = new();
protected readonly List _canonicalUrlParts = new();
protected readonly List _headCustomParts = new();
protected readonly List _metaDescriptionParts = new();
protected readonly List _metaKeywordParts = new();
protected readonly List _pageCssClassParts = new();
protected readonly List _titleParts = new();
protected string _activeAdminMenuSystemName;
protected string _editPageUrl;
#endregion
#region Ctor
public NopHtmlHelper(AppSettings appSettings,
HtmlEncoder htmlEncoder,
IActionContextAccessor actionContextAccessor,
INopAssetHelper bundleHelper,
Lazy localizationService,
IStoreContext storeContext,
IUrlHelperFactory urlHelperFactory,
IWebHostEnvironment webHostEnvironment,
SeoSettings seoSettings)
{
_appSettings = appSettings;
_htmlEncoder = htmlEncoder;
_actionContextAccessor = actionContextAccessor;
_bundleHelper = bundleHelper;
_localizationService = localizationService;
_storeContext = storeContext;
_urlHelperFactory = urlHelperFactory;
_webHostEnvironment = webHostEnvironment;
_seoSettings = seoSettings;
}
#endregion
#region Utilities
protected static string GetAssetKey(string[] keys, string suffix)
{
ArgumentNullException.ThrowIfNull(keys?.Length > 0 ? keys : null, nameof(keys));
var hashInput = string.Join(',', keys);
var input = MD5.HashData(Encoding.Unicode.GetBytes(hashInput));
var key = string.Concat(WebEncoders.Base64UrlEncode(input));
if (!string.IsNullOrEmpty(suffix))
key += suffix;
return key.ToLower();
}
#endregion
#region Methods
///
/// Add title element to the ]]>
///
/// Title part
public virtual void AddTitleParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_titleParts.Add(part);
}
///
/// Append title element to the ]]>
///
/// Title part
public virtual void AppendTitleParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_titleParts.Insert(0, part);
}
///
/// Generate all title parts
///
/// A value indicating whether to insert a default title
/// Title part
/// A task that represents the asynchronous operation
/// The task result contains generated HTML string
public virtual async Task GenerateTitleAsync(bool addDefaultTitle = true, string part = "")
{
AppendTitleParts(part);
var store = await _storeContext.GetCurrentStoreAsync();
var defaultTitle = await _localizationService.Value.GetLocalizedAsync(store, s => s.DefaultTitle);
var specificTitle = string.Join(_seoSettings.PageTitleSeparator, _titleParts.AsEnumerable().Reverse().ToArray());
string result;
if (!string.IsNullOrEmpty(specificTitle))
{
if (addDefaultTitle)
//store name + page title
switch (_seoSettings.PageTitleSeoAdjustment)
{
case PageTitleSeoAdjustment.PagenameAfterStorename:
{
result = string.Join(_seoSettings.PageTitleSeparator, defaultTitle, specificTitle);
}
break;
case PageTitleSeoAdjustment.StorenameAfterPagename:
default:
{
result = string.Join(_seoSettings.PageTitleSeparator, specificTitle, defaultTitle);
}
break;
}
else
//page title only
result = specificTitle;
}
else
//store name only
result = defaultTitle;
return new HtmlString(_htmlEncoder.Encode(result ?? string.Empty));
}
///
/// Add meta description element to the ]]>
///
/// Meta description part
public virtual void AddMetaDescriptionParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_metaDescriptionParts.Add(part);
}
///
/// Append meta description element to the ]]>
///
/// Meta description part
public virtual void AppendMetaDescriptionParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_metaDescriptionParts.Insert(0, part);
}
///
/// Generate all description parts
///
/// Meta description part
/// A task that represents the asynchronous operation
/// The task result contains generated HTML string
public virtual async Task GenerateMetaDescriptionAsync(string part = "")
{
AppendMetaDescriptionParts(part);
var metaDescription = string.Join(", ", _metaDescriptionParts.AsEnumerable().Reverse().ToArray());
var result = !string.IsNullOrEmpty(metaDescription)
? metaDescription
: await _localizationService.Value.GetLocalizedAsync(await _storeContext.GetCurrentStoreAsync(),
s => s.DefaultMetaDescription);
return new HtmlString(_htmlEncoder.Encode(result ?? string.Empty));
}
///
/// Add meta keyword element to the ]]>
///
/// Meta keyword part
public virtual void AddMetaKeywordParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_metaKeywordParts.Add(part);
}
///
/// Append meta keyword element to the ]]>
///
/// Meta keyword part
public virtual void AppendMetaKeywordParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_metaKeywordParts.Insert(0, part);
}
///
/// Generate all keyword parts
///
/// Meta keyword part
/// A task that represents the asynchronous operation
/// The task result contains generated HTML string
public virtual async Task GenerateMetaKeywordsAsync(string part = "")
{
AppendMetaKeywordParts(part);
var metaKeyword = string.Join(", ", _metaKeywordParts.AsEnumerable().Reverse().ToArray());
var result = !string.IsNullOrEmpty(metaKeyword)
? metaKeyword
: await _localizationService.Value.GetLocalizedAsync(await _storeContext.GetCurrentStoreAsync(),
s => s.DefaultMetaKeywords);
return new HtmlString(_htmlEncoder.Encode(result ?? string.Empty));
}
///
/// Add script element
///
/// A location of the script element
/// Script path (minified version)
/// Script path (full debug version). If empty, then minified version will be used
/// A value indicating whether to exclude this script from bundling
public virtual void AddScriptParts(ResourceLocation location, string src, string debugSrc = "", bool excludeFromBundle = false)
{
if (!_scriptParts.ContainsKey(location))
_scriptParts.Add(location, new List());
if (string.IsNullOrEmpty(src))
return;
if (!string.IsNullOrEmpty(debugSrc) && _webHostEnvironment.IsDevelopment())
src = debugSrc;
ArgumentNullException.ThrowIfNull(_actionContextAccessor.ActionContext);
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
_scriptParts[location].Add(new ScriptReferenceMeta
{
ExcludeFromBundle = excludeFromBundle,
IsLocal = urlHelper.IsLocalUrl(src),
Src = urlHelper.Content(src)
});
}
///
/// Append script element
///
/// A location of the script element
/// Script path (minified version)
/// Script path (full debug version). If empty, then minified version will be used
/// A value indicating whether to exclude this script from bundling
public virtual void AppendScriptParts(ResourceLocation location, string src, string debugSrc = "", bool excludeFromBundle = false)
{
if (!_scriptParts.ContainsKey(location))
_scriptParts.Add(location, new List());
if (string.IsNullOrEmpty(src))
return;
if (!string.IsNullOrEmpty(debugSrc) && _webHostEnvironment.IsDevelopment())
src = debugSrc;
ArgumentNullException.ThrowIfNull(_actionContextAccessor.ActionContext);
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
_scriptParts[location].Insert(0, new ScriptReferenceMeta
{
ExcludeFromBundle = excludeFromBundle,
IsLocal = urlHelper.IsLocalUrl(src),
Src = urlHelper.Content(src)
});
}
///
/// Generate all script parts
///
/// A location of the script element
/// Generated HTML string
public virtual IHtmlContent GenerateScripts(ResourceLocation location)
{
if (!_scriptParts.TryGetValue(location, out var value) || value == null)
return HtmlString.Empty;
if (!_scriptParts.Any())
return HtmlString.Empty;
var result = new StringBuilder();
var woConfig = _appSettings.Get();
var pathBase = _actionContextAccessor.ActionContext?.HttpContext.Request.PathBase ?? PathString.Empty;
if (woConfig.EnableJavaScriptBundling && value.Any(item => !item.ExcludeFromBundle))
{
var sources = value.Where(item => !item.ExcludeFromBundle && item.IsLocal)
.Select(item => item.Src)
.Distinct().ToArray();
var bundleKey = string.Concat("/js/", GetAssetKey(sources, woConfig.JavaScriptBundleSuffix), ".js");
var bundleAsset = _bundleHelper.GetOrCreateJavaScriptAsset(bundleKey, sources);
var route = _bundleHelper.CacheBusting(bundleAsset);
result.AppendFormat("",
MimeTypes.TextJavascript, pathBase, route);
}
var scripts = value.Where(item => !woConfig.EnableJavaScriptBundling || item.ExcludeFromBundle || !item.IsLocal)
.Distinct();
foreach (var item in scripts)
{
if (!item.IsLocal)
{
result.AppendFormat("", MimeTypes.TextJavascript, item.Src);
result.Append(Environment.NewLine);
continue;
}
var asset = _bundleHelper.GetOrCreateJavaScriptAsset(item.Src);
var route = _bundleHelper.CacheBusting(asset);
result.AppendFormat("",
MimeTypes.TextJavascript, pathBase, route);
result.Append(Environment.NewLine);
}
return new HtmlString(result.ToString());
}
///
/// Add inline script element
///
/// A location of the script element
/// Script
public virtual void AddInlineScriptParts(ResourceLocation location, string script)
{
if (!_inlineScriptParts.ContainsKey(location))
_inlineScriptParts.Add(location, new());
if (string.IsNullOrEmpty(script))
return;
if (_inlineScriptParts[location].Contains(script))
return;
_inlineScriptParts[location].Add(script);
}
///
/// Append inline script element
///
/// A location of the script element
/// Script
public virtual void AppendInlineScriptParts(ResourceLocation location, string script)
{
if (!_inlineScriptParts.ContainsKey(location))
_inlineScriptParts.Add(location, new());
if (string.IsNullOrEmpty(script))
return;
if (_inlineScriptParts[location].Contains(script))
return;
_inlineScriptParts[location].Insert(0, script);
}
///
/// Generate all inline script parts
///
/// A location of the script element
/// Generated HTML string
public virtual IHtmlContent GenerateInlineScripts(ResourceLocation location)
{
if (!_inlineScriptParts.TryGetValue(location, out var value) || value == null)
return HtmlString.Empty;
if (!_inlineScriptParts.Any())
return HtmlString.Empty;
var result = new StringBuilder();
foreach (var item in value)
{
result.Append(item);
result.Append(Environment.NewLine);
}
return new HtmlString(result.ToString());
}
///
/// Add CSS element
///
/// Script path (minified version)
/// Script path (full debug version). If empty, then minified version will be used
/// A value indicating whether to exclude this style sheet from bundling
public virtual void AddCssFileParts(string src, string debugSrc = "", bool excludeFromBundle = false)
{
if (string.IsNullOrEmpty(src))
return;
if (!string.IsNullOrEmpty(debugSrc) && _webHostEnvironment.IsDevelopment())
src = debugSrc;
ArgumentNullException.ThrowIfNull(_actionContextAccessor.ActionContext);
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
_cssParts.Add(new CssReferenceMeta
{
ExcludeFromBundle = excludeFromBundle,
IsLocal = urlHelper.IsLocalUrl(src),
Src = urlHelper.Content(src)
});
}
///
/// Append CSS element
///
/// Script path (minified version)
/// Script path (full debug version). If empty, then minified version will be used
/// A value indicating whether to exclude this style sheet from bundling
public virtual void AppendCssFileParts(string src, string debugSrc = "", bool excludeFromBundle = false)
{
if (string.IsNullOrEmpty(src))
return;
if (!string.IsNullOrEmpty(debugSrc) && _webHostEnvironment.IsDevelopment())
src = debugSrc;
ArgumentNullException.ThrowIfNull(_actionContextAccessor.ActionContext);
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
_cssParts.Insert(0, new CssReferenceMeta
{
ExcludeFromBundle = excludeFromBundle,
IsLocal = urlHelper.IsLocalUrl(src),
Src = urlHelper.Content(src)
});
}
///
/// Generate all CSS parts
///
/// Generated HTML string
public virtual IHtmlContent GenerateCssFiles()
{
if (!_cssParts.Any())
return HtmlString.Empty;
ArgumentNullException.ThrowIfNull(_actionContextAccessor.ActionContext);
var result = new StringBuilder();
var woConfig = _appSettings.Get();
var pathBase = _actionContextAccessor.ActionContext?.HttpContext.Request.PathBase ?? PathString.Empty;
if (woConfig.EnableCssBundling && _cssParts.Any(item => !item.ExcludeFromBundle))
{
var bundleSuffix = woConfig.CssBundleSuffix;
if (CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft)
bundleSuffix += ".rtl";
var sources = _cssParts
.Where(item => !item.ExcludeFromBundle && item.IsLocal)
.Distinct()
//remove the application path from the generated URL if exists
.Select(item => item.Src).ToArray();
var bundleKey = string.Concat("/css/", GetAssetKey(sources, bundleSuffix), ".css");
var bundleAsset = _bundleHelper.GetOrCreateCssAsset(bundleKey, sources);
var route = _bundleHelper.CacheBusting(bundleAsset);
result.AppendFormat("",
MimeTypes.TextCss, pathBase, route);
}
var styles = _cssParts
.Where(item => !woConfig.EnableCssBundling || item.ExcludeFromBundle || !item.IsLocal)
.Distinct();
foreach (var item in styles)
{
if (!item.IsLocal)
{
result.AppendFormat("", MimeTypes.TextCss, item.Src);
result.Append(Environment.NewLine);
continue;
}
var asset = _bundleHelper.GetOrCreateCssAsset(item.Src);
var route = _bundleHelper.CacheBusting(asset);
result.AppendFormat("",
MimeTypes.TextCss, pathBase, route);
result.AppendLine();
}
return new HtmlString(result.ToString());
}
///
/// Add canonical URL element to the ]]>
///
/// Canonical URL part
/// Whether to use canonical URLs with query string parameters
public virtual void AddCanonicalUrlParts(string part, bool withQueryString = false)
{
if (string.IsNullOrEmpty(part))
return;
if (withQueryString)
{
//add ordered query string parameters
var queryParameters = _actionContextAccessor.ActionContext.HttpContext.Request.Query.OrderBy(parameter => parameter.Key)
.ToDictionary(parameter => parameter.Key, parameter => parameter.Value.ToString());
part = QueryHelpers.AddQueryString(part, queryParameters);
}
_canonicalUrlParts.Add(part);
}
///
/// Append canonical URL element to the ]]>
///
/// Canonical URL part
public virtual void AppendCanonicalUrlParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_canonicalUrlParts.Insert(0, part);
}
///
/// Generate all canonical URL parts
///
/// Generated HTML string
public virtual IHtmlContent GenerateCanonicalUrls()
{
var result = new StringBuilder();
foreach (var canonicalUrl in _canonicalUrlParts)
{
result.AppendFormat("", canonicalUrl);
result.Append(Environment.NewLine);
}
return new HtmlString(result.ToString());
}
///
/// Add any custom element to the ]]> element
///
/// The entire element. For example, ]]>
public virtual void AddHeadCustomParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_headCustomParts.Add(part);
}
///
/// Append any custom element to the ]]> element
///
/// The entire element. For example, ]]>
public virtual void AppendHeadCustomParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_headCustomParts.Insert(0, part);
}
///
/// Generate all custom elements
///
/// Generated HTML string
public virtual IHtmlContent GenerateHeadCustom()
{
//use only distinct rows
var distinctParts = _headCustomParts.Distinct().ToList();
if (!distinctParts.Any())
return HtmlString.Empty;
var result = new StringBuilder();
foreach (var path in distinctParts)
{
result.Append(path);
result.Append(Environment.NewLine);
}
return new HtmlString(result.ToString());
}
///
/// Add CSS class to the ]]> element
///
/// CSS class
public virtual void AddPageCssClassParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_pageCssClassParts.Add(part);
}
///
/// Append CSS class to the ]]> element
///
/// CSS class
public virtual void AppendPageCssClassParts(string part)
{
if (string.IsNullOrEmpty(part))
return;
_pageCssClassParts.Insert(0, part);
}
///
/// Generate all title parts
///
/// CSS class
/// Generated string
public virtual string GeneratePageCssClasses(string part = "")
{
AppendPageCssClassParts(part);
var result = string.Join(" ", _pageCssClassParts.AsEnumerable().Reverse().ToArray());
if (string.IsNullOrEmpty(result))
return string.Empty;
return _htmlEncoder.Encode(result);
}
///
/// Specify "edit page" URL
///
/// URL
public virtual void AddEditPageUrl(string url)
{
_editPageUrl = url;
}
///
/// Get "edit page" URL
///
/// URL
public virtual string GetEditPageUrl()
{
return _editPageUrl;
}
///
/// Specify system name of admin menu item that should be selected (expanded)
///
/// System name
public virtual void SetActiveMenuItemSystemName(string systemName)
{
_activeAdminMenuSystemName = systemName;
}
///
/// Get system name of admin menu item that should be selected (expanded)
///
/// System name
public virtual string GetActiveMenuItemSystemName()
{
return _activeAdminMenuSystemName;
}
///
/// Get the route name associated with the request rendering this page
///
/// A value indicating whether to build the name using engine information unless otherwise specified
/// Route name
public virtual string GetRouteName(bool handleDefaultRoutes = false)
{
var actionContext = _actionContextAccessor.ActionContext;
if (actionContext is null)
return string.Empty;
var httpContext = actionContext.HttpContext;
var routeName = httpContext.GetEndpoint()?.Metadata.GetMetadata()?.RouteName ?? string.Empty;
if (!string.IsNullOrEmpty(routeName) && routeName != "areaRoute")
return routeName;
//then try to get a generic one (actually it's an action name, not the route)
if (httpContext.GetRouteValue(NopRoutingDefaults.RouteValue.SeName) is not null &&
httpContext.GetRouteValue(NopRoutingDefaults.RouteValue.Action) is string actionName)
return actionName;
if (handleDefaultRoutes)
return actionContext.ActionDescriptor switch
{
ControllerActionDescriptor controllerAction => string.Concat(controllerAction.ControllerName, controllerAction.ActionName),
CompiledPageActionDescriptor compiledPage => string.Concat(compiledPage.AreaName, compiledPage.ViewEnginePath.Replace("/", "")),
PageActionDescriptor pageAction => string.Concat(pageAction.AreaName, pageAction.ViewEnginePath.Replace("/", "")),
_ => actionContext.ActionDescriptor.DisplayName?.Replace("/", "") ?? string.Empty
};
return routeName;
}
#endregion
#region Nested classes
///
/// JS file meta data
///
protected partial record ScriptReferenceMeta
{
///
/// A value indicating whether to exclude the script from bundling
///
public bool ExcludeFromBundle { get; init; }
///
/// A value indicating whether the src is local
///
public bool IsLocal { get; init; }
///
/// Src for production
///
public string Src { get; init; }
}
///
/// CSS file meta data
///
protected partial record CssReferenceMeta
{
///
/// A value indicating whether to exclude the script from bundling
///
public bool ExcludeFromBundle { get; init; }
///
/// Src for production
///
public string Src { get; init; }
///
/// A value indicating whether the Src is local
///
public bool IsLocal { get; init; }
}
#endregion
}