I have been working on implementing a custom RSS feed in sitecore 6.4. My custom behaviour is very limited, all i effectively wanted to is add a link for author (our author field is a reference field so we cannot use the built in author attribution).
I overrode RenderItem() on the PublicFeed class so that i could make use of my own implementation of the FeedRenderer class (where the author logic is housed). my approach follows this pattern outlined by John West for adding your own rendering behaviour:
public class MyPUblicFeed: PublicFeed
{
protected override SyndicationItem RenderItem(Item item)
{
Assert.ArgumentNotNull(item, "item");
Control rendererControl = FeedUtil.GetFeedRendering(item);
if (rendererControl == null)
{
return null;
}
using (new ContextItemSwitcher(item))
{
var myRenderer= rendererControl as MyFeedRenderer;
if (myRenderer!= null)
{
myRenderer.Database = SitecoreHelper.CurrentDatabase.Name;
return myRenderer.RenderItem();
}
var renderer = rendererControl as Sitecore.Web.UI.WebControls.FeedRenderer;
if (renderer != null)
{
renderer.Database = SitecoreHelper.CurrentDatabase.Name;
return renderer.RenderItem();
}
}
throw new InvalidOperationException("FeedRenderer rendering must be of Sitecore.Web.UI.WebControls.FeedRenderer type");
}
}
And now for my rendering class:
public class MyFeedRenderer: Sitecore.Web.UI.WebControls.FeedRenderer
{
public override SyndicationItem RenderItem()
{
Item item = base.GetItem();
var syndicationItem = base.RenderItem();
//unfortunately we have to parse params again :(
FeedRenderingParameters feedRenderingParameter = FeedRenderingParameters.Parse(base.Parameters);
AddAuthor(syndicationItem, item, feedRenderingParameter);
return syndicationItem;
}
private static void AddAuthor(SyndicationItem syndicationItem, Item item, FeedRenderingParameters feedRenderingParameter)
{
//clear out authors added by base class
syndicationItem.Authors.Clear();
//logic for adding author here
}
}
this all works great, outputting exactly what i want, but the caching element doesn't appear to be working. I have set the cacheable flag on the actual item itself with a timespan of 01:00:00. This didn't appear to work - if i put a breakpoint in either of the above classes it is hit everytime the feed is requested.
so then i tried to enable caching at a control level, turning caching on with VaryByData for the MyFeedRenderer rendering. alas this isn't working either, the breakpoint is hit every time.
Can anyone offer any advice on this matter? the documentation simply recommends turning it on on the actual feed item, not at the Rendering level, but neither seem to be working for me. Interestingly HTML caching is working elsewhere - is RSS also put into the HTML cache?
Thanks in advance,
Nick
-Ensure the Cacheable checkbox in the feed definition item is checked.
-Ensure that you have published the feed definition item.
-If you do not populate the Cache Duration field in the feed definition item, it should default to one day.
-Feeds appear to cache in Sitecore.Syndication.FeedManager.Cache rather than the site output cache. Inspect that cache object in the Visual Studio debugger after calling your feed, and then again after calling that feed a second time, to try to see if any records appear, and if multiple cache keys appear for the same feed. Investigate the Render() method; if PublicFeed.IsCacheable() returns false (depending on the Cacheable field in the feed definition item), PublicFeed.Render() does not cache.
-Ensure nothing else clears caches between your requests for the feed.
SDN forum thread: http://sdn.sitecore.net/forum/ShowPost.aspx?PostID=40591
Related
I'm trying to develop a CodeFluent aspect to set a property of a entity to be a full-text index.
I've found this link, which does something similar to what I'm aiming for.
http://blog.codefluententities.com/2012/11/27/using-the-sql-server-template-producer-to-generate-clustered-indexes/
However this uses a SQL template producer. Are there anyway to set a property to be a full-text index entirely in the aspect itself, so I don't have to install/maintain both template producer and aspect for all projects?
Here's the C# aspect code I have so far:
public class FullTextIndexing : IProjectTemplate
{
public static readonly XmlDocument Descriptor;
public const string Namespace = "http://www.softfluent.com/aspects/samples/FullTextIndexing";
static FullTextIndexing()
{
Descriptor = new XmlDocument();
Descriptor.LoadXml(
#"<cf:project xmlns:cf='http://www.softfluent.com/codefluent/2005/1' defaultNamespace='FullTextIndexing'>
<cf:pattern name='Full Text Indexing' namespaceUri='" + Namespace + #"' preferredPrefix='fti' step='Tables'>
<cf:message class='_doc'>CodeFluent Full Text Indexing Aspect</cf:message>
<cf:descriptor name='fullTextIndexing'
typeName='boolean'
category='Full Text Indexing'
targets='Property'
defaultValue='false'
displayName='Full-Text Index'
description='Determines if property should be full text indexed.' />
</cf:pattern>
</cf:project>");
}
public Project Project { get; set; }
public XmlDocument Run(IDictionary context)
{
if (context == null || !context.Contains("Project"))
{
// we are probably called for meta data inspection, so we send back the descriptor xml
return Descriptor;
}
// the dictionary contains at least these two entries
Project = (Project)context["Project"];
// the dictionary contains at least these two entries
XmlElement element = (XmlElement)context["Element"];
Project project = (Project)context["Project"];
foreach (Entity entity in project.Entities)
{
Console.WriteLine(">>PROPERTY LOGGING FOR ENTITY "+entity.Name.ToUpper()+":<<");
foreach (Property property in entity.Properties)
{
Log(property);
if(MustFullTextIndex(property))
{
Console.WriteLine("CHANGING PROPERTY");
property.TypeName = "bool";
Log(property);
}
}
}
// we have no specific Xml to send back, but aspect description
return Descriptor;
}
private static bool MustFullTextIndex(Property property)
{
return property != null && property.IsPersistent && property.GetAttributeValue("fullTextIndexing", Namespace, false);
}
private static void Log(Property property)
{
Console.WriteLine(property.Trace());
}
}
EDIT ONE:
Following Meziantou's answer, I'm trying to create a template producer, but it's giving me compilation errors when I try to add the new template producer to the project producers list, so I'm probably doing it wrong.
The error says:
Cannot convert type 'CodeFluent.Model.Producer' to 'CodeFluent.Producers.SqlServer.TemplateProducer'
Here's the code I have thus far:
public XmlDocument Run(IDictionary context)
{
if (context == null || !context.Contains("Project"))
{
// we are probably called for meta data inspection, so we send back the descriptor xml
return Descriptor;
}
// the dictionary contains at least these two entries
XmlElement element = (XmlElement)context["Element"];
Project project = (Project)context["Project"];
CodeFluent.Producers.SqlServer.TemplateProducer producer = new CodeFluent.Producers.SqlServer.TemplateProducer();
producer.AddNamespace("CodeFluent.Model");
producer.AddNamespace("CodeFluent.Model.Persistence");
producer.AddNamespace("CodeFluent.Producers.SqlServer");
Console.WriteLine(producer.Element);
//TODO: Need to figure out how to modify the actual template's contents
project.Producers.Add(producer); //Error happens here
// we have no specific Xml to send back, but aspect description
return Descriptor;
}
In the sample code, the aspect is used only because it has a descriptor. Descriptors are used by CodeFluent Entities to populate the property grid:
<cf:descriptor name="IsClusteredIndex" typeName="boolean" targets="Property" defaultValue="false" displayName="IsClusteredIndex" />
So when you set the value of this property to true or false, the xml attribute ns:IsClusteredIndex is added or removed from the xml file.
Then the SQL Template reads the value of the attribute to generate the expected SQL file:
property.GetAttributeValue("sa:IsClusteredIndex", false)
So the aspect is not mandatory, but provides a graphical interface friendly way to add/remove the attribute. If you don't need to integrate into the graphical interface, you can safely remove the aspect.
If your goal is to integrate into the graphical interface, you need an aspect (XML or DLL) or a producer. If you don't want to create a producer, you can embed the template into your aspect. During the build, you can extract the SQL template and add the SQL Template producer to the project, this way everything is located in the aspect.
I'm using AngleSharp to parse HTML5 at the moment what I'm doing is wrapping the elements I want to parse with a little bit of HTML to make it a valid HTML5 and then use the parser on that, is there a better of doing it? meaning, parsing specific elements directly and validate that the structure is indeed HTML5?
Hm, a little example would be nice. But AngleSharp does support fragment parsing, which sounds like the thing you want. In general fragment parsing is also applied when you set properties like InnerHtml, which transform strings to DOM nodes.
You can use the ParseFragment method of the HtmlParser class to get a list of nodes contained in the given source code. An example:
using AngleSharp.Parser.Html;
// ...
var source = "<div><span class=emphasized>Works!</span></div>";
var parser = new HtmlParser();
var nodes = parser.ParseFragment(source, null);//null = no context given
if (nodes.Length == 0)
Debug.WriteLine("Apparently something bad happened...");
foreach (var node in nodes)
{
// Examine the node
}
Usually all nodes will be IText or IElement types. Also comments (IComment) are possible. You will never see IDocument or IDocumentFragment nodes attached to such an INodeList. However, since HTML5 is quite robust it is very likely that you will never experience "errors" using this method.
What you can do is to look for (parsing) errors. You need to provide an IConfiguration that exposes an event aggregator, which collects such events. The simplest implementation for aggregating only such events (without possibility of adding / removing multiple handlers) is the following:
using AngleSharp.Events;
// ...
class SimpleEventAggregator : IEventAggregator
{
readonly List<HtmlParseErrorEvent> _errors = new List<HtmlParseErrorEvent>();
public void Publish<TEvent>(TEvent data)
{
var error = data as HtmlParseErrorEvent;
if (error != null)
_errors.Add(error);
}
public List<HtmlParseErrorEvent> Errors
{
get { return _errors; }
}
public void Subscribe<TEvent>(ISubscriber<TEvent> listener) { }
public void Unsubscribe<TEvent>(ISubscriber<TEvent> listener) { }
}
The simplest way to use the event aggregator with a configuration is to instantiate a new (provided) Configuration. Here as a sample snippet.
using AngleSharp;
// ...
var errorEvents = new SimpleEventAggregator();
var config = new Configuration(events: errorEvents);
Please note: Every error that is reported is an "official" error (according to W3C spec.). These errors do not indicate that the provided code is malicious or invalid, just that something is not following the spec and that a fallback had to be applied.
Hope this answers your question. If not, then please let me know.
Update Updated the answer for the latest version of AngleSharp.
I can't sort this weird issue out and I have tried anything and everything I can think of.
I got 5 pages, everyone of them passing variables with navigation this way:
Pass:
NavigationSerice.Navigate(new Uri("/myPage.xaml?key=" + myVariable, UriKind.Relative));
Retrieve:
If (NavigationContext.QueryString.ContainsKey(myKey))
{
String retrievedVariable = NavigationContext.QueryString["myKey"].toString();
}
I open a list on many pages and one of the pages automatically deletes an item from the list actualProject (actualProject is a variable for a string list). Then, when I go so far back that I reach a specific page - the app throws an exception. Why? I have no idea.
The code that deletes the item:
// Remove the active subject from the availible subjects
unlinkedSubjects.Remove(actualSubject);
unlinkedsubjectsListBox.ItemsSource = null;
unlinkedsubjectsListBox.ItemsSource = unlinkedSubjects;
Then the page that throws the exception's OnNavigatedTo event:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (NavigationContext.QueryString.ContainsKey("key"))
{
actualProject = NavigationContext.QueryString["key"];
try
{
//Read subjectList from IsolatedStorage
subjectList = readSetting(actualProject) != null ? (List<String>)readSetting(actualProject) : new List<String>();
//Put the subjectList into the subjectListBox
subjectListBox.ItemsSource = subjectList;
//Set the subjectsPageTitle to the "actualProject" value, to display the name of the current open project at the top of the screen
subjectsPageTitle.Text = actualProject;
}
catch (Exception)
{
if (language.Equals("en."))
{
// Language is set to english
MessageBox.Show("Couldn't open the project, please try again or please report the error to Accelerated Code - details on the about page");
}
else if (language.Equals("no."))
{
// Language is set to norwegian
MessageBox.Show("Kunne ikke åpne prosjektet, vennligst prøv igjen eller rapporter problemet til Accelerated Code - du finner detaljer på om-siden");
}
}
}
}
Exception:
_exception {System.ArgumentException: Value does not fall within the expected range.} System.Exception {System.ArgumentException}
My theory:
The app kind of loads the currently opened and modified List. Is that possible? No idea.
So there are a number of ways to pass data between pages.
The way you have chosen is the least suggested.
You can use the PhoneApplicationService.Current dictionary but this is messy also if you have a ton of variables, doesn't persist after app shut down and could be simplified.
I wrote a free DLL that kept this exact scenario in mind called EZ_iso.
You can find it here
Basically what you would do to use it is this.
[DataContractAttribute]
public class YourPageVars{
[DataMember]
public Boolean Value1 = false;
[DataMember]
public String Value2 = "And so on";
[DataMember]
public List<String> MultipleValues;
}
Once you have your class setup you can pass it easily between pages
YourPageVars vars = new YourPageVars { /*Set all your values*/ };
//Now we save it
EZ_iso.IsolatedStorageAccess.SaveFile("PageVars",vars);
That's it! Now you can navigate and retrieve the file.
YourPageVars vars = (YourPageVars)EZ_iso.IsolatedStorageAccess.GetFile("PageVars",typeof(YorPageVars));
This is nice because you can use it for more than navigation. You can use it for anything that would require Isolated storage. This data is serialized to the device now so even if the app shuts down it will remain. You can of course always delete the file if you choose as well.
Please make sure to refer to the documentation for any exceptions you have. If you still need help feel free to hit me up on twitter #Anth0nyRussell or amr#AnthonyRussell.info
We recently upgraded from Ektron 8.6 to 9.0 (Ektron CMS400.NET, Version: 9.00 SP2(Build 9.0.0.249)).
I have some code (below) which we use to display links to items in a taxonomy. Under 8.6, this would show library items if they had been added to the taxonomy. As of 9.0, it no longer displays library items. It still works for DMS items and normal pages (all first class content in Ektron).
private List<ContentData> getTaxonomyItems(long TaxonomyId)
{
listContentManager = new ContentManager();
criteria = new ContentTaxonomyCriteria(ContentProperty.Id, EkEnumeration.OrderByDirection.Ascending);
criteria.PagingInfo = new Ektron.Cms.PagingInfo(400); // there's a lot of items and I don't want to page them.
criteria.AddFilter(TaxonomyId, true); // this gets sub taxonomies too :)
List<ContentData> contentList = listContentManager.GetList(criteria);
return contentList;
}
(I would love to simply say to users to use the DMS instead of the library, but we have a security requirement and I'm not aware of a way I can enforce security on DMS items like we can with library items by dropping a webconfig file in the library folder.)
Is this a bug that anyone else has experienced?
Or is there a problem with my code (did an API change in the upgrade to 9.0)?
Thanks.
I ended up emailing Ektron support in Sydney (I'm in Australia), and they said:
I would expect ContentManager to only return content, not library
items – must have been a loophole which is now closed. Taxonomy is the
way to go.
So I used some of the code they provided and came up with the following, which appears to work...
private List<TaxonomyItemData> getTaxonomyItems(long TaxonomyId)
{
List<TaxonomyItemData> list = new List<TaxonomyItemData>();
TaxonomyManager taxManager = new TaxonomyManager(Ektron.Cms.Framework.ApiAccessMode.Admin);
TaxonomyCriteria taxonomyCriteria = new Ektron.Cms.Organization.TaxonomyCriteria();
taxonomyCriteria.AddFilter(Ektron.Cms.Organization.TaxonomyProperty.Path,
Ektron.Cms.Common.CriteriaFilterOperator.StartsWith, GetTaxonomyPathById(TaxonomyId));
List<TaxonomyData> TaxonomyDataList = taxManager.GetList(taxonomyCriteria);
foreach (TaxonomyData taxd in TaxonomyDataList)
{
TaxonomyData taxTree = taxManager.GetTree(taxd.Path,
1, // depth. doesn't seem to work. have to manually tranverse lower taxonomies.
true, // include items
null,
Ektron.Cms.Common.EkEnumeration.TaxonomyType.Content,
Ektron.Cms.Common.EkEnumeration.TaxonomyItemsSortOrder.taxonomy_item_display_order);
foreach (TaxonomyItemData taxItem in taxTree.TaxonomyItems)
{
list.Add(taxItem);
}
}
return list;
}
private static String GetTaxonomyPathById(long taxonomyId)
{
TaxonomyManager tMgr = new TaxonomyManager();
TaxonomyData tData = tMgr.GetItem(taxonomyId);
if (tData != null)
{
return tData.Path;
}
return "";
}
This code fetches items for all the child taxonomies as well as returning library items.
The one problem is that it fetches duplicates for some items, but those are easy to clean out.
I was also told by Ektron that...
TaxonomyManager.GetItem(“{path}”) is a more efficient way to get the
categories
That's why I've included the GetTaxonomyPathById() method (inspired by this blog post: http://www.nimbleuser.com/blog/posts/2009/iterating-through-ektron-content-in-multiple-taxonomies-via-directly-interfacing-with-search-indexing-services/ )
I'm coming from a SQL Server background, and experimenting with Redis in .NET using ServiceStack. I don't mean for Redis to be a full replacement for SQL Server, but I just wanted to get a basic idea of how to use it so I could see where we might make good use of it.
I'm struggling with what I think is a pretty basic issue. We have a list of items that are maintained in a couple of different data stores. For the sake of simplicity, assume the definition of the item is basic: an integer id and a string name. I'm trying to do the following:
Store an item
Retrieve an item if we only know its id
Overwrite an existing item if we only know its id
Show all the items for that specific type
And here's some of the code I've put together:
public class DocumentRepositoryRedis
{
private static string DOCUMENT_ID_KEY_BASE = "document::id::";
public IQueryable<Document> GetAllDocuments()
{
IEnumerable<Document> documentsFromRedis;
using (var documents = new RedisClient("localhost").As<Document>())
{
documentsFromRedis = documents.GetAll();
}
return documentsFromRedis.AsQueryable();
}
public Document GetDocument(int id)
{
Document document = null;
using (var redisDocuments = new RedisClient("localhost").As<Document>())
{
var documentKey = GetKeyByID(document.ID);
if (documentKey != null)
document = redisDocuments.GetValue(documentKey);
}
return document;
}
public void SaveDocument(Document document)
{
using (var redisDocuments = new RedisClient("localhost").As<Document>())
{
var documentKey = GetKeyByID(document.ID);
redisDocuments.SetEntry(documentKey, document);
}
}
private string GetKeyByID(int id)
{
return DOCUMENT_ID_KEY_BASE + id.ToString();
}
}
It all seems to work - except for GetAllDocuments. That's returning 0 documents, regardless of how many documents I have stored. What am I doing wrong?
The typed Redis client also gives you access to the non-typed methods - since Redis ultimately doesn't know or care about your object types. So when you use the client.SetEntry() method, it bypasses some of the typed client's features and just stores the object by a key. You'll want to use the client.Store method since it goes ahead and creates a SET in Redis with all the object IDs related to your type. This SET is important because it's what the GetAll method relies on to serve back all the objects to you. The client.Store method does infer the ID automatically so you'll want to play around with it.
You'd change your GetDocument(int id) and SaveDocument(Document document) methods to use the client.GetById(string id) method, and you'd use client.Store(T value) method. You won't need your GetKeyByID() method anymore. I believe your Document object will need an "Id" property for the typed client to infer your object ID.