Sitefinity 5.1 - Adding Search Scope for Custom Module
I've just spent a whole afternoon looking for a solution to this and haven't found anything but dead ends. My goal is the following: I have several custom modules (coded from scratch, not from the module builder) that have items that I would like to integrate into the Sitefinity search engine.
My understanding is that I need to do something (whatever that is) that will make my custom modules show up as additional options on the Create/Edit Search Index page.
I've read comments from Telerik staff from 2011 that roughly say "don't try this now, because it will be much easier in the Q2 release," but there don't seem to be any answers after the Q2 release stating what the much easier way is (at least not that I've found. It's entirely possible I'm just looking for the wrong thing).
I've unsuccessfully tried a number of suggestions, and even tried to create a custom Pipe based on the ContentInboundPipe (yay, ILSpy!) and tried registering it like the NewsModule is registering its ContentInboundPipe (as much as possible), but without effect. My module is implementing the IPublishingEnabledModule interface, and I can see my ConfigurePublishing method being called, but nothing seems to happen.
I'm not even sure where to start looking at this point. Heck, I'm not even sure at this point that I'm supposed to be implementing an Inbound Pipe vs. an Outbound one.
Can someone at least give me a starting point?
Thanks,
- Nathan Frank
public
class
StaffProfilesModule : ContentModuleBase, IPublishingEnabledModule
public
void
ConfigurePublishing()
PublishingSystemFactory.RegisterPipe(StaffProfilesPipe.PipeName,
typeof
(StaffProfilesPipe));
var pipeSettings = StaffProfilesPipe.GetDefaultInboundPipeSettings();
PublishingSystemFactory.RegisterPipeSettings(StaffProfilesPipe.PipeName, pipeSettings);
var mappingsList = StaffProfilesPipe.GetDefaultMappings();
PublishingSystemFactory.RegisterPipeMappings(StaffProfilesPipe.PipeName,
true
, mappingsList);
var definitions = StaffProfilesPipe.CreateDefaultPipeDefinitions();
PublishingSystemFactory.RegisterPipeDefinitions(StaffProfilesPipe.PipeName, definitions);
var templatePipeSettings = PublishingSystemFactory.GetPipeSettings(StaffProfilesPipe.PipeName);
PublishingSystemFactory.RegisterTemplatePipe(
"SearchItemTemplate"
, templatePipeSettings);
public
class
StaffProfilesPipe : IPipe, IPushPipe, IPullPipe, IInboundPipe
public
const
string
PipeName =
"StaffProfilesPipe"
;
private
SitefinityContentPipeSettings _pipeSettings;
private
IPublishingPointBusinessObject _publishingPoint;
private
IDefinitionField[] _definitionFields;
private
string
_publishingProviderName;
public
virtual
string
PublishingProviderName
get
if
(String.IsNullOrEmpty(_publishingProviderName))
_publishingProviderName = Config.Get<PublishingConfig>().DefaultProvider;
return
_publishingProviderName;
set
_publishingProviderName = value;
public
string
Name
get
return
PipeName;
public
virtual
IDefinitionField[] Definition
get
if
(_definitionFields ==
null
)
string
text =
null
;
if
(_pipeSettings !=
null
)
text = _pipeSettings.ContentTypeName;
if
(
string
.IsNullOrEmpty(text) ||
!PublishingSystemFactory.ContentPipeDefinitionsRegistered(Name, text))
_definitionFields = PublishingSystemFactory.GetPipeDefinitions(Name);
else
_definitionFields = PublishingSystemFactory.GetContentPipeDefinitions(Name, text);
return
_definitionFields;
public
virtual
PipeSettings PipeSettings
get
return
_pipeSettings;
set
Initialize(value);
public
virtual
Type PipeSettingsType
get
return
typeof
(SitefinityContentPipeSettings);
public
static
List<Mapping> GetDefaultMappings()
var mappingsList =
new
List<Mapping>
PublishingSystemFactory.CreateMapping(PublishingConstants.FieldTitle,
ConcatenationTranslator.TranslatorName,
true
,
PublishingConstants.FieldTitle),
PublishingSystemFactory.CreateMapping(PublishingConstants.FieldContent,
ConcatenationTranslator.TranslatorName,
false
,
PublishingConstants.FieldDescription),
PublishingSystemFactory.CreateMapping(PublishingConstants.FieldItemHash,
TransparentTranslator.TranslatorName,
false
,
PublishingConstants.FieldItemHash),
PublishingSystemFactory.CreateMapping(PublishingConstants.FieldPipeId,
TransparentTranslator.TranslatorName,
false
,
PublishingConstants.FieldPipeId),
PublishingSystemFactory.CreateMapping(PublishingConstants.FieldLink,
TransparentTranslator.TranslatorName,
false
,
PublishingConstants.FieldPipeId),
PublishingSystemFactory.CreateMapping(PublishingConstants.FieldContentType,
TransparentTranslator.TranslatorName,
false
,
PublishingConstants.FieldContentType)
;
return
mappingsList;
public
static
IList<IDefinitionField> CreateDefaultPipeDefinitions()
return
new
IDefinitionField[]
new
SimpleDefinitionField(PublishingConstants.FieldTitle,
Res.Get<PublishingMessages>().ContentTitle),
new
SimpleDefinitionField(PublishingConstants.FieldContent,
Res.Get<PublishingMessages>().ContentContent),
new
SimpleDefinitionField(PublishingConstants.FieldLink,
Res.Get<PublishingMessages>().ContentLink),
new
SimpleDefinitionField(PublishingConstants.FieldItemHash,
Res.Get<PublishingMessages>().ItemHash),
new
SimpleDefinitionField(PublishingConstants.FieldPipeId,
Res.Get<PublishingMessages>().PipeId)
;
public
static
SitefinityContentPipeSettings GetDefaultInboundPipeSettings()
return
new
SitefinityContentPipeSettings
ContentTypeName =
typeof
(StaffProfileItem).FullName,
IsInbound =
true
,
PipeName = PipeName,
IsActive =
true
,
MaxItems = 0,
InvocationMode = PipeInvokationMode.Push
;
public
void
Initialize(PipeSettings pipeSettings)
if
(!(pipeSettings
is
SitefinityContentPipeSettings))
throw
new
ArgumentException(
"Expected pipe settings of type SitefinityContentPipeSettings"
);
_pipeSettings = (pipeSettings
as
SitefinityContentPipeSettings);
_publishingPoint = PublishingSystemFactory.GetPublishingPoint(_pipeSettings.PublishingPoint);
_definitionFields =
null
;
public
virtual
IEnumerable<WrapperObject> GetConvertedItemsForMapping(
params
object
[] items)
throw
new
NotImplementedException();
public
virtual
bool
CanProcessItem(
object
item)
var publishingSystemEventInfo = item
as
PublishingSystemEventInfo;
if
(publishingSystemEventInfo !=
null
)
return
(publishingSystemEventInfo).ItemAction ==
"SystemObjectDeleted"
||
CanProcessItem((publishingSystemEventInfo).Item);
if
(item ==
null
)
return
false
;
var wrapperObject = item
as
WrapperObject;
if
(wrapperObject !=
null
)
return
CanProcessItem((wrapperObject).WrappedObject);
var content = item
as
IContent;
if
(content !=
null
)
Type contentType =
TypeResolutionService.ResolveType(((SitefinityContentPipeSettings) PipeSettings).ContentTypeName);
if
(contentType.IsInstanceOfType(item))
return
true
;
return
false
;
public
virtual
PipeSettings GetDefaultSettings()
return
PublishingSystemFactory.CreatePipeSettings(Name, PublishingManager.GetManager(PublishingProviderName));
public
virtual
string
GetPipeSettingsShortDescription(PipeSettings initSettings)
return
"Case Studies Inbound Pipe"
;
public
virtual
void
CleanUp(
string
transactionName)
throw
new
NotImplementedException();
public
virtual
void
PushData(IList<PublishingSystemEventInfo> items)
var addList =
new
List<WrapperObject>();
var removeList =
new
List<WrapperObject>();
foreach
(PublishingSystemEventInfo current
in
items)
try
string
contentTypeName = ((SitefinityContentPipeSettings) PipeSettings).ContentTypeName;
if
(!
string
.IsNullOrEmpty(contentTypeName) && current.ItemType != contentTypeName)
Type contentType = TypeResolutionService.ResolveType(contentTypeName);
Type itemType = TypeResolutionService.ResolveType(current.ItemType);
if
(!contentType.IsAssignableFrom(itemType))
continue
;
var wrapperObject =
new
WrapperObject(PipeSettings, current.Item)
MappingSettings = PipeSettings.Mappings,
Language = current.Language
;
if
(_pipeSettings.LanguageIds.Count <= 0 ||
_pipeSettings.LanguageIds.Contains(wrapperObject.Language))
string
itemAction;
if
((itemAction = current.ItemAction) !=
null
)
if
(itemAction !=
"SystemObjectDeleted"
)
if
(itemAction !=
"SystemObjectAdded"
)
if
(itemAction ==
"SystemObjectModified"
)
StaffProfileItem contentItem = GetContentItem(current);
SetProperties(wrapperObject, contentItem);
removeList.Add(wrapperObject);
addList.Add(wrapperObject);
else
StaffProfileItem contentItem2 = GetContentItem(current);
SetProperties(wrapperObject, contentItem2);
addList.Add(wrapperObject);
else
removeList.Add(wrapperObject);
catch
(Exception ex)
this
.HandleError(
"Error when push data for item action 0 for item 1."
.Arrange(
new
[] current.ItemAction, current.Item), ex);
if
(removeList.Count > 0)
_publishingPoint.RemoveItems(removeList);
if
(addList.Count > 0)
_publishingPoint.AddItems(addList);
public
virtual
IList<WrapperObject> GetData()
var wrapperObjects = LoadWrapperObjectItems();
return
wrapperObjects;
public
virtual
void
ToPublishingPoint()
var wrapperObjects = LoadWrapperObjectItems();
var items = wrapperObjects
.Select(item =>
new
PublishingSystemEventInfo
Item = item,
ItemAction = RelatedActionsConstants.SystemObjectModified,
Language = item.Language
)
.ToList();
PushData(items);
protected
virtual
StaffProfileItem GetContentItem(WrapperObject wrappedObject)
if
(wrappedObject ==
null
)
return
null
;
if
(wrappedObject.WrappedObject ==
null
)
return
null
;
var contentItem = wrappedObject.WrappedObject
as
StaffProfileItem;
if
(contentItem !=
null
)
return
contentItem;
return
GetContentItem((WrapperObject) wrappedObject.WrappedObject);
protected
virtual
StaffProfileItem GetContentItem(PublishingSystemEventInfo item)
var wrappedObject = item.Item
as
WrapperObject;
if
(wrappedObject !=
null
)
return
GetContentItem(wrappedObject);
return
(StaffProfileItem) item.Item;
protected
virtual
IList<WrapperObject> LoadWrapperObjectItems()
IEnumerable<StaffProfileItem> items = GetContentItems();
return
items.Select(ConvertToWraperObject).ToList();
protected
IList<StaffProfileItem> GetContentItems()
if
(_pipeSettings ==
null
)
throw
new
InvalidOperationException(
"Pipe settings are required!"
);
StaffProfilesManager manager = StaffProfilesManager.GetManager(_pipeSettings.ProviderName);
var query = manager.GetStaffProfiles();
if
(_pipeSettings.MaxItems > 0)
query = query.Take(_pipeSettings.MaxItems);
var list = query.OrderByDescending(x => x.PublicationDate).ToList();
return
list;
protected
virtual
WrapperObject ConvertToWraperObject(StaffProfileItem item)
var obj =
new
WrapperObject(PipeSettings, item)
MappingSettings = PipeSettings.Mappings,
Language = PipeSettings.LanguageIds.FirstOrDefault()
;
SetProperties(obj, item);
return
obj;
protected
virtual
void
SetProperties(WrapperObject wrapperObject, StaffProfileItem item)
wrapperObject.AddProperty(PublishingConstants.FieldTitle, item.Title);
wrapperObject.AddProperty(PublishingConstants.FieldContent, item.Content);
wrapperObject.AddProperty(PublishingConstants.FieldItemHash, GenerateItemHash(item));
wrapperObject.AddProperty(PublishingConstants.FieldIdentifier, item.Id);
wrapperObject.AddProperty(PublishingConstants.FieldPipeId, _pipeSettings.Id);
wrapperObject.AddProperty(PublishingConstants.FieldLink, GetUrl(item));
wrapperObject.AddProperty(PublishingConstants.FieldContentType, _pipeSettings.ContentTypeName);
protected
virtual
string
GenerateItemHash(StaffProfileItem item)
var builder =
new
StringBuilder();
builder.Append(item.Id);
builder.Append(
"|"
);
builder.Append(item.Title);
var hasher =
new
SHA1CryptoServiceProvider();
byte
[] originalBytes = Encoding.UTF8.GetBytes(builder.ToString());
byte
[] encodedBytes = hasher.ComputeHash(originalBytes);
return
Convert.ToBase64String(encodedBytes);
protected
string
GetUrl(StaffProfileItem item)
var url = item.Urls.FirstOrDefault();
if
(url !=
null
)
return
url.Url;
return
null
;
Following up with the results of a support ticket.
Your code worked great - THANK YOU!! I had been trying to make the code in the documentation work for a very long time. You have made my day, if not my week. :)
You can also do your registration code for the publishing system stuff in the Initialize (not Install) method of the module I've found... but I like this method better.
Did anyone figure out how to get new search scopes to show up in existing indexes though? It's annoying having to delete and completely recreate my index every time a new type is added, etc.
Dear Nathan,
First, I would like to thank you for having the time to post this solution online, it is being very helpful.
Second, I have some doubts about your code that I would like to ask if you could help me with. Here they go:
Imagine that your module has 3 fields: a title that is metadata, a summary that is a localized string, and contacts, that is also a localized string.
How would this fields be used in the GetdefaultMappings, CreateDeafultPipeDefinitions, SetProperties and how would I go about to use them there
And what will the "Additional fields for indexing" in the Search & indexes in the UI be for these fields the module has?
Thank you very much for your time and patience.
Regards,
Pedro
I tried to search only in Title, using "Title" as the "Additional fields for indexing" but for some reason the search returned 0 results although I have items for my custom module created with the title I searched for. Anybody has any idea why this doesn't work?
Thank you very much