Thursday, September 19, 2013

"This operation is only valid on generic types." Exception in Sitecore 7 (Initial release)

Have you ever run into "This operation is only valid on generic types." exception? My team has. I believe its a bug with Sitecore code where IndexFieldEnumerableConverter calls GetGenericTypeDefinition method without checking if the type of the property is in fact generic.

The reason why we ran into this issues was the type of collection properties on classes that search results from Solr are being mapped to. Initially List type was used. Updating all properties from List to IEnumerable solved the issue.

Server Error in '/' Application.
This operation is only valid on generic types.
Description: An unhandled exception occurred.
Exception Details: System.InvalidOperationException: This operation is only valid on generic types.
...
Stack Trace:
[InvalidOperationException: This operation is only valid on generic types.]
System.RuntimeType.GetGenericTypeDefinition() +13998682
Sitecore.ContentSearch.Converters.IndexFieldEnumerableConverter.GetElementType(Type destinationType) +389
Sitecore.ContentSearch.Converters.IndexFieldEnumerableConverter.GetListAndElementType(Type destinationType, Type& elementType, Type& listType) +156
Sitecore.ContentSearch.Converters.IndexFieldEnumerableConverter.ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType) +288
Sitecore.ContentSearch.Converters.IndexFieldStorageValueFormatter.ReadFromIndexStorage(Object indexValue, Type destinationType) +355
Sitecore.ContentSearch.DocumentTypeMapInfo.SetProperty(Object target, String propertyName, String documentFieldName, Object value) +544
Sitecore.ContentSearch.SolrProvider.Mapping.SolrDocumentPropertyMapper.ReadDocumentFields(Dictionary`2 document, IEnumerable`1 fieldNames, DocumentTypeMapInfo documentTypeMapInfo, IEnumerable`1 virtualFieldProcessors, TElement result) +942
Sitecore.ContentSearch.DefaultDocumentMapper`1.MapToType(TDocument document, SelectMethod selectMethod, IEnumerable`1 virtualFieldProcessors, SearchSecurityOptions securityOptions) +283
Sitecore.ContentSearch.SolrProvider.<GetSearchResults>d__a.MoveNext() +977
System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +536
System.Linq.Enumerable.ToList(IEnumerable`1 source) +80

...

Tuesday, September 17, 2013

Sitecore.ePubStudio. Taking control over ePub books creation.

I just finished working on ePub generation functionality in one of my projects. The outcome turned out to be pretty interesting. Judge for yourself: https://github.com/jcore/Sitecore.ePubStudio

Sitecore.SharedModules.ePubStudio

A tool that allows you to create ePub files using Sitecore content. You'll find this studio to be very similar in implementation to Sitecore APS Module. Indeed APS allows you to go very granular on what content to use and where in generating PDF. Unfortunately, I didn't find a way to simply extend APS to generate different output format. This tool allows you to generate book structure and html within each chapter.
The code has been tested only with Sitecore 7 and some templates use new Sitecore 7 fields. Application I was creating it for is an MVC one, hence Sitecore.Mvc.dll reference in the project.
It also depends on DotNetEpub library (https://github.com/gonzoua/DotNetEpub) which I found to be a very lean and working nicely.

Friday, August 30, 2013

Sitecore and APS Saga. Configurations, setting up of Print Studio Projects, PDF generation

A little bit of history

I have been working on this project where we need to generate a PDF version of biographies that would be looking nice enough for print. Another goal is to join these biographies into one brochure. We are a Sitecore shop, and needless to say APS (Adaptive Print Studio) module was the first one to be considered. Everything sounds great during the sales pitch, and looking through JetStream solution was very convincing. Then the real world came up on us. I started implementing it and realized that it is not as simple as I would hope.

My APS journey was a road of extensive back and forth with Sitecore support team, digging through documentation, trying things out, researching through writing code that I hoped would produce necessary results and so on until I was introduced to Mark Demeny from Sitecore. He was very helpful and provided me with information I was so desperately seeking. Only after speaking with Mark, I started to understand the magnitude of APS and what it is capable of.

I figured I would share what I've learnt and results I've managed to accomplish. If you are looking into possibility of using APS module for your solution, this post might save you a few days of searching for answers.

Task at hand

Here is quick overview of the requirements:
  • Create PDF version (print quality) for every biography on the site (1,200 of them);

  • Join some of these biographies and related articles into a book (PDF). This should be author controlled. Book needs to be personalized for every user who chooses to download it.
Overview of implementation turned out to be a long document with lots of screenshots. I was debating whether to post it as a bunch of posts or as one PDF file and decided in favor of a PDF. I believe it will be easier to read and work with if you decide to reuse some of the code included in it.

Overview of implementation (PDF):



Configuring Elmah in GAC


If you are running several applications on the same server and prefer not to configure Elmah separately for each application, you can configure Elmah in GAC. The following article was extremely helpful and covers most of the process: http://www.codeproject.com/Articles/235201/ELMAH-in-the-GAC-Using-ELMAH-from-the-Global-Assem However following it didn't really get me where I wanted and my Elmah logging still didn't work. So here is what I ended up doing.

Steps

  1. Install Elmah strongly signed assembly to GAC.
  2. Add Elmah connection string to Connection Strings in features view (IIS 7) on server level.
  3. Change .NET Framework version to 4.0
  4. Make sure all apps are running .NET 4 (in my case they are).
  5. Open C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\web.config as admin
  6. Add the following lines: 
<configsections> 
    ...
    <!-- Begin an ELMAH specific section -->
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false"
         type="Elmah.SecuritySectionHandler, Elmah, Version=1.2.14706.0, Culture=neutral, PublicKeyToken=d91c50d0d0b9de11"/>
      <section name="errorLog" requirePermission="false"
         type="Elmah.ErrorLogSectionHandler, Elmah, Version=1.2.14706.0, Culture=neutral, PublicKeyToken=d91c50d0d0b9de11"/>
    </sectionGroup>
    <!-- End an ELMAH specific section -->

</configSections>

  <!-- Begin an ELMAH specific section -->
  <elmah>
    <security allowRemoteAccess="yes" />
    <errorLog type="Elmah.SqlErrorLog, Elmah, Version=1.2.14706.0, Culture=neutral, PublicKeyToken=d91c50d0d0b9de11"
       connectionStringName="ELMAH" />
  </elmah>
    <!-- End an ELMAH specific section -->

 <!-- Begin an ELMAH specific section -->
    <location path="elmah.axd">
        <system.web>
            <authorization>
                <deny users="?"/>
            </authorization>
        </system.web>
    </location>
    <!-- End an ELMAH specific section -->


  1. In Features View on the server level open Modules and add : Name = Elmah, Type = Elmah.ErrorLogModule, Elmah, Version=1.2.14706.0, Culture=neutral, PublicKeyToken=d91c50d0d0b9de11
  2. In Features View on the server level open Handler Mappings and add Managed Handler. Name = Elmah-Logging, Path=elmah.axd, Type = Elmah.SecuritySectionHandler, Elmah, Version=1.2.14706.0, Culture=neutral, PublicKeyToken=d91c50d0d0b9de11
  3. Make an app throw an exception.
  4. Check Elmah DB for logged errors.

Wednesday, May 29, 2013

Programmatically updating Rendering datasource

I've recently discovered an undocumented feature of Sitecore and decided to share my experience.

The task at hand was to import data from a thrid party system into Sitecore generating items based on specific template. In the standard values of that template I had a few MVC renderings (views) assigned. A lot of those renderings had to have datasource set to be pointing either to the newly created item for imported record or to a child of that item. Updating datasource for all assigned in SV renderings turned out to be not so straight forward task. The first thing I attempted is to update Datasource property on each rendering, which turned out to be a wrong decision. Updating this property makes Layout Details windows to display duplicate renderings, even though there are none. You don't have duplicates, but each rendering has two different values if you look at the raw value of the layout field. Needless to say that rendering engine had issues with processing this value as well. What I ended up doing is not updating Datasource property, but one of DynamicProperties called "s:ds". Surprisingly, when you update it's value, system also updates Datasource property and everything starts magically working. Here is what I ended up doing:

LayoutDefinition layout = LayoutDefinition.Parse(bioItem[Sitecore.FieldIDs.LayoutField]);

foreach (DeviceDefinition device in layout.Devices) {
   if (device.Renderings != null) { 
      for(var i =0; i < device.Renderings.Count;i++) { 
         RenderingDefinition rendering = (RenderingDefinition)device.Renderings[i];     
         if (rendering.ItemID == renderingId) { 
            //rendering.Datasource = renderingDatasource;  // creates duplicates rendering in the layout details window.  
            rendering.DynamicProperties.Where(p => p.Name == "s:ds").ToList().ForEach(p => p.Value = datasource); 
         } 
      } 
   } 

Wednesday, May 22, 2013

Sitecore with MVC 4

Task at hand:

Sitecore works pretty nicely with MVC 4 until you try to use default MVC functionality like bundling. When you attempt to use it and run the solution in release mode, you'll see all your bundles returning 404. The reason for that is Mvc.IllegalRoutes setting in Sitecore.Mvc.config. John West has written as series of post regarding this functionality, but I figured I would post my solution.

Solution:

  • Remove {controller}/{action}/{id} from Mvc.IllegalRoutes:
    <setting name="Mvc.IllegalRoutes" value="" />
  • Create a new class for a route contraint like so: 
public class SitecoreConstraint : IRouteConstraint
    {
        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (Sitecore.Context.Item != null && Sitecore.Context.Item.Visualization.Layout != null)
            {
                return false;                
            }
            return true;
        }
    }
  • Add newly created constraint to RouteConfig:
public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                constraints: new { sitecoreRoute = new SitecoreConstraint() }
            );
        }
    }

That should solve the routing problem and allow you to use bundles with Sitecore.

UPDATE:

I just upgraded our solution from 7.0 to 7.2 (Update-2) and bundles seem to be working without any issue when I add "/bundles" to "IgnoreUrlPrefixes". No modification of Mvc.IllegalRoutes was required.

One more thing I had to change to make css @import statement work.

Instead of:

bundles.Add(new StyleBundle("~/bundles/css").Include("~/assets/design_new/css/main.css""~/assets/design_new/css/all.css"));


I had to place all css files into the same directory and use CssRewriteUrlTransform object:

bundles.Add(new StyleBundle("~/bundles/css").Include("~/assets/design_new/css/*.css"new CssRewriteUrlTransform()));

Wednesday, May 8, 2013

Sitecore 6.6 MVC EditFrame Helper

If you ever attempted using EditFrame with MVC, you probably had to write a helper to render it out. I figured I would share an EditFrame helper code I came up with. I'm using MVC4 with Sitecore 6.6. Edit Frame seems to be rendering the same way as it does with ASP.NET Forms.

C# Helper code:


public static class EditorFrameHelper
    {
        public static EditFrame EditFrameControl;

        private class FrameEditor : IDisposable
        {
            private bool disposed;
            private HtmlHelper html;

            public FrameEditor(HtmlHelper html, string dataSource = null, string buttons = null)
            {
                this.html = html;
                EditorFrameHelper.EditFrameControl = new EditFrame
                {
                    DataSource = dataSource ?? "/sitecore/content/home",
                    Buttons = buttons ?? "/sitecore/content/Applications/WebEdit/Edit Frame Buttons/Default"
                };
                HtmlTextWriter output = new HtmlTextWriter(html.ViewContext.Writer);
                EditorFrameHelper.EditFrameControl.RenderFirstPart(output);
            }

            public void Dispose()
            {
                if (disposed) return;

                disposed = true;

                EditorFrameHelper.EditFrameControl.RenderLastPart(new HtmlTextWriter(html.ViewContext.Writer));
                EditorFrameHelper.EditFrameControl.Dispose();
            }
        }

        public static IDisposable EditFrame(this HtmlHelper html, string dataSource = null, string buttons = null)
        {
            return new FrameEditor(html, dataSource, buttons);
        }
    }

View code:

@using (Html.EditFrame(Model.Key.SitecoreItem.Paths.FullPath,"/sitecore/content/Applications/WebEdit/Edit Frame Buttons/RelatedContentSection"))
{
    <h4 id="@Model.Key.SitecoreItem.Name.HtmlAnchor()">@Model.Key.PanelType.Value</h4>
}