Percussion rhythmyx CMS Web Service and c#
I've been asked by a client to implement a module that request Percussion Rhythmyx CMS Web Service to search for some documents and get their published url.
I had a big issue because the existing Rhythmyx Web Service does not provide the document's published url. They just provide the Assembly url. I had to implement a small algorithm to get the assembly url and build the published url:
1. I call the FindItem service to get services details
2. I call the GetAssemblyUrls service to get the assembly url of the found items
3. For each Item, I call via HTTP the AssemblyUrl with a specific snipet
4. I parse the returned snipet html content to pick the Published uri
5. I concatenete the resulted uri with the server base url to obtain the full publish http url.
Here is the source code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Services.Protocols;
using PROJ.Common.WSRhythmyx;
namespace PROJ.Common.Rhythmyx
{
/// <summary>
/// Wrapper class to access CMS-Rhythmyx web services
/// </summary>
public class RhythmyxManager : IDisposable
{
#region Properties
/// <summary>
/// Security service to login and logout
/// </summary>
private securitySOAP _svcSecurity;
/// <summary>
/// Content service to get content data (search/get items)
/// </summary>
private contentSOAP _svcContent;
/// <summary>
/// Contains all RhythmyxManager configuration data
/// </summary>
private RhythmyxConfig _config;
#endregion Properties
#region Constructor
/// <summary>
/// Initialize config data
/// </summary>
public RhythmyxManager()
{
_config = new RhythmyxConfig();
_svcSecurity = new securitySOAP();
_svcSecurity.Url = _buildAddress(_svcSecurity.Url);
}
#endregion Constructor
#region Public Methods
/// <summary>
/// Calls the login service to get the session ID
/// and build the proxy header to transmit the sessionID
/// </summary>
public void Login()
{
LoginResponse loginResponse = null;
try
{
//Call Service
loginResponse = _svcSecurity.Login(_getLoginRequest());
}
catch (SoapException ex)
{
_logException(ex, "login");
throw new CMSServiceCallException(ex);
}
catch (System.Net.WebException ex)
{
_logException(ex, "login");
throw new CMSNotAccessibleException(ex);
}
catch (Exception ex)
{
_logException(ex, "login");
throw new UnSpecifiedCMSException(ex);
}
//Create ContentService
_svcContent = _createContentService(loginResponse.PSLogin.sessionId);
}
/// <summary>
/// Logout the Rhythmyx session
/// </summary>
public void Logout()
{
if (_svcContent != null && _svcSecurity != null)
{
LogoutRequest logoutReq = new LogoutRequest();
logoutReq.SessionId = _svcContent.PSAuthenticationHeaderValue.Session;
//Call Service
try
{
_svcSecurity.Logout(logoutReq);
}
catch (SoapException ex)
{
_logException(ex, "logout");
throw new CMSServiceCallException(ex);
}
catch (System.Net.WebException ex)
{
_logException(ex, "logout");
throw new CMSNotAccessibleException(ex);
}
catch (Exception ex)
{
_logException(ex, "logout");
throw new UnSpecifiedCMSException(ex);
}
}
}
/// <summary>
/// Call FindItems webs service, and get details for each found items
/// Search is executed on title field OR on sys_contentid field (Item ID)
/// Search extract only pdf files
/// </summary>
/// <param name="searchParams">Search criterias</param>
/// <param name="getUrls">
/// If false, does not extract published url -> to optimize response time
/// Url can be extracted later with the GetItemUrl method
/// </param>
/// <returns>Items found</returns>
public RhythmyxItems.RhythmyxDataTable FindItems(string title, string id, string fileName, bool getUrls)
{
if (string.IsNullOrEmpty(title) && string.IsNullOrEmpty(id) && string.IsNullOrEmpty(fileName)) throw new CMSEmptySearchCriteriaException();
RhythmyxItems.RhythmyxDataTable res = new RhythmyxItems.RhythmyxDataTable();
#region Set search params
PSSearchField[] searchParams = new PSSearchField[2];
//Set service method params
FindItemsRequest req = new FindItemsRequest();
req.loadOperations = true;
req.PSSearch = new PSSearch();
req.PSSearch.PSSearchParams = new PSSearchParams();
req.PSSearch.useDbCaseSensitivity = false;
req.PSSearch.PSSearchParams.Parameter = searchParams;
///PDF only search
searchParams[0] = _createParam("sys_suffix", operatorTypes.equal, ".pdf", connectorTypes.and);
///Title search criteria
if (!string.IsNullOrEmpty(title))
{
req.PSSearch.PSSearchParams.Title = new PSSearchParamsTitle();
req.PSSearch.PSSearchParams.Title.Value = "%" + title + "%";
req.PSSearch.PSSearchParams.Title.Operator = operatorTypes.like;
req.PSSearch.PSSearchParams.Title.Connector = connectorTypes.and;
}
else if (!string.IsNullOrEmpty(id))
{
searchParams[1] = _createParam("sys_contentid", operatorTypes.equal, id, connectorTypes.and);
}
else
{
searchParams[1] = _createParam("filename", operatorTypes.like, "%" + fileName + "%", connectorTypes.and);
}
#endregion Set search params
#region Create returned structure
req.PSSearch.PSSearchParams.SearchResults = new PSSearchParamsSearchResults();
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField = new PSSearchResultField[10];
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[0] = _setSearchParamField("author");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[1] = _setSearchParamField("displaytitle");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[2] = _setSearchParamField("sys_title");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[3] = _setSearchParamField("filename");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[4] = _setSearchParamField("sys_lang");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[5] = _setSearchParamField("description");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[6] = _setSearchParamField("item_file_attachment_size");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[7] = _setSearchParamField("sys_suffix");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[8] = _setSearchParamField("reference");
req.PSSearch.PSSearchParams.SearchResults.PSSearchResultField[9] = _setSearchParamField("sys_currentview");
#endregion
#region Call Service
PSSearchResults[] list = null;
try
{
list = _svcContent.FindItems(req);
}
catch (SoapException ex)
{
_logException(ex, "FindItems");
throw new CMSServiceCallException(ex);
}
catch (System.Net.WebException ex)
{
_logException(ex, "FindItems");
throw new CMSNotAccessibleException(ex);
}
catch (Exception ex)
{
_logException(ex, "FindItems");
throw new UnSpecifiedCMSException(ex);
}
#endregion Call Service
#region Extract data from result structure
foreach (PSSearchResults val in list)
{
RhythmyxItems.RhythmyxRow row = res.NewRhythmyxRow();
row.Name = val.name;
row.ContentType = val.ContentType.name;
foreach (PSSearchResultsFields field in val.Fields)
{
if (!string.IsNullOrEmpty(field.Value))
{
switch (field.name)
{
case "sys_contentid": { row.Id = Convert.ToInt64(field.Value); break; }
case "author": { row.Author = field.Value; break; }
case "displaytitle": { row.DisplayTitle = field.Value; break; }
case "sys_title": { row.SystemTitle = field.Value; break; }
case "filename": { row.FileName = field.Value; break; }
case "sys_lang": { row.Language = field.Value; break; }
case "description": { row.Description = field.Value; break; }
case "item_file_attachment_size": { row.Size = field.Value; break; }
case "sys_suffix": { row.Type = field.Value; break; }
case "reference": { row.Reference = field.Value; break; }
case "sys_currentview": { row.CurrentView = field.Value; break; }
}
}
}
res.AddRhythmyxRow(row);
}
int count = 0;
long[] ids = new long[res.Count];
foreach (RhythmyxItems.RhythmyxRow item in res)
{
ids[count] = item.Id;
count++;
}
if (getUrls)
{
string[] intranetUrls = _getUrls(ids, EnvConf.EEnvironment.INTRANET);
string[] internetUrls = _getUrls(ids, EnvConf.EEnvironment.INTERNET);
count = 0;
foreach (RhythmyxItems.RhythmyxRow item in res)
{
item.IntranetUrl = intranetUrls[count];
item.InternetUrl = internetUrls[count];
count++;
}
}
#endregion Extract data from result structure
return res;
}
/// <summary>
/// Gets the Item's published URL
/// </summary>
/// <param name="id"></param>
/// <param name="siteName"></param>
/// <param name="environmentBaseUrl"></param>
/// <returns></returns>
public string GetItemUrl(long itemID, EnvConf.EEnvironment environment)
{
string res = "";
#region Set services parameters
GetAssemblyUrlsRequest req = new GetAssemblyUrlsRequest();
req.Id = new long[] { itemID };
req.Template = _config.TemplateName;
req.Context = _config.ContextID;
req.ItemFilter = _config.ItemFilter;
req.Site = _config.GetEnvironment(environment).SiteName;
#endregion Set services parameters
#region Call Service
GetAssemblyUrlsResponse resul = null;
try
{
resul = _svcContent.GetAssemblyUrls(req);
}
catch (SoapException ex)
{
_logException(new CMSServiceCallException(ex), "GetItemUrl");
}
catch (System.Net.WebException ex)
{
_logException(ex, "GetItemUrl");
throw new CMSNotAccessibleException(ex);
}
catch (Exception ex)
{
_logException(ex, "GetItemUrl");
throw new UnSpecifiedCMSException(ex);
}
#endregion Call Service
#region Get Url
if (resul != null && resul.Urls != null && resul.Urls.Length > 0)
{
string snipetUrl = resul.Urls[0];
string itemPublishedUri = _extractUrlFromHtml(snipetUrl);
if (!string.IsNullOrEmpty(itemPublishedUri))
res = _config.GetEnvironment(environment).BaseUrl + itemPublishedUri;
}
#endregion
return res;
}
#endregion Public Methods
#region Private methods
/// <summary>
/// Create the login request iwth config data
/// </summary>
/// <returns>Login request containing authentication credentials</returns>
private LoginRequest _getLoginRequest()
{
LoginRequest loginReq = new LoginRequest();
loginReq.Username = ConfigHelper.RhythmyxUserName;
loginReq.Password = ConfigHelper.RhythmyxPassword;
loginReq.Community = ConfigHelper.RhythmyxCommunity;
return loginReq;
}
/// <summary>
/// Create the ContentService and sets its Authentication header with the session ID
/// </summary>
/// <param name="sessionID">Session ID to keep session open</param>
/// <returns>Content Service</returns>
private contentSOAP _createContentService(string sessionID)
{
contentSOAP svcContent = new contentSOAP();
svcContent.Url = _buildAddress(svcContent.Url);
svcContent.PSAuthenticationHeaderValue = new PSAuthenticationHeader();
svcContent.PSAuthenticationHeaderValue.Session = sessionID;
return svcContent;
}
/// <summary>
/// Build the WebServiceAdress with the configuration keys
/// </summary>
/// <param name="srcAddress">default Service adress to be modified to get the configured address</param>
/// <returns>Target service address</returns>
private string _buildAddress(String srcAddress)
{
int pathStart = srcAddress.IndexOf("/Rhythmyx/");
return ConfigHelper.RhythmyxWSUrl.Substring(0, pathStart) + srcAddress.Substring(pathStart);
}
/// <summary>
/// Create a search param (search criteria and its value searched for) to provide to the findItem service
/// </summary>
/// <param name="name">Field name criteria</param>
/// <param name="op">Operator</param>
/// <param name="value">value searched for the specified field</param>
/// <param name="connector">Connector as "or", "and"</param>
/// <returns>Param object</returns>
private PSSearchField _createParam(string name, operatorTypes op, string value, connectorTypes connector)
{
PSSearchField param = new PSSearchField();
param.name = name;
param.Operator = op;
param.Value = value;
param.Connector = connector;
return param;
}
/// <summary>
/// Logs an exception via LogHelper
/// </summary>
/// <param name="ex">Exception to log</param>
/// <param name="operation">Operation that has aborted</param>
private void _logException(Exception ex, string operation)
{
LogHelper.Log(this, "CMS " + operation + " failed", ex, LogHelper.ELogSeverity.Error);
}
/// <summary>
/// Create search param field
/// </summary>
/// <param name="criteriaName"></param>
private PSSearchResultField _setSearchParamField(string criteriaName)
{
PSSearchResultField res = new PSSearchResultField();
res.name = criteriaName;
return res;
}
/// <summary>
/// Calls the Assembly URL Snipet and extract the Published URI from it
/// </summary>
/// <param name="snipetUrl">Url of the page containing the published URI</param>
/// <returns>Published URI</returns>
private string _extractUrlFromHtml(string snipetUrl)
{
string snipetContent = "";
try
{
snipetContent = HttpHelper.GetHttpRequest(snipetUrl, ConfigHelper.RhythmyxUserName, ConfigHelper.RhythmyxPassword);
}
catch (System.Net.WebException ex)
{
_logException(ex, "GetSnipetContent");
throw new CMSNotAccessibleException(ex);
}
catch (Exception ex)
{
_logException(ex, "GetSnipetContent");
throw new UnSpecifiedCMSException(ex);
}
if (!string.IsNullOrEmpty(snipetContent))
{
string startString = "href=\"";
snipetContent = snipetContent.Substring(snipetContent.IndexOf(startString) + startString.Length);
return snipetContent.Substring(0, snipetContent.IndexOf("\""));
}
return "";
}
/// <summary>
/// Calls the GetAssemblyUrls service to get the Item's assembly urls and construct the published urls
/// </summary>
/// <param name="ids"></param>
/// <param name="templateName"></param>
/// <param name="contextID"></param>
/// <param name="itemfilter"></param>
/// <param name="siteName"></param>
/// <param name="environmentBaseUrl"></param>
/// <returns></returns>
private string[] _getUrls(long[] ids, EnvConf.EEnvironment environment)
{
string[] res = new string[ids.Length];
#region Set services parameters
GetAssemblyUrlsRequest req = new GetAssemblyUrlsRequest();
req.Id = ids;
req.Template = _config.TemplateName;
req.Context = _config.ContextID;
req.ItemFilter = _config.ItemFilter;
req.Site = _config.GetEnvironment(environment).SiteName;
//req.FolderPath = "//Sites/intranet/Assets/Docs";//"1394";
#endregion Set services parameters
#region Call Service
GetAssemblyUrlsResponse resul = null;
try
{
resul = _svcContent.GetAssemblyUrls(req);
}
catch (SoapException ex)
{
_logException(new CMSServiceCallException(ex), "GetItemUrl");
}
catch (System.Net.WebException ex)
{
_logException(ex, "GetItemUrl");
throw new CMSNotAccessibleException(ex);
}
catch (Exception ex)
{
_logException(ex, "GetItemUrl");
throw new UnSpecifiedCMSException(ex);
}
#endregion Call Service
#region Get Urls
if (resul != null && resul.Urls != null && resul.Urls.Length > 0)
{
for (int i = 0; i < ids.Length; i++)
{
string snipetUrl = resul.Urls[i];
string itemPublishedUri = _extractUrlFromHtml(snipetUrl);
if(!string.IsNullOrEmpty(itemPublishedUri))
res[i] = _config.GetEnvironment(environment).BaseUrl + itemPublishedUri;
}
}
#endregion
return res;
}
#endregion Private methods
#region IDisposable Members
/// <summary>
/// Dispose all web services proxy and session data
/// </summary>
public void Dispose()
{
//Dispose resources
if (_svcSecurity != null)
{
_svcSecurity.Dispose();
_svcSecurity = null;
}
if (_svcContent != null)
{
_svcContent.Dispose();
_svcContent = null;
}
}
#endregion
}
}