When working on features for clients, there are times when a need arises to create a custom tool. Often the first instinct is to create a SPEAK app. This is not always the best solution. Sometimes you need something a bit more context aware and seamless in its integration. This is where editor panels come in to play. In reality I find that an editor panel is often far easier to create, and that it can create a better user experience.
There are many editor panels that we use every day, such as the one that shows up when you select a folder which gives you options of items to insert as well as shows the child items. A media folder is similar by having buttons to upload media. Sometimes there are features of a site that may be better managed in the context of that feature, even though all the data for that feature is not necessarily in one place.
Creating an editor panel is a simple process, you simply need to create a page and point an item in the core database to it. The page does not really have many rules as Sitecore will simply load it in an iframe. There are several parameters that are passed to the page such as language, version, id, and database. If you need the context of the website, you will need to find that or fake it in the context. That is a topic for a different post though.
To keep this short, I am going to assume that you can create a page and access the parameters passed in. This could even be multiple pages since it is in the context of an iframe. All you need to do is create an editor item in Applications/Content Editor/Editors, and point it to your page. To have it load on an item, you need to go onto the item (or the standard values of that item template) and add it in the Editors field. It really is as simple as that.
An editor like this can work very well for imports, and on bucketed data as you can create a view for the data and even a way to edit it in a structured way that is more efficient than navigating the entire bucket.
Sunday, June 19, 2016
Saturday, April 23, 2016
Using Sitecore xDB with a ASP.NET Handler (ashx file)
In Sitecore using xDB requires a Session in order to maintain the context as I have mentioned in my previous posts about using xDB without an active Session. While it is possible in a handler to persist data without the Session, it is definitely preferred as it is far more reliable than any methods you can use to work around it.
By default in .NET a handler does not maintain the Session as it is intended to be very light weight. This is very easy to resolve though by simply making it inherit IReadOnlySessionState or IRequiresSessionState. Once your handler inherits one of those interfaces it will get the Session and xDB will work.
By default in .NET a handler does not maintain the Session as it is intended to be very light weight. This is very easy to resolve though by simply making it inherit IReadOnlySessionState or IRequiresSessionState. Once your handler inherits one of those interfaces it will get the Session and xDB will work.
Wednesday, September 30, 2015
Managing the Sitecore xDB Contact Card without requiring the Tracker Revisited
Intro
Previously I mentioned how to update the contact card
without the session. I found that I was
occasionally still getting locking issues, so I looked into it a bit
further. What I had was a little
redundant for saving the update.
Saving Updates
When saving the updates really you do not need to call
SaveAndReleaseContact, FlushContactToXdb should do the trick, but you should
call it with the overload that takes two parameters and passin a new
ContactSaveOptions with release set to true, and a new LeaseOwner.
var manager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
manager.FlushContactToXdb(contact, new ContactSaveOptions(true, new LeaseOwner(AnalyticsSettings.ClusterName, LeaseOwnerType.WebCluster)));
manager.SaveAndReleaseContact(contact);
Conclusion
While this is only a short note on the last post, be sure to
wrap your contact manager to ensure you maintain the same contact card between
your updates and when you flush the card or your changes will be lost, and only
identify the contact when absolutely necessary to prevent a chance for
potential conflict with locking.
Monday, August 31, 2015
Sitecore Caching Revisited
Previously I wrote about using the Sitecore Custom
Cache. Unfortunately I don’t think I put
enough detail in that post. Some may
have been a little confused by it.
Creating the Cache
Really creating a cache is almost too easy, you simply
create a class that inherits from CustomCache and then implement SetObject and GetObject as I said in my previous post.
public class MyCustomCache : CustomCache
{
public MyCustomCache(string name, long maxSize)
: base(name, maxSize)
{
}
public void SetObject(string key, object value)
{
base.SetObject(key, value, Sitecore.Reflection.TypeUtil.SizeOfObject());
}
public new object GetObject(object key)
{
return base.GetObject(key);
}
}
The Cache Manager
The problem with stopping here is though we have the cache
we do not have an instance of it created.
This is also a very simple task.
The easiest way to implement it is just to create a static instance of
it somewhere. More properly though would
be to create a static cache manager.
public static class MyCacheManager
{
private static readonly MyCustomCache Cache = new MyCustomCache(
"My.NameSpace.Custom.Cache",
StringUtil.ParseSizeString(Sitecore.Configuration.Settings.GetSetting("My.Custom.Cache.Size.Setting.Name", "5MB")));
public static object Get(string key)
{
return Cache.GetObject(key);
}
public static void Set(string key, object value)
{
Cache.SetObject(key, value);
}
}
Clearing the Cache
It really is that easy.
It may be wise though to add a cache clearing mechanism as well maybe
add a new static method to the manager.
public static void Clear()
{
Cache.Clear();
}
Of course you still need to trigger this call using the Sitecore
Pipelines which is also very easy, just add the call anywhere Sitecore normally
clears the cache.
Managing the Sitecore xDB Contact Card without requiring the Tracker
The Normal Way
When working with a user profile in Sitecore 8, we generally
get the contact card by using Tracker.Current.Contact. Of course if the tracker is null we will call
Tracker.Start(), and of course that would also mean the Contact is null so we
would also need to call Tracker.Identify().
This works out well in most situations since the tracker is
maintained across the session. If you
don’t have a session though this could mean a big headache. You would normally need to create a Session
and an HttpContext, then create and push a tracker into the stack. While this is not excessively resource
intensive when you are doing a lot of calls it can add up quickly.
Getting the Card
There is an alternate way to get the card though. You can use the ContactManager to load the
contact by username, if there is not a contact identified by that username then
it will return null and you can then create a new Contact and insert it.
var user = Context.User.Identity.Name;
if (string.IsNullOrEmpty(user)) return null;
var contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
var contact = contactManager.LoadContactReadOnly(user);
Saving Updates
If you are updating this contact keep in mind that it will
not persist across the session so you will need to flush it to the database
before the end of the request. This is
also done with the Contact Manager by calling FlushContactToXdb and passing the
contact, then by calling SaveAndReleaseContact also passing in the
contact.
var manager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
manager.FlushContactToXdb(contact);
manager.SaveAndReleaseContact(contact);
Submitting your Session
Just to be on the safe side of things it is not a bad idea
to submit your context. You may be
thinking that that requires a session.
Fortunately Sitecore can create a very lightweight session context for
this use. You can use the
SessionContextManager and call GetSession passing in the contact id, an empty
guid, and two Boolean true values. Once
you have the session you can call Submit on the SessionContextManager and pass
this session in.
var contextManager =
Factory.CreateObject("tracking/sessionContextManager", true) as SessionContextManagerBase;
var session = contextManager.GetSession(c.ContactId, Guid.Empty, true, true);
contextManager.Submit(session);
While this solution is not perfect, you can expand on this solution
to build a robust contact manager that does not depend on there being an active
session.
Friday, July 31, 2015
Sitecore Serialization Synchronization Service (S4)
I know most of my posts have been instructional so far, so it is about time I make a bit of an announcement. I am working on a new tool for Sitecore Development. I would love input from the community of what their biggest expectations for this tool would be.
This tool is called the Sitecore Serialization Synchronization Service or S4 for short. Basically it is a synchronization tool that connects to your Sitecore instance and keeps serialized items in sync. You update an item and the item in the file system will be updated as well, this also goes the other way around. You pull the latest from source control and it adds or modifies files in your sync folder and it pushes those changes into Sitecore.
At this point the serialization engine is almost complete, the biggest key for me here is performance. I do not want this tool to take much memory or processor power. I also do not expect to have any sort of Visual Studio integration, at least not that is required for use of the tool.
I do not plan to implement any sort of Code Gen or Code Deployment in this tool. Visual Studio (and other IDE's) already have this functionality, so though it might be easier with a tool it is not 100% necessary. I plan to do a tutorial on Code Gen with serialized Sitecore Items or possibly with S4 as a data provider.
S4 also will not deploy on build since it is not integrated with Visual Studio. While at first this may sound like a breaking feature, S4 is intended to keep your items in sync at all time so there is no need to deploy the items on build.
If there are any questions, comments or requests with this tool please let me know. I appreciate all feedback. If the response is large enough I will push the development of this tool a bit harder. As it stands right now I should hopefully have a beta ready (likely just base functionality without a friendly UI or anything) in the next month or so, so please check back often for updates.
This tool is called the Sitecore Serialization Synchronization Service or S4 for short. Basically it is a synchronization tool that connects to your Sitecore instance and keeps serialized items in sync. You update an item and the item in the file system will be updated as well, this also goes the other way around. You pull the latest from source control and it adds or modifies files in your sync folder and it pushes those changes into Sitecore.
At this point the serialization engine is almost complete, the biggest key for me here is performance. I do not want this tool to take much memory or processor power. I also do not expect to have any sort of Visual Studio integration, at least not that is required for use of the tool.
I do not plan to implement any sort of Code Gen or Code Deployment in this tool. Visual Studio (and other IDE's) already have this functionality, so though it might be easier with a tool it is not 100% necessary. I plan to do a tutorial on Code Gen with serialized Sitecore Items or possibly with S4 as a data provider.
S4 also will not deploy on build since it is not integrated with Visual Studio. While at first this may sound like a breaking feature, S4 is intended to keep your items in sync at all time so there is no need to deploy the items on build.
If there are any questions, comments or requests with this tool please let me know. I appreciate all feedback. If the response is large enough I will push the development of this tool a bit harder. As it stands right now I should hopefully have a beta ready (likely just base functionality without a friendly UI or anything) in the next month or so, so please check back often for updates.
Leveraging Sitecore's Caching Mechanism
In the time I have been developing in Sitecore I have seen many different approaches to caching, but only once have I seen someone actually use caching based on Sitecore's cache service. It is in fact almost how scary it is to implement.
Basically all you have to do is create a class that inherits from Sitecore.Caching.CustomCache. Of course you need to implement a constructor that calls the base constructor, and override the SetObject and GetObject methods. That is pretty much it though.
This cache will be handled in the same admin page as the other Sitecore caches, and if you install the Cache Admin or Cache Manager shared source modules you can easily use them to monitor and manage the caches without needing to build your own custom admin page.
I highly recommend at least investigating this approach to anyone considering putting in a custom cache on their site. It is efficient enough for most scenarios as well as being very easy to setup potentially saving you hours of coding.
Basically all you have to do is create a class that inherits from Sitecore.Caching.CustomCache. Of course you need to implement a constructor that calls the base constructor, and override the SetObject and GetObject methods. That is pretty much it though.
This cache will be handled in the same admin page as the other Sitecore caches, and if you install the Cache Admin or Cache Manager shared source modules you can easily use them to monitor and manage the caches without needing to build your own custom admin page.
I highly recommend at least investigating this approach to anyone considering putting in a custom cache on their site. It is efficient enough for most scenarios as well as being very easy to setup potentially saving you hours of coding.
Monday, June 29, 2015
Getting Started with Sitecore Content Search using Lucene or Solr
Using Sitecore Content Search with Solr is basically
identical to that with Lucene at the general use level, with the exception of
the configuration. Solr does have some
more advanced features that Lucene does not have.
To get started though with either
all we need is a search context. We can
get this using the context item (or any item for that matter). We simply cast the item to the
SitecoreIndexableItem class and pass it into ContentSearchManager.CreatesearchContext
and that will give us our context.
This is generally set up
as:
using (var context = ContentSearchManager.CreateSearchContext((SitecoreIndexableItem)item)){
//search code….
}
using (var context = ContentSearchManager.CreateSearchContext((SitecoreIndexableItem)item)){
//search code….
}
From inside this context we can
get our search results by calling the generic method GetQueryable that takes
all types that derive from SearchResultItem on our context and using Linq to
filter our results.
An example where we get all
items under the current item:
var results = context.GetQueryable<SearchResultItem>().Where(i => i.Paths.Contains(item.ID));
var results = context.GetQueryable<SearchResultItem>().Where(i => i.Paths.Contains(item.ID));
If you have custom fields you
want to search on it is highly recommended that you create a class that derives
from SearchResultItem and add your fields as properties. What is really nice with this is that
Sitecore content search will reflect on this class and do the search on those
properties.
An example with an item
with an author field:
public class MyCustomResultItem: SearchResultItem{
public string Author { get; set; }
}
public class MyCustomResultItem: SearchResultItem{
public string Author { get; set; }
}
var results = context.GetQueryable<MyCustomResultItem>().Where(i => string.Equals(i.Author,”me”));
At this point you can iterate through results and get the
items using .GetItem() on each resulting item.
Using SignalR with Sitecore
If you are unfamiliar with SignalR and need to set it up I
will be posting an intro to SignalR on my other blog (Programming Medeley) in a
few days. Otherwise reading this post I
will assume that you have a basic understanding of what SignalR is and how it
works.
When developing in Sitecore with SignalR there is one major
issue that you will run into (aside from basic routing). That issue is the fact that you do not have a
Sitecore site context within your SignalR context. This causes issues with getting item URLs or
using the Glass Mapper.
Missing Site Context
The fix for this is actually quite simple though. You simply need to set the Sitecore site
context on every request.
To get the SiteContext you simply call
Sitecore.Sites.SiteContextFactory.GetSiteContext and pass in the hostname (which
can be found within the context of the Hub as this.Context.Request.Url.Host)
and an empty path and it will give you a SiteContext.
Once we have this all we have to do is set the current site
to this context by setting Sitecore.Context.Site to this SiteContext.
I use the single line:
Sitecore.Context.Site = Sitecore.Context.Site ?? SitecontextFactory.GetSiteContext(this.Context.Request.Url.Host, “/”);
Sitecore.Context.Site = Sitecore.Context.Site ?? SitecontextFactory.GetSiteContext(this.Context.Request.Url.Host, “/”);
Routing
As for the routing issue that you
may run into, this is the same as setting up WebApi. You need to ensure the route is registered in
the Application_Start of the Global.asax.
The command for this is (using
the Global Configuration):
GlobalConfiguration.Configuration.Routes.MapHttpRoute(“signalrHub”, “signalr/{controller}”);
GlobalConfiguration.Configuration.Routes.MapHttpRoute(“signalrHub”, “signalr/{controller}”);
Sunday, May 31, 2015
Saving Form data with the Web Forms For Marketers MVC Form API
As many people have noticed with the Web Forms For Marketers MVC Forms the data does not serialize correctly so using custom save actions you cannot get the data from the form correctly.
There are a few rather complex workarounds for this mostly involving parsing the form from the form values in the http response.
The easiest solution to this issue is to create your own Form Processor which you can inject with a configuration file.
There are a few rather complex workarounds for this mostly involving parsing the form from the form values in the http response.
The easiest solution to this issue is to create your own Form Processor which you can inject with a configuration file.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <wffm> <formProcessors> <processor type="MyNamespace.Forms.MVC.Processors.FormProcessor, MyNamespace.Forms.MVC" /> </formProcessors> </wffm> </sitecore> </configuration>
The processor itself is actually quite easy to write you simply need a class that inherits Sitecore.Forms.MVC.Processors.FormProcessor, then you can implement your save action in the FormSuccessValidation event.
namespace MyNamespace.Forms.MVC.Processors { public class FormProcessor : FormProcessor { public FormProcessor() { SuccessValidation += FormSuccessValidation; } private void SaveForm(FormModel form) { var Fields = form.Sections.SelectMany(i => i.Fields); } public void FormSuccessValidation(object source, FormEventArgs args) { SaveForm(args.Form); } } }You can see from my code that you can access the fields through the given model in the form.Sections property. I find that this is much easier to implement than a custom save action (though it will not show up in the drop down list which may be a problem for some people, you can use this to store the data to access in a custom save action).If you have any questions related to Web Forms For Marketers please don't hesitate to ask as I have spent a lot of time in that code lately.
Error 500.19 Configuration File Too Long Sitecore Web.config
It is quite well known that the web.config file in Sitecore is monstrous. It takes very little to make it large enough to get the 500.19 .NET error with the file being too large. Most resolutions to this involve modifying the registry, but it does not really have to be that difficult.
The easiest solution is to take the Sitecore node out of the file and put it in a separate file similar to how the connection strings are pulled out.
The easiest way to get the whole node selected would be to use a text editor that supports regular expressions and use the expression (<sitecore)[\s\S]*(</sitecore>) to select it. Then you simply cut it and past it into a file (I generally put it in a file called Sitecore.config in the App_Config folder).
After this you need to put an empty Sitecore node where you removed the old one from and add a configsource attribute that points to the new config file.
<sitecore configSource="App_Config/Sitecore.config" />
The easiest solution is to take the Sitecore node out of the file and put it in a separate file similar to how the connection strings are pulled out.
The easiest way to get the whole node selected would be to use a text editor that supports regular expressions and use the expression (<sitecore)[\s\S]*(</sitecore>) to select it. Then you simply cut it and past it into a file (I generally put it in a file called Sitecore.config in the App_Config folder).
After this you need to put an empty Sitecore node where you removed the old one from and add a configsource attribute that points to the new config file.
<sitecore configSource="App_Config/Sitecore.config" />
Tuesday, April 14, 2015
Language Fallback: A word of caution
The
concept of Language Fallback in Sitecore is great, actually having the ability
to have Sitecore return content in a different language if none exists in the
current context is tremendously helpful.
Even the concept behind this functionality is pretty straight
forward.
The
module simply checks if there is a version in the current language. If not it checks if there is a fallback for
the current language and repeats the process on that language if there is until
it either finds a version of the item or creates an empty item to return.
Due
to the simplicity of this module it has continued to work with every version of
Sitecore since it was introduced, and will likely continue working for years to
come.
Unfortunately,
if you think about the recursion in this module there is a large problem that
can happen. Endless loops can occur when
languages eventually fall back to the original language.
The
current module
in the marketplace written by Alex Shyba does not have checks to ensure that
you do not have an endless loop. Nikola Gotsev and I ran into
this problem a while back where we had a language that would fall back to
another language that fell back to the first language. Not knowing this content error had happened
led us into a bit of a nightmare of disabling modules and checking changes to
find what brought our environment to its’ knees.
Nikola
actually posted a fix
for this last month. This fix simply
includes checking if we have already fallen back to a specific language (by
enumerating fallback languages in a list).
This solves the problem no matter how complex the chain is.
Thursday, April 9, 2015
Dynamic Key Placeholders Revisited
A little over a year ago I did a post on Dynamic Key Placeholders, since then I have noticed
there was some information lacking, such as what versions of Sitecore it works
with, and how to get Placeholder Settings to work.
From my testing this should work in every version of Sitecore from
6.5 to 8.0, and likely will continue working in previous and future versions as
well.
The Old Way
By default when
using dynamic key placeholders you can create placeholder settings for each
index of the key. Such as if the
original key was Placeholder, you
would need to create settings for Placeholder1,
Placeholder2, etc… This can lead to a
lot of work, and a cluttered collection of placeholder settings.
A New Way to Do It
An easier way would
be to create an override of the GetAllowedRenderings processor to allow for an
experience more like the original when creating placeholder settings.
A Quick Modification to the Original
In the original
source for the dynamic key placeholders we need to make a simple modification. In the part of the code that we set the key I have added the # symbol in there to allow us to
split it apart far easier to create an override for Placeholder Settings. You can use any symbol you want, but keep I
will continue with this one for the rest of the post.
// Count represents
how many of the same container have already been added to the page
if (renderings.Any())
{
_dynamicKey = _key + "#" + renderings.Count();
}
Our New Processor
The big changes though are in the addition of overriding the
GetAllowedRenderings processor. The change
is not too complex as we are basically mirroring what Sitecore is already
doing, but with the exception of overriding the placeholder key.
To override the key
we want to split the key first into the segments by the / character and then
for each segment we use the split on the # to remove our index from the dynamic
key giving us the original key.
After that we want
to try to get the placeholder item using our cleaned up key. Of course if there is a device specified we
should get the item in the context of that device.
If the Item exists
we should get the list of renderings it supports.
public class GetAllowedRenderings : Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings
{
/// <summary>
/// This is a method
based on the original Sitecore method to allow for dynamic placeholder keys
/// </summary>
/// <param name="args">The Placeholder
Rendering Args (Device, Key, Database, etc...)</param>
public new void Process(GetPlaceholderRenderingsArgs args)
{
Assert.IsNotNull((object)args, "args");
//Remove the dynamic
indexing from the key
var key = string.Join("/", args.PlaceholderKey.Split('/').Select(i => i.Split('#')[0]));
//Get the
placeholder item, if there is a Device selected get it using that Device
Context
Item placeholderItem = null;
if (ID.IsNullOrEmpty(args.DeviceId))
{
placeholderItem = Client.Page.GetPlaceholderItem(key, args.ContentDatabase, args.LayoutDefinition);
}
else
{
using (new DeviceSwitcher(args.DeviceId, args.ContentDatabase))
{
placeholderItem = Client.Page.GetPlaceholderItem(key, args.ContentDatabase, args.LayoutDefinition);
}
}
//If there was a
placeholder item (Placeholder Settings) set the allowed renderings
if (placeholderItem != null)
{
args.HasPlaceholderSettings
= true;
bool
allowedControlsSpecified;
args.PlaceholderRenderings
= this.GetRenderings(placeholderItem,
out
allowedControlsSpecified);
if
(allowedControlsSpecified)
args.Options.ShowTree = false;
}
}
}
Our Configuration Change
Finally we have the configuration file to enable
this override, this is simply done by replacing the processor in the
getPlaceholderRenderings pipeline.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<getPlaceholderRenderings>
<processor patch:instead="*[@type='Sitecore.Pipelines.GetPlaceholderRenderings.GetAllowedRenderings,
Sitecore.Kernel']" type="[Your Method], [Your Assembly] />
</getPlaceholderRenderings>
</pipelines>
</sitecore>
</configuration>
You can find the source from this post at https://github.com/musicman89/Sitecore-Dynamic-Key-Placeholders
Update (4/10/2015):
You can find a
Shared Source Module created at the Sitecore Hackathon 2015 that incorporates
this concept at https://marketplace.sitecore.net/en/Modules/Integrated_Dynamic_Placeholders.aspx
by Jamie Stump and Mark Servais, do note that I had no involvement in the
creation of this module.
Thank you Jamie
Stump for pointing this out.
Subscribe to:
Posts (Atom)