Inhalte aufrufen

Profilbild

Smartstore "Standard" erweitern - das richtige Vorgehen?


  • Bitte melden Sie sich an, um eine Antwort zu verfassen.
3 Antworten zu diesem Thema

Umfrage: Smartstore "Standard" erweitern - das richtige Vorgehen? (2 Mitglied(er) haben ihre Stimme abgegeben)

Ist das für dich als Dev auch interessant?

  1. Ja (2 Stimmen [100.00%] - Anzeigen)

    Prozentsatz der Stimmen: 100.00%

  2. Nein (0 Stimmen [0.00%])

    Prozentsatz der Stimmen: 0.00%

Abstimmen Gäste können nicht abstimmen

#1 frodo_ff

frodo_ff

    Newbie

  • Members
  • 5 Beiträge

Geschrieben: 24 September 2018 - 16:32

Moin moin!

 

Bedauerlicherweise hat weder der Quellcode, noch die Doku noch das Forum eine richtig schöne Erklärung, wie man den Shop erweitert, ohne euren (im Übrigen leckeren) Source anzufassen und für etwaige Updates gut aufgestellt zu sein.

 

Also versuche ich jetzt in diesem Forum mein bisheriges Vorgehen zu erläutern und ihr könnt mir ja vielleicht verraten, wie es weitergehen könnte / sollte.

 

Anforderung:

In der Administration des Kunden (Admin/Customer/Edit/x) soll ein weiteres Textfeld zur Verfügung stehen, in welches ich die Kundennummer aus dem möglicherweise verwendeten ERP-System erfasst werden soll, nennen wir es: string ErpArticleNo

 

Aktuelle Umsetzung bis zum Punkt "Fragezeichen":

  1. Ich habe eine kleine View erstellt die über den WidgetController gerendert wird. In diesem Feld wird der Inhalt richtig angezeigt. Der HtmlHelper ist nur ein Wrapper für eure Helper, sodass mir sämtliche Label, Controls und Validation-Elemente, in einer Zeile generiert werden. Das klappt auch. Ich kann im Filter auch ohne weiteren "EventHandler" oder "ModelBinder" auf das Element zugreifen.
    @model SmartStore.Admin.Models.Customers.CustomerModel
    
    <script>
    	var erpCustomerNrHtmlControl = '@Html.OwnSmartStoreTextBox("CustomProperties[ErpCustomerNo]", Model.CustomProperties.Get("ErpCustomerNo"), new { }, labelText: "ErpCustomerNo")';
    	$('#Username').closest('.adminContent').append(erpCustomerNrHtmlControl);
    </script>
    		
    
  2. Ich habe einen Action-Filter implementiert, der zwischen Aufrufen und Speichern der Edit-Maske unterscheidet. Je nachdem, was gerade Phase ist, wird entweder in das CustomerModel.CustomProperties["ErpArticleNo"] geschrieben oder gelesen. Und jetzt kommt es, was das eigentliche Problem ist: Wohin mit den Daten!? In einigen Beiträgen habe ich das "neue" - aber nirgends erklärte "Syncmapping" probiert. Quälcode:
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
    	if (filterContext.ActionDescriptor.ActionName.Equals("Edit") &&
    	    filterContext.HttpContext.Request.HttpMethod == "POST" &&
    	    filterContext.Controller.ViewData.ModelState.IsValid)
    	{
    		var model = (CustomerModel)filterContext.ActionParameters["model"];
    
    		if (model != null && model.CustomProperties.Keys.Contains("ErpCustomerNo"))
    		{
    			var syncedMap = _syncMappingService.GetSyncMappingByEntity(_customerService.GetCustomerById(model.Id), PluginInfo.SystemName);
    			if (syncedMap != null)
    			{
    				syncedMap.CustomString = model.CustomProperties["ErpCustomerNo"].ToString();
    				_syncMappingService.UpdateSyncMapping(syncedMap);
    			}
    			else
    			{
    				_syncMappingService.InsertSyncMapping(_customerService.GetCustomerByEmail(model.Email),
    					PluginInfo.SystemName, "ErpCustomerNo");
    			}
    		}
    	}
    }
    
    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
    	// open customerInfo view (register widget with additional field)
    	if (filterContext.ActionDescriptor.ActionName.Equals("Edit") &&
    		filterContext.HttpContext.Request.HttpMethod == "GET")
    	{
    		// get model?
    		if (filterContext.Result is ViewResultBase result)
    		{
    			if (result.Model is CustomerModel model)
    			{
    				var syncedMap =
    					_syncMappingService.GetSyncMappingByEntity(_customerService.GetCustomerById(model.Id),
    						PluginInfo.SystemName);
    
    				model.CustomProperties.Add("ErpCustomerNo", syncedMap == null ? "Baumwolle" : syncedMap.CustomString);
    
    				_widgetProvider.Value.RegisterAction("admin_content_after", "ErpCustomerNoView", "Widget", new { area = "PluginXYZ", model });
    			}
    		}
    	}
    }
    

    Ist die SyncMapping Idee überhaupt sinnvoll für den Anwendungsfall? Wenn nein - wie wäre die Empfehlung es stattdessen zu lösen? Wenn ja, dann wäre ich euch sehr dankbar, wenn ihr die konkrete Idee dahinter noch einmal erläutern würdet und vielleicht mit dem korrekten Aufruf des Services um es zu veranschaulichen. Ich glaube dieses Problem würde nicht nur mir helfen! Mir ist bewusst, dass der SyncService gerade falsch bestückt wird. (Es wird nichts gespeichert). Aber den Anwendungsfall den ihr mit den SyncMappingTests abdeckt habe ich ja auch nicht..

 

Viele Grüße,

 

Fro.

 


  • RidgeOi, Brantot und BlackGaX gefällt das

#2 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3302 Beiträge

Geschrieben: 24 September 2018 - 19:55

Prinzipiell hier SyncMapping zu verwenden, ist völlig korrekt. Ich würde aber anstatt CustomString für einen einzigen Feldwert zu benutzen, ein neues Model erstellen und dieses serialisiert (z.B. als Json) in CustomString ablegen, dann lässt sich das leichter erweitern.
 
SyncMapping ist eine abstrakte Entität, um eigene Daten an eine bestehende Entität zu binden, ohne dabei selbst die Datenbank bzw. Datenbankstruktur erweitern zu müssen. Beispiel:
var syncMapping = _syncMappingService.GetSyncMappingByEntity(customer.Id, "Customer", "MyCompany.MyPluginName");
if (syncMapping != null)
{
	syncMapping.CustomString = JsonConvert.SerializeObject(my_model_instance);
	_syncMappingService.UpdateSyncMapping(syncMapping);
}
else
{
	syncMapping = new SyncMapping
	{
		EntityId = customer.Id,
		EntityName = "Customer",
		ContextName = "MyCompany.MyPluginName",
		SourceKey = yourExternalId,	//optional
		CustomString = JsonConvert.SerializeObject(my_model_instance)
	};
	_syncMappingService.InsertSyncMapping(syncMapping);
}
Bei einem einzigen Feldwert wäre der Gebrauch von GenericAttribute wahrscheinlicher einfacher (wird für die Customer Entität bereits häufig genutzt). Beispiel Customer Avatar: GetAttributeSaveAttribute, zum Löschen SaveAttribute z.B. mit (string)null aufrufen.

  • Brantot gefällt das

Schöne Grüße aus Düsseldorf,
Marcus Gesing


#3 frodo_ff

frodo_ff

    Newbie

  • Members
  • 5 Beiträge

Geschrieben: 25 September 2018 - 19:49

Moin Marcus,

 

danke für die super ausführliche Antwort. Zwei Anmerkungen habe ich noch.

 

  1. Ich wollte, in Voraussicht, dass da noch weitere Anpassungen kommen werden, direkt das Syncmapping nutzen. Hat jetzt auch geklappt. Allerdings ist "SourceKey" nicht optional, sondern wird bei NullOrEmpty zu einem DBValidationError führen. Ist es mir denn frei überlassen, was ich dort in dem Fall hineinschreibe? Würde den Wert nie benötigen, denke ich.
  2. Wenn ich es ähnlich handhabe wie ihr in einem Beispiel und in der View für die CustomProperties folgendes Feld nutze:
    var erpCustomerNrHtmlControl = '@Html.OwnSmartStoreTextBox("CustomProperties[ErpCustomerNo]", Model.CustomProperties.Get("ErpCustomerNo"), new { }, labelText: "ErpCustomerNo")';
    

    Dann wird verblüffenderweise beim ModelBinding aus dem string der aus der Textbox übermittelt wird ein string[], sodass ich 

    extendedModel.ErpCustomerNo = ((string[])model.CustomProperties.Get("ErpCustomerNo")).First();
    

    nutzen muss. Könnt ihr euch das erklären? So sieht das generierte Input-Feld aus: 

    <input id="CustomProperties_ErpCustomerNo_" name="CustomProperties[ErpCustomerNo]" type="text" value="tEST">
    

    Vielen Dank für die perfekte Arbeit und diesen Support.



#4 Marcus Gesing

Marcus Gesing

    SmartStore AG

  • Administrators
  • 3302 Beiträge

Geschrieben: 25 September 2018 - 21:09

1. Ja, dann einfach einen leeren String speichern.
 
2. Das Model Binding ist in dieser Form nicht wie gedacht. Wenn da noch weitere Anpassungen zu erwarten sind, dann könnte es mit der obigen Lösung eng werden. Auch das mit dem Javascript/Jquery würde ich so nicht machen. Leider etwas kompliziert und schwer zu erklären. So wie beim Google Merchant Center Plugin wäre es korrekt:
- neues Model anlegen.
- neue Partial View anlegen.
- neue Action Methode für die Partial View anlegen.
- Partial View per Widget oder TabStripCreated rendern lassen.
- Model per ModelBoundEvent verarbeiten und serialisiert als SyncMapping speichern.
 
Ganz wichtig ist dieses __Type__ und das HtmlFieldPrefix. Nur darüber kann der Model Binder Sub-Models automatisch via CustomProperties binden.

Schöne Grüße aus Düsseldorf,
Marcus Gesing