Guten Morgen,
Wir sind ein Brillenhersteller und möchten gerne auf SmartStore umsteigen.
Da wir aber als B2B-Shop zusätzliche Anforderungen haben (zusätzliche Daten zu jedem Produkt als default Werte) bin ich jetzt gerade dabei, diese zu integrieren.
Um diese Daten aber in den jeweiligen View (meist ProductsController, aber auch im Checkout etc müssen die vom Kunden geänderten Daten vorhanden sein) zu integrieren, fehlen mir noch ein paar Hook-Möglichkeiten.
Um dabei Merge-Conflicts weitestgehend aus dem Weg zu gehen, könnte man die Controller nicht in folgender Weise erweitern:
(Beispiel am ProductsController)
public async Task<IActionResult> ProductDetails(int productId, ProductVariantQuery query) {
// Sync on purpose because of large column.
var product = await _db.Products
.AsSplitQuery()
.IncludeMedia()
.IncludeManufacturers()
.FindByIdAsync(productId);
if (product == null || product.IsSystemProduct)
return NotFound();
// Is published? Check whether the current user has a "Manage catalog" permission.
// It allows him to preview a product before publishing.
if (!product.Published && !await Services.Permissions.AuthorizeAsync(Permissions.Catalog.Product.Read))
return NotFound();
// ACL (access control list).
if (!await _aclService.AuthorizeAsync(product))
return NotFound();
// Store mapping.
if (!await _storeMappingService.AuthorizeAsync(product))
return NotFound();
// Is product individually visible?
if (product.Visibility == ProductVisibility.Hidden)
{
// Find parent grouped product.
var parentGroupedProduct = await _db.Products.FindByIdAsync(product.ParentGroupedProductId, false);
if (parentGroupedProduct == null)
return NotFound();
var seName = await parentGroupedProduct.GetActiveSlugAsync();
if (seName.IsEmpty())
return NotFound();
var routeValues = new RouteValueDictionary
{
{ "SeName", seName }
};
// Add query string parameters.
Request.Query.Each(x => routeValues.Add(x.Key, Request.Query[x.Value].ToString()));
return RedirectToRoute("Product", routeValues);
}
// Prepare the view model
var model = await _helper.MapProductDetailsPageModelAsync(product, query);
// Some cargo data
model.PictureSize = _mediaSettings.ProductDetailsPictureSize;
model.HotlineTelephoneNumber = _contactDataSettings.HotlineTelephoneNumber.NullEmpty();
if (_seoSettings.CanonicalUrlsEnabled)
{
model.CanonicalUrl = Url.RouteUrl("Product", new { model.SeName }, Request.Scheme);
}
model.MetaProperties = await model.MapMetaPropertiesAsync();
// Save as recently viewed
_recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id);
// Activity log
Services.ActivityLogger.LogActivity(KnownActivityLogTypes.PublicStoreViewProduct, T("ActivityLog.PublicStore.ViewProduct"), product.Name);
// Breadcrumb
if (_catalogSettings.CategoryBreadcrumbEnabled)
{
await _helper.GetBreadcrumbAsync(_breadcrumb, ControllerContext, product);
// 'Continue shopping' URL.
var customer = Services.WorkContext.CurrentCustomer;
if (!customer.IsSystemAccount)
{
var categoryUrl = _breadcrumb.Trail?.LastOrDefault()?.GenerateUrl(Url);
if (categoryUrl.HasValue())
{
customer.GenericAttributes.LastContinueShoppingPage = categoryUrl;
}
}
_breadcrumb.Track(new MenuItem
{
Text = model.Name,
Rtl = model.Name.CurrentLanguage.Rtl,
EntityId = product.Id,
Url = Url.RouteUrl("Product", new { model.SeName })
});
}
//////////////////////////////////////////////////////////////////////////////////
// Anstatt direkt das Model zurückzugeben zuerst die virtuelle Methode aufrufen.
//////////////////////////////////////////////////////////////////////////////////
var optionallyChangedModel = await ChangeModel(model);
return View(optionallyChangedModel.ProductTemplateViewPath, optionallyChangedModel);
}
// Virtuelle Methode, um in abgeleiteten Controllern erweiterte Models zurückzuliefern
// ohne die Logik des Shops zu berühren
public virtual async Task<ProductDetailsModel> ChangeModel(ProductDetailsModel model)
{
return model;
}
Minimale Änderung am Controller selbst, aber damit könnte man dann im eigenen Modul den Controller ableiten und die zusätzliche benötigten Daten nachladen und das erweiterte ProductDetailsModel zurückliefern,
public class AdjustedProductController : ProductController
{
private DbSet<AdditionalProductData> _additionalProducts;
protected DbSet<AdditionalProductData> AdditionalProducts
{
get => _additionalProducts ??= _db.Set<AdditionalProductData>();
set => _additionalProducts = value;
}
private readonly SmartDbContext _db;
public AdjustedProductController(SmartDbContext db, IWebHelper webHelper, IProductService productService,
IProductAttributeService productAttributeService, IRecentlyViewedProductsService recentlyViewedProductsService, IAclService aclService,
IStoreMappingService storeMappingService, IMediaService mediaService, ICustomerService customerService, MediaSettings mediaSettings,
CatalogSettings catalogSettings, CatalogHelper helper, IBreadcrumb breadcrumb, SeoSettings seoSettings,
ContactDataSettings contactDataSettings, CaptchaSettings captchaSettings, LocalizationSettings localizationSettings,
PrivacySettings privacySettings, Lazy<IMessageFactory> messageFactory, Lazy<ProductUrlHelper> productUrlHelper,
Lazy<IProductAttributeFormatter> productAttributeFormatter, Lazy<IProductAttributeMaterializer> productAttributeMaterializer,
Lazy<IStockSubscriptionService> stockSubscriptionService) : base(db, webHelper, productService, productAttributeService,
recentlyViewedProductsService, aclService, storeMappingService, mediaService, customerService, mediaSettings, catalogSettings, helper,
breadcrumb, seoSettings, contactDataSettings, captchaSettings, localizationSettings, privacySettings, messageFactory,
productUrlHelper, productAttributeFormatter, productAttributeMaterializer, stockSubscriptionService)
{
_db = db;
}
public override async Task<ProductDetailsModel> ChangeModel(ProductDetailsModel model) {
var additionalProductDetailsModel = await MapperFactory.MapAsync<ProductDetailsModel, AdjustedProductDetailsModel>(model);
var addData = await AdditionalProducts.FirstOrDefaultAsync(x => x.ProductId == additionalProductDetailsModel.Id);
if (addData != null)
{
additionalProductDetailsModel.AdditionalData = await MapperFactory.MapAsync<AdditionalProductData, AdditionalProductDataModel>(addData);
}
return additionalProductDetailsModel;
}
}
um dann im angepassten View Zugriff auf die zusätzlichen Daten zu haben.
Hab das ganze auch schon ausprobiert, funktionieren würde es tadellos.
AdditionalProductDataModel hat auch nur ein paar string-Properties, die eben aus einer zusätzlichen Table ausgelesen werden sollen als Default-Werte pro Produkt, aber vom Kunden änderbar sein sollen.
Das würde die Erweiterbarkeit des Shops sehr vereinfachen und Merge-Conflicts wären praktisch ausgeschlossen.
Namen etc der Methoden/Variablen sind natürlich subject to change. Hab das nur mal als proof of concept gemacht, und ich glaube, dass das eine sinnvolle Erweiterung wäre für den Shop an sich.
Viele Kunden haben spezielle Anforderungen, die sich so relativ einfach lösen lassen würden.
Wäre schön, wenn wir uns über das Thema mal austauschen könnten.
Ich könnte auch die Änderungen mal als PR machen, macht allerdings nur Sinn, wenn die Benamungsstrategie klar wäre und das überhaupt gewollt wird.
MfG
Chris