The Adxstudio Portals contain a URL Provider that will return a URL for the CMS entities so the content can be navigated to within a browser. In order for the URL Provider to be able to retrieve a URL to content associated with a custom entity, a custom URL Provider must be implemented in the portal. This is an integral step to allowing search to provide links to search results of custom entity content within a portal.

Custom URL Provider

The following is an example of a custom URL Provider

using System;
using System.Linq;
using Adxstudio.Xrm.Web.Providers;
using Microsoft.Xrm.Portal;
using Microsoft.Xrm.Portal.Cms;
using Microsoft.Xrm.Portal.Web;
using Microsoft.Xrm.Portal.Web.Providers;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

namespace Site.Configuration
{
	/// <summary>
	/// Custom <see cref="IEntityUrlProvider"/> for the site. Adds support for kbarticle URLs.
	/// </summary>
	/// <remarks>
	/// Support for kbarticles is added to this provider so that they can appear in index search results.
	/// </remarks>
	public class UrlProvider : IEntityUrlProvider, IContentMapEntityUrlProvider
	{
		private readonly IEntityUrlProvider _innerEntityUrlProvider;
		private readonly IContentMapEntityUrlProvider _innerContentMapEntityUrlProvider;

		public UrlProvider(IEntityUrlProvider innerEntityUrlProvider, IContentMapEntityUrlProvider innerContentMapEntityUrlProvider)
		{
			_innerEntityUrlProvider = innerEntityUrlProvider;
			_innerContentMapEntityUrlProvider = innerContentMapEntityUrlProvider;
		}

		public string GetUrl(OrganizationServiceContext context, Entity entity)
		{
			return entity.IsPortalKbArticle() ? GetWebKbArticleUrl(context, entity, PortalContext.Current) : _innerEntityUrlProvider.GetUrl(context, entity);
		}

		public ApplicationPath GetApplicationPath(OrganizationServiceContext context, Entity entity)
		{
			return _innerEntityUrlProvider.GetApplicationPath(context, entity);
		}

		public string GetUrl(Adxstudio.Xrm.Cms.ContentMap map, Adxstudio.Xrm.Cms.EntityNode node)
		{
			return _innerContentMapEntityUrlProvider.GetUrl(map, node);
		}

		public ApplicationPath GetApplicationPath(Adxstudio.Xrm.Cms.ContentMap map, Adxstudio.Xrm.Cms.EntityNode node)
		{
			return _innerContentMapEntityUrlProvider.GetApplicationPath(map, node);
		}

		private static string GetWebKbArticleUrl(OrganizationServiceContext serviceContext, Entity entity, IPortalContext portalContext)
		{
			if (!entity.IsPortalKbArticle())
			{
				throw new ArgumentException("Entity is not a web knowledge base article.", "entity");
			}

			if (portalContext == null)
			{
				return null;
			}

			var website = serviceContext.CreateQuery("adx_website").FirstOrDefault(w => w.GetAttributeValue<Guid>("adx_websiteid") == portalContext.Website.Id);

			if (website == null)
			{
				return null;
			}
	
			
			var articlePage = serviceContext.GetPageBySiteMarkerName(website, "KB Article");

			if (articlePage == null)
			{
				return null;
			}

			var articlePageUrl = serviceContext.GetUrl(articlePage);

			if (articlePageUrl == null)
			{
				return null;
			}

			var articleUrl = new UrlBuilder(articlePageUrl);

			articleUrl.QueryString.Set("id", entity.Id.ToString());

			return WebsitePathUtility.ToAbsolute(portalContext.Website, articleUrl.PathWithQueryString);
		}
	}
}

Example extension method to assert entity type.

using Microsoft.Xrm.Sdk;
using Site.Library;

namespace Site.Configuration
{
	internal static class EntityExtensions
	{
		public static bool IsPortalKbArticle(this Entity entity)
		{
			return entity != null
				&& entity.LogicalName == "kbarticle"
				&& entity.GetAttributeValue<int?>("statecode").GetValueOrDefault() == (int)Enums.KbArticleState.Published
				&& entity.GetAttributeValue<bool?>("msa_publishtoweb").GetValueOrDefault();
		}
	}
}

Add a Custom Dependency Provider

To inject your custom URL Provider we must create a custom dependency provider.

using Adxstudio.Xrm.Web.Providers;
using Microsoft.Xrm.Portal.Configuration;
using Microsoft.Xrm.Portal.Web.Providers;

namespace Site.Configuration
{
	/// <summary>
	/// Custom <see cref="IDependencyProvider"/> for the site. Injects <see cref="UrlProvider"/> as
	/// supported <see cref="IEntityUrlProvider"/>.
	/// </summary>
	public class DependencyProvider : Adxstudio.Xrm.Web.Configuration.DependencyProvider
	{
		public DependencyProvider(string portalName) : base(portalName) {}

		public override T GetDependency<T>(string name)
		{
			if (typeof(T) == typeof(IEntityUrlProvider))
			{
				return new UrlProvider(base.GetDependency<IEntityUrlProvider>(name), base.GetDependency<IContentMapEntityUrlProvider>(name)) as T;
			}

			return base.GetDependency<T>(name);
		}
	}
}

The custom Dependency Provider must be specified in the web.config file by adding the following element to the portals config section. Note: The namespace may vary depending on your project.

<dependencyProvider type="Site.Configuration.DependencyProvider, Site" />

The resulting configuration will look like the following

<microsoft.xrm.portal>
	<portals>
		<add name="Xrm" websiteName="Customer Portal">
			<websiteSelector type="Microsoft.Xrm.Portal.Cms.WebsiteSelectors.NameWebsiteSelector, Microsoft.Xrm.Portal" />
			<dependencyProvider type="Site.Configuration.DependencyProvider, Site" />
			<crmEntitySecurityProvider type="Site.Configuration.SecurityProvider, Site" />
		</add>
	</portals>
</microsoft.xrm.portal>