Komponenten-Builder für: Product detail card

Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsGallery.cshtml"
System.ArgumentNullException: Value cannot be null. (Parameter 'source')
   at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_e68fde25ba804b42ad710c31fc20a77f.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> @using Dynamicweb.Ecommerce.ProductCatalog @using Dynamicweb.Frontend @using System.IO @using System.Text.RegularExpressions; @functions { public ProductViewModel product { get; set; } = new ProductViewModel(); public string galleryLayout { get; set; } public string[] supportedImageFormats { get; set; } public string[] supportedVideoFormats { get; set; } public string[] supportedDocumentFormats { get; set; } public string[] allSupportedFormats { get; set; } public class RatioSettings { public string Ratio { get; set; } public string CssClass { get; set; } public string CssVariable { get; set; } public string Fill { get; set; } } public RatioSettings GetRatioSettings(string size = "desktop") { var ratioSettings = new RatioSettings(); string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); ratio = ratio != "0" ? ratio : ""; string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; cssClass = ratio != "" && ratio == "fill" && size == "mobile" ? " ratio" : cssClass; cssVariable = ratio != "" && ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; ratioSettings.Ratio = ratio; ratioSettings.CssClass = cssClass; ratioSettings.CssVariable = cssVariable; ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; return ratioSettings; } public string GetColumnClass(int total, int assetNumber) { string colClass = total > 1 ? "g-col-lg-6" : "g-col-12"; colClass = galleryLayout == "full-first" && assetNumber == 0 ? "g-col-12" : colClass; colClass = galleryLayout == "full-last" && assetNumber == (total - 1) ? "g-col-12" : colClass; colClass = galleryLayout == "advanced-grid" && assetNumber > 1 ? "g-col-4" : colClass; colClass = galleryLayout == "advanced-grid" && total == 1 ? "g-col-12" : colClass; colClass = galleryLayout == "advanced-grid" && total == 3 && assetNumber == 2 ? "g-col-12" : colClass; colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 2 ? "g-col-6" : colClass; colClass = galleryLayout == "advanced-grid" && total == 4 && assetNumber == 3 ? "g-col-6" : colClass; colClass = galleryLayout == "advanced-grid" && total == 6 && assetNumber == 5 ? "g-col-12" : colClass; colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 5 ? "g-col-6" : colClass; colClass = galleryLayout == "advanced-grid" && total == 7 && assetNumber == 6 ? "g-col-6" : colClass; colClass = galleryLayout == "advanced-grid" && total == 9 && assetNumber == 8 ? "g-col-12" : colClass; return colClass; } public string GetArrowsColor() { var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); var arrowsColor = invertColor ? " carousel-dark" : string.Empty; return arrowsColor; } public Dictionary<string, object> GetVideoParams(MediaViewModel asset, string size) { string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; string type = GetVideoType(asset.Value); bool openInModal = Model.Item.GetString("OpenVideoInModal") == "true" ? true : false; bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); var videoParams = new Dictionary<string, object>(); videoParams.Add("AssetName", asset.Name); videoParams.Add("AssetVideoType", type); videoParams.Add("AssetDisplayName", asset.DisplayName); videoParams.Add("OpenVideoInModal", openInModal); videoParams.Add("VideoAutoPlay", autoPlay); videoParams.Add("Size", size); videoParams.Add("Id", Model.ID); return videoParams; } public string GetVideoType(string assetValue) { string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : string.Empty; type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; type = string.IsNullOrEmpty(type) ? "selfhosted" : type; return type; } public string GetYoutubeScreenDump(string assetValue, string quality) { var regex = new Regex(@"(?:youtube\.com\/.*[\?&]v=|youtu\.be\/)([\w-]+)"); Match match = regex.Match(assetValue); string videoId = match.Success ? match.Groups[1].Value : string.Empty; string youtubeThumbnail = $"https://img.youtube.com/vi/{videoId}/{quality}.jpg"; return youtubeThumbnail; } } @{ @* Get the product data *@ if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) { product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; } else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) { var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); if (productList?.Products is object) { product = productList.Products[0]; } } } @if (product is object) { @* Supported formats *@ supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); @* Collect the assets *@ var selectedAssetCategories = Model.Item.GetList("ImageAssets")?.GetRawValue().OfType<string>(); bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); IEnumerable<MediaViewModel> assetsList = new MediaViewModel[]{}; assetsList = assetsList.Union(assetsImages); assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); int totalAssets = 0; foreach (MediaViewModel asset in assetsList) { var assetValue = asset.Value; foreach (string format in allSupportedFormats) { if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { totalAssets++; } } } if (totalAssets == 0) { if (defaultImageFallback) { assetsList = new List<MediaViewModel>(){ product.DefaultImage }; totalAssets = 1; } else { assetsList = new List<MediaViewModel>(){ }; totalAssets = 0; } } @* Layout settings *@ string spacing = Model.Item.GetRawValueString("Spacing", "4"); spacing = spacing == "none" ? "gap-0" : spacing; spacing = spacing == "small" ? "gap-3" : spacing; spacing = spacing == "large" ? "gap-4" : spacing; galleryLayout = Model.Item.GetRawValueString("Layout", "grid"); string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; var badgeParms = new Dictionary<string, object>(); badgeParms.Add("size", "h5"); badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); badgeParms.Add("campaignBadgesValues", Model.Item.GetList("CampaignBadges")?.GetRawValue().OfType<string>().ToList()); bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; DateTime createdDate = product.Created.Value; bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; @* Get assets from selected categories or get all assets *@ if (totalAssets != 0 && assetsList.Count() != 0) { int desktopAssetNumber = 0; int mobileAssetNumber = 0; int mobileAssetThumbnailNumber = 0; int modalAssetNumber = 0; @* Desktop: Show the gallery on large screens *@ <div class="d-none d-lg-block h-100 position-relative @theme item_@Model.Item.SystemName.ToLower() desktop"> <div class="grid @spacing"> @foreach (MediaViewModel asset in assetsList) { var assetName = asset.Value; foreach (string format in allSupportedFormats) { if (assetName.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { string size = "desktop"; string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; string assetValue = asset.Value; <div class="@GetColumnClass(totalAssets, desktopAssetNumber)"> <div class="h-100 @(imageTheme)"> @foreach (string imageFormat in supportedImageFormats) { //Images if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { string productName = product.Name; string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); RatioSettings ratioSettings = GetRatioSettings(size); var parms = new Dictionary<string, object>(); parms.Add("alt", productName); parms.Add("itemprop", "image"); if (totalAssets > 1) { parms.Add("columns", 2); } else { parms.Add("columns", 1); } parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); if (!string.IsNullOrEmpty(asset.DisplayName)) { parms.Add("title", asset.DisplayName); } if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); } else { parms.Add("cssClass", "mw-100 mh-100"); } <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) </div> </a> } } @foreach (string videoFormat in supportedVideoFormats) { //Videos if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { if (Model.Item.GetString("OpenVideoInModal") == "true") { string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; string type = GetVideoType(asset.Value); string videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeDataString(asset.Value) : string.Empty; videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : videoScreendumpPath; string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; string productName = product.Name; productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; RatioSettings ratioSettings = GetRatioSettings(size); <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> @if (type != "selfhosted") { <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" /> } else { string videoType = Path.GetExtension(asset.Value).ToLower(); <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> </video> } </div> </div> } else { var videoParams = GetVideoParams(asset, size); @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); } } } @foreach (string documentFormat in supportedDocumentFormats) { //Documents if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) { string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; string productName = product.Name; string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; string imageLinkPath = imagePath; RatioSettings ratioSettings = GetRatioSettings(size); var parms = new Dictionary<string, object>(); parms.Add("alt", productName); parms.Add("itemprop", "image"); parms.Add("fullwidth", true); parms.Add("columns", Model.GridRowColumnCount); if (!string.IsNullOrEmpty(asset.DisplayName)) { parms.Add("title", asset.DisplayName); } if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); } else { parms.Add("cssClass", "mw-100 mh-100"); } <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Translate("Download")"> <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) } else { } </div> </a> } } </div> </div> desktopAssetNumber++; } } } </div> @if (showBadges) { <div class="position-absolute top-0 left-0 p-2 p-lg-3 w-100"> @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) </div> } </div> @* Mobile: Show the thumbs on small screens *@ <div class="d-block d-lg-none mx-lg-0 position-relative @theme item_@Model.Item.SystemName.ToLower() mobile"> <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> <div class="carousel-inner h-100"> @foreach (MediaViewModel asset in assetsList) { var assetValue = asset.Value; foreach (string format in allSupportedFormats) { if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { string activeSlide = mobileAssetNumber == 0 ? "active" : ""; string size = "mobile"; string imageTheme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; <div class="carousel-item @activeSlide" data-bs-interval="99999"> <div class="h-100 @(imageTheme)"> @foreach (string supportedFormat in supportedImageFormats) { //Images if (assetValue.IndexOf(supportedFormat, StringComparison.OrdinalIgnoreCase) >= 0) { string productName = product.Name; string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); RatioSettings ratioSettings = GetRatioSettings(size); var parms = new Dictionary<string, object>(); parms.Add("alt", productName); parms.Add("itemprop", "image"); if (totalAssets > 1) { parms.Add("columns", 2); } else { parms.Add("columns", 1); } parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); if (!string.IsNullOrEmpty(asset.DisplayName)) { parms.Add("title", asset.DisplayName); } if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); } else { parms.Add("cssClass", "mw-100 mh-100"); } <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) </div> </a> } } @foreach (string videoFormat in supportedVideoFormats) { //Videos if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { if (Model.Item.GetString("OpenVideoInModal") == "true") { string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; string type = GetVideoType(asset.Value); string videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeDataString(asset.Value) : string.Empty; videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : videoScreendumpPath; string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : ""; string productName = product.Name; productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; RatioSettings ratioSettings = GetRatioSettings(size); <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@desktopAssetNumber"> <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> @if (type != "selfhosted") { <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" > } else { string videoType = Path.GetExtension(asset.Value).ToLower(); <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> </video> } </div> </div> } else { Dictionary<string, object> videoParams = GetVideoParams(asset, size); @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); } } } @foreach (string documentFormat in supportedDocumentFormats) { //Documents if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) { string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; string productName = product.Name; string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; string imageLinkPath = imagePath; RatioSettings ratioSettings = GetRatioSettings(size); var parms = new Dictionary<string, object>(); parms.Add("alt", productName); parms.Add("itemprop", "image"); parms.Add("fullwidth", true); parms.Add("columns", Model.GridRowColumnCount); if (!string.IsNullOrEmpty(asset.DisplayName)) { parms.Add("title", asset.DisplayName); } if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); } else { parms.Add("cssClass", "mw-100 mh-100"); } <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download title="@Translate("Download")"> <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) } else { } </div> </a> } } </div> </div> mobileAssetNumber++; } } } </div> </div> @if (totalAssets > 1) { <div id="SmallScreenImagesThumbnails_@Model.ID" class="d-flex flex-nowrap gap-2 overflow-x-auto my-3"> @foreach (MediaViewModel asset in assetsList) { var assetValue = asset.Value; foreach (string format in allSupportedFormats) { if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; string type = GetVideoType(asset.Value); string videoScreendumpPath = type == "youtube" ? GetYoutubeScreenDump(asset.Value, "maxresdefault") : string.Empty; videoScreendumpPath = type == "selfhosted" ? System.Uri.EscapeDataString(asset.Value) : videoScreendumpPath; string videoJsClass = type == "vimeo" ? "js-vimeo-video-thumbnail" : string.Empty; string productName = product.Name; productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; <div class="ratio ratio-4x3 border outline-none" style="flex:0 0 80px" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@mobileAssetThumbnailNumber"> @foreach (string videoFormat in supportedVideoFormats) { if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath+"play-circle.svg")</div> </div> } } @if (type != "selfhosted") { <img src="@(videoScreendumpPath)" class="p-1 @videoJsClass mw-100 mh-100" data-asset-value="@asset.Value" style="object-fit: cover;" alt="@productName" @assetTitle> } else { string videoType = Path.GetExtension(asset.Value).ToLower(); <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> <source src="@(videoScreendumpPath)#t=0.001" type="video/@videoType.Replace(".", "")"> </video> } </div> mobileAssetThumbnailNumber++; } } } </div> } @if (showBadges) { <div class="position-absolute top-0 left-0 p-2 p-lg-3"> @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) </div> } </div> @* Modal with slides *@ <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> <div class="modal-dialog modal-dialog-centered modal-xl"> <div class="modal-content"> <div class="modal-header visually-hidden"> <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body p-2 p-lg-3 h-100"> <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> <div class="carousel-inner h-100 @theme"> @foreach (MediaViewModel asset in assetsList) { var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; foreach (string format in allSupportedFormats) { if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { string imagePath = assetValue; string activeSlide = modalAssetNumber == 0 ? "active" : ""; var parms = new Dictionary<string, object>(); parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); parms.Add("columns", Model.GridRowColumnCount); parms.Add("eagerLoadNewImages", Model.Item.GetBoolean("DisableLazyLoading")); parms.Add("doNotUseGetimage", Model.Item.GetBoolean("DisableGetImage")); <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> @foreach (string imageFormat in supportedImageFormats) { if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) } } @foreach (string videoFormat in supportedVideoFormats) { if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { Dictionary<string, object> videoParams = GetVideoParams(asset, "modal"); @RenderPartial("Components/VideoPlayer.cshtml", new FileViewModel { Path = asset.Value }, videoParams); } } </div> modalAssetNumber++; } } } <button class="carousel-control-prev carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="visually-hidden">@Translate("Previous")</span> </button> <button class="carousel-control-next carousel-control-area" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="visually-hidden">@Translate("Next")</span> </button> </div> </div> </div> </div> </div> </div> } else if (Pageview.IsVisualEditorMode) { RatioSettings ratioSettings = GetRatioSettings("desktop"); <div class="h-100 @theme"> <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;" alt="@Translate("Missing image")"> </div> </div> } }
Product Specification will be shown here if any
Error executing template "Designs/Swift/Paragraph/Just_Comments.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_f93fb5c9dd7b4d9c864135a781d6a059.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> @using Dynamicweb.Ecommerce.ProductCatalog @using Dynamicweb.Frontend @using System.Text.RegularExpressions @functions { public ProductViewModel product { get; set; } = new ProductViewModel(); public PageInfoViewModel page { get; set; } = new PageInfoViewModel(); public static string ConvertUrlsToLinks(string text) { var regex = new Regex(@"(?<!<[^>]*)\b(http|https)://([\w-]+.)+[\w-]+(/[\w- ./?%&=]*)?\b"); var matches = regex.Matches(text); foreach (Match match in matches) { text = text.Replace(match.Value, $"<a href='{match.Value}' target='_blank'>{match.Value}</a>"); } return text; } } @{ ProductViewModel product = null; page = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); Uri currentUrl = Dynamicweb.Context.Current.Request.Url; if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) { product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; } else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) { ProductListViewModel productList = page.Item.GetValue("DummyProduct") != null ? page.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); if (productList?.Products is object) { product = productList.Products[0]; } } Dynamicweb.Content.Commenting.CommentFilter filter = new Dynamicweb.Content.Commenting.CommentFilter(); filter.ItemType = "ecomProduct"; filter.ItemId = product.Id; filter.LanguageId = product.LanguageId; Dynamicweb.Content.Commenting.CommentCollection comments = new Dynamicweb.Content.Commenting.CommentService().GetComments(filter); var isLoggedIn = Dynamicweb.Security.UserManagement.UserContext.Current.IsLoggedOn; var currentUser = Dynamicweb.Security.UserManagement.UserContext.Current.User; string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", string.Empty).Trim().ToLower() : string.Empty; } <div class="container mb-3"> <div class="small mb-1"> @Translate("You can leave a comment here.") </div> <div class="row"> @if (isLoggedIn) { <form method="post" action="@currentUrl" id="commentform"> <input type="hidden" name="Comment.Command" id="Comment.Command" value="create" /> <!--Set to false if you want to approve comments before publishing them--> <input type="hidden" name="Comment.Active" value="true" /> <!-- Product comments --> <input type="hidden" name="Comment.ItemType" value="ecomProduct" /> <input type="hidden" name="Comment.ItemId" value="@product.Id" /> <input type="hidden" name="Comment.LangId" value="@product.LanguageId" /> <!--Comment.Continue takes an URL to redirect the user to after submitting their comment --> <input type="hidden" name="Comment.Continue" value="@currentUrl" /> <!--Notification email details--> <input type="hidden" name="Comment.Notify" value="true" /> <input type="hidden" name="Comment.NotifyTemplate" value="Comments/Notify.cshtml" /> <input type="hidden" name="Comment.NotifySubject" id="Comment.NotifySubject" value="New Comment on Mountainbikemuseum: " /> <input type="hidden" name="Comment.NotifySenderEmail" value="justin@justdynamics.nl" /> <input type="hidden" name="Comment.NotifySenderName" value="Justin Sjouw" /> <input type="hidden" name="Comment.NotifyEmail" value="monitoring@techteacher.dev" /> <!-- Comment Input Fields--> <div class="form-group"> <label for="Comment.Name">@Translate("Name")</label> <input type="text" class="form-control" name="Comment.Name" id="Comment.Name" value="@currentUser.Name" readonly /> </div> <div class="form-group"> <label for="Comment.Email">@Translate("E-mail (Will not be published on the website")</label> <input type="text" class="form-control" name="Comment.Email" id="Comment.Email" value="@currentUser.Email" readonly /> </div> <div class="form-group mt-3"> <label for="Comment.Text">@Translate("Your story about this bike") </label> <textarea name="Comment.Text" id="Comment.Text" style="display: none;"></textarea> <div id="editor" class="pell" style="height: auto;"></div> </div> <input class="btn btn-primary mt-2" type="submit" value="@Translate("Post my Story")" /> </form> } else { <div class="col-md-12 panel panel-default"> <div class="text-center"><i></i></div> <div class="text-center"><a class="btn btn-primary" href="/Default.aspx?ID=509&GoBackToPage=@currentUrl">@Translate("Sign in to comment")</a></div> </div> } </div> @foreach (Dynamicweb.Content.Commenting.Comment comment in comments) { <div class="card p-3 mt-2"> <div class="d-flex justify-content-between align-items-center"> <div class="user d-flex flex-row align-items-center"> <img src="https://api.dicebear.com/7.x/initials/svg?seed=@comment.Name" width="30" class="user-img rounded-circle mr-2" alt="avatar"> <span class="m-3"><small class="font-weight-bold text-primary">@comment.Name</small></span> </div> <div class="text-end"> <p class="text-capitalize">@comment.CreatedDate</p> </div> </div> <div> <span><small class="font-weight-bold">@ConvertUrlsToLinks(comment.Text)</small></span> </div> </div> } </div> <script src="https://unpkg.com/pell"></script> <script type="module"> const editor = pell.init({ element: document.getElementById('editor'), onChange: html => { document.getElementById('Comment.Text').textContent = html }, defaultParagraphSeparator: 'div', styleWithCSS: true, actions: [ 'bold', 'underline', { name: 'italic', result: () => pell.exec('italic') }, { name: 'backColorNone', icon: '<div style="color: #000000;">N</div>', title: 'Highlight Color', result: () => { pell.exec('removeFormat'); pell.exec('foreColor', '#000000'); } }, { name: 'backColorA', icon: '<div style="display: inline-block; width: 18px; height: 24px; line-height: 20px; vertical-align: middle; background-color:#147ed9; color: white;">A</div>', title: 'Highlight Color', result: () => { pell.exec('backColor', '#147ed9'); pell.exec('foreColor', '#ffffff'); } }, { name: 'backColorB', icon: '<div style="display: inline-block; width: 18px; height: 24px; line-height: 20px; vertical-align: middle; background-color:#381853; color: white;">B</div>', title: 'Highlight Color', result: () => { pell.exec('backColor', '#381853'); pell.exec('foreColor', '#ffffff'); } }, { name: 'backColorC', icon: '<div style="display: inline-block; width: 18px; height: 24px; line-height: 20px; vertical-align: middle; background-color:#f72680;">C</div>', title: 'Highlight Color', result: () => { pell.exec('backColor', '#f72680'); pell.exec('foreColor', '#ffffff'); } }, { name: 'backColorD', icon: '<div style="display: inline-block; width: 18px; height: 24px; line-height: 20px; vertical-align: middle; background-color:#fc7701;">D</div>', title: 'Highlight Color', result: () => { pell.exec('backColor', '#fc7701'); pell.exec('foreColor', '#000000'); } }, { name: 'backColorE', icon: '<div style="display: inline-block; width: 18px; height: 24px; line-height: 20px; vertical-align: middle; background-color:#f8b002;">E</div>', title: 'Highlight Color', result: () => { pell.exec('backColor', '#f8b002') pell.exec('foreColor', '#000000'); } }, { name: 'backColorF', icon: '<div style="display: inline-block; width: 18px; height: 24px; line-height: 20px; vertical-align: middle; background-color:#fffa22;">F</div>', title: 'Highlight Color', result: () => { pell.exec('backColor', '#fffa22'); pell.exec('foreColor', '#000000'); } }, { name: 'image', result: () => { const url = window.prompt('Enter the image URL including https://') try { new URL(url); // This will throw an error if the URL is invalid pell.exec('insertImage', url) } catch (_) { alert('Invalid Image URL'); } } }, { name: 'link', result: () => { const url = window.prompt('Enter the link URL including https://') try { new URL(url); // This will throw an error if the URL is invalid pell.exec('createLink', url) } catch (_) { alert('Invalid Link URL'); } } } ], }) </script> <script> window.onload = function() { var links = document.querySelectorAll(".card a"); links.forEach(function(link) { link.target = "_blank"; }); } </script>