Skip to content

Instantly share code, notes, and snippets.

@Vapok
Last active December 15, 2016 19:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Vapok/502baeb141fc634b3c35cd4cdbf6495c to your computer and use it in GitHub Desktop.
Save Vapok/502baeb141fc634b3c35cd4cdbf6495c to your computer and use it in GitHub Desktop.
Pipelined Contact Processing Provider
/*
NOTE THAT THIS IS THE DECOMPILED ContactProcessingProvider
This is actually exposing a bug in the ContactProcessingProvider.
The ContactProcessingProvider is used when a CSV File is uploaded through List Manager.
The bug is that the MapContact() method is the meeting point between the uploaded CSV data,
and the existing contact record. Looking at the original method, you'll see that the existing
contact information is completely disregard, and the contact template created from the CSV file
is utilized. This overwrites any and all data in Mongo for that contact record, including
OTHER facet fields that might have been previously populated, but not part of the CSV file.
The fix here is that I have overriden the ContactProcessingProvider and have implemented a pipeline to be utilized.
The pipeline processors, have the ability to make decisions points on whether we want to use data
from the CSV file, or from the Existing Contact.
*/
using Sitecore.Analytics.Data;
using Sitecore.Analytics.Data.Bulk;
using Sitecore.Analytics.Data.Bulk.Contact;
using Sitecore.Analytics.Model.Entities;
namespace Sitecore.ListManagement.Analytics.Data
{
public class ContactProcessingProvider : ContactProcessingProviderBase<IContactTemplate>
{
public ContactProcessingProvider(ContactRepositoryBase contactRepository, IContactTemplateFactory contactTemplateFactory, BulkOperationManager<IContactTemplate, KnownContactSet, IContactUpdateResult> operationManager, IWorkItemSetManager<KnownContactSet, IContactTemplate> setManager)
: base(contactRepository, contactTemplateFactory, operationManager, setManager)
{
}
protected override string GetContactIdentifier(IContactTemplate contactData)
{
return contactData.Identifiers.Identifier;
}
protected override IContactTemplate MapContact(IContactTemplate inComingContact, Sitecore.Analytics.Tracking.Contact existingContact)
{
/*
THERE IS ACTUALLY A BUG HERE.
The Existing Contact is completely ignored, and the copied information from the CSV file completely overwrites the contact record.
*/
return inComingContact;
}
}
}
using Sitecore.Analytics.Tracking;
using Sitecore.ListManagement.Analytics.Data;
using Sitecore.Pipelines;
namespace SitecoreHacker.Sandbox.CustomSitecore.Facets
{
public class PipelinedContactProcessingProvider : ContactProcessingProvider
{
public PipelinedContactProcessingProvider(ContactRepositoryBase contactRepository,
IContactTemplateFactory contactTemplateFactory,
BulkOperationManager<IContactTemplate, KnownContactSet, IContactUpdateResult> operationManager,
IWorkItemSetManager<KnownContactSet, IContactTemplate> setManager)
: base(contactRepository, contactTemplateFactory, operationManager, setManager)
{
}
protected override IContactTemplate MapContact(IContactTemplate contactSource, Contact existingContact)
{
if (existingContact != null)
{
var mapContactArgs = new MapContactFacetsOnImportArgs
{
ContactTemplateDestination = contactSource,
ExistingContact = existingContact
};
//I Made this a Pipeline so that I could extend it in the future without mucking with this method.
//The thought process is that I would create a processor for each custom facet (or OOTB facet)
//that I want to carry over
CorePipeline.Run("listManagement.mapContactFacetsOnImport", mapContactArgs);
return mapContactArgs.ContactTemplateDestination;
}
return contactSource;
}
}
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<!-- PATCH THIS CONFIG to Use the Pipelined Contact Processing Provider Above -->
<!-- CONTACTS PROCESSING PROVIDER
Performs the communication with current data engine when processing contacts.
-->
<contactProcessingProvider type="SitecoreHacker.Sandbox.CustomSitecore.Facets.PipelinedContactProcessingProvider, SitecoreHacker.Sandbox">
<!-- <contactProcessingProvider type="Sitecore.ListManagement.Analytics.Data.ContactProcessingProvider, Sitecore.ListManagement.Analytics"> -->
<param ref="/sitecore/contactRepository" />
<param ref="/sitecore/model/entities/contact/template" />
<param name="operationManager" type="Sitecore.Analytics.Data.Bulk.Contact.ContactBulkUpdateManager, Sitecore.Analytics" />
<param name="setManager" type="Sitecore.Analytics.Data.Bulk.Contact.KnownContactSetManager, Sitecore.Analytics" />
</contactProcessingProvider>
</sitecore>
</configuration>
using Sitecore.Analytics.Model.Entities;
using Sitecore.Analytics.Tracking;
using Sitecore.Pipelines;
namespace SitecoreHacker.Sandbox.CustomSitecore.Pipelines.ListManager.MapContactsOnImport
{
public class MapContactFacetsOnImportArgs : PipelineArgs
{
public Contact ExistingContact { get; set; }
public IContactTemplate ContactTemplateDestination { get; set; }
}
}
using SitecoreHacker.Sandbox.CustomSitecore.Facets;
using Sitecore.Diagnostics;
namespace SitecoreHacker.Sandbox.CustomSitecore.Pipelines.ListManager.MapContactsOnImport
{
public class MapContactMarketingProfile
{
public virtual void Process(MapContactFacetsOnImportArgs args)
{
Assert.ArgumentNotNull(args.ExistingContact, "ContactTemplateDestination");
// This is the Existing Contact Facet
var facet = args.ExistingContact.GetFacet<IMarketingData>("Marketing Data");
//This is the Contact Template generated from the CSV Upload, and contains data from the CSV
var source = args.ContactTemplateDestination.GetFacet<IMarketingData>("Marketing Data");
//Here, make decisions based on which data to use. In this example, I'm checking the CSV value.
//If the CSV value is NULL (meaning there was no mapped field from the uploader), then I automatically use
//whatever value is in the existing contact facet.
//If it's not null, in this example, then I'm making the assumption, that I WANT to use the data from the CSV file.
//You can make other decision points here too, based on business logic.
source.Channel_Type = source.Channel_Type ?? facet.Channel_Type;
source.Company_Crm_Id = source.Company_Crm_Id ?? facet.Company_Crm_Id;
source.Company_Name = source.Company_Name ?? facet.Company_Name;
source.Crm_ID = source.Crm_ID ?? facet.Crm_ID;
source.Promotional_Code = source.Promotional_Code ?? facet.Promotional_Code;
source.Region = source.Region ?? facet.Region;
source.Type = source.Type ?? facet.Type;
}
}
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<listManagement.mapContactFacetsOnImport>
<processor type="SitecoreHacker.Sandbox.CustomSitecore.Pipelines.ListManager.MapContactsOnImport.MapContactMarketingProfile, SitecoreHacker.Sandbox" />
</listManagement.mapContactFacetsOnImport>
</pipelines>
</sitecore>
</configuration>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment