Ein großes Ziel bei der Erweiterung des Shops ist der Erhalt der Releasefähigkeit. Leider haben die Anpassungsmöglichkeiten im Rahmen eines Plugins ihre Grenzen, wenn es darum geht, bestehende Views um Informationen zu erweitern. Ein Beispiel hierfür ist die Preisdarstellung, da der Shop immer von der Verkaufsgröße 1 ausgeht, es bei unseren Kunden aber durchaus möglich ist, dass der Preis pro 100 Stk gilt. Zudem fehlt die Angabe der Verkaufseinheit in der Preisdarstellung, was für uns insbesondere wichtig ist, wenn es sich eben nicht um Stück handelt oder der Artikel mit unterschiedlichen Verkaufseinheiten (Stück, Pack, Karton, Palette) angeboten wird.
Wir sind jetzt folgendermaßen vorgegangen:
Im Namespace SmartStore.Models haben wir eine neue Datei _NVExtModel eingefügt, in welches wir die Model-Extensions als partial class schreiben. Damit bleiben die SmartStore-Modele unangetastet, können aber trotzdem beliebig erweitert werden.
Da die Modelle in den jeweiligen Controller-Actions gefüllt werden, wollten wir einen Mechanismus schaffen, der uns die Model befüllen lässt, ohne dass wir den Code hierfür direkt in den Controller schreiben. Da sich die Controller in einem Plugin nicht ableiten lassen, um gezielt Methoden zu überschreiben, haben wir uns dafür entschieden, auch hier den Weg über die Partial Class zu gehen. Wir haben also in den Namespace SmartStore.Web.Controllers eine Datei _NVExtController eingefügt, welche Partial Classes für die Controller enthält. Soll ein Model angepasst werden (in unserem Test z.B. das ProductOverviewModel), legen wir hier eine entsprechende Methode „Customize“ an.
namespace SmartStore.Web.Controllers { public partial class CatalogController { protected IEnumerable<ProductOverviewModel> Customize(IEnumerable<ProductOverviewModel> products) { return CustomizingManager.Customize(products); } } }
In dem Controller ist die einzige Anpassung der Aufruf der Customize-Methode
protected IEnumerable<ProductOverviewModel> PrepareProductOverviewModels( //… return Customize(models); }
Der Customizing-Manager arbeitet ähnlich wie der RouteProvider. Beim Starten der Applikation wird geprüft, ob ein Plugin über eine Implementierung des ICustomizingProvider-Interface verfügt, und ggf. die Interface-Methode RegisterCustomizings aufgerufen.
namespace SmartStore.Web.Framework.Customizer { public class CustomizingPublisher : ICustomizingPublisher { private readonly ITypeFinder _typeFinder; public CustomizingPublisher(ITypeFinder typeFinder) { this._typeFinder = typeFinder; } public void RegisterCustomizings() { var customizingProviderTypes = _typeFinder.FindClassesOfType<ICustomizingProvider>(); var customizingProviders = new List<ICustomizingProvider>(); foreach (var providerType in customizingProviderTypes) { if (!PluginManager.IsActivePluginAssembly(providerType.Assembly)) { continue; } var provider = Activator.CreateInstance(providerType) as ICustomizingProvider; customizingProviders.Add(provider); } customizingProviders.Each(cp => cp.RegisterCustomizings()); } } }
********************
namespace SmartStore.Plugin.Import.eNVenta { public class CustomizingProvider :ICustomizingProvider { public void RegisterCustomizings() { CustomizingManager.Register<IEnumerable<ProductOverviewModel>>(CustomModel); CustomizingManager.Register<PictureModel>(CustomModel2); } public IEnumerable<ProductOverviewModel> CustomModel(IEnumerable<ProductOverviewModel> products) { foreach (var product in products) { product.NVArticleID = "TEST"; } return products; } public PictureModel CustomModel2(PictureModel products) { return products; } } }
********************
Das Interface erlaubt das Registrieren von Delegates, welche mit dem anzupassenden Typen übereinstimmen. In dem Test wurde das Customizing für List<ProductOverviewModel> durchgeführt. Durch diesen Mechanismus ist es jetzt möglich, die Logik für das ermitteln der individuellen Informationen in das Plugin auszulagern, wo z.B. auch der externe Webservice oder die Custom-Repositories zur Verfügung stehen.
Der CustomizingManager hat jetzt noch die Aufgabe, die entsprechenden Delegates, sofern verfügbar, aufzurufen
public class CustomizingManager { private static Dictionary<Type, object> delegateMap = new Dictionary<Type, object>(); public static T Customize<T>(T t) { var result = t; object tmp; if (delegateMap.TryGetValue(typeof(T), out tmp)) { List<Func<T, T>> list = (List<Func<T, T>>) tmp; foreach (var action in list) { result = action(result); } } return result; } public static void Register<T>(Func<T, T> method) { object tmp; if (!delegateMap.TryGetValue(typeof(T), out tmp)) { tmp = new List<Func<T, T>>(); delegateMap[typeof(T)] = tmp; } List<Func<T, T>> list = (List<Func<T, T>>) tmp; list.Add(method); } }
Wir können durch diesen Ansatz die Logik zur Ermittlung der Inhalte in das Plugin auslagern, wo uns der WebService bzw. unser ObjectContext zur Verfügung steht. Für die Darstellung wird uns nichts anderes übrig bleiben, als die Views direkt anzupassen. Trotzdem finde ich die Lösung recht Elegant, weil der Eingriff in die Controller überschaubar bleibt.