Showing posts with label Cross-site publishing. Show all posts
Showing posts with label Cross-site publishing. Show all posts

Sunday, April 27, 2014

Create custom search result source programmatically in Sharepoint 2013

If you need to customize search results on your Sharepoint 2013 site, e.g. add additional search rules which will be used with the keywords entered by the user in the search box, then you will need to create custom search result source (there are other ways to do it also, but custom result source is the most straightforward and designed exactly for such requirements). Result source may be created on site collection level and marked as default. In this case it will be used on default search results page or in content by search web parts (if another result source is not specified in web parts properties explicitly). In this post I will show how to create custom search result source programmatically during provisioning. It will help to automate installation process.

Let’s create result source which will have additional filter for the News by managed metadata property Country. I.e. we want to show only those news which are tagged for appropriate country. Here is how it can be done:

   1: string query = "{{?{{searchTerms}} -ContentClass=urn:content-class:SPSPeople " +
   2:     "(ContentType<>News OR (ContentType=News AND Countries:\"Russia\"}}";
   3:  
   4: SPSite site = ...;
   5: var searchAppProxy = getSearchServiceApplicationProxy(site);
   6: var federationManager = new FederationManager(searchAppProxy);
   7: var searchOwner = new SearchObjectOwner(SearchObjectLevel.SPSite, site.RootWeb);
   8: var resultSource = federationManager.CreateSource(searchOwner);
   9: resultSource.Name = "Localized Search Results";
  10: resultSource.ProviderId =
  11:     federationManager.ListProviders()["Local SharePoint Provider"].Id;
  12: resultSource.CreateQueryTransform(new QueryTransformProperties(), query);
  13: resultSource.Commit();
  14: federationManager.UpdateDefaultSource(resultSource.Id, searchOwner);

Here is the parts included to our query:

?{searchTerms} – placeholder for the keywords entered by the user in search box;
-ContentClass=urn:content-class:SPSPeople – exclude people from search result;
(ContentType<>News OR (ContentType=News AND Countries:"Russia"} – show news which are tagged for Russia. Other content is shown regardless of the country.

These rules are combined by space which means AND operator in KQL. As result only those content will be shown in the search results which matches to all rules.

Note that on line 14 we also set created result source as default (federationManager.UpdateDefaultSource(…)). Note that after that catalog connections will disappear in Site settings > Manage catalog connections > Connect to a catalog and you won’t be able to connect to publishing catalog. I wrote about this problem in Problem with missing catalog connections when use customized search result source in Sharepoint 2013 article. Workaround is to set OTB result source “Local SharePoint Results” as default temporary, connect to catalog and set custom result source back to default.

Saturday, April 19, 2014

Connect to publishing catalog programmatically in Sharepoint 2013

In this post I will describe the process of connecting to publishing catalog programmatically. As you probably know in Sharepoint a lot of actions can be done manually in UI. However if we develop solution with repeatable deployments it is more efficient to automate as much actions as possible. Almost all actions which you may do via UI can be done programmatically or by script in Sharepoint (in some cases you will need to use internal classes via Reflection, but it is still possible). And connecting to the publishing catalog is not exception from this point of view.

First of all before to go forward I recommend to check my previous articles about cross-site publishing and catalogs in Sharepoint 2013, especially Provision Sharepoint 2013 site collection with cross-site publishing. It will help to understand on which step during provisioning methods shown below should be called and other important moments:

Problem with cross site publishing catalog connections when use SPExport/SPImport for copying sites
Restore Sharepoint site with configured cross-site publishing on different environment
Cross site publishing and anonymous access in Sharepoint 2013
Problem with connecting to catalog on site collections imported with SPExport/SPImport API in Sharepoint

First of all we created Site scope feature for configuring search-related functionalities. This feature was activated with assumption that it can be activated and deactivated several times on the site collection. So for each step we have both configuration in FeatureActivated() method of feature receiver and deleting of configuration in FeatureDeactivating() method:

   1: public class SiteSearchEventReceiver : SPFeatureReceiver
   2: {
   3:     public override void FeatureActivated(SPFeatureReceiverProperties properties)
   4:     {
   5:         SPSite site = properties.Feature.Parent as SPSite;
   6:         SearchConfigurationHelper.ConnectAndConfigureNewsPublishingCatalog(
   7:             site.RootWeb, "My Catalog");
   8:         ...
   9:     }
  10:  
  11:     public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
  12:     {
  13:         SPSite site = properties.Feature.Parent as SPSite;
  14:         SearchConfigurationHelper.DeleteNewsPublishingCatalogConnection(site.RootWeb);
  15:         ...
  16:     }
  17: }

In SearchConfigurationHelper.ConnectAndConfigureNewsPublishingCatalog() we used slightly modified CatalogSubscriber from this MSDN example:

   1: public class CatalogSubscriber
   2: {
   3:     string portalSiteUrl;
   4:     string publishingCatalogUrl;
   5:     string subscribingWebServerRelativeUrl;
   6:     public CatalogSubscriber(string publishingCatalogUrl, string portalSiteUrl,
   7:         string subscribingWebServerRelativeUrl = "/")
   8:     {
   9:         if (string.IsNullOrWhiteSpace(portalSiteUrl) ||
  10:             string.IsNullOrWhiteSpace(publishingCatalogUrl))
  11:         {
  12:             throw new ArgumentNullException("A valid url must be provided for " +
  13:                 "publishingCatalogUrl and portalSiteUrl");
  14:         }
  15:         this.portalSiteUrl = portalSiteUrl;
  16:         this.publishingCatalogUrl = publishingCatalogUrl;
  17:         this.subscribingWebServerRelativeUrl = subscribingWebServerRelativeUrl;
  18:     }
  19:  
  20:     public void ConnectToCatalog()
  21:     {
  22:         using (SPSite publishingPortalSite = new SPSite(this.portalSiteUrl))
  23:         {
  24:             // Get the connection manager for the site where the catalog content
  25:             // will be rendered.
  26:             CatalogConnectionManager manager = new CatalogConnectionManager(
  27:                 publishingPortalSite, createResultSource: true);
  28:             // Use "Contains" to check whether a connection exists.
  29:             if (manager.Contains(this.publishingCatalogUrl))
  30:             {
  31:                 throw new ArgumentException(string.Format("A connection to {0} " +
  32:                     "already exists", this.publishingCatalogUrl));
  33:             }
  34:             // Get the catalog to connect to.
  35:             CatalogConnectionSettings settings =
  36:                 PublishingCatalogUtility.GetPublishingCatalog(publishingPortalSite,
  37:                     this.publishingCatalogUrl);
  38:             settings.ConnectedWebServerRelativeUrl =
  39:                 this.subscribingWebServerRelativeUrl;
  40:  
  41:             manager.AddCatalogConnection(settings);
  42:             // Perform any other adds/Updates
  43:             // ...
  44:             // Finally commit connection changes to store.
  45:             manager.Update();
  46:         }
  47:     }
  48:  
  49:     public void ConnectToCatalog(string[] orderedPropertiesForURLRewrite,
  50:         string taxonomyFieldManagedProperty)
  51:     {
  52:  
  53:         using (SPSite publishingPortalSite = new SPSite(this.portalSiteUrl))
  54:         {
  55:             // Get the connection manager for the site where the catalog
  56:             // content will be rendered.
  57:             CatalogConnectionManager manager =
  58:                 new CatalogConnectionManager(publishingPortalSite);
  59:             // Use "Contains" to check whether a connection exists.
  60:             if (manager.Contains(this.publishingCatalogUrl))
  61:             {
  62:                 throw new ArgumentException(string.Format("A connection to {0} " +
  63:                     "already exists", this.publishingCatalogUrl));
  64:             }
  65:             // Get the catalog to connect to.
  66:             CatalogConnectionSettings settings =
  67:                 PublishingCatalogUtility.GetPublishingCatalog(publishingPortalSite,
  68:                     this.publishingCatalogUrl);
  69:  
  70:             manager.AddCatalogConnection(settings,
  71:                 // If the items in the catalog should have rewritten urls, specify
  72:                 // what properties should be used in rewriting the url.
  73:                 orderedPropertiesForURLRewrite,
  74:                 this.subscribingWebServerRelativeUrl,
  75:                 // The managed property which contains the category for the catalog
  76:                 // item. Typically starts with owstaxid, when created by the system.
  77:                 taxonomyFieldManagedProperty);
  78:             // Perform any other adds/Updates
  79:             // ...
  80:             // Finally commit connection changes to store.
  81:             manager.Update();
  82:         }
  83:     }
  84:  
  85:     public void ConnectToCatalog(string newUrlTemplate,
  86:         string taxonomyFieldManagedProperty,
  87:         bool isCustomCatalogItemUrlRewriteTemplate,
  88:         string catalogName)
  89:     {
  90:         using (SPSite publishingPortalSite = new SPSite(this.portalSiteUrl))
  91:         {
  92:             // Get the connection manager for the site where the catalog
  93:             // content will be rendered.
  94:             CatalogConnectionManager manager =
  95:                 new CatalogConnectionManager(publishingPortalSite);
  96:             // Use "Contains" to check whether a connection exists.
  97:             if (manager.Contains(this.publishingCatalogUrl))
  98:             {
  99:                 throw new ArgumentException(string.Format("A connection to {0} " +
 100:                     "already exists", this.publishingCatalogUrl));
 101:             }
 102:             // Get the catalog to connect to.
 103:             //CatalogConnectionSettings settings =
 104:                 PublishingCatalogUtility.GetPublishingCatalog(publishingPortalSite,
 105:                     this.publishingCatalogUrl);
 106:             var settings = this.getCatalogByName(publishingPortalSite, catalogName);
 107:             if (settings == null)
 108:             {
 109:                 throw new Exception(string.Format("Can't connect to catalog '{0}': " +
 110:                     "it is not found", catalogName));
 111:             }
 112:  
 113:             manager.AddCatalogConnection(settings,
 114:                 // If the items in the catalog should have rewritten urls,
 115:                 // specify what properties should be used in rewriting the url.
 116:                 newUrlTemplate,
 117:                 this.subscribingWebServerRelativeUrl,
 118:                 // The managed property which contains the category for the catalog
 119:                 // item. Typically starts with owstaxid, when created by the system.
 120:                 taxonomyFieldManagedProperty,
 121:                 isCustomCatalogItemUrlRewriteTemplate);
 122:             // Perform any other adds/Updates
 123:             // ...
 124:             // Finally commit connection changes to store.
 125:             manager.Update();
 126:         }
 127:     }
 128:  
 129:     private CatalogConnectionSettings getCatalogByName(SPSite site,
 130:         string catalogName)
 131:     {
 132:         int num;
 133:         var catalogs = PublishingCatalogUtility.GetPublishingCatalogs(site, 0, 10,
 134:             string.Empty, out num);
 135:         if (catalogs == null)
 136:         {
 137:             return null;
 138:         }
 139:         foreach (var c in catalogs)
 140:         {
 141:             if (c.CatalogName == catalogName)
 142:             {
 143:                 return c;
 144:             }
 145:         }
 146:         return null;
 147:     }
 148:  
 149:     public void UpdateCatalogUrlTemplate(string newUrlTemplate)
 150:     {
 151:         using (SPSite publishingPortalSite = new SPSite(this.portalSiteUrl))
 152:         {
 153:             // Get the connection manager for the site where the catalog
 154:             // content will be rendered.
 155:             CatalogConnectionManager manager =
 156:                 new CatalogConnectionManager(publishingPortalSite);
 157:             // Get the current settings
 158:             CatalogConnectionSettings settings =
 159:                 manager.GetCatalogConnectionSettings(this.publishingCatalogUrl);
 160:             if (settings == null)
 161:             {
 162:                 throw new ArgumentException(
 163:                     string.Format("This site is not connected to catalog {0}",
 164:                         this.publishingCatalogUrl));
 165:             }
 166:             settings.CatalogItemUrlRewriteTemplate = newUrlTemplate;
 167:             // Update in memory
 168:             manager.UpdateCatalogConnection(settings);
 169:             // Perform any other adds/Updates
 170:  
 171:             // Finally Commit the update(s) to store.
 172:             manager.Update();
 173:         }
 174:     }
 175:  
 176:     public void DeleteCatalog()
 177:     {
 178:         using (SPSite publishingPortalSite = new SPSite(this.portalSiteUrl))
 179:         {
 180:             // Get the connection manager for the site where the catalog
 181:             // content will be rendered.
 182:             CatalogConnectionManager manager =
 183:                 new CatalogConnectionManager(publishingPortalSite);
 184:             // Use "Contains" to check whether a connection exists.
 185:             if (manager.Contains(this.publishingCatalogUrl))
 186:             {
 187:                 // Get the current settings
 188:                 CatalogConnectionSettings settings =
 189:                     manager.GetCatalogConnectionSettings(
 190:                         this.publishingCatalogUrl);
 191:  
 192:                 manager.DeleteCatalogConnection(this.publishingCatalogUrl);
 193:                 // Perform any other adds/Updates
 194:  
 195:                 // Finally Commit the update(s) to store.
 196:                 manager.Update();
 197:             }
 198:         }
 199:     }
 200: }

Here we used method getCatalogByName() (lines 126-147) which returns catalog from site collection by its name which work more stable than those which was used in MSDN example.

And in SearchConfigurationHelper.DeleteNewsPublishingCatalogConnection() method which is called in FeatureDeactivating() method we just called CatalogSubscriber.DeleteCatalog() method shown above (lines 176-197):

   1: public static void DeleteNewsPublishingCatalogConnection(SPWeb web)
   2: {
   3:     string newsCatalogUrl = ...;
   4:     var catalogSubscriber = new CatalogSubscriber(newsCatalogUrl, web.Url);
   5:     catalogSubscriber.DeleteCatalog();
   6: }

Hope that this information will help you if you will need to connect to publishing catalog programmatically.