using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers; using Microsoft.Extensions.Primitives; using Nop.Core; using Nop.Core.Events; using Nop.Core.Http.Extensions; using Nop.Core.Infrastructure; using Nop.Services.Localization; using Nop.Web.Framework.Events; using Nop.Web.Framework.Models; using Nop.Web.Framework.Mvc.Filters; using Nop.Web.Framework.UI; namespace Nop.Web.Framework.Controllers; /// /// Base controller /// [HttpsRequirement] [PublishModelEvents] [SignOutFromExternalAuthentication] [ValidatePassword] [SaveIpAddress] [SaveLastActivity] [SaveLastVisitedPage] [ForceMultiFactorAuthentication] public abstract partial class BaseController : Controller { #region Rendering /// /// Render component to string /// /// Component type /// Arguments /// /// A task that represents the asynchronous operation /// The task result contains the result /// protected virtual async Task RenderViewComponentToStringAsync(Type componentType, object arguments = null) { var helper = new DefaultViewComponentHelper( EngineContext.Current.Resolve(), HtmlEncoder.Default, EngineContext.Current.Resolve(), EngineContext.Current.Resolve(), EngineContext.Current.Resolve()); using var writer = new StringWriter(); var context = new ViewContext(ControllerContext, NullView.Instance, ViewData, TempData, writer, new HtmlHelperOptions()); helper.Contextualize(context); var result = await helper.InvokeAsync(componentType, arguments); result.WriteTo(writer, HtmlEncoder.Default); await writer.FlushAsync(); return writer.ToString(); } /// /// Render partial view to string /// /// View name /// Model /// /// A task that represents the asynchronous operation /// The task result contains the result /// protected virtual async Task RenderPartialViewToStringAsync(string viewName, object model) { //get Razor view engine var razorViewEngine = EngineContext.Current.Resolve(); //create action context var actionContext = new ActionContext(HttpContext, RouteData, ControllerContext.ActionDescriptor, ModelState); //set view name as action name in case if not passed if (string.IsNullOrEmpty(viewName)) viewName = ControllerContext.ActionDescriptor.ActionName; //partial view are not part of the controller life cycle. //hence, we could no use action filters to intercept the Models being returned //as we do in the /Nop.Web.Framework/Mvc/Filters/PublishModelEventsAttribute.cs for controllers switch (model) { case BaseNopModel nopModel: { var eventPublisher = EngineContext.Current.Resolve(); //we publish the ModelPrepared event for all models as the BaseNopModel, //so you need to implement IConsumer> interface to handle this event eventPublisher.ModelPreparedAsync(nopModel).Wait(); break; } case IEnumerable modelCollection: { var eventPublisher = EngineContext.Current.Resolve(); //we publish the ModelPrepared event for collection as the IEnumerable, //so you need to implement IConsumer>> interface to handle this event eventPublisher.ModelPreparedAsync(modelCollection).Wait(); break; } } //set model ViewData.Model = model; //try to get a view by the name var viewResult = razorViewEngine.FindView(actionContext, viewName, false); if (viewResult.View == null) { //or try to get a view by the path viewResult = razorViewEngine.GetView(null, viewName, false); if (viewResult.View == null) throw new ArgumentNullException($"{viewName} view was not found"); } await using var stringWriter = new StringWriter(); var viewContext = new ViewContext(actionContext, viewResult.View, ViewData, TempData, stringWriter, new HtmlHelperOptions()); await viewResult.View.RenderAsync(viewContext); return stringWriter.GetStringBuilder().ToString(); } #endregion #region Notifications /// /// Error's JSON data /// /// Error text /// Error's JSON data protected JsonResult ErrorJson(string error) { return Json(new { error }); } /// /// Error's JSON data /// /// Error messages /// Error's JSON data protected JsonResult ErrorJson(object errors) { return Json(new { error = errors }); } /// /// Display "Edit" (manage) link (in public store) /// /// Edit page URL protected virtual void DisplayEditLink(string editPageUrl) { var nopHtmlHelper = EngineContext.Current.Resolve(); nopHtmlHelper.AddEditPageUrl(editPageUrl); } #endregion #region Localization /// /// Add locales for localizable entities /// /// Localizable model /// Language service /// Locales protected virtual async Task AddLocalesAsync(ILanguageService languageService, IList locales) where TLocalizedModelLocal : ILocalizedLocaleModel { await AddLocalesAsync(languageService, locales, null); } /// /// Add locales for localizable entities /// /// Localizable model /// Language service /// Locales /// Configure action protected virtual async Task AddLocalesAsync(ILanguageService languageService, IList locales, Action configure) where TLocalizedModelLocal : ILocalizedLocaleModel { foreach (var language in await languageService.GetAllLanguagesAsync(true)) { var locale = Activator.CreateInstance(); locale.LanguageId = language.Id; configure?.Invoke(locale, locale.LanguageId); locales.Add(locale); } } #endregion #region Security /// /// Access denied view /// /// Access denied view protected virtual IActionResult AccessDeniedView() { var webHelper = EngineContext.Current.Resolve(); //return Challenge(); return RedirectToAction("AccessDenied", "Security", new { pageUrl = webHelper.GetRawUrl(Request) }); } /// /// Access denied JSON data /// /// /// A task that represents the asynchronous operation /// The task result contains the access denied JSON data /// protected virtual async Task AccessDeniedJsonAsync() { var localizationService = EngineContext.Current.Resolve(); return ErrorJson(await localizationService.GetResourceAsync("Admin.AccessDenied.Description")); } #endregion #region Cards and tabs /// /// Save selected card name /// /// Card name to save /// A value indicating whether a message should be persisted for the next request. Pass null to ignore public virtual void SaveSelectedCardName(string cardName, bool persistForTheNextRequest = true) { //keep this method synchronized with //"GetSelectedCardName" method of \Nop.Web.Framework\Extensions\HtmlExtensions.cs ArgumentException.ThrowIfNullOrEmpty(cardName); const string dataKey = "nop.selected-card-name"; if (persistForTheNextRequest) { TempData[dataKey] = cardName; } else { ViewData[dataKey] = cardName; } } /// /// Save selected tab name /// /// Tab name to save; empty to automatically detect it /// A value indicating whether a message should be persisted for the next request. Pass null to ignore public virtual async Task SaveSelectedTabNameAsync(string tabName = "", bool persistForTheNextRequest = true) { //default root tab SaveSelectedTabName(tabName, await Request.GetFormValueAsync("selected-tab-name"), null, persistForTheNextRequest); //child tabs (usually used for localization) //Form is available for POST only if (!Request.IsPostRequest() || !Request.HasFormContentType) return; var form = await Request.ReadFormAsync(); foreach (var item in form) if (item.Key.StartsWith("selected-tab-name-", StringComparison.InvariantCultureIgnoreCase)) SaveSelectedTabName(null, item.Value, item.Key["selected-tab-name-".Length..], persistForTheNextRequest); } /// /// Save selected tab name /// /// Tab name to save; empty to automatically detect it /// A value indicating whether a message should be persisted for the next request. Pass null to ignore /// Selected tab name /// A prefix for child tab to process protected virtual void SaveSelectedTabName(string tabName, StringValues selectedTabName, string dataKeyPrefix, bool persistForTheNextRequest) { //keep this method synchronized with //"GetSelectedTabName" method of \Nop.Web.Framework\Extensions\HtmlExtensions.cs if (string.IsNullOrEmpty(tabName)) tabName = selectedTabName; if (string.IsNullOrEmpty(tabName)) return; var dataKey = "nop.selected-tab-name"; if (!string.IsNullOrEmpty(dataKeyPrefix)) dataKey += $"-{dataKeyPrefix}"; if (persistForTheNextRequest) TempData[dataKey] = tabName; else ViewData[dataKey] = tabName; } #endregion }