In our project we had to bring the twitter and instagram content as content in EPiServer so editor can dragged twitter and instagram into content area.
Firstly, Our company uses spredfast to manage the social content. therefore, we had to use spredfast api to fetch the twitter and instagram content and show it as content in EPiServer content provider.
I am not going to show the spredfast api part. We will assume we are fetching twitter and instagram through api and saving it in our local database as json. We will retrieve this json coming from our local database and convert that json c# classes to EPiServer contetn classes and serve it to EPiServer content provider.
It is how it looks like
This is how it looks like after dragging four Social Content in Content Area
Parent class for Social Post For Json
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace NRL.Shared.Models.Social
{
public abstract class SocialPost
{
public string Entity_id { get; set; }
public object Id { get; set; }
/// <summary>
/// Gets Entity Id for API Callling. For Instagram Spredfast API accepts entity_id for twitter it accepts id
/// </summary>
public abstract string ApiEntityId { get; }
public abstract DateTimeOffset Created { get; set; }
public abstract SocialType SocialType { get; }
public abstract string AvailableText { get; }
}
public enum SocialType
{
Twitter,
Instagram,
}
}
Below is the trimmed version of twitter json and c# class
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Globalization;
namespace NRL.Shared.Models.Social.Twitter
{
public class TwitterPost : SocialPost
{
private DateTimeOffset? createdDate = null;
public override string ApiEntityId => Id_str;
public override SocialType SocialType => SocialType.Twitter;
public override DateTimeOffset Created
{
get
{
if (!createdDate.HasValue)
{
return DateTimeOffset.ParseExact(Created_at, "ddd MMM dd HH:mm:ss K yyyy", new CultureInfo("en-AU"));
}
return createdDate.Value;
}
set
{
createdDate = value;
}
}
public override string AvailableText
{
get
{
return string.IsNullOrWhiteSpace(Full_Text) ? Text ?? string.Empty : Full_Text;
}
}
public string Created_at { get; set; }
public string Id_str { get; set; }
public string Full_Text { get; set; }
public string Text { get; set; }
public bool Truncated { get; set; }
public Entities Entities { get; set; }
public User User { get; set; }
}
public class Medium
{
public string Media_url { get; set; }
}
public class User
{
public string Screen_name { get; set; }
}
public class Entities
{
public Medium[] Media { get; set; }
}
}
Below is the trimmed version of instagram json and c# class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NRL.Shared.Models.Social.Instagram
{
public class InstagramPost : SocialPost
{
private DateTimeOffset? createdDate = null;
public User User { get; set; }
public Images Images { get; set; }
public string Link { get; set; }
public override string ApiEntityId => Entity_id;
public override SocialType SocialType => SocialType.Instagram;
public override DateTimeOffset Created
{
get
{
if (!createdDate.HasValue)
{
return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(Created_Time));
}
return createdDate.Value;
}
set
{
createdDate = value;
}
}
public override string AvailableText
{
get
{
return string.IsNullOrWhiteSpace(Caption?.Text) ? Caption.Text ?? string.Empty : Caption.Text;
}
}
public string Created_Time { get; set; }
public Caption Caption { get; set; }
}
public class User
{
public string Username { get; set; }
}
public class Image
{
public string Url { get; set; }
}
public class Images
{
public Image Standard_Resolution { get; set; }
}
public class Caption
{
public string Text { get; set; }
}
}
We would now need twitter and instagram content class for EPiServer. EPiServer will need ContentFolder and Content Class to show in edit section therefore, we would need to convert TwitterPost and InstagramPost class to Twitter and Instagram Content Class.
SocialContent Base Class
using EPiServer.Core;
using EPiServer.DataAnnotations;
using EPiServer.Shell.ObjectEditing;
using NRL.Web.Business.Models.Pages;
using NRL.Web.Business.Selection;
using System.ComponentModel.DataAnnotations;
using System;
using NRL.Shared.Models.Social;
namespace NRL.Web.Business.Models.Social
{
[ContentType(GUID = "B2E4619D-9826-4D07-ADF7-9949E89CA6B8")]
public abstract class SocialContent : ContentBase
{
[Display(Name= "Social Type")]
[SelectOne(SelectionFactoryType = typeof(EnumSelectionFactory<SocialType>))]
public virtual SocialType SocialType { get; set; }
[Display(Name = "Id")]
public virtual string Id { get; set; }
public abstract ContentPageType ContentPageType { get; }
public abstract DateTime Published { get; }
public abstract string GetImageUrl();
public abstract int GetLength();
public abstract string GetSummary();
public abstract string GetTopic();
}
public enum ContentType
{
ContentFolder,
DateContentFolder,
TwitterContent,
InstagramContent,
}
}
TwitterContent Class
[ContentType(GUID = "E4B96F8A-7DFF-41E0-B42B-D4C36337DD80", DisplayName = "Twitter Content", AvailableInEditMode = false)]
public class TwitterContent : SocialContent
{
[Ignore]
public TwitterPost Post { get; set; }
public override ContentPageType ContentPageType => ContentPageType.Social;
public override DateTime Published => Post.Created.DateTime;
public override string GetImageUrl()
{
var image = Post.Entities?.Media?.FirstOrDefault();
return Business.Helpers.UrlHelper.ImageUrl(image?.Media_url);
}
public override int GetLength()
{
return Post.AvailableText?.Length ?? 0;
}
public override string GetSummary()
{
return Post.AvailableText?.SafeSubString(0, 100);
}
public override string GetTopic()
{
return "Twitter";
}
}
InstagramContent Class
[ContentType(GUID = "0B25DF14-285F-4647-B1B5-262E952C1781", DisplayName = "Instagram Content", AvailableInEditMode = false)]
public class InstagramContent : SocialContent
{
[Ignore]
public InstagramPost Post { get; set; }
public override ContentPageType ContentPageType => ContentPageType.Social;
public override DateTime Published => Post.Created.DateTime;
public override string GetImageUrl()
{
var image = Post.Images?.Standard_Resolution?.Url;
return Business.Helpers.UrlHelper.ImageUrl(image);
}
public override int GetLength()
{
return Post.Caption?.Text?.Length ?? 0;
}
public override string GetSummary()
{
return Post.Caption?.Text?.SafeSubString(0, 100);
}
public override string GetTopic()
{
return "Instagram";
}
}
Social FolderContent Class
[ContentType(GUID = "DE8A22EF-EDD4-4F42-BD8B-4DEFFE3C8244", DisplayName = "Spredfast Folder", AvailableInEditMode = false, GroupName = "Social")]
public class SocialContentFolder : ContentFolder
{
[Ignore]
public SocialType SocialType { get; set; }
}
Social Content Provider Class
public sealed class SocialContentProvider : ContentProvider
{
private static readonly object Padlock = new object();
private static SocialContentProvider instance = null;
private IdentityMappingService identityMappingService;
private IContentTypeRepository contentTypeRepository;
private IContentFactory contentFactory;
public const string DateFolderFormat = "dd-MM-yyyy";
public const string Key = "social";
public const string InstagramKey = "Instagram_Streams";
public const string TwitterKey = "Twitter_Streams";
private SocialContentProvider()
{
identityMappingService = ServiceLocator.Current.GetInstance<IdentityMappingService>();
contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();
contentFactory = ServiceLocator.Current.GetInstance<IContentFactory>();
/* Create provider root if not exists
* Provider root will be different for each site so that they can be cached and view seperately
* Add configuration settings for entry point and capabilites */
var socialRoot = GetEntryPoint();
var providerValues = new NameValueCollection();
providerValues.Add(ContentProviderElement.EntryPointString, socialRoot.ContentLink.ToString());
providerValues.Add(ContentProviderElement.CapabilitiesString, "Create, Edit, Delete, Search");
/* initialize and register the provider */
Initialize(Key, providerValues);
}
public static SocialContentProvider Instance
{
get
{
lock (Padlock)
{
if (instance == null)
{
instance = new SocialContentProvider();
}
return instance;
}
}
}
public SiteDefinition CurrentDefinition
{
get
{
return SiteDefinition.Current;
}
}
public List<InstagramPost> InstagramFeeds
{
get
{
// Load Instagram Json and convert to instagram post
}
}
public List<TwitterPost> TwitterPosts
{
get
{
// Load Twitter Json and convert to twiter post
}
}
protected override void SetCacheSettings(ContentReference contentReference, IEnumerable<GetChildrenReferenceResult> children, CacheSettings cacheSettings)
{
// Set a low cache setting so new items are fetched from data source, but keep the
// items already fetched for a long time in the cache.
cacheSettings.SlidingExpiration = System.Web.Caching.Cache.NoSlidingExpiration;
cacheSettings.AbsoluteExpiration = DateTime.Now.AddMinutes(5);
base.SetCacheSettings(contentReference, children, cacheSettings);
}
protected override ContentResolveResult ResolveContent(ContentReference contentLink)
{
var mappedItem = identityMappingService.Get(contentLink);
if (mappedItem == null)
return null;
return ResolveContent(mappedItem);
}
protected override ContentResolveResult ResolveContent(Guid contentGuid)
{
var mappedItem = identityMappingService.Get(contentGuid);
if (mappedItem == null)
return null;
return ResolveContent(mappedItem);
}
private IContent LoadContent(MappedIdentity mappedIdentity, ILanguageSelector languageSelector)
{
ContentType socialContentType;
SocialType socialType;
SetSocialContentType(mappedIdentity.ExternalIdentifier, out socialContentType, out socialType);
string entityId = null;
switch (socialContentType)
{
case ContentType.DateContentFolder:
return CreateFolder(mappedIdentity, socialContentType, mappedIdentity.ExternalIdentifier.Segments[4]);
case ContentType.ContentFolder:
return CreateFolder(mappedIdentity, socialContentType, socialType.ToString());
case ContentType.TwitterContent:
{
/* Get twitter entity id */
entityId = mappedIdentity.ExternalIdentifier.Segments[4];
var twitterPost = TwitterPosts.FirstOrDefault(t => t.ApiEntityId.Equals(entityId));
return CreateTwitter(mappedIdentity, socialContentType, twitterPost);
}
case ContentType.InstagramContent:
{
/* Get instagram entity id */
entityId = mappedIdentity.ExternalIdentifier.Segments[4];
var instagramPost = InstagramFeeds.FirstOrDefault(t => t.ApiEntityId.Equals(entityId));
return CreateInstagram(mappedIdentity, socialContentType, instagramPost);
}
}
return null;
}
protected override IContent LoadContent(ContentReference contentLink, ILanguageSelector languageSelector)
{
MappedIdentity mappedItem = identityMappingService.Get(contentLink);
if (mappedItem == null)
return null;
return LoadContent(mappedItem, languageSelector);
}
protected override IList<GetChildrenReferenceResult> LoadChildrenReferencesAndTypes(ContentReference contentLink, string languageID, out bool languageSpecific)
{
languageSpecific = false;
var children = new List<GetChildrenReferenceResult>();
/* If user is expanding root folder then show tiwtter and instagram folders */
if (EntryPoint.CompareToIgnoreWorkID(contentLink))
{
children.Add(new GetChildrenReferenceResult { ContentLink = identityMappingService.Get(MappedIdentity.ConstructExternalIdentifier(ProviderKey, $"{SocialType.Twitter.ToString()}/{ContentType.ContentFolder.ToString()}/{contentLink.ID}/{CurrentDefinition.Id}/"), true).ContentLink, IsLeafNode = false, ModelType = typeof(SocialContentFolder) });
children.Add(new GetChildrenReferenceResult { ContentLink = identityMappingService.Get(MappedIdentity.ConstructExternalIdentifier(ProviderKey, $"{SocialType.Instagram.ToString()}/{ContentType.ContentFolder.ToString()}/{contentLink.ID}/{CurrentDefinition.Id}/"), true).ContentLink, IsLeafNode = false, ModelType = typeof(SocialContentFolder) });
return children;
}
var mappedItem = identityMappingService.Get(contentLink);
if (mappedItem == null)
return children;
ContentType socialContentType;
SocialType socialType;
SetSocialContentType(mappedItem.ExternalIdentifier, out socialContentType, out socialType);
switch (socialType)
{
case SocialType.Instagram:
children.AddRange(GetInstagramChildReference(socialContentType, contentLink, mappedItem));
break;
case SocialType.Twitter:
children.AddRange(GetTwitterChildReference(socialContentType, contentLink, mappedItem));
break;
}
return children;
}
public static ContentFolder GetEntryPoint()
{
var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
var folder = contentRepository.GetBySegment(ContentReference.RootPage, Key, LanguageSelector.AutoDetect()) as ContentFolder;
if (folder == null)
{
folder = contentRepository.GetDefault<ContentFolder>(ContentReference.RootPage);
folder.Name = Key;
contentRepository.Save(folder, SaveAction.Publish, AccessLevel.NoAccess);
}
return folder;
}
private IEnumerable<GetChildrenReferenceResult> GetInstagramChildReference(ContentType socialContentType, ContentReference parentContentLink, MappedIdentity mappedItem)
{
var children = new List<GetChildrenReferenceResult>();
var instagramStreams = InstagramFeeds.GroupBy(g => g.Created.ToTimeZone(StateTimeZones.Nsw).Date).Select(s => new
{
Date = s.Key,
Feeds = s.ToList<InstagramPost>()
});
switch (socialContentType)
{
case ContentType.DateContentFolder:
{
var dateGroupedInstagrams = instagramStreams.FirstOrDefault(i => i.Date.ToString(DateFolderFormat).Equals(mappedItem.ExternalIdentifier.Segments[4]))?.Feeds;
if (dateGroupedInstagrams != null)
{
children.AddRange(GetChildren(
dateGroupedInstagrams,
(InstagramPost i) => MappedIdentity.ConstructExternalIdentifier(ProviderKey, $"{SocialType.Instagram.ToString()}/{ContentType.InstagramContent.ToString()}/{parentContentLink.ID}/{i.ApiEntityId.ToString()}"),
typeof(InstagramContent),
true));
}
}
break;
case ContentType.ContentFolder:
{
children.AddRange(GetChildren(
instagramStreams.Select(s => s.Date).ToList<DateTime>(),
(DateTime date) => MappedIdentity.ConstructExternalIdentifier(ProviderKey, $"{SocialType.Instagram.ToString()}/{ContentType.DateContentFolder.ToString()}/{parentContentLink.ID}/{date.ToString(DateFolderFormat)}"),
typeof(SocialContentFolder)));
}
break;
}
return children;
}
private IEnumerable<GetChildrenReferenceResult> GetTwitterChildReference(ContentType socialContentType, ContentReference parentContentLink, MappedIdentity mappedItem)
{
var children = new List<GetChildrenReferenceResult>();
var twitterPosts = TwitterPosts.GroupBy(g => g.Created.ToTimeZone(StateTimeZones.Nsw).Date).Select(s => new
{
Date = s.Key,
Posts = s.ToList<TwitterPost>()
});
switch (socialContentType)
{
case ContentType.DateContentFolder:
{
var dateGroupedTwitter = twitterPosts.FirstOrDefault(i => i.Date.ToString(DateFolderFormat).Equals(mappedItem.ExternalIdentifier.Segments[4]))?.Posts;
if (dateGroupedTwitter != null)
{
children.AddRange(GetChildren(
dateGroupedTwitter,
(TwitterPost i) => MappedIdentity.ConstructExternalIdentifier(ProviderKey, $"{SocialType.Twitter.ToString()}/{ContentType.TwitterContent.ToString()}/{parentContentLink.ID}/{i.ApiEntityId.ToString()}"),
typeof(TwitterContent),
true));
}
}
break;
case ContentType.ContentFolder:
{
children.AddRange(GetChildren(
twitterPosts.Select(s => s.Date).ToList<DateTime>(),
(DateTime date) => MappedIdentity.ConstructExternalIdentifier(ProviderKey, $"{SocialType.Twitter.ToString()}/{ContentType.DateContentFolder.ToString()}/{parentContentLink.ID}/{date.ToString(DateFolderFormat)}"),
typeof(SocialContentFolder)));
}
break;
}
return children;
}
private IEnumerable<GetChildrenReferenceResult> GetChildren<T>(List<T> inputList, Func<T, Uri> createUniqueUri, Type modelType, bool isLeafNode = false)
{
var children = new List<GetChildrenReferenceResult>();
foreach (var t in inputList)
{
var externalIdentifier = identityMappingService.Get(createUniqueUri(t), true);
children.Add(new GetChildrenReferenceResult
{
ContentLink = externalIdentifier.ContentLink,
IsLeafNode = isLeafNode,
ModelType = modelType
});
}
return children;
}
private void SetSocialContentType(Uri externalIdentifier, out ContentType socialContentType, out SocialType socialType)
{
/* Getting social type */
socialType = (SocialType)Enum.Parse(typeof(SocialType), RemoveEndingSlash(externalIdentifier.Segments[1]), true);
/* Getting social content type */
socialContentType = (ContentType)Enum.Parse(typeof(ContentType), RemoveEndingSlash(externalIdentifier.Segments[2]), true);
}
private IContent CreateContent(MappedIdentity mappedIdentity, ContentType contentType, Type modelType, string name, DateTime createDateTime)
{
return CreateContent(mappedIdentity.ContentLink.ID, mappedIdentity.ContentGuid, int.Parse(RemoveEndingSlash(mappedIdentity.ExternalIdentifier.Segments[3])), contentType, modelType, name, createDateTime);
}
private IContent CreateContent(int contentId, Guid contentGuid, int parentContentId, ContentType contentType, Type modelType, string name, DateTime createDateTime)
{
/* Find parent */
var parentLink = EntryPoint;
/* Getting parent id */
if (contentType != ContentType.ContentFolder)
parentLink = new ContentReference(parentContentId, ProviderKey);
EPiServer.DataAbstraction.ContentType epiContentType = contentTypeRepository.Load(modelType);
var content = contentFactory.CreateContent(epiContentType);
content.ContentTypeID = epiContentType.ID;
content.ParentLink = parentLink;
content.ContentGuid = contentGuid;
content.ContentLink = new ContentReference(contentId, ProviderKey);
content.Name = name;
var securable = content as IContentSecurable;
securable.GetContentSecurityDescriptor().AddEntry(new AccessControlEntry(EveryoneRole.RoleName, AccessLevel.Read));
var versionable = content as IVersionable;
if (versionable != null)
{
versionable.Status = VersionStatus.Published;
versionable.IsPendingPublish = false;
versionable.StartPublish = createDateTime.AddDays(-1);
}
var changeTrackable = content as IChangeTrackable;
if (changeTrackable != null)
{
changeTrackable.Created = createDateTime;
changeTrackable.Changed = createDateTime;
changeTrackable.Saved = createDateTime;
}
return content;
}
private string RemoveEndingSlash(string virtualPath)
{
return !string.IsNullOrEmpty(virtualPath) && virtualPath[virtualPath.Length - 1] == '/' ? virtualPath.Substring(0, virtualPath.Length - 1) : virtualPath;
}
private SocialContentFolder CreateFolder(MappedIdentity mappedIdentiy, ContentType contentType, string name)
{
var content = CreateContent(mappedIdentiy, contentType, typeof(SocialContentFolder), name, DateTime.Now.ToStatesDateTime(StateTimeZones.Nsw.ToString())) as SocialContentFolder;
return content;
}
private InstagramContent CreateInstagram(int contentId, Guid contentGuid, int parentContentId, ContentType contentType, InstagramPost instagramPost)
{
var name = $"@{instagramPost.User.Username}: {instagramPost.Caption?.Text?.SafeSubString(0, 200)}";
var content = CreateContent(contentId, contentGuid, parentContentId, contentType, typeof(InstagramContent), name, instagramPost.Created.ToTimeZone(StateTimeZones.Nsw).DateTime) as InstagramContent;
content.Id = instagramPost.ApiEntityId.ToString();
content.SocialType = SocialType.Instagram;
content.Post = instagramPost;
return content;
}
private InstagramContent CreateInstagram(MappedIdentity mappedIdentiy, ContentType contentType, InstagramPost instagramPost)
{
return CreateInstagram(mappedIdentiy.ContentLink.ID, mappedIdentiy.ContentGuid, int.Parse(RemoveEndingSlash(mappedIdentiy.ExternalIdentifier.Segments[3])), contentType, instagramPost);
}
private TwitterContent CreateTwitter(int contentId, Guid contentGuid, int parentContentId, ContentType contentType, TwitterPost twitterPost)
{
var name = $"@{twitterPost.User.Screen_name}: {twitterPost.AvailableText.SafeSubString(0, 200)}";
var content = CreateContent(contentId, contentGuid, parentContentId, contentType, typeof(TwitterContent), name, twitterPost.Created.ToTimeZone(StateTimeZones.Nsw).DateTime) as TwitterContent;
content.Id = twitterPost.ApiEntityId.ToString();
content.SocialType = SocialType.Twitter;
content.Post = twitterPost;
return content;
}
private TwitterContent CreateTwitter(MappedIdentity mappedIdentiy, ContentType contentType, TwitterPost twitterPost)
{
return CreateTwitter(mappedIdentiy.ContentLink.ID, mappedIdentiy.ContentGuid, int.Parse(RemoveEndingSlash(mappedIdentiy.ExternalIdentifier.Segments[3])), contentType, twitterPost);
}
private ContentResolveResult ResolveContent(MappedIdentity mappedItem)
{
ContentType socialContentType;
SocialType socialType;
SetSocialContentType(mappedItem.ExternalIdentifier, out socialContentType, out socialType);
Type type = null;
switch (socialContentType)
{
case ContentType.DateContentFolder:
case ContentType.ContentFolder:
type = typeof(SocialContentFolder);
break;
case ContentType.TwitterContent:
type = typeof(TwitterContent);
break;
case ContentType.InstagramContent:
type = typeof(InstagramContent);
break;
}
var contentRefernce = new ContentReference(mappedItem.ContentLink.ID, ProviderKey);
var contentType = contentTypeRepository.Load(type);
return new ContentResolveResult()
{
ContentLink = contentRefernce,
UniqueID = mappedItem.ContentGuid,
ContentUri = ConstructContentUri(contentType.ID, contentRefernce, mappedItem.ContentGuid)
};
}
}
Initializing Content Provider
Registering SocialContentProvider with EPiServer
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ContentProviderConfig : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
/* Register provider to episerver this can be done through config as well */
var providerManager = context.Locate.Advanced.GetInstance<IContentProviderManager>();
providerManager.ProviderMap.AddProvider(SocialContentProvider.Instance);
}
public void Uninitialize(InitializationEngine context) { }
}
Social Repository Descriptor
This class is social repository descriptor which tells the social component what is the key and root of the component and its contained and creatable types.
[ServiceConfiguration(typeof(IContentRepositoryDescriptor))]
public class SocialRepositoryDescriptor : ContentRepositoryDescriptorBase
{
protected Injected<IContentProviderManager> ContentProviderManager { get; set; }
public override string Key => SocialContentProvider.Key;
public override string Name => SocialContentProvider.Key;
public override IEnumerable<ContentReference> Roots { get { return new List<ContentReference> { ContentProviderManager.Service.GetProvider(SocialContentProvider.Key).EntryPoint }; } }
public override IEnumerable<Type> ContainedTypes { get { return new[] { typeof(TwitterContent), typeof(InstagramContent) }; } }
public override IEnumerable<Type> MainNavigationTypes { get { return new[] { typeof(ContentFolder), typeof(SocialContentFolder) }; } }
public override IEnumerable<Type> CreatableTypes { get { return new[] { typeof(TwitterContent), typeof(InstagramContent) }; } }
public bool ChangeContextOnItemSelection { get { return true; } }
}
ChangeContextOnItemSelection allows EPiServer to load content item preview on click
Social Component Registering
This class registers the Social Component in asset panel and after this Social Tab will be available in asset pane.
[Component]
public class SocialComponent : ComponentDefinitionBase
{
public SocialComponent()
: base("epi-cms/widget/HierarchicalList")
{
Categories = new string[] { "content" };
Title = "Social";
Description = "All the social for the given site";
SortOrder = 1000;
PlugInAreas = new[] { PlugInArea.Assets };
Settings.Add(new Setting("repositoryKey", SocialContentProvider.Key));
}
}
Template Descriptor
We can reuse alloy preview controller to preview the Twitter and Instagram content
[TemplateDescriptor(
Inherited = true,
TemplateTypeCategory = TemplateTypeCategories.MvcController,
AvailableWithoutTag = false,
ModelType = typeof(TwitterContent),
Tags = new[] { RenderingTags.Edit, RenderingTags.Preview })]
[TemplateDescriptor(
Inherited = true,
TemplateTypeCategory = TemplateTypeCategories.MvcController,
AvailableWithoutTag = false,
ModelType = typeof(InstagramContent),
Tags = new[] { RenderingTags.Edit, RenderingTags.Preview })]
public class SocialCardPreviewController : ActionControllerBase
{
public ActionResult Index(SocialContent currentContent)
{
var model = new NewsPageViewModel(currentContent));
return View(model);
}
}
TblMappedIdentity table
This part is tricky as we need to convert our social feed into EPiServer content. We can create unique url and then map that unique url to int id through above mentioned table. So when EPiServer comes back with content link or content link id we can fetch back the unique url and fetch back our social details.
Couple of key notes
In our project we had multiple sites where we wanted to show different folder structure and different content for each sites. However, the content provider is intializes once in lifetime so we can only set one root folder. Having said that we would need to create the sub folder based on the site guid so we can distinguish between different site and fetch content based on current site.