Tuesday, May 27, 2014

Sitecore and Solr: Configure DataImportHandler for External Data Extraction

These instructions are based on a Sitecore 7.0 installation of SOLR and will use the folder structures based on it with Jetti, SOLR 4.5, multiple cores, etc.  It will also assume the installation of SOLR is up and running.

1) Edit the solrconfig.xml file for the current core
Example: \example\solr\core\conf\

2) Add new request handler:

----------- 
<!-- DataImporter -->
  <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
      <str name="config">data-config.xml</str>
    </lst>
  </requestHandler>
------------

3) Create new file "data-config.xml" in the same folder or wherever the path is as specified in the requestHandler

Example of file content:

<?xml version="1.0" encoding="UTF-8" ?>
<dataConfig>
<dataSource name="ds1"
            type="JdbcDataSource"
            driver="com.microsoft.sqlserver.jdbc.SQLServerDriver"
            url="jdbc:sqlserver://server;databaseName=dbname"
            user="user"
            password="pwd"
            readOnly="true" />
    <document>
        <entity name="user"
            dataSource="ds1"
            query="select * from [dbo].[users]"
            >
            <field column="id" name="_id" />
            <field column="email" name="_email" />           
            <field column="first_name" name="_fname" />
            <field column="last_name" name="_lname" />       
        </entity>
    </document>
</dataConfig>

Datasource type is jdbcdatasource
Driver is com.microsoft.sqlserver.jdbc.SQLServerDriver
Url format is jdbc:sqlserver://server;databaseName=dbname
Also make sure column names do not have spaces or weird characters

4) Make sure the dataimport jar files are in place.  You can get the solr-dataimporthandler*.jar from the dist folder.

Copy the files into:
\example\solr-webapp\webapp\WEB-INF\lib\


5) Install SQL Server JDBC driver
http://msdn.microsoft.com/en-us/sqlserver/aa937724

Run the downloaded installer and you will be prompted to unzip the files to a location. 
Unzip to any location.

In the unzipped folder look for \sqljdbc_3.0\enu\sqljdbc4.jar and copy to:

\example\solr-webapp\webapp\WEB-INF\lib\


6) Verify that everything has worked by browsing to you SOLR admin URL and selecting the core with the dataimporter.  Click the dataimport tab and execute.  If all is well, your indexes would be created and you can perform queries as usual.


Friday, May 16, 2014

Obtain Sitecore Context Item From Custom Control Field

Let's say you are not satisfied with the out-of-the-box Image field control or you just want to build a totally new cool custom field.  You would do this by inheriting from either an existing field or from the Sitecore.Web.UI.HtmlControls class.

public class Image : Sitecore.Shell.Applications.ContentEditor.Image
{ ... }

Since this is a field control, there is no Execute method like a Command that accepts a CommandContext input parameter, how do we extract the context item that contains the current field we are looking at?

You can try calling the base method GetItem() but this returns the Content Editor item and is not what you want.  You can use Intellisense to bring up a whole bunch of other base methods that has the word "Item" in the name but none of those help.  They either return the Content Editor item or the field control as an item but not exactly the context item that contains the current field you are looking at.

Fortunately there is Viewstate information.  This information is only available when you dig deeper into the base class Sitecore.Web.UI.HtmlControls.  If you inspect this class via a decompiler like dotPeek, you can see extensive references to the GetViewStateString or GetViewStateBool methods.  These methods obtain values saved as key/value pairs that are part of the Viewstate information.  The key that we are concerned with in this scenario is "ItemId".

Simply put, to obtain the context item id, we have to put this property in our custom field control class:

public string ItemID
{
            get
            {
                return base.GetViewStateString("ItemID");
            }
            set
            {
                Assert.ArgumentNotNullOrEmpty(value, "value");
                base.SetViewStateString("ItemID", value);
            }
}

Then, to resolve the actual Sitecore item, you can call something like:

Sitecore.Data.Database.GetDatabase("master").GetItem(ItemID)
 
 

Friday, May 9, 2014

Sitecore: Multi-site Setup with Bing Analytics Files (BingSiteAuth.xml)

Recently, we ran into a dilemma involving a Sitecore multi-site environment.  As we know, this means, one instance of Sitecore, one content tree, one code base, one root directory, but multiple sites reading from different nodes in the tree.  Since we only have one code base running on one instance of IIS, how can we possibly have different files for each domain in the root level?

Simply put, Bing analytics requires that each website has a file called "BingSiteAuth.xml" be dropped into the root directory of each website.  This file contains a unique ID number that the Bing search engine uses to identify your site as legitimate.  In a normal single website instance with or without Sitecore, we can just copy this file into the root directory.  Easy enough, problem solved.  But in a multi-site Sitecore setup with one code base shared among all your sites, how can this be achieved?  All your sites would need to have a file with the same file name but since all the sites share a common set of files, all you can have is one copy of that file shared by all your sites.  This is not just a Sitecore dilemma, but a dilemma for all CMS sites that have the multi-site setup option.  Of course if Bing altered their way to make sure every file have unique names, that would be great so you can have 50 files with different names for 50 different sites.  But this is not the case, so we have to find a way to get around it.

One way to achieve this is by creating an Http Handler.  As we all know, an Http Handler basically tells IIS what to do when a file with a specific name or extension is encountered.  For our purposes, we would need to create a handler for the "BingSiteAuth.xml" file name.  Let's begin.

1) Create a new handler class that inherits from System.Web.IHttpHandler:



2) You must implement ProcessRequest and IsReusable methods to satisfy the interface.

3) In ProcessRequest, this is where you do the bulk of the work.  You need to detect the current website and output some text.  We have decided not to stream a file, but rather save the file information in a text field in each of the website home content items.  But since this is a standard Http Handler and not something in Sitecore, we do NOT have access to the Sitecore context because it is not yet resolved at the time the handler is fired.  What do we do now?  Rest assured that even though the context is not available, we can use Sitecore factory methods to pull up site information in multiple steps.  All you have to do is figure out if the "sc_site" parameter is set.  If it is, we have the site name.  If not, we could use the domain name to perform the lookup.  Of course, all this information has to match the <sites> defined in the web.config file.



At this point, we should have the site information.  Now we need to get at the starting node of this site and check the field that contains the text for the BingSiteAuth.xml file.  If the field is not defined or there is no value for the field, we throw a 404 exception.



4) Now we have the working handler but we have to register he handler.  To do that we have to append an entry to the web.config section in two places:

<system.webServer><handlers>

and

<system.web><httpHandlers>



Keep in mind that the same entry is used in both places except the version inside httpHandlers does not have the name attribute.

5) Make sure to modify the template of the home item to include a field called "BingSiteAuth".



6) Rebuild your code and try browsing to:

http://www.mycoolsite.com/BingSiteAuth.xml.  You should see a file with the exact information stored in the field above.






Sitecore Tips: Renaming Items

Renaming an item is a very straightforward process, right?  Right click on an item in the content tree, click rename, and a dialog appears asking for a new name.  It cannot get simpler than that.

Invalid Names

Right out of the box, Sitecore performs validations on the new item name you choose.  Sitecore does not like invalid characters and makes sure you do not use them or it prompts you for another name.  Can you get around this?  Of course you can, this is Sitecore after all.

If you inspect the web.config file under UI pipelines, you will see this section:



This is the UI pipeline with all the processes that take place when a user renames an item.  Keep in mind that adding a new item also calls this pipeline when you are prompted to enter a name for the new item.  If you use a decompiler and inspect the code for all the processors and methods called, you will see that the method that does the name validation is in "GetNewName" which calls:



This indicates that the item name is validated via a regex call and it is also stored as a setting key-value pair in the web.config file:



For this example we will modify the regex expression to match names that contain a literal dot or period:



Save and reload Sitecore and try renaming a new item to be "www.yahoo.com" and you will see the error message no longer appears and items with dots in the name will pass all validations.

Renaming Item Name Programmatically

Using the above example, we can ensure the item name of our choice passes checks and gets accepted by Sitecore.  But this still doesn't address the problem of using bad names.  If you create a new item under home with the name "www.yahoo.com", the item path for this new item would be "/sitecore/content/home/www.yahoo.com".

If this is not ideal and you rather convert the item name to be something else, such as replacing the dots with hyphens, while keeping the display name intact you can add some post processing to the item name.  To do this you must add an event handler to the "item:saved" event:



Add a new handler at the end to handle renaming the item.  Of course, it would be best to isolate this new handler to an external config file in the includes section.  Use a decompiler to inspect the other handlers to understand how to create a new handler if you need assistance in this area.

Basically, you want to do some name modifications.  With our example, we want to replace all white spaces and dots with dashes.  Follow these basic steps:

item.Editing.BeginEdit();
string processedName = ItemUtil.ProposeValidItemName(item.Name.ToLower().Replace(' ', '-').Replace('.', '-'));
item.Appearance.DisplayName = item.Name;
item.Name = processedName;
item.Editing.EndEdit();

Of course, you would have to put in your try/catches and other validations too but the above should get you started.


Summary

This illustrates how powerful Sitecore is for developers and how customizable everything is.  Add a new entry in a config file, create a new class with some methods, and change the way things work to customize to your specific needs.

Wednesday, May 7, 2014

Sitecore Rant: Speak UI and 7.1

Recently, we were given the task of using Sitecore 7.1 for a client that requires a single-instance, multi-site environment. We also need to set it up in a way which allows for dynamic additions of home nodes without the need to update the <site> definitions in the config files.

How do we accomplish this? Simple, with a custom site resolver that scans the nodes on the content tree.  The nodes must inherited from a certain template to indicate they are homepage nodes.  You can get more details from an older post:

http://mrstevenzhao.blogspot.com/2014/04/sitecore-multi-site-setup-wo-updating.html

Everything is going smoothly when at one point, we attempted to swap out an image with another image in the media library via the content editor.  Hit "browse" and you will see a dialog window.  What is this dialog that you see?  It is totally different from what you have seen in the past.  It looks like the media library dialog got a facelift.  The worst part is, it doesn't work after applying the custom site resolver.  I did a little research and found out this is the new "Speak UI" library of modals and dialogs that are part of the Sitecore 7.1 release.

I firebugged this sucker and checked out all the HTTP requests being made.  Apparently this dialog window makes quite a few.  Here is a sample of how the URLs are for the requests via service calls:

http://local.domain/-/item/v1/sitecore/shell?facetsRootItemId={7F43D3D0-CAC6-45D8-96FE-B76F4A117F9B}&search=&root={3D6658D8-A0BF-4E75-B3E2-D050FABCF4E1}&searchConfig={B0DF45DF-EA31-4C11-9E34-98B41DF549C5}&sc_content=master&language=en&format=%24convert_date_to_friendly_format&fields=__Created|Dimensions&pageIndex=0&pageSize=20

As you can see, the query path is not like other conventional paths.  This one has the "/-/" part after the domain.  This could probably be messing with the custom site resolver.  I did further investigating and found out that a custom HTTP handler is defined in the web.config to handle these new URLs. The handler could be at fault and not handle the URLs properly in conjunction with the custom site resolver.  I did not spend too much time investigating the issue and it is definitely not worth rewriting the handler.

As a simple fix, I disabled the Speak UI library for now until we find a legitimate use for it besides looking prettier.  If you disable it, the original dialog window to browse the media library is used, which is fine.  Until the Speak UI handler is improved or Sitecore finds a way to make it handle URLs in a better way, I will be sticking with the traditional dialogs that WORK.

Disable the Speak UI simply by renaming these 3 files to have the ".disabled" file extension:

Sitecore.Speak.config
Sitecore.Speak.ItemWebApi.config
Sitecore.Speak.Mvc.config