An error occurred while attaching module (Dynamicweb.Frontend.Content)

System.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the login process. (provider: SSL Provider, error: 0 - An existing connection was forcibly closed by the remote host.) ---> System.ComponentModel.Win32Exception (0x80004005): An existing connection was forcibly closed by the remote host
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.Open()
   at Dynamicweb.Data.DatabaseConnectionProvider.CreateConnection(Boolean open)
   at Dynamicweb.Data.Database.CreateConnection()
   at Dynamicweb.Data.Database.CreateDataReader(CommandBuilder commandBuilder, IDbConnection connection, IDbTransaction transaction, Int32 commandTimeout)
   at Dynamicweb.Ecommerce.Products.ProductRepository.GetProductById(String productId, String productVariantId, String productLanguageId)
   at Dynamicweb.Ecommerce.Products.ProductService.FetchMissingProductsInternal(IProductRepository repo, IEnumerable`1 keys)
   at Dynamicweb.Caching.ServiceCache`2.GetCache(IEnumerable`1 keys)
   at Dynamicweb.Caching.ServiceCache`2.GetCache(TKey key)
   at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, User user, Boolean showUntranslated)
   at Dynamicweb.Ecommerce.Products.ProductService.GetProductById(String productId, String productVariantId, String productLanguageId, Boolean useAssortments)
   at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.IsProductInCorrectGroup(String groupId, String productId, String variantId, String languageId)
   at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.RenderProduct(String productId, String variantId, String groupId, ProductCatalogSettings settings)
   at Dynamicweb.Ecommerce.ProductCatalog.ProductCatalogFrontend.GetContent()
   at Dynamicweb.Frontend.Content.GetModuleOutput(Paragraph paragraph, PageView pageview)
ClientConnectionId:dca765d3-c106-4fda-92e6-a6d6cb01b715
Error Number:10054,State:0,Class:20

Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_2af8b6348c3243ad84491cdcb4f25a41.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsImage.cshtml:line 78
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string galleryLayout { get; set; } 9 public string[] supportedImageFormats { get; set; } 10 public string[] supportedVideoFormats { get; set; } 11 public string[] supportedDocumentFormats { get; set; } 12 public string[] allSupportedFormats { get; set; } 13 14 public class RatioSettings { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings(string size = "desktop") { 22 var ratioSettings = new RatioSettings(); 23 24 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 25 ratio = ratio != "0" ? ratio : ""; 26 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 27 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 28 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 29 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 30 31 ratioSettings.Ratio = ratio; 32 ratioSettings.CssClass = cssClass; 33 ratioSettings.CssVariable = cssVariable; 34 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 35 36 return ratioSettings; 37 } 38 39 public string GetArrowsColor() 40 { 41 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 42 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 43 return arrowsColor; 44 } 45 } 46 47 @{ 48 ProductViewModel product = null; 49 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 50 { 51 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 52 } 53 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 54 { 55 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 56 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 57 58 if (productList?.Products is object) 59 { 60 product = productList.Products[0]; 61 } 62 } 63 } 64 65 @if (product is object) { 66 @* Supported formats *@ 67 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 68 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 69 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 70 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 71 72 @* Collect the assets *@ 73 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 74 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 75 76 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 77 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 78 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 79 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 80 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[]{}; 81 assetsList = assetsList.Union(assetsImages); 82 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 83 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 84 85 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 86 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 87 88 int totalAssets = 0; 89 if (showOnlyPrimaryImage == false) { 90 foreach (MediaViewModel asset in assetsList) { 91 var assetValue = asset.Value; 92 foreach (string format in allSupportedFormats) { 93 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 94 totalAssets++; 95 } 96 } 97 } 98 } 99 100 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 101 { 102 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 103 totalAssets = 1; 104 } 105 106 107 @* Theme settings *@ 108 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 109 110 var badgeParms = new Dictionary<string, object>(); 111 badgeParms.Add("size", "h5"); 112 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 113 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 114 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 115 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 116 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 117 118 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 119 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 120 DateTime createdDate = product.Created.Value; 121 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 122 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 123 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 124 125 @* Get assets from selected categories or get all assets *@ 126 if (totalAssets != 0) { 127 int assetNumber = 0; 128 int thumbnailNumber = 0; 129 int modalAssetNumber = 0; 130 131 <div class="h-100@(theme) position-relative item_@Model.Item.SystemName.ToLower()"> 132 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 133 <div class="carousel-inner h-100"> 134 @foreach (MediaViewModel asset in assetsList) { 135 var assetValue = asset.Value; 136 foreach (string format in allSupportedFormats) { 137 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 138 string activeSlide = assetNumber == 0 ? "active" : ""; 139 140 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 141 @{@RenderAsset(asset, assetNumber, "mobile")} 142 </div> 143 assetNumber++; 144 } 145 } 146 } 147 </div> 148 </div> 149 150 @if (totalAssets > 1) { 151 <div id="SmallScreenImagesThumbnails_@Model.ID" class="grid grid-10 gap-2 overflow-x-auto my-3"> 152 @foreach (MediaViewModel asset in assetsList) { 153 var assetValue = asset.Value; 154 foreach (string format in allSupportedFormats) { 155 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 156 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 157 imagePath = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/mqdefault.jpg" : imagePath; 158 string imagePathThumb = imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath; 159 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 160 161 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 162 string vimeoJsClass = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 163 164 bool isDocument = false; 165 foreach (string documentFormat in supportedDocumentFormats) { 166 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 167 isDocument = true; 168 } 169 } 170 171 string assetName = asset.Name; 172 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 173 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 174 175 if (!isDocument) { 176 RatioSettings ratioSettings = GetRatioSettings("desktop"); 177 178 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> 179 <div class="d-flex align-items-center justify-content-center overflow-hidden position-absolute h-100"> 180 @foreach (string videoFormat in supportedVideoFormats) { //Videos 181 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 182 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 183 } 184 } 185 </div> 186 @if (imagePathThumb.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) { 187 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 @vimeoJsClass w-100 h-100" style="object-fit: contain" data-video-id="@videoId"> 188 } else { 189 string videoType = Path.GetExtension(asset.Value).ToLower(); 190 191 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 192 <source src="@imagePathThumb" type="video/@videoType.Replace(".", "")"> 193 </video> 194 } 195 </div> 196 } else { 197 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer" download title="@asset.Value"> 198 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 199 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 200 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 201 </div> 202 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 203 } else { 204 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 205 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 206 </div> 207 } 208 </a> 209 } 210 211 thumbnailNumber++; 212 } 213 } 214 } 215 </div> 216 } 217 218 @if (showBadges) { 219 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 220 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 221 </div> 222 } 223 </div> 224 225 @* Modal with slides *@ 226 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 227 <div class="modal-dialog modal-dialog-centered modal-xl"> 228 <div class="modal-content"> 229 <div class="modal-header visually-hidden"> 230 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 231 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 232 </div> 233 <div class="modal-body p-2 p-lg-3 h-100"> 234 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 235 <div class="carousel-inner h-100"> 236 @foreach (MediaViewModel asset in assetsList) { 237 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 238 foreach (string format in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) { 239 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 240 string imagePath = assetValue; 241 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 242 243 var parms = new Dictionary<string, object>(); 244 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 245 parms.Add("fullwidth", true); 246 parms.Add("columns", Model.GridRowColumnCount); 247 248 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 249 @foreach (string imageFormat in supportedImageFormats) { //Images 250 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 252 } 253 } 254 255 @foreach (string videoFormat in supportedVideoFormats) { //Videos 256 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 257 {@RenderVideoPlayer(asset, "modal")} 258 } 259 } 260 </div> 261 262 modalAssetNumber++; 263 } 264 } 265 } 266 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 267 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 268 <span class="visually-hidden">@Translate("Previous")</span> 269 </button> 270 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 271 <span class="carousel-control-next-icon" aria-hidden="true"></span> 272 <span class="visually-hidden">@Translate("Next")</span> 273 </button> 274 </div> 275 </div> 276 </div> 277 </div> 278 </div> 279 </div> 280 } else if (Pageview.IsVisualEditorMode) { 281 RatioSettings ratioSettings = GetRatioSettings("desktop"); 282 283 <div class="h-100 @theme"> 284 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 285 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 286 </div> 287 </div> 288 } 289 } else if (Pageview.IsVisualEditorMode) { 290 <div class="alert alert-dark m-0">@Translate("No products available")</div> 291 } 292 293 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop") { 294 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 295 string assetValue = asset.Value; 296 297 <div class="h-100 @(theme)"> 298 @foreach (string format in supportedImageFormats) { //Images 299 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 300 {@RenderImage(asset, assetNumber, size)} 301 } 302 } 303 @foreach (string format in supportedVideoFormats) { //Videos 304 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 305 if (Model.Item.GetString("OpenVideoInModal") == "true") { 306 {@RenderVideoScreendump(asset, assetNumber, size)} 307 } else { 308 {@RenderVideoPlayer(asset, size)} 309 } 310 } 311 } 312 @foreach (string format in supportedDocumentFormats) { //Documents 313 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 314 {@RenderDocument(asset, assetNumber, size)} 315 } 316 } 317 </div> 318 } 319 320 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop") { 321 if (product is object) 322 { 323 string productName = product.Name; 324 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 325 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 326 327 RatioSettings ratioSettings = GetRatioSettings(size); 328 329 var parms = new Dictionary<string, object>(); 330 parms.Add("alt", productName + asset.Keywords); 331 parms.Add("itemprop", "image"); 332 parms.Add("fullwidth", true); 333 parms.Add("columns", Model.GridRowColumnCount); 334 if (!string.IsNullOrEmpty(asset.DisplayName)) { 335 parms.Add("title", asset.DisplayName); 336 } 337 338 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 339 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 340 } else { 341 parms.Add("cssClass", "mw-100 mh-100"); 342 } 343 344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 346 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 347 </div> 348 </a> 349 } 350 } 351 352 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop") { 353 if (product is object) 354 { 355 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 356 357 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 358 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 359 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 360 361 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 362 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 363 364 string productName = product.Name; 365 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 366 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 367 368 RatioSettings ratioSettings = GetRatioSettings(size); 369 370 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 371 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 372 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 373 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 374 { 375 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 376 } 377 else 378 { 379 string videoType = Path.GetExtension(asset.Value).ToLower(); 380 381 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 382 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 383 </video> 384 } 385 </div> 386 </div> 387 388 <script> 389 function CheckIfVideoThumbnailExist(image) { 390 if (image.width == 120) { 391 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 392 image.src = lowQualityImage; 393 } 394 } 395 </script> 396 } 397 } 398 399 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop") { 400 if (product is object) 401 { 402 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 403 string assetValue = asset.Value; 404 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 405 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 406 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 407 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 408 409 string openInModal = Model.Item.GetString("OpenVideoInModal"); 410 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 411 412 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 413 <span class="visually-hidden" itemprop="name">@assetName</span> 414 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 415 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 416 417 @if (type != "selfhosted") 418 { 419 <div 420 id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size" 421 class="plyr__video-embed" 422 data-plyr-provider="@(type)" 423 data-plyr-embed-id="@videoId" 424 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 425 </div> 426 427 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 428 429 <script type="module"> 430 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size', { 431 type: 'video', 432 youtube: { 433 noCookie: true, 434 showinfo: 0 435 }, 436 fullscreen: { 437 enabled: true, 438 iosNative: true, 439 } 440 }); 441 442 @if (autoPlay && openInModal == "false") 443 { 444 <text> 445 player.config.autoplay = true; 446 player.config.muted = true; 447 player.config.volume = 0; 448 player.media.loop = true; 449 450 player.on('ready', function() { 451 if (player.config.autoplay === true) { 452 player.media.play(); 453 } 454 }); 455 </text> 456 } 457 458 @if (openInModal == "true") 459 { 460 <text> 461 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 462 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 463 player.media.pause(); 464 }) 465 </text> 466 } 467 </script> 468 } 469 else 470 { 471 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 472 string videoType = Path.GetExtension(assetValue).ToLower(); 473 474 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls> 475 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 476 </video> 477 } 478 </div> 479 } 480 } 481 482 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop") { 483 if (product is object) 484 { 485 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 486 487 string productName = product.Name; 488 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 489 string imageLinkPath = imagePath; 490 491 RatioSettings ratioSettings = GetRatioSettings(size); 492 493 var parms = new Dictionary<string, object>(); 494 parms.Add("alt", productName + asset.Keywords); 495 parms.Add("itemprop", "image"); 496 parms.Add("fullwidth", true); 497 parms.Add("columns", Model.GridRowColumnCount); 498 if (!string.IsNullOrEmpty(asset.DisplayName)) { 499 parms.Add("title", asset.DisplayName); 500 } 501 502 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 503 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 504 } else { 505 parms.Add("cssClass", "mw-100 mh-100"); 506 } 507 508 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 509 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 510 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 511 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 512 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 513 } 514 </div> 515 </a> 516 } 517 } 518

Error executing template "Designs/Swift/Paragraph/Swift_ProductSpecification.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_5c5123684cfb462d894bd9e10d697a19.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductSpecification.cshtml:line 28
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 4 @{ 5 ProductViewModel product = null; 6 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 7 { 8 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 9 } 10 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 11 { 12 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 13 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 14 15 if (productList?.Products is object) 16 { 17 product = productList.Products[0]; 18 } 19 } 20 } 21 22 @if (product is object) { 23 IEnumerable<string> selectedDisplayGroupIds = Model.Item.GetRawValueString("DisplayGroups").Split(',').ToList(); 24 List<CategoryFieldViewModel> displayGroups = new List<CategoryFieldViewModel>(); 25 26 foreach (var selection in selectedDisplayGroupIds) 27 { 28 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values) 29 { 30 if (selection == group.Id) 31 { 32 int fieldsWithNoValueOrZero = 0; 33 34 foreach (var field in group.Fields) 35 { 36 if (string.IsNullOrEmpty(field.Value.Value.ToString())) 37 { 38 fieldsWithNoValueOrZero++; 39 } 40 } 41 42 if (fieldsWithNoValueOrZero != group.Fields.Count) 43 { 44 displayGroups.Add(group); 45 } 46 } 47 } 48 } 49 50 bool showProductFields = Model.Item.GetBoolean("ProductFields"); 51 52 bool hideTitle = Model.Item.GetBoolean("HideTitle"); 53 54 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 55 56 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-4"); 57 58 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 59 contentPadding = contentPadding == "none" ? string.Empty : contentPadding; 60 contentPadding = contentPadding == "small" ? " p-2 p-md-3" : contentPadding; 61 contentPadding = contentPadding == "large" ? " p-4 p-md-5" : contentPadding; 62 63 string layout = Model.Item.GetRawValueString("Layout", "list"); 64 string size = Model.Item.GetRawValueString("Size", "full"); 65 string gaps = size == "full" ? " gap-4" : " gap-2"; 66 67 68 if (Pageview.IsVisualEditorMode && displayGroups.Count() == 0) 69 { 70 product.ProductFields.Clear(); 71 product.ProductFields.Add(Translate("Width"), new FieldValueViewModel { Name = Translate("Width"), Value = "99cm" }); 72 product.ProductFields.Add(Translate("Height"), new FieldValueViewModel { Name = Translate("Height"), Value = "195cm" }); 73 showProductFields = true; 74 } 75 76 if (layout == "commas") 77 { 78 gaps = size == "full" ? " gap-4" : " gap-2"; 79 80 } 81 82 <div class="h-100@(gaps)@(theme)@(contentPadding) item_@Model.Item.SystemName.ToLower()"> 83 <div class="grid"> 84 @if ((product.ProductFields != null && Model.Item.GetBoolean("ProductFields")) || (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) || (displayGroups.Count != 0)) { 85 if (!hideTitle) 86 { 87 <h2 class="g-col-12 @titleFontSize">@Model.Item.GetString("Title")</h2> 88 } 89 } 90 91 @if (displayGroups.Count != 0) 92 { 93 if (layout != "accordion") 94 { 95 foreach (var group in displayGroups) 96 { 97 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 98 99 if (!hideHeader) { 100 <h4 class="g-col-12 h4 mb-0">@group.Name</h4> 101 } 102 103 { @RenderFieldsFromList(group.Fields, layout) } 104 105 } 106 } 107 else 108 { 109 <div class="g-col-12"> 110 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 111 @foreach (var group in displayGroups) 112 { 113 <div class="accordion-item"> 114 <h2 class="accordion-header" id="SpecificationHeading_@group.Id"> 115 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Id"> 116 @group.Name 117 </button> 118 </h2> 119 <div id="SpecificationItem_@group.Id" class="accordion-collapse collapse" aria-labelledby="SpecificationHeading_@group.Id" data-bs-parent="#Specifications_@Model.ID"> 120 <div class="accordion-body"> 121 @{ @RenderFieldsFromList(group.Fields, "list") } 122 </div> 123 </div> 124 </div> 125 } 126 </div> 127 </div> 128 } 129 } 130 131 @if (product.ProductFields != null && showProductFields) 132 { 133 if (product.ProductFields.Count > 0) 134 { 135 if (layout != "accordion") 136 { 137 {@RenderFieldsFromList(product.ProductFields, layout) } 138 } 139 else 140 { 141 <div class="g-col-12"> 142 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 143 <div class="accordion-item"> 144 <h2 class="accordion-header" id="SpecificationHeading_@Model.ID"> 145 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@Model.ID" aria-expanded="false" aria-controls="SpecificationItem_@Model.ID"> 146 @Translate("Specifications") 147 </button> 148 </h2> 149 <div id="SpecificationItem_@Model.ID" class="accordion-collapse" aria-labelledby="SpecificationHeading_@Model.ID" data-bs-parent="#Specifications_@Model.ID"> 150 <div class="accordion-body"> 151 @{ @RenderFieldsFromList(product.ProductFields, "List") } 152 </div> 153 </div> 154 </div> 155 </div> 156 </div> 157 } 158 } 159 } 160 161 @if (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) 162 { 163 if (product.ProductCategories.Count > 0) 164 { 165 if (layout != "accordion") 166 { 167 foreach (var group in product.ProductCategories) 168 { 169 CategoryFieldViewModel category = group.Value; 170 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 171 172 if (!hideHeader) { 173 <h4 class="g-col-12 h4 mb-0">@group.Value.Name</h4> 174 } 175 176 { @RenderFieldsFromList(category.Fields, layout) } 177 } 178 } 179 else 180 { 181 <div class="g-col-12"> 182 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 183 @foreach (var group in product.ProductCategories) 184 { 185 CategoryFieldViewModel category = group.Value; 186 187 <div class="accordion-item"> 188 <h2 class="accordion-header" id="SpecificationHeading_@group.Value.Id"> 189 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Value.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Value.Id"> 190 @group.Value.Name 191 </button> 192 </h2> 193 <div id="SpecificationItem_@group.Value.Id" class="accordion-collapse" aria-labelledby="SpecificationHeading_@group.Value.Id" data-bs-parent="#Specifications_@Model.ID"> 194 <div class="accordion-body"> 195 @{ @RenderFieldsFromList(category.Fields, "list") } 196 </div> 197 </div> 198 </div> 199 } 200 </div> 201 </div> 202 } 203 } 204 } 205 </div> 206 </div> 207 } 208 else if (Pageview.IsVisualEditorMode) 209 { 210 <div class="alert alert-warning m-0">@Translate("No products available")</div> 211 } 212 213 @helper RenderFieldsFromList(Dictionary<string, FieldValueViewModel> fields, string layout) 214 { 215 string size = Model.Item.GetRawValueString("Size", "full"); 216 string gaps = size != "full" ? " gap-1" : string.Empty; 217 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 218 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 219 220 if (layout == "columns") { 221 <div class="g-col-12"> 222 <div class="grid@(gaps)"> 223 @foreach (var field in fields) 224 { 225 {@RenderField(field.Value, layout)} 226 } 227 </div> 228 </div> 229 } 230 if (layout == "list") { 231 <div class="g-col-12"> 232 <dl class="grid@(gaps)"> 233 @foreach (var field in fields) 234 { 235 {@RenderField(field.Value, layout)} 236 } 237 </dl> 238 </div> 239 } 240 if (layout == "table") 241 { 242 string tableSize = size == "full" ? "" : " table-sm"; 243 <div class="g-col-12"> 244 <table class="table table-striped@(tableSize)"> 245 @foreach (var field in fields) 246 { 247 {@RenderField(field.Value, layout)} 248 } 249 </table> 250 </div> 251 } 252 if (layout == "bullets") 253 { 254 string listSize = size == "full" ? "" : "m-0 p-0 lh-1 fs-7 opacity-75"; 255 string listStyle = size == "full" ? "" : "style=\"list-style-position: inside\""; 256 <div class="g-col-12"> 257 <ul class="@listSize" @listStyle> 258 @foreach (var field in fields) 259 { 260 {@RenderField(field.Value, layout)} 261 } 262 </ul> 263 </div> 264 } 265 if (layout == "commas") 266 { 267 List<string> featuresList = new List<string>(); 268 269 foreach (var field in fields) 270 { 271 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 272 273 if (field.Value?.Value != null) 274 { 275 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 276 { 277 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 278 279 //Hack to support field type providers with a single value 280 if (values.FirstOrDefault() != null) 281 { 282 firstListItemValue = values.FirstOrDefault().Value; 283 } 284 } 285 } 286 287 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.Value.ToString() != "0" && field.Value.Value.ToString() != "0.0")) 288 { 289 if (field.Value.Value is object && !string.IsNullOrEmpty(field.Value.Value.ToString())) 290 { 291 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 292 { 293 List<string> options = new List<string>(); 294 foreach (FieldOptionValueViewModel option in field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>) 295 { 296 if (!string.IsNullOrWhiteSpace(option.Value)) 297 { 298 if (option.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 299 { 300 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>"; 301 options.Add(colorSpan); 302 } 303 else if (!string.IsNullOrEmpty(option.Value)) 304 { 305 options.Add(option.Name); 306 } 307 } 308 } 309 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray())); 310 if ((Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 311 { 312 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray())); 313 } 314 315 if (!string.IsNullOrEmpty(optionsString)) 316 { 317 if (!hideFieldLabels) 318 { 319 featuresList.Add(field.Value.Name + ": " + optionsString); 320 } 321 else 322 { 323 featuresList.Add(optionsString); 324 } 325 } 326 } 327 else 328 { 329 if (!string.IsNullOrWhiteSpace(field.Value.Value.ToString())) 330 { 331 if (field.Value.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 332 { 333 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + field.Value.Value + "\"></span>"; 334 335 if (!hideFieldLabels) 336 { 337 featuresList.Add(field.Value.Name + ": " + colorSpan); 338 } 339 else 340 { 341 featuresList.Add(colorSpan); 342 } 343 } 344 else 345 { 346 if (!hideFieldLabels) 347 { 348 featuresList.Add(field.Value.Name + ": " + field.Value.Value.ToString()); 349 } 350 else 351 { 352 featuresList.Add(field.Value.Value.ToString()); 353 } 354 } 355 } 356 } 357 } 358 } 359 } 360 361 string featuresString = (string.Join(", ", featuresList.Select(x => x.ToString()).ToArray())); 362 363 <div class="g-col-12 opacity-75 fs-7">@featuresString</div> 364 } 365 } 366 367 @helper RenderField(FieldValueViewModel field, string layout) 368 { 369 string size = Model.Item.GetRawValueString("Size", "full"); 370 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 371 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 372 bool noValues = false; 373 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 374 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 375 376 if (!string.IsNullOrEmpty(fieldValue)) 377 { 378 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 379 { 380 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 381 noValues = values.Count > 0 ? false : true; 382 383 //Hack to support field type providers with a single value 384 if (values.FirstOrDefault() != null) 385 { 386 firstListItemValue = values.FirstOrDefault().Value; 387 } 388 } 389 } 390 391 if (!string.IsNullOrEmpty(fieldValue) && noValues == false) 392 { 393 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.ToString() != "0" && field.Value.ToString() != "0.0")) 394 { 395 if (layout == "columns") 396 { 397 398 <div class="grid g-col-6 g-col-lg-4 gap-1"> 399 @if (!hideFieldLabels) 400 { 401 <dt class="g-col-12 g-col-lg-4">@field.Name</dt> 402 } 403 <dd class="g-col-12 g-col-lg-8 mb-0 text-break"> 404 @{ @RenderFieldValue(field) } 405 </dd> 406 </div> 407 } 408 if (layout == "list") 409 { 410 if (!hideFieldLabels) 411 { 412 <dt class="g-col-4">@field.Name</dt> 413 } 414 <dd class="g-col-8 mb-0 text-break"> 415 @{ @RenderFieldValue(field) } 416 </dd> 417 } 418 if (layout == "table") 419 { 420 <tr> 421 @if (!hideFieldLabels) 422 { 423 <th class="w-25 w-lg-50" scope="row">@field.Name</th> 424 } 425 <td class="text-break"> 426 @{ @RenderFieldValue(field) } 427 </td> 428 </tr> 429 } 430 if (layout == "bullets") 431 { 432 <li> 433 @if (!hideFieldLabels) 434 { 435 <strong>@field.Name</strong> 436 } 437 <span> 438 @{ @RenderFieldValue(field) } 439 </span> 440 </li> 441 } 442 } 443 } 444 } 445 446 @helper RenderFieldValue(FieldValueViewModel field) 447 { 448 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 449 450 bool isLink = field?.Type == "Link"; 451 bool isColor = false; 452 bool isBrandName = field?.SystemName == "Brand_name"; 453 454 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue; 455 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue; 456 457 458 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>)) 459 { 460 int valueCount = 0; 461 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 462 int totalValues = values.Count; 463 464 foreach (FieldOptionValueViewModel option in values) 465 { 466 if (!string.IsNullOrEmpty(option.Value)) 467 { 468 if (option.Value.Substring(0, 1) == "#") 469 { 470 isColor = true; 471 } 472 } 473 474 if (!isColor) 475 { 476 @option.Name 477 } 478 else 479 { 480 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Name"></span> 481 } 482 483 if (valueCount != totalValues && valueCount < (totalValues - 1)) 484 { 485 if (isColor) 486 { 487 <text> </text> 488 } 489 else 490 { 491 <text>, </text> 492 } 493 } 494 valueCount++; 495 } 496 } 497 else 498 { 499 if (fieldValue.Substring(0, 1) == "#") 500 { 501 isColor = true; 502 } 503 504 if (!isColor) 505 { 506 if (isLink) 507 { 508 string linktTitle = !fieldValue.Contains("aspx") ? fieldValue : Translate("Go to link"); 509 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "target=\"_blank\"" : string.Empty; 510 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "rel=\"noopener\"" : string.Empty; 511 512 <a href="@field.Value" title="@field.Name" @target @rel>@linktTitle</a> 513 } 514 else if (isBrandName) 515 { 516 <span itemprop="brand" itemtype="https://schema.org/Brand" itemscope> 517 <span itemprop="name">@fieldValue</span> 518 </span> 519 } 520 else 521 { 522 @fieldValue 523 } 524 525 } 526 else 527 { 528 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span> 529 } 530 } 531 } 532

Opret dig som kunde under "Log ind" for at se dine priser,
eller send os en forespørgsel "her"

Error executing template "/Designs/Swift/Paragraph/Swift_ProductSpecification_Custom.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at CompiledRazorTemplates.Dynamic.RazorEngine_7b3884b4b5fc4cbabc323126ad5f421a.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductSpecification_Custom.cshtml:line 29
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 4 @{ 5 ProductViewModel product = null; 6 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 7 { 8 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 9 } 10 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 11 { 12 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 13 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 14 15 if (productList?.Products is object) 16 { 17 product = productList.Products[0]; 18 } 19 } 20 } 21 22 @if (product is object) 23 { 24 IEnumerable<string> selectedDisplayGroupIds = Model.Item.GetRawValueString("DisplayGroups").Split(',').ToList(); 25 List<CategoryFieldViewModel> displayGroups = new List<CategoryFieldViewModel>(); 26 27 foreach (var selection in selectedDisplayGroupIds) 28 { 29 foreach (CategoryFieldViewModel group in product.FieldDisplayGroups.Values) 30 { 31 if (selection == group.Id) 32 { 33 int fieldsWithNoValueOrZero = 0; 34 35 foreach (var field in group.Fields) 36 { 37 if (string.IsNullOrEmpty(field.Value.Value.ToString())) 38 { 39 fieldsWithNoValueOrZero++; 40 } 41 } 42 43 if (fieldsWithNoValueOrZero != group.Fields.Count) 44 { 45 displayGroups.Add(group); 46 } 47 } 48 } 49 } 50 51 bool showProductFields = Model.Item.GetBoolean("ProductFields"); 52 53 bool hideTitle = Model.Item.GetBoolean("HideTitle"); 54 55 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 56 57 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "display-4"); 58 59 string contentPadding = Model.Item.GetRawValueString("ContentPadding", ""); 60 contentPadding = contentPadding == "none" ? string.Empty : contentPadding; 61 contentPadding = contentPadding == "small" ? " p-2 p-md-3" : contentPadding; 62 contentPadding = contentPadding == "large" ? " p-4 p-md-5" : contentPadding; 63 64 string layout = Model.Item.GetRawValueString("Layout", "list"); 65 string size = Model.Item.GetRawValueString("Size", "full"); 66 string gaps = size == "full" ? " gap-4" : " gap-2"; 67 68 69 if (Pageview.IsVisualEditorMode && displayGroups.Count() == 0) 70 { 71 product.ProductFields.Clear(); 72 product.ProductFields.Add(Translate("Width"), new FieldValueViewModel { Name = Translate("Width"), Value = "99cm" }); 73 product.ProductFields.Add(Translate("Height"), new FieldValueViewModel { Name = Translate("Height"), Value = "195cm" }); 74 showProductFields = true; 75 } 76 77 if (layout == "commas") 78 { 79 gaps = size == "full" ? " gap-4" : " gap-2"; 80 81 } 82 83 <div class="h-100@(gaps)@(theme)@(contentPadding) item_@Model.Item.SystemName.ToLower()"> 84 <div class="grid"> 85 @if ((product.ProductFields != null && Model.Item.GetBoolean("ProductFields")) || (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) || (displayGroups.Count != 0)) { 86 if (!hideTitle) 87 { 88 <h2 class="g-col-12 @titleFontSize">@Model.Item.GetString("Title")</h2> 89 } 90 } 91 92 @if (displayGroups.Count != 0) 93 { 94 if (layout != "accordion") 95 { 96 foreach (var group in displayGroups) 97 { 98 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 99 100 if (!hideHeader) { 101 <h4 class="g-col-12 h4 mb-0">@group.Name</h4> 102 } 103 104 { @RenderFieldsFromList(group.Fields, layout) } 105 106 } 107 } 108 else 109 { 110 <div class="g-col-12"> 111 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 112 @foreach (var group in displayGroups) 113 { 114 <div class="accordion-item"> 115 <h2 class="accordion-header" id="SpecificationHeading_@group.Id"> 116 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Id"> 117 @group.Name 118 </button> 119 </h2> 120 <div id="SpecificationItem_@group.Id" class="accordion-collapse collapse" aria-labelledby="SpecificationHeading_@group.Id" data-bs-parent="#Specifications_@Model.ID"> 121 <div class="accordion-body"> 122 @{ @RenderFieldsFromList(group.Fields, "list") } 123 </div> 124 </div> 125 </div> 126 } 127 </div> 128 </div> 129 } 130 } 131 132 @if (product.ProductFields != null && showProductFields) 133 { 134 if (product.ProductFields.Count > 0) 135 { 136 if (layout != "accordion") 137 { 138 {@RenderFieldsFromList(product.ProductFields, layout) } 139 } 140 else 141 { 142 <div class="g-col-12"> 143 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 144 <div class="accordion-item"> 145 <h2 class="accordion-header" id="SpecificationHeading_@Model.ID"> 146 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@Model.ID" aria-expanded="false" aria-controls="SpecificationItem_@Model.ID"> 147 @Translate("Specifications") 148 </button> 149 </h2> 150 <div id="SpecificationItem_@Model.ID" class="accordion-collapse" aria-labelledby="SpecificationHeading_@Model.ID" data-bs-parent="#Specifications_@Model.ID"> 151 <div class="accordion-body"> 152 @{ @RenderFieldsFromList(product.ProductFields, "List") } 153 </div> 154 </div> 155 </div> 156 </div> 157 </div> 158 } 159 } 160 } 161 162 @if (product.ProductCategories != null && Model.Item.GetBoolean("CategoryFields")) 163 { 164 if (product.ProductCategories.Count > 0) 165 { 166 if (layout != "accordion") 167 { 168 foreach (var group in product.ProductCategories) 169 { 170 CategoryFieldViewModel category = group.Value; 171 bool hideHeader = Model.Item.GetBoolean("HideGroupHeaders"); 172 173 if (!hideHeader) { 174 <h4 class="g-col-12 h4 mb-0">@group.Value.Name</h4> 175 } 176 177 { @RenderFieldsFromList(category.Fields, layout) } 178 } 179 } 180 else 181 { 182 <div class="g-col-12"> 183 <div class="accordion accordion-flush w-100" id="Specifications_@Model.ID"> 184 @foreach (var group in product.ProductCategories) 185 { 186 CategoryFieldViewModel category = group.Value; 187 188 <div class="accordion-item"> 189 <h2 class="accordion-header" id="SpecificationHeading_@group.Value.Id"> 190 <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#SpecificationItem_@group.Value.Id" aria-expanded="false" aria-controls="SpecificationItem_@group.Value.Id"> 191 @group.Value.Name 192 </button> 193 </h2> 194 <div id="SpecificationItem_@group.Value.Id" class="accordion-collapse" aria-labelledby="SpecificationHeading_@group.Value.Id" data-bs-parent="#Specifications_@Model.ID"> 195 <div class="accordion-body"> 196 @{ @RenderFieldsFromList(category.Fields, "list") } 197 </div> 198 </div> 199 </div> 200 } 201 </div> 202 </div> 203 } 204 } 205 } 206 </div> 207 </div> 208 } 209 else if (Pageview.IsVisualEditorMode) 210 { 211 <div class="alert alert-warning m-0">@Translate("No products available")</div> 212 } 213 214 @helper RenderFieldsFromList(Dictionary<string, FieldValueViewModel> fields, string layout) 215 { 216 string size = Model.Item.GetRawValueString("Size", "full"); 217 string gaps = size != "full" ? " gap-1" : string.Empty; 218 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 219 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 220 221 if (layout == "columns"){ 222 <div class="g-col-12"> 223 <div class="grid@(gaps)"> 224 @foreach (var field in fields) 225 { 226 {@RenderField(field.Value, layout)} 227 } 228 </div> 229 </div> 230 } 231 if (layout == "list") { 232 <div class="g-col-12"> 233 <dl class="grid@(gaps)"> 234 @foreach (var field in fields) 235 { 236 {@RenderField(field.Value, layout)} 237 } 238 </dl> 239 </div> 240 } 241 if (layout == "table") 242 { 243 string tableSize = size == "full" ? "" : " table-sm"; 244 <div class="g-col-12"> 245 <table class="table table-striped@(tableSize)"> 246 @foreach (var field in fields) 247 { 248 {@RenderField(field.Value, layout)} 249 } 250 </table> 251 </div> 252 } 253 if (layout == "bullets") 254 { 255 string listSize = size == "full" ? "" : "m-0 p-0 lh-1 fs-7 opacity-75"; 256 string listStyle = size == "full" ? "" : "style=\"list-style-position: inside\""; 257 <div class="g-col-12"> 258 <ul class="@listSize" @listStyle> 259 @foreach (var field in fields) 260 { 261 {@RenderField(field.Value, layout)} 262 } 263 </ul> 264 </div> 265 } 266 if (layout == "commas") 267 { 268 List<string> featuresList = new List<string>(); 269 270 foreach (var field in fields) 271 { 272 if (field.Value.SystemName == "MaxCanBeBuilt") 273 { 274 if (field.Value.Value is int) 275 { 276 field.Value.Value = (int)field.Value.Value > 0 ? Translate("Yes") : Translate("No"); 277 } 278 } 279 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 280 281 if (field.Value?.Value != null) 282 { 283 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 284 { 285 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 286 287 //Hack to support field type providers with a single value 288 if (values.FirstOrDefault() != null) 289 { 290 firstListItemValue = values.FirstOrDefault().Value; 291 } 292 } 293 } 294 295 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.Value.ToString() != "0" && field.Value.Value.ToString() != "0.0")) 296 { 297 if (field.Value.Value is object && !string.IsNullOrEmpty(field.Value.Value.ToString())) 298 { 299 if (field.Value.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 300 { 301 List<string> options = new List<string>(); 302 foreach (FieldOptionValueViewModel option in field.Value.Value as System.Collections.Generic.List<FieldOptionValueViewModel>) 303 { 304 if (!string.IsNullOrWhiteSpace(option.Value)) 305 { 306 if (option.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 307 { 308 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + option.Value + "\"></span>"; 309 options.Add(colorSpan); 310 } 311 else if (!string.IsNullOrEmpty(option.Value)) 312 { 313 options.Add(option.Name); 314 } 315 } 316 } 317 string optionsString = (string.Join(", ", options.Select(x => x.ToString()).ToArray())); 318 if ((Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 319 { 320 optionsString = (string.Join(" ", options.Select(x => x.ToString()).ToArray())); 321 } 322 323 if (!string.IsNullOrEmpty(optionsString)) 324 { 325 if (!hideFieldLabels) 326 { 327 featuresList.Add(field.Value.Name + ": " + optionsString); 328 } 329 else 330 { 331 featuresList.Add(optionsString); 332 } 333 } 334 } 335 else 336 { 337 if (!string.IsNullOrWhiteSpace(field.Value.Value.ToString())) 338 { 339 if (field.Value.Value.ToString().Contains("#") && (Translate(field.Value.Name) == Translate("Color") || Translate(field.Value.Name) == Translate("Colour"))) 340 { 341 string colorSpan = "<span class=\"colorbox-sm\" style=\"background-color: " + field.Value.Value + "\"></span>"; 342 343 if (!hideFieldLabels) 344 { 345 featuresList.Add(field.Value.Name + ": " + colorSpan); 346 } 347 else 348 { 349 featuresList.Add(colorSpan); 350 } 351 } 352 else 353 { 354 if (!hideFieldLabels) 355 { 356 featuresList.Add(field.Value.Name + ": " + field.Value.Value.ToString()); 357 } 358 else 359 { 360 featuresList.Add(field.Value.Value.ToString()); 361 } 362 } 363 } 364 } 365 } 366 } 367 } 368 369 string featuresString = (string.Join(", ", featuresList.Select(x => x.ToString()).ToArray())); 370 371 <div class="g-col-12 opacity-75 fs-7">@featuresString</div> 372 } 373 } 374 375 @helper RenderField(FieldValueViewModel field, string layout) 376 { 377 378 string size = Model.Item.GetRawValueString("Size", "full"); 379 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 380 bool hideFieldLabels = Model.Item.GetBoolean("HideFieldLabels"); 381 bool noValues = false; 382 string firstListItemValue = string.Empty; //Hack to support field type providers with a single value 383 bool hideFieldsWithZeroValue = Model.Item.GetBoolean("HideFieldsWithZeroValue"); 384 385 if (!string.IsNullOrEmpty(fieldValue)) 386 { 387 if (field.Value.GetType() == typeof(System.Collections.Generic.List<FieldOptionValueViewModel>)) 388 { 389 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 390 noValues = values.Count > 0 ? false : true; 391 392 //Hack to support field type providers with a single value 393 if (values.FirstOrDefault() != null) 394 { 395 firstListItemValue = values.FirstOrDefault().Value; 396 } 397 } 398 } 399 400 if (!string.IsNullOrEmpty(fieldValue) && noValues == false) 401 { 402 if (!hideFieldsWithZeroValue || (firstListItemValue != "0" && firstListItemValue != "0.0" && field.Value.ToString() != "0" && field.Value.ToString() != "0.0")) 403 { 404 if (layout == "columns") 405 { 406 407 <div class="grid g-col-6 g-col-lg-4 gap-1"> 408 @if (!hideFieldLabels) 409 { 410 <dt class="g-col-12 g-col-lg-4">@field.Name</dt> 411 } 412 <dd class="g-col-12 g-col-lg-8 mb-0 text-break"> 413 @{ @RenderFieldValue(field) } 414 </dd> 415 </div> 416 } 417 if (layout == "list") 418 { 419 if (!hideFieldLabels) 420 { 421 <dt class="g-col-4">@field.Name</dt> 422 } 423 <dd class="g-col-8 mb-0 text-break"> 424 @{ @RenderFieldValue(field) } 425 </dd> 426 } 427 if (layout == "table") 428 { 429 <tr> 430 @if (!hideFieldLabels) 431 { 432 <th class="w-25 w-lg-50" scope="row">@field.Name</th> 433 } 434 <td class="text-break"> 435 @{ @RenderFieldValue(field) } 436 </td> 437 </tr> 438 } 439 if (layout == "bullets") 440 { 441 <li> 442 @if (!hideFieldLabels) 443 { 444 <strong>@field.Name</strong> 445 } 446 <span> 447 @{ @RenderFieldValue(field) } 448 </span> 449 </li> 450 } 451 } 452 } 453 } 454 455 @helper RenderFieldValue(FieldValueViewModel field) 456 { 457 string fieldValue = field?.Value != null ? field.Value.ToString() : ""; 458 459 bool isLink = field?.Type == "Link"; 460 bool isColor = false; 461 bool isBrandName = field?.SystemName == "Brand_name"; 462 463 fieldValue = fieldValue == "False" ? Translate("No") : fieldValue; 464 fieldValue = fieldValue == "True" ? Translate("Yes") : fieldValue; 465 466 467 if (field.SystemName == "MaxCanBeBuilt") 468 { 469 fieldValue = (int)field.Value > 0 ? Translate("Yes") : Translate("No"); 470 } 471 472 if (field.Value.GetType() == typeof(System.Collections.Generic.List<Dynamicweb.Ecommerce.ProductCatalog.FieldOptionValueViewModel>)) 473 { 474 int valueCount = 0; 475 System.Collections.Generic.List<FieldOptionValueViewModel> values = field.Value as System.Collections.Generic.List<FieldOptionValueViewModel>; 476 int totalValues = values.Count; 477 478 foreach (FieldOptionValueViewModel option in values) 479 { 480 if (!string.IsNullOrEmpty(option.Value)) 481 { 482 if (option.Value.Substring(0, 1) == "#") 483 { 484 isColor = true; 485 } 486 } 487 488 if (!isColor) 489 { 490 @option.Name 491 } 492 else 493 { 494 <span class="colorbox-sm" style="background-color: @option.Value" title="@option.Name"></span> 495 } 496 497 if (valueCount != totalValues && valueCount < (totalValues - 1)) 498 { 499 if (isColor) 500 { 501 <text> </text> 502 } 503 else 504 { 505 <text>, </text> 506 } 507 } 508 valueCount++; 509 } 510 } 511 else 512 { 513 if (fieldValue.Substring(0, 1) == "#") 514 { 515 isColor = true; 516 } 517 518 if (!isColor) 519 { 520 if (isLink) 521 { 522 string linktTitle = !fieldValue.Contains("aspx") ? fieldValue : Translate("Go to link"); 523 string target = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "target=\"_blank\"" : string.Empty; 524 string rel = Pageview.AreaSettings.GetBoolean("OpenLinksInNewTab") && fieldValue.Contains("http") ? "rel=\"noopener\"" : string.Empty; 525 526 <a href="@field.Value" title="@field.Name" @target @rel>@linktTitle</a> 527 } 528 else if (isBrandName) 529 { 530 <span itemprop="brand" itemtype="https://schema.org/Brand" itemscope> 531 <span itemprop="name">@fieldValue</span> 532 </span> 533 } 534 else 535 { 536 @fieldValue 537 } 538 539 } 540 else 541 { 542 <span class="colorbox-sm" style="background-color: @fieldValue" title="@fieldValue"></span> 543 } 544 } 545 } 546
Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.cshtml"
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_2bf238684d4241219833f7ea52bf2e68.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable_Custom.cshtml:line 72
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] supportedImageFormats { get; set; } 9 public string[] supportedVideoFormats { get; set; } 10 public string[] supportedDocumentFormats { get; set; } 11 public string[] allSupportedFormats { get; set; } 12 13 public class RatioSettings 14 { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings() 22 { 23 var ratioSettings = new RatioSettings(); 24 25 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 26 ratio = ratio != "0" ? ratio : ""; 27 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 28 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 29 30 ratioSettings.Ratio = ratio; 31 ratioSettings.CssClass = cssClass; 32 ratioSettings.CssVariable = cssVariable; 33 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 34 35 return ratioSettings; 36 } 37 } 38 39 @{ 40 @* Get the product data *@ 41 ProductViewModel product = null; 42 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 43 { 44 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 45 } 46 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 47 { 48 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 49 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 50 51 if (productList?.Products is object) 52 { 53 product = productList.Products[0]; 54 } 55 } 56 } 57 58 @if (product is object) 59 { 60 @* Supported formats *@ 61 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 62 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 63 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx", ".sat", ".stp", ".step", ".igs", ".iges", ".ipt", ".dwg", ".dxf", ".dwf" }; 64 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 65 66 @* Collect the assets *@ 67 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 68 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 69 70 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 71 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 72 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 73 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 74 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 75 76 assetsList = assetsList.Union(assetsImages); 77 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 78 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 79 80 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 81 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 82 83 int totalAssets = 0; 84 if (showOnlyPrimaryImage == false) 85 { 86 foreach (MediaViewModel asset in assetsList) 87 { 88 var assetValue = asset.Value; 89 foreach (string format in allSupportedFormats) 90 { 91 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 92 { 93 totalAssets++; 94 } 95 } 96 } 97 } 98 99 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 100 { 101 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 102 totalAssets = 1; 103 } 104 105 int videoNumber = 0; 106 107 @* Layout settings *@ 108 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 109 spacing = spacing == "none" ? "p-0" : spacing; 110 spacing = spacing == "small" ? "p-3" : spacing; 111 spacing = spacing == "large" ? "p-5" : spacing; 112 113 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 114 115 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 116 117 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 118 int modalVideoNumber = 0; 119 120 @* Get assets from selected categories or get all assets *@ 121 122 if (totalAssets != 0 && assetsList.Any()) 123 { 124 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 125 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 126 { 127 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 128 129 <h3 class="@titleFontSize mb-3"> 130 @Model.Item.GetString("Title") 131 </h3> 132 } 133 134 <div class="table-responsive"> 135 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 136 <thead> 137 <tr> 138 @if (!hideThumbnails) 139 { 140 <th style="width:60px">&nbsp;</th> 141 } 142 <th>@Translate("Name")</th> 143 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 144 <th class="text-end" style="width:100px">@Translate("File type")</th> 145 </tr> 146 </thead> 147 <tbody class="border-top-0"> 148 @foreach (MediaViewModel asset in assetsList) 149 { 150 var assetValue = asset.Value; 151 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 152 153 bool isVideo = false; 154 foreach (string format in supportedVideoFormats) 155 { //Videos 156 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 157 { 158 isVideo = true; 159 } 160 } 161 162 if (!isVideo) 163 { 164 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 165 long fileSize = 0; 166 167 if (File.Exists(filePath)) 168 { 169 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 170 171 foreach (string format in allSupportedFormats) 172 { 173 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 174 { 175 <tr> 176 @if (!hideThumbnails) 177 { 178 @RenderAsset(asset) 179 } 180 <td> 181 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 182 @assetName 183 </a> 184 </td> 185 <td class="text-end d-none d-lg-table-cell"> 186 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 187 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 188 </a> 189 </td> 190 <td class="text-end">@format</td> 191 </tr> 192 } 193 } 194 } 195 } 196 else 197 { 198 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 199 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 200 201 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 202 @if (!hideThumbnails) 203 { 204 @RenderAsset(asset) 205 } 206 <td> 207 @assetName 208 </td> 209 <td class="d-none d-lg-table-cell">&nbsp;</td> 210 <td align="right">@videoType</td> 211 </tr> 212 213 videoNumber++; 214 } 215 } 216 </tbody> 217 </table> 218 </div> 219 220 @foreach (MediaViewModel asset in assetsList) 221 { 222 var assetName = asset.Value.ToLower(); 223 224 foreach (string format in supportedVideoFormats) 225 { //Videos 226 if (assetName.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 227 { 228 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 229 <div class="modal-dialog modal-dialog-centered modal-xl"> 230 <div class="modal-content"> 231 <div class="modal-header visually-hidden"> 232 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 233 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 234 </div> 235 <div class="modal-body p-2 p-lg-3 h-100"> 236 @{ @RenderVideoPlayer(asset) } 237 </div> 238 </div> 239 </div> 240 </div> 241 242 modalVideoNumber++; 243 } 244 } 245 } 246 </div> 247 } 248 else if (Pageview.IsVisualEditorMode) 249 { 250 <div class="h-100 @theme"> 251 <div class="alert alert-dark m-0"> 252 @Translate("No assets are available") 253 </div> 254 </div> 255 } 256 } 257 258 @helper RenderAsset(MediaViewModel asset) 259 { 260 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 261 string assetValue = asset.Value; 262 263 <td class="@(theme) px-0"> 264 @foreach (string format in supportedImageFormats) 265 { //Images 266 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 267 { 268 @RenderImage(asset) 269 } 270 } 271 @foreach (string format in supportedVideoFormats) 272 { //Videos 273 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 274 { 275 @RenderVideoScreendump(asset) 276 } 277 } 278 @foreach (string format in supportedDocumentFormats) 279 { //Documents 280 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 281 { 282 @RenderDocument(asset) 283 } 284 } 285 </td> 286 } 287 288 @helper RenderImage(MediaViewModel asset) 289 { 290 string productName = product.Name; 291 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 292 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 293 string imageLinkPath = imagePath; 294 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 295 296 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 297 298 RatioSettings ratioSettings = GetRatioSettings(); 299 300 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 301 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 302 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 303 </div> 304 </a> 305 } 306 307 @helper RenderVideoScreendump(MediaViewModel asset) 308 { 309 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 310 311 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 312 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 313 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 314 315 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 316 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 317 318 string productName = product.Name; 319 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 320 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 321 322 RatioSettings ratioSettings = GetRatioSettings(); 323 324 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 325 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 326 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 327 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 328 { 329 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 330 } 331 else 332 { 333 string videoType = Path.GetExtension(asset.Value).ToLower(); 334 335 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 336 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 337 </video> 338 } 339 </div> 340 </div> 341 } 342 343 @helper RenderDocument(MediaViewModel asset) 344 { 345 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 346 string productName = product.Name; 347 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 348 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 349 string imageLinkPath = imagePath; 350 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 351 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 352 353 RatioSettings ratioSettings = GetRatioSettings(); 354 355 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 356 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 357 { 358 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 359 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 360 </div> 361 } 362 else 363 { 364 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 365 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 366 </div> 367 } 368 </a> 369 } 370 371 @helper RenderVideoPlayer(MediaViewModel asset) 372 { 373 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 374 string assetValue = asset.Value; 375 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 376 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 377 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 378 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 379 380 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 381 <span class="visually-hidden" itemprop="name">@assetName</span> 382 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 383 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 384 @if (type != "selfhosted") 385 { 386 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 387 class="plyr__video-embed" 388 data-plyr-provider="@(type)" 389 data-plyr-embed-id="@videoId" 390 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 391 </div> 392 393 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 394 <script type="module"> 395 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 396 type: 'video', 397 youtube: { 398 noCookie: true, 399 showinfo: 0 400 }, 401 fullscreen: { 402 enabled: true, 403 iosNative: true, 404 } 405 }); 406 407 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 408 modal.addEventListener('hidden.bs.modal', function (event) { 409 player.media.pause(); 410 }) 411 }); 412 </script> 413 } 414 else 415 { 416 string videoType = Path.GetExtension(assetValue).ToLower(); 417 418 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 419 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 420 </video> 421 } 422 </div> 423 } 424
Error executing template "/Designs/Swift/Paragraph/Swift_ProductDetailsMediaTable_Custom.cshtml"
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_2bf238684d4241219833f7ea52bf2e68.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsMediaTable_Custom.cshtml:line 72
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string[] supportedImageFormats { get; set; } 9 public string[] supportedVideoFormats { get; set; } 10 public string[] supportedDocumentFormats { get; set; } 11 public string[] allSupportedFormats { get; set; } 12 13 public class RatioSettings 14 { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings() 22 { 23 var ratioSettings = new RatioSettings(); 24 25 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 26 ratio = ratio != "0" ? ratio : ""; 27 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 28 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 29 30 ratioSettings.Ratio = ratio; 31 ratioSettings.CssClass = cssClass; 32 ratioSettings.CssVariable = cssVariable; 33 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 34 35 return ratioSettings; 36 } 37 } 38 39 @{ 40 @* Get the product data *@ 41 ProductViewModel product = null; 42 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 43 { 44 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 45 } 46 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 47 { 48 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 49 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 50 51 if (productList?.Products is object) 52 { 53 product = productList.Products[0]; 54 } 55 } 56 } 57 58 @if (product is object) 59 { 60 @* Supported formats *@ 61 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 62 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 63 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx", ".sat", ".stp", ".step", ".igs", ".iges", ".ipt", ".dwg", ".dxf", ".dwf" }; 64 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 65 66 @* Collect the assets *@ 67 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 68 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 69 70 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 71 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 72 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 73 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 74 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[] { }; 75 76 assetsList = assetsList.Union(assetsImages); 77 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 78 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 79 80 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 81 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 82 83 int totalAssets = 0; 84 if (showOnlyPrimaryImage == false) 85 { 86 foreach (MediaViewModel asset in assetsList) 87 { 88 var assetValue = asset.Value; 89 foreach (string format in allSupportedFormats) 90 { 91 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 92 { 93 totalAssets++; 94 } 95 } 96 } 97 } 98 99 if ((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null)) 100 { 101 assetsList = new List<MediaViewModel>() { product.DefaultImage }; 102 totalAssets = 1; 103 } 104 105 int videoNumber = 0; 106 107 @* Layout settings *@ 108 string spacing = Model.Item.GetRawValueString("Spacing", "p-0"); 109 spacing = spacing == "none" ? "p-0" : spacing; 110 spacing = spacing == "small" ? "p-3" : spacing; 111 spacing = spacing == "large" ? "p-5" : spacing; 112 113 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 114 115 bool hideThumbnails = Model.Item.GetBoolean("HideThumbnails"); 116 117 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 118 int modalVideoNumber = 0; 119 120 @* Get assets from selected categories or get all assets *@ 121 122 if (totalAssets != 0 && assetsList.Any()) 123 { 124 <div class="@spacing@(theme) item_@Model.Item.SystemName.ToLower()"> 125 @if (!string.IsNullOrEmpty(Model.Item.GetString("Title")) && !Model.Item.GetBoolean("HideTitle")) 126 { 127 string titleFontSize = Model.Item.GetRawValueString("TitleFontSize", "h3"); 128 129 <h3 class="@titleFontSize mb-3"> 130 @Model.Item.GetString("Title") 131 </h3> 132 } 133 134 <div class="table-responsive"> 135 <table class="table table-hover align-middle mb-0" style="table-layout: fixed;"> 136 <thead> 137 <tr> 138 @if (!hideThumbnails) 139 { 140 <th style="width:60px">&nbsp;</th> 141 } 142 <th>@Translate("Name")</th> 143 <th class="text-end d-none d-lg-table-cell">@Translate("Download")</th> 144 <th class="text-end" style="width:100px">@Translate("File type")</th> 145 </tr> 146 </thead> 147 <tbody class="border-top-0"> 148 @foreach (MediaViewModel asset in assetsList) 149 { 150 var assetValue = asset.Value; 151 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 152 153 bool isVideo = false; 154 foreach (string format in supportedVideoFormats) 155 { //Videos 156 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 157 { 158 isVideo = true; 159 } 160 } 161 162 if (!isVideo) 163 { 164 string filePath = Dynamicweb.Context.Current.Server.MapPath(assetValue); 165 long fileSize = 0; 166 167 if (File.Exists(filePath)) 168 { 169 fileSize = new System.IO.FileInfo(filePath) != null ? new System.IO.FileInfo(filePath).Length / 1024 : 0; 170 171 foreach (string format in allSupportedFormats) 172 { 173 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 174 { 175 <tr> 176 @if (!hideThumbnails) 177 { 178 @RenderAsset(asset) 179 } 180 <td> 181 <a href="@assetValue" class="text-decoration-none text-break" download="@assetName" title="@assetName"> 182 @assetName 183 </a> 184 </td> 185 <td class="text-end d-none d-lg-table-cell"> 186 <a href="@assetValue" class="text-decoration-none" download="@assetName" title="@assetName"> 187 @fileSize KB <div class="icon-2" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 188 </a> 189 </td> 190 <td class="text-end">@format</td> 191 </tr> 192 } 193 } 194 } 195 } 196 else 197 { 198 string videoType = asset.Value.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || asset.Value.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "Youtube" : ""; 199 videoType = asset.Value.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "Vimeo" : videoType; 200 201 <tr data-bs-toggle="modal" data-bs-target="#modal_@(Model.ID)_@videoNumber" style="cursor: pointer"> 202 @if (!hideThumbnails) 203 { 204 @RenderAsset(asset) 205 } 206 <td> 207 @assetName 208 </td> 209 <td class="d-none d-lg-table-cell">&nbsp;</td> 210 <td align="right">@videoType</td> 211 </tr> 212 213 videoNumber++; 214 } 215 } 216 </tbody> 217 </table> 218 </div> 219 220 @foreach (MediaViewModel asset in assetsList) 221 { 222 var assetName = asset.Value.ToLower(); 223 224 foreach (string format in supportedVideoFormats) 225 { //Videos 226 if (assetName.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 227 { 228 <div class="modal fade js-video-modal" id="modal_@(Model.ID)_@modalVideoNumber" tabindex="-1" aria-labelledby="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber" aria-hidden="true"> 229 <div class="modal-dialog modal-dialog-centered modal-xl"> 230 <div class="modal-content"> 231 <div class="modal-header visually-hidden"> 232 <h5 class="modal-title" id="productDetailsTableModalTitle_@(Model.ID)_@modalVideoNumber">@product.Title</h5> 233 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 234 </div> 235 <div class="modal-body p-2 p-lg-3 h-100"> 236 @{ @RenderVideoPlayer(asset) } 237 </div> 238 </div> 239 </div> 240 </div> 241 242 modalVideoNumber++; 243 } 244 } 245 } 246 </div> 247 } 248 else if (Pageview.IsVisualEditorMode) 249 { 250 <div class="h-100 @theme"> 251 <div class="alert alert-dark m-0"> 252 @Translate("No assets are available") 253 </div> 254 </div> 255 } 256 } 257 258 @helper RenderAsset(MediaViewModel asset) 259 { 260 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 261 string assetValue = asset.Value; 262 263 <td class="@(theme) px-0"> 264 @foreach (string format in supportedImageFormats) 265 { //Images 266 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 267 { 268 @RenderImage(asset) 269 } 270 } 271 @foreach (string format in supportedVideoFormats) 272 { //Videos 273 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 274 { 275 @RenderVideoScreendump(asset) 276 } 277 } 278 @foreach (string format in supportedDocumentFormats) 279 { //Documents 280 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) 281 { 282 @RenderDocument(asset) 283 } 284 } 285 </td> 286 } 287 288 @helper RenderImage(MediaViewModel asset) 289 { 290 string productName = product.Name; 291 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 292 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 293 string imageLinkPath = imagePath; 294 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 295 296 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 297 298 RatioSettings ratioSettings = GetRatioSettings(); 299 300 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download> 301 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 302 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle itemprop="image"> 303 </div> 304 </a> 305 } 306 307 @helper RenderVideoScreendump(MediaViewModel asset) 308 { 309 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 310 311 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 312 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 313 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg" : videoScreendumpPath; 314 315 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 316 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 317 318 string productName = product.Name; 319 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 320 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 321 322 RatioSettings ratioSettings = GetRatioSettings(); 323 324 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 325 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 326 <div class="icon-2 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 327 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 328 { 329 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;"> 330 } 331 else 332 { 333 string videoType = Path.GetExtension(asset.Value).ToLower(); 334 335 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 336 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 337 </video> 338 } 339 </div> 340 </div> 341 } 342 343 @helper RenderDocument(MediaViewModel asset) 344 { 345 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 346 string productName = product.Name; 347 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 348 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 349 string imageLinkPath = imagePath; 350 imagePath = $"/Admin/Public/GetImage.ashx?image={imagePath}&width=60&format=webp"; 351 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 352 353 RatioSettings ratioSettings = GetRatioSettings(); 354 355 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@productName"> 356 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) 357 { 358 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 359 <img loading="lazy" src="@imagePath" class="mw-100 mh-100" alt="@productName" @assetTitle /> 360 </div> 361 } 362 else 363 { 364 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 365 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 366 </div> 367 } 368 </a> 369 } 370 371 @helper RenderVideoPlayer(MediaViewModel asset) 372 { 373 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 374 string assetValue = asset.Value; 375 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 376 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 377 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 378 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 379 380 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 381 <span class="visually-hidden" itemprop="name">@assetName</span> 382 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 383 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 384 @if (type != "selfhosted") 385 { 386 <div id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)" 387 class="plyr__video-embed" 388 data-plyr-provider="@(type)" 389 data-plyr-embed-id="@videoId" 390 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 391 </div> 392 393 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 394 <script type="module"> 395 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)', { 396 type: 'video', 397 youtube: { 398 noCookie: true, 399 showinfo: 0 400 }, 401 fullscreen: { 402 enabled: true, 403 iosNative: true, 404 } 405 }); 406 407 document.querySelectorAll('.js-video-modal').forEach(function (modal) { 408 modal.addEventListener('hidden.bs.modal', function (event) { 409 player.media.pause(); 410 }) 411 }); 412 </script> 413 } 414 else 415 { 416 string videoType = Path.GetExtension(assetValue).ToLower(); 417 418 <video preload="auto" class="h-100 w-100" style="object-fit: cover;" controls> 419 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 420 </video> 421 } 422 </div> 423 } 424
Error executing template "Designs/Swift/Paragraph/Swift_ProductDetailsImage.cshtml"
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Enumerable.Where[TSource](IEnumerable`1 source, Func`2 predicate)
   at CompiledRazorTemplates.Dynamic.RazorEngine_2af8b6348c3243ad84491cdcb4f25a41.Execute() in D:\dynamicweb.net\Solutions\brdklee.cloud.dynamicweb-cms.com\files\Templates\Designs\Swift\Paragraph\Swift_ProductDetailsImage.cshtml:line 78
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.<RunCompile>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   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> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using Dynamicweb.Frontend 4 @using System.IO 5 6 @functions { 7 public ProductViewModel product { get; set; } = new ProductViewModel(); 8 public string galleryLayout { get; set; } 9 public string[] supportedImageFormats { get; set; } 10 public string[] supportedVideoFormats { get; set; } 11 public string[] supportedDocumentFormats { get; set; } 12 public string[] allSupportedFormats { get; set; } 13 14 public class RatioSettings { 15 public string Ratio { get; set; } 16 public string CssClass { get; set; } 17 public string CssVariable { get; set; } 18 public string Fill { get; set; } 19 } 20 21 public RatioSettings GetRatioSettings(string size = "desktop") { 22 var ratioSettings = new RatioSettings(); 23 24 string ratio = Model.Item.GetRawValueString("ImageAspectRatio", ""); 25 ratio = ratio != "0" ? ratio : ""; 26 string cssClass = ratio != "" && ratio != "fill" ? " ratio" : ""; 27 string cssVariable = ratio != "" && ratio != "fill" ? "--bs-aspect-ratio: " + ratio : ""; 28 cssClass = ratio == "fill" && size == "mobile" ? " ratio" : cssClass; 29 cssVariable = ratio == "fill" && size == "mobile" ? "--bs-aspect-ratio: 66%" : cssVariable; 30 31 ratioSettings.Ratio = ratio; 32 ratioSettings.CssClass = cssClass; 33 ratioSettings.CssVariable = cssVariable; 34 ratioSettings.Fill = ratio == "fill" ? " h-100" : ""; 35 36 return ratioSettings; 37 } 38 39 public string GetArrowsColor() 40 { 41 var invertColor = Model.Item.GetBoolean("InvertModalArrowsColor"); 42 var arrowsColor = invertColor ? " carousel-dark" : string.Empty; 43 return arrowsColor; 44 } 45 } 46 47 @{ 48 ProductViewModel product = null; 49 if (Dynamicweb.Context.Current.Items.Contains("ProductDetails")) 50 { 51 product = (ProductViewModel)Dynamicweb.Context.Current.Items["ProductDetails"]; 52 } 53 else if (Pageview.Page.Item["DummyProduct"] != null && Pageview.IsVisualEditorMode) 54 { 55 var pageViewModel = Dynamicweb.Frontend.ContentViewModelFactory.CreatePageInfoViewModel(Pageview.Page); 56 ProductListViewModel productList = pageViewModel.Item.GetValue("DummyProduct") != null ? pageViewModel.Item.GetValue("DummyProduct") as ProductListViewModel : new ProductListViewModel(); 57 58 if (productList?.Products is object) 59 { 60 product = productList.Products[0]; 61 } 62 } 63 } 64 65 @if (product is object) { 66 @* Supported formats *@ 67 supportedImageFormats = new string[] { ".jpg", ".jpeg", ".webp", ".png", ".gif", ".bmp", ".tiff" }; 68 supportedVideoFormats = new string[] { "youtu.be", "youtube", "vimeo", ".mp4", ".webm" }; 69 supportedDocumentFormats = new string[] { ".pdf", ".docx", ".xlsx", ".ppt", "pptx" }; 70 allSupportedFormats = supportedImageFormats.Concat(supportedVideoFormats).Concat(supportedDocumentFormats).ToArray(); 71 72 @* Collect the assets *@ 73 var selectedAssetCategories = Model.Item.GetRawValueString("ImageAssets").Split(',').ToList(); 74 bool includeImagePatternImages = Model.Item.GetBoolean("ImagePatternImages"); 75 76 @* Needed image data collection to support both DefaultImage, ImagePatterns and Image Assets *@ 77 string defaultImage = product.DefaultImage != null ? product.DefaultImage.Value : ""; 78 IEnumerable<MediaViewModel> assetsImages = product.AssetCategories.Where(x => selectedAssetCategories.Contains(x.SystemName)).SelectMany(x => x.Assets); 79 assetsImages = assetsImages.OrderByDescending(x => x.Value.Equals(defaultImage)); 80 IEnumerable<MediaViewModel> assetsList = new MediaViewModel[]{}; 81 assetsList = assetsList.Union(assetsImages); 82 assetsList = includeImagePatternImages ? assetsList.Union(product.ImagePatternImages) : assetsList; 83 assetsList = includeImagePatternImages && assetsList.Count() == 0 ? assetsList.Append(product.DefaultImage) : assetsList; 84 85 bool defaultImageFallback = Model.Item.GetBoolean("DefaultImageFallback"); 86 bool showOnlyPrimaryImage = Model.Item.GetBoolean("ShowOnlyPrimaryImage"); 87 88 int totalAssets = 0; 89 if (showOnlyPrimaryImage == false) { 90 foreach (MediaViewModel asset in assetsList) { 91 var assetValue = asset.Value; 92 foreach (string format in allSupportedFormats) { 93 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 94 totalAssets++; 95 } 96 } 97 } 98 } 99 100 if((totalAssets == 0 && product.DefaultImage != null && selectedAssetCategories.Count() == 0) || (showOnlyPrimaryImage == true && product.DefaultImage != null) || totalAssets == 0 && defaultImageFallback) 101 { 102 assetsList = new List<MediaViewModel>(){ product.DefaultImage }; 103 totalAssets = 1; 104 } 105 106 107 @* Theme settings *@ 108 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? " theme " + Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower() : ""; 109 110 var badgeParms = new Dictionary<string, object>(); 111 badgeParms.Add("size", "h5"); 112 badgeParms.Add("saleBadgeType", Model.Item.GetRawValue("SaleBadgeType")); 113 badgeParms.Add("saleBadgeCssClassName", Model.Item.GetRawValue("SaleBadgeDesign")); 114 badgeParms.Add("newBadgeCssClassName", Model.Item.GetRawValue("NewBadgeDesign")); 115 badgeParms.Add("newPublicationDays", Model.Item.GetInt32("NewPublicationDays")); 116 badgeParms.Add("campaignBadgesValues", Model.Item.GetRawValueString("CampaignBadges")); 117 118 bool saleBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("SaleBadgeDesign")) && Model.Item.GetRawValueString("SaleBadgeDesign") != "none" ? true : false; 119 bool newBadgeEnabled = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("NewBadgeDesign")) && Model.Item.GetRawValueString("NewBadgeDesign") != "none" ? true : false; 120 DateTime createdDate = product.Created.Value; 121 bool showBadges = saleBadgeEnabled && product.Discount.Price != 0 ? true : false; 122 showBadges = (newBadgeEnabled && Model.Item.GetInt32("NewPublicationDays") == 0) || (newBadgeEnabled && (createdDate.AddDays(Model.Item.GetInt32("NewPublicationDays")) > DateTime.Now)) ? true : showBadges; 123 showBadges = !string.IsNullOrEmpty(Model.Item.GetRawValueString("CampaignBadges")) ? true : showBadges; 124 125 @* Get assets from selected categories or get all assets *@ 126 if (totalAssets != 0) { 127 int assetNumber = 0; 128 int thumbnailNumber = 0; 129 int modalAssetNumber = 0; 130 131 <div class="h-100@(theme) position-relative item_@Model.Item.SystemName.ToLower()"> 132 <div id="SmallScreenImages_@Model.ID" class="carousel@(GetArrowsColor())" data-bs-ride="carousel"> 133 <div class="carousel-inner h-100"> 134 @foreach (MediaViewModel asset in assetsList) { 135 var assetValue = asset.Value; 136 foreach (string format in allSupportedFormats) { 137 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 138 string activeSlide = assetNumber == 0 ? "active" : ""; 139 140 <div class="carousel-item @activeSlide" data-bs-interval="99999"> 141 @{@RenderAsset(asset, assetNumber, "mobile")} 142 </div> 143 assetNumber++; 144 } 145 } 146 } 147 </div> 148 </div> 149 150 @if (totalAssets > 1) { 151 <div id="SmallScreenImagesThumbnails_@Model.ID" class="grid grid-10 gap-2 overflow-x-auto my-3"> 152 @foreach (MediaViewModel asset in assetsList) { 153 var assetValue = asset.Value; 154 foreach (string format in allSupportedFormats) { 155 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 156 string imagePath = Dynamicweb.Context.Current.Server.UrlEncode(assetValue); 157 imagePath = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + assetValue.Substring(assetValue.LastIndexOf('/') + 1) + "/mqdefault.jpg" : imagePath; 158 string imagePathThumb = imagePath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) < 0 && imagePath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0 ? $"/Admin/Public/GetImage.ashx?image={imagePath}&width=180&format=webp" : imagePath; 159 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 160 161 string videoId = assetValue.Substring(assetValue.LastIndexOf('/') + 1); 162 string vimeoJsClass = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 163 164 bool isDocument = false; 165 foreach (string documentFormat in supportedDocumentFormats) { 166 if (assetValue.IndexOf(documentFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 167 isDocument = true; 168 } 169 } 170 171 string assetName = asset.Name; 172 assetName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 173 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 174 175 if (!isDocument) { 176 RatioSettings ratioSettings = GetRatioSettings("desktop"); 177 178 <div class="border outline-none @(ratioSettings.CssClass)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-target="#SmallScreenImages_@Model.ID" data-bs-slide-to="@thumbnailNumber"> 179 <div class="d-flex align-items-center justify-content-center overflow-hidden position-absolute h-100"> 180 @foreach (string videoFormat in supportedVideoFormats) { //Videos 181 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 182 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 183 } 184 } 185 </div> 186 @if (imagePathThumb.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) { 187 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 @vimeoJsClass w-100 h-100" style="object-fit: contain" data-video-id="@videoId"> 188 } else { 189 string videoType = Path.GetExtension(asset.Value).ToLower(); 190 191 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 192 <source src="@imagePathThumb" type="video/@videoType.Replace(".", "")"> 193 </video> 194 } 195 </div> 196 } else { 197 <a href="@assetValue" class="ratio ratio-4x3 border outline-none" style="cursor: pointer" download title="@asset.Value"> 198 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 199 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 200 <div class="icon-3 position-absolute text-light" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 201 </div> 202 <img src="@imagePathThumb" alt="@assetName" @assetTitle class="p-0 p-lg-1 mw-100 mh-100" style="object-fit: cover;"> 203 } else { 204 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 205 <div class="icon-3 position-absolute" style="z-index: 1">@ReadFile(iconPath + "file-text.svg")</div> 206 </div> 207 } 208 </a> 209 } 210 211 thumbnailNumber++; 212 } 213 } 214 } 215 </div> 216 } 217 218 @if (showBadges) { 219 <div class="position-absolute top-0 left-0 p-2 p-lg-3"> 220 @RenderPartial("Components/EcommerceBadge.cshtml", product, badgeParms) 221 </div> 222 } 223 </div> 224 225 @* Modal with slides *@ 226 <div class="modal fade swift_products-details-images-modal" id="modal_@Model.ID" tabindex="-1" aria-labelledby="productDetailsGalleryModalTitle_@Model.ID" aria-hidden="true"> 227 <div class="modal-dialog modal-dialog-centered modal-xl"> 228 <div class="modal-content"> 229 <div class="modal-header visually-hidden"> 230 <h5 class="modal-title" id="productDetailsGalleryModalTitle_@Model.ID">@product.Title</h5> 231 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 232 </div> 233 <div class="modal-body p-2 p-lg-3 h-100"> 234 <div id="ModalCarousel_@Model.ID" class="carousel@(GetArrowsColor()) h-100" data-bs-ride="carousel"> 235 <div class="carousel-inner h-100"> 236 @foreach (MediaViewModel asset in assetsList) { 237 var assetValue = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 238 foreach (string format in supportedImageFormats.Concat(supportedVideoFormats).ToArray()) { 239 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 240 string imagePath = assetValue; 241 string activeSlide = modalAssetNumber == 0 ? "active" : ""; 242 243 var parms = new Dictionary<string, object>(); 244 parms.Add("cssClass", "d-block mw-100 mh-100 m-auto"); 245 parms.Add("fullwidth", true); 246 parms.Add("columns", Model.GridRowColumnCount); 247 248 <div class="carousel-item @activeSlide h-100" data-bs-interval="99999"> 249 @foreach (string imageFormat in supportedImageFormats) { //Images 250 if (assetValue.IndexOf(imageFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 251 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 252 } 253 } 254 255 @foreach (string videoFormat in supportedVideoFormats) { //Videos 256 if (assetValue.IndexOf(videoFormat, StringComparison.OrdinalIgnoreCase) >= 0) { 257 {@RenderVideoPlayer(asset, "modal")} 258 } 259 } 260 </div> 261 262 modalAssetNumber++; 263 } 264 } 265 } 266 <button class="carousel-control-prev" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="prev"> 267 <span class="carousel-control-prev-icon" aria-hidden="true"></span> 268 <span class="visually-hidden">@Translate("Previous")</span> 269 </button> 270 <button class="carousel-control-next" type="button" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide="next"> 271 <span class="carousel-control-next-icon" aria-hidden="true"></span> 272 <span class="visually-hidden">@Translate("Next")</span> 273 </button> 274 </div> 275 </div> 276 </div> 277 </div> 278 </div> 279 </div> 280 } else if (Pageview.IsVisualEditorMode) { 281 RatioSettings ratioSettings = GetRatioSettings("desktop"); 282 283 <div class="h-100 @theme"> 284 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)"> 285 <img src="/Files/Images/missing_image.jpg" loading="lazy" decoding="async" class="mh-100 mw-100" style="object-fit: cover;"> 286 </div> 287 </div> 288 } 289 } else if (Pageview.IsVisualEditorMode) { 290 <div class="alert alert-dark m-0">@Translate("No products available")</div> 291 } 292 293 @helper RenderAsset(MediaViewModel asset, int assetNumber, string size = "desktop") { 294 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("ImageTheme")) ? " theme " + Model.Item.GetRawValueString("ImageTheme").Replace(" ", "").Trim().ToLower() : ""; 295 string assetValue = asset.Value; 296 297 <div class="h-100 @(theme)"> 298 @foreach (string format in supportedImageFormats) { //Images 299 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 300 {@RenderImage(asset, assetNumber, size)} 301 } 302 } 303 @foreach (string format in supportedVideoFormats) { //Videos 304 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 305 if (Model.Item.GetString("OpenVideoInModal") == "true") { 306 {@RenderVideoScreendump(asset, assetNumber, size)} 307 } else { 308 {@RenderVideoPlayer(asset, size)} 309 } 310 } 311 } 312 @foreach (string format in supportedDocumentFormats) { //Documents 313 if (assetValue.IndexOf(format, StringComparison.OrdinalIgnoreCase) >= 0) { 314 {@RenderDocument(asset, assetNumber, size)} 315 } 316 } 317 </div> 318 } 319 320 @helper RenderImage(MediaViewModel asset, int number, string size = "desktop") { 321 if (product is object) 322 { 323 string productName = product.Name; 324 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 325 string imageLinkPath = Dynamicweb.Context.Current.Server.UrlEncode(imagePath); 326 327 RatioSettings ratioSettings = GetRatioSettings(size); 328 329 var parms = new Dictionary<string, object>(); 330 parms.Add("alt", productName + asset.Keywords); 331 parms.Add("itemprop", "image"); 332 parms.Add("fullwidth", true); 333 parms.Add("columns", Model.GridRowColumnCount); 334 if (!string.IsNullOrEmpty(asset.DisplayName)) { 335 parms.Add("title", asset.DisplayName); 336 } 337 338 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 339 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 340 } else { 341 parms.Add("cssClass", "mw-100 mh-100"); 342 } 343 344 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 345 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 346 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 347 </div> 348 </a> 349 } 350 } 351 352 @helper RenderVideoScreendump(MediaViewModel asset, int number, string size = "desktop") { 353 if (product is object) 354 { 355 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 356 357 string videoScreendumpPath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : ""; 358 string videoId = videoScreendumpPath.Substring(videoScreendumpPath.LastIndexOf('/') + 1); 359 videoScreendumpPath = videoScreendumpPath.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || videoScreendumpPath.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg" : videoScreendumpPath; 360 361 string vimeoJsClass = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "js-vimeo-video-thumbnail" : ""; 362 videoScreendumpPath = videoScreendumpPath.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "" : videoScreendumpPath; 363 364 string productName = product.Name; 365 productName += !string.IsNullOrEmpty(asset.Keywords) ? " " + asset.Keywords : ""; 366 string assetTitle = !string.IsNullOrEmpty(asset.DisplayName) ? "title=\"" + asset.DisplayName + "\"" : ""; 367 368 RatioSettings ratioSettings = GetRatioSettings(size); 369 370 <div class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable); cursor: pointer" data-bs-toggle="modal" data-bs-target="#modal_@Model.ID"> 371 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100" data-bs-target="#ModalCarousel_@Model.ID" data-bs-slide-to="@number"> 372 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "play-circle.svg")</div> 373 @if (videoScreendumpPath.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) < 0) 374 { 375 <img src="@videoScreendumpPath" loading="lazy" decoding="async" alt="@productName" @assetTitle class="@vimeoJsClass mw-100 mh-100" data-video-id="@videoId" style="object-fit: cover;" onload="CheckIfVideoThumbnailExist(this)"> 376 } 377 else 378 { 379 string videoType = Path.GetExtension(asset.Value).ToLower(); 380 381 <video preload="auto" class="h-100 w-100" style="object-fit: contain;"> 382 <source src="@asset.Value" type="video/@videoType.Replace(".", "")"> 383 </video> 384 } 385 </div> 386 </div> 387 388 <script> 389 function CheckIfVideoThumbnailExist(image) { 390 if (image.width == 120) { 391 const lowQualityImage = "https://img.youtube.com/vi/@(videoId)/hqdefault.jpg" 392 image.src = lowQualityImage; 393 } 394 } 395 </script> 396 } 397 } 398 399 @helper RenderVideoPlayer(MediaViewModel asset, string size = "desktop") { 400 if (product is object) 401 { 402 string assetName = !string.IsNullOrEmpty(asset.DisplayName) ? asset.DisplayName : asset.Name; 403 string assetValue = asset.Value; 404 string videoId = asset.Value.Substring(asset.Value.LastIndexOf('/') + 1); 405 string type = assetValue.IndexOf("youtu.be", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf("youtube", StringComparison.OrdinalIgnoreCase) >= 0 ? "youtube" : ""; 406 type = assetValue.IndexOf("vimeo", StringComparison.OrdinalIgnoreCase) >= 0 ? "vimeo" : type; 407 type = assetValue.IndexOf(".mp4", StringComparison.OrdinalIgnoreCase) >= 0 || assetValue.IndexOf(".webm", StringComparison.OrdinalIgnoreCase) >= 0 ? "selfhosted" : type; 408 409 string openInModal = Model.Item.GetString("OpenVideoInModal"); 410 bool autoPlay = Model.Item.GetBoolean("VideoAutoPlay"); 411 412 <div class="h-100" itemscope itemtype="https://schema.org/VideoObject"> 413 <span class="visually-hidden" itemprop="name">@assetName</span> 414 <span class="visually-hidden" itemprop="contentUrl">@asset.Value</span> 415 <span class="visually-hidden" itemprop="thumbnailUrl">@asset.Value</span> 416 417 @if (type != "selfhosted") 418 { 419 <div 420 id="player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size" 421 class="plyr__video-embed" 422 data-plyr-provider="@(type)" 423 data-plyr-embed-id="@videoId" 424 style="--plyr-color-main: var(--swift-foreground-color); height: 100%"> 425 </div> 426 427 <script type="module" src="/Files/Templates/Designs/Swift/Assets/js/plyr.js"></script> 428 429 <script type="module"> 430 var player = new Plyr('#player_@(Pageview.CurrentParagraph.ID)_@(videoId)_@size', { 431 type: 'video', 432 youtube: { 433 noCookie: true, 434 showinfo: 0 435 }, 436 fullscreen: { 437 enabled: true, 438 iosNative: true, 439 } 440 }); 441 442 @if (autoPlay && openInModal == "false") 443 { 444 <text> 445 player.config.autoplay = true; 446 player.config.muted = true; 447 player.config.volume = 0; 448 player.media.loop = true; 449 450 player.on('ready', function() { 451 if (player.config.autoplay === true) { 452 player.media.play(); 453 } 454 }); 455 </text> 456 } 457 458 @if (openInModal == "true") 459 { 460 <text> 461 var productDetailsGalleryModal = document.querySelector('#modal_@Model.ID') 462 productDetailsGalleryModal.addEventListener('hidden.bs.modal', function (event) { 463 player.media.pause(); 464 }) 465 </text> 466 } 467 </script> 468 } 469 else 470 { 471 string autoPlayAttributes = (autoPlay && openInModal == "false") ? "loop autoplay muted playsinline" : ""; 472 string videoType = Path.GetExtension(assetValue).ToLower(); 473 474 <video preload="auto" @autoPlayAttributes class="h-100 w-100" style="object-fit: cover;" controls> 475 <source src="@assetValue" type="video/@videoType.Replace(".", "")"> 476 </video> 477 } 478 </div> 479 } 480 } 481 482 @helper RenderDocument(MediaViewModel asset, int number, string size = "desktop") { 483 if (product is object) 484 { 485 string iconPath = "/Files/Templates/Designs/Swift/Assets/icons/"; 486 487 string productName = product.Name; 488 string imagePath = !string.IsNullOrEmpty(asset.Value) ? asset.Value : product.DefaultImage.Value; 489 string imageLinkPath = imagePath; 490 491 RatioSettings ratioSettings = GetRatioSettings(size); 492 493 var parms = new Dictionary<string, object>(); 494 parms.Add("alt", productName + asset.Keywords); 495 parms.Add("itemprop", "image"); 496 parms.Add("fullwidth", true); 497 parms.Add("columns", Model.GridRowColumnCount); 498 if (!string.IsNullOrEmpty(asset.DisplayName)) { 499 parms.Add("title", asset.DisplayName); 500 } 501 502 if (ratioSettings.Ratio == "fill" && galleryLayout != "grid") { 503 parms.Add("cssClass", "w-100 h-100 image-zoom-lg-l-hover"); 504 } else { 505 parms.Add("cssClass", "mw-100 mh-100"); 506 } 507 508 <a href="@imageLinkPath" class="d-block @(ratioSettings.CssClass)@(ratioSettings.Fill)" style="@(ratioSettings.CssVariable)" download alt="@Translate("Download")"> 509 <div class="d-flex align-items-center justify-content-center overflow-hidden h-100"> 510 <div class="icon-5 position-absolute" style="z-index: 1">@ReadFile(iconPath + "download.svg")</div> 511 @if (asset.Value.IndexOf(".pdf", StringComparison.OrdinalIgnoreCase) >= 0) { 512 @RenderPartial("Components/Image.cshtml", new FileViewModel { Path = imagePath }, parms) 513 } 514 </div> 515 </a> 516 } 517 } 518
Ved at klikke 'Acceptér Alle' så giver du til tilladelse til at vi må indsamle information om dig til forskellige formål, hvilket inkluderer: Funktionalitet, Statistik og Marketing