Experience Sitecore ! | More than 200 articles about the best DXP by Martin Miles

Experience Sitecore !

More than 200 articles about the best DXP by Martin Miles

Sussex Sitecore UserGroup - 22 March 2017


What's on? Finally I have overcome all hardships on arranging with the venue, sponsorship etc. and am proud to announce the first Sussex Sitecore User Group!

When does it happen? It will take place on Wednesday 22nd of March in Brighton - the heart and largest city of Sussex. One of complexities of finding a meeting place was to find an adequate venue in less than 5 minutes walk from Brighton train station as I realised that most of guests will come from either London, or other parts of Sussex but in any case they will arrive at Brighton station.

How to get there? Brighton is only 1 hour of travel from London Bridge or Victoria station. Direct trains also go there from Kings Cross / St. Pancrass, Blackfriars and Farrington. There will be a train at 17.42 from Victoria, 17.20 from Blackfriars and 17.57 from London Bridge. Please see the train times planner to find appropriate option. And the venue itself is less than 5 minutes of walk from station:


Address: 30 Cheapside, Brighton BN1 4GD (main entrance on Blackman Street)

What is agenda? We are planning to have a traditional Sitecore event with complementary drinks and snacks but with what is more important - very interesting topics plus open discussion.

Any contact? Would you like to present - please PM me on Slack @martin_miles or Twitter @SitecoreMartin.

Please RSVP at the event's MeetUp page so that we have a god idea of numbers.

Looking forward to meet you there!

Got a handy new tool for "finding ends" of presentation items - Rendering Chrome for Components

I have recently started working on a new project with a non-transparent structure. By saying non-transparent I mean non trivial locations for the code and non-matching layout and rendering items to their filesystem counterparts. So I was looking for the convenient way to identify matched pairs.

Luckily I remembered JammyKam presenting something similar at London UserGroup in January 2017 and refereed to his blog in order to find Rendering Chrome for Components package (module - ?). So I will briefly go through his solution show how it helped me and what it does:


  1. Install package as normal
  2. Add a namespace into web.config from /Views folder:
    <add namespace="ForwardSlash.SC.RenderingChrome.HtmlHelpers"></add>
    
  3. Append this to a containers element so that it generates an attribute:
    @Html.Sitecore().ContainerChrome()
  4. Now, if you go to Experience Editor and open View tab, you'll see a new checkbox Highlight Renderings clicking which turns all magic on


Here's the result:


    It works not only in Chrome, as you see I run it in firebug.

    Hope it will help you as much as it already has helped me.

    References:

    - original post by Kamruz Jaman

    - sources on GitHub

    - presentation slides from Sitecore User Group London (January 2017) - 3.5MB

    Why am I getting "Item is not a template" error on data items in Content Editor?

    Very simple and even stupid error I have met few times, just want to describe it here to make it google-searchable so that it might help someone.

    Image you're going across you content items , and when clicking by one of your data items, you see similar screen, saying: Item "/sitecore/content/Home/Data/Simple item" is not a template.



    First of all, why does I got yellow screen? Well, the error is quite descriptive, saying that my Simple item is not a template.

    Sure, it isn't, we know that and can prove that by clicking Content tab, that will show us exactly the item's data:


    But why on earth do I see other two tabs, that shouldn't be there for data item?

    The answer can be either of two cases:

    1. Simple silly case - the template of you item is either directly inherited from default Template item (located at /sitecore/templates/System/Templates/Template) rather than Standard template (/sitecore/templates/System/Templates/Standard template).


    In that case simply replace one with Standard template to fix.

    2. More complex case - when you're likely to have a complicated inheritance chain, especially if you are working with Helix or playing around Habitat. It that case your data item is based on a composite template, that is likely to inherit multiple other templates, at least one of which inherits from Template rather than Standard template, exactly as described in a case above. Solution is the same as above - identify the culprit and change inheritance to Standard template.

    Finally your data will be based on correct set of templates and you won't evidence unwanted tabs anymore.

    Hope this post helps someone!

    How to make Content Editor search by plain GUIDs without braces and dashes? (part 1)

    How many times while inspecting the code you've encountered something similar to:

    
    

    Of course, you've guessed that A50CC32DAC854E3D9FC3A6BFDE1C577E is nothing else but GUID of an item being modified for URL. And of course, in order to look up what this item is, you've pasted it into search box of Content Editor, as the easiest way to search. Content Editor in turn did not find anything as obviously that value isn't a properly formatted GUID. As a mature Sitecore developer, probably had to append braces and insert dashes in appropriate format, so that items becomes discoverable via search.

    To overcome this, I created animprovement module that will allow you search by both plain and properly formatted GUIDs.

    As for a starting point, I decided to find out what exactly happens after user submits value into a search box. Looking up ajax POST call to the backend suggested me that it should be a pipeline somewhere behind Content Editor application. Decompiling Sitecore.Kernel and Sitecore.Client prompted me to search pipeline, that is located at Sitecore.Pipelines.Search namespace of Sitecore.Kernel.

    The very first step of that pipeline is called IDResolver and does exactly what he is named for: resolves an item for a search term in case it is properly formatted GUID and exists in database. If not - it passed arguments further down the pipeline.

    So the idea is to create an extra processor after IDResolver that will do basically the same business but for search terms that are plain GUIDs. Okay, here's the code:

        public class SearchByPlainGuids
        {
            public void Process(SearchArgs args)
            {
                Assert.ArgumentNotNull((object)args, "args");
    
                if (string.IsNullOrWhiteSpace(args.TextQuery))
                    return;
    
                if (args.Type == SearchType.ContentEditor && IsPlainGuid(args.TextQuery))
                {
                    Item obj = args.Database.GetItem(new ID(MakeGuidQuery(args.TextQuery)));
                    if (obj != null)
                    {
                        SearchResult result = SearchResult.FromItem(obj);
                        args.Result.AddResultToCategory(result, Translate.Text("Direct Hit"));
                    }
                    args.AbortPipeline();
                }
            }
    
            private string MakeGuidQuery(string plainGuid)
            {
                return "{" + plainGuid.Substring(0, 8) + "-" + plainGuid.Substring(8, 4) + "-" + plainGuid.Substring(12, 4)
                       + "-" + plainGuid.Substring(16, 4) + "-" + plainGuid.Substring(20, 12) + "}";
            }
    
            private bool IsPlainGuid(string query)
            {
                string pattern = @"^[0-9a-fA-F]{8}[0-9a-fA-F]{4}[0-9a-fA-F]{4}[0-9a-fA-F]{4}[0-9a-fA-F]{12}$";
    
                Regex regex = new Regex(pattern);
    
                return regex.IsMatch(query);
            }
        }
    

    To make this code function, let's build that into a DLL and reference that DLL from config patch file located within include folder:

    
    
    
    
    
          
        
      
    
    
    

    Voila! Now you're able to search by item GUID regardless of its format.


    Please feel free to download ready-to-use package (5.7KB) for Sitecore 8.1 and 8.2 or source code (9.1KB).

    P.S. It all looks and works well, however what can be done better? First of all, we've modifies search pipeline, so our processor is called with every call of given pipeline. Next, we add extra DLL and extra config include file to reference that DLL. Wouldn't it better, if GUIDs were checked and modified right before entering that pipeline, ideally at the front-end? Let's try to make this function at part 2 of this story.

    Happy new year, Sitecore!

    Dear friends,

    Thank you everyone who was reading my blog, who supported me and Sitecore Link project. Thank you, Sitecore Community!
    It was amazing year, you all made an awesome progress - 25% of entire Sitecore content has been generated within past year.

    I hope we all together can make even better progress in 2017 and will enjoy new excellent version of our beloved platform, as usual. Looking forward to meet you all at the evens that we all are anticipating,

    Happy new 2017 year!
    Martin Miles



      Sitecore Link project - summing up the year 2016 and announcing great plans for 2017

      This blog post partially intersects with my speech at London User Group in November.

      2016

      More than a year has passed since I've initially launched Sitecore Link in October 2015 and now I can definitely conclude that it was one of my greatest ideas. Initially launched as just a collection of links for my own day-today necessities, Sitecore Link grew up to the greatest knowledge repository for our beloved platform with 13,000 entries of information. Currently it absorbed almost all the technical information from various sources - blogs, YouTube videos, StackOverflow and StackExchange questions, community portal and of course - the official documentation.

      But apart from that Sitecore Link also plays another bigger role. As It now covers almost all blogosphere and documentation layer for Sitecore - it becomes quite precise identifier of what is going in Sitecore Community, trends and metrics of our productivity.

      The total stats for the project can be found by this link, however here's a brief of figures:



      As you may see from stats from URL provided above - SItecore Community has never been so productive before as in was 2016. The average growth for blogs, authors ranged between 30% - 50% and that corresponds to previous official figures saying that Sitecore has expanded the market up to 38% during 2016. At the same time 20% of all Sitecore materials was produced during 2016 and that sounds unbelievable!

      Having all these figures, Sitecore Link introduces the annual Community Award for the most productive authors, please follow the link to see the details.

      2017

      Current look and feel of Sitecore Link, its design and UX have overcame its desired capacity many times and definitely need to be replaced with the adequate and modern approach. Current code still origins from the first version with 1-2 thousands of links, with inline searching and filtering being productive in real-time. But today it handles 10X more of content and needs to address current and any of potential future growths so will be properly re-developed in order to remain among other productivity tools for Sitecore professionals.

      That is what I am going to focus at in 2017. So what are my plans for 2017 for Sitecore Link?

      1. Content will become more accurate, and will be split into multiple more precise categories. That process already happens - I clean-up names to be more descriptive, and I also split many large categories. It will keep going, especially with the release of 8.3 (and newer versions) number of categories will be growing.

      2. Design and UX, as mentioned above, will totally change to address such a large volume of information, also should include improved navigation.

      3. Search and filtering implementations are yet to be determined, as I am not yet decided which exactly approach to take. But what is for sure - search should be quick and accurate.

      4. Automation is currently at very good state. Being an invisible part of solution, it plays crucial role for the project and it wouldn't be possible to achieve that level of data coverage without automation. There isn't much to happen, except that I plan to aggregate it all into "automation of automation", to see why - please read below.

      5. Analytics. If you are reading this blog, I assume you're already a Sitecore professional with certain level of awareness on how Experience Analytics work for Sitecore 8 (and newer). A similar approach will be implemented for Sitecore Link project so that it will be possible to have a reporting module showing precise stats and identifying trends of Sitecore Community. It would become possible to track the dynamics of entire community or drill down to productivity of specific blogs, see popularity changes for specific categories and lack or clarity of official documentation comparing to how often a topics is asked about on StackExchange platforms etc. That would become possible thanks to few automations that do daily snapshots of entire activity and compare that to previous data.

      6. Going open-source to GitHub as soon as everything mentioned above is implemented. That includes both code and data.

      7. Make it community governed stands last but not the least in my plans for 2017! At the moment Sitecore Link allocates almost all of my leisure time and takes even more than that from my sleep, regardless of automations. As we know, scalability is the key to success! That's why I plan to introduce few people co-working on this project, it will be the next logical step after the project relocates to GitHub.

      All these plans seem to be quite ambitious, so let's make it all happen!

      Thank you for all of you for the feedbacks, please feel free to let me know any of thoughts, ideas or anything else regarding Sitecore Link project.

      Media Aliases in Sitecore 8.2 Update 1

      With recent release of Sitecore 8.2 Update 1 we have got a really much fun on Azure and getting closer to PaaS. it was so much buzz about it at #Sitecore Twitter, so that another handy feature has been overlooked. I am now talking about Media Aliases in Sitecore. One would probably ask few questions about that;

      - Haven't we had it previously in Sitecore, wasn't that doable in principle?
      - It wasn't, at least not out of the box. Previously if you wanted to achieve that functionality you had to implement your own ItemResolver pipeline processor and combine that with MediaProvider so that the links to media items were generated using aliases. That required custom code being built and re-deployed, that was especially painful for solutions already live. Of course such an approach couldn't be called an easy way.

      - But why at all do I need Media aliases?
      - Good question. Why would you use normal aliases with Sitecore then? You may find yourself working with the platform for few years so far without even thinking about that functionality. However otherwise like me, you may have a SEO Maniac as your manager (in good sense of word "maniac", for sure) who constantly generates optimization tasks for you. In my case I also had a Marketing Department who wanted to have nice and simple URLs for certain features, so that they can easily tell that over the phone.

      - Still didn't get that. Would it be better just to demonstrate an example of very obvious use case?
      - Without any problems. For the sake of this experiment, I have installed clean vanilla Sitecore 8.2 update 1. With that instance, I want to host my professional CV and easily tell that URL to numerous employment agents so that they could get that URL straight away or even remember it. I also want to make it as simple and minimal as possible. So that people type an URL and immediately get the PDF document downloaded. And of course, I don't want to have any file extension in URL or anything that complicates.
      That is an ideal scenario for implementing Media Alias. It is indeed very easy, below are few steps how to make it work:

      1. Navigate to /Sitecore/System/Aliases and create a new child item of type Alias. Give the item simplest name you can, but avoid that name matching any of existing pages, as in that case alias would take a priority and a page becomes unreachable.
      2. While editing alias item, click Insert media link in order to open Insert media link dialog box.
      3. Select (or upload if required) correspondent Media Item that will be processed.
      4. Hit Insert button. That's it - your new Media Alias is ready
      5. Do not forget to publish your alias (and media item) so that it becomes available from Content Delivery database(s).



      As soon as I completed these 5 steps, I can now able to download my CV in one click with nice and clean URL: http://sandbox/cv Also, I want to warn that Media Aliases have the same limitations as the normal ones.



      That's it - nice, clean and simple.
      I hope Media Aliases will help you to deliver even more user-friendly solution!

      Helix project, MVC routing and the form posting back to controller. Part 3 - Adding validation

      In previous post we have created an MVC form, that submits to a feature controller and set up the routing to make it all work. In this part we'll add validation to that form. This part does not differ from traditional MVC approach, however let's make our form smooth and complete.

      1. Add validation controls into a page for each of inputs:

      @using (Html.BeginRouteForm(MvcSettings.SitecoreRouteName, FormMethod.Post))
      {
          @Html.LabelFor(x => x.FirstName)
          @Html.TextBoxFor(x => x.FirstName)
          @Html.ValidationMessageFor(x => x.FirstName)
      
          @Html.LabelFor(x => x.LastName)
          @Html.TextBoxFor(x => x.LastName)
          @Html.ValidationMessageFor(x => x.LastName)
      
          @Html.LabelFor(x => x.Email)
          @Html.TextBoxFor(x => x.Email)
          @Html.ValidationMessageFor(x => x.Email)
      
      
      
      
      
      
      
      
      
      }
      
      2. Model needs to be updated with validation action filter attributes, provided by DataAnnotations:
      using System.ComponentModel.DataAnnotations;
      
      namespace YourSolution.Feature.Test.Models
      {
          public class TestModel
          {
              [Display(Name = nameof(FirstNameLabel), ResourceType = typeof(TestModel))]
              [Required(ErrorMessageResourceName = nameof(Required), ErrorMessageResourceType = typeof(TestModel))]
              [MinLength(3, ErrorMessageResourceName = nameof(MinimumLength), ErrorMessageResourceType = typeof(TestModel))]
              public string FirstName { get; set; }
      
              [Display(Name = nameof(LastNameLabel), ResourceType = typeof(TestModel))]
              [Required(ErrorMessageResourceName = nameof(Required), ErrorMessageResourceType = typeof(TestModel))]
              [MinLength(3, ErrorMessageResourceName = nameof(MinimumLength), ErrorMessageResourceType = typeof(TestModel))]
              public string LastName { get; set; }
      
              [Display(Name = nameof(EmailLabel), ResourceType = typeof(TestModel))]
              [EmailAddress(ErrorMessageResourceName = nameof(InvalidEmailAddress), ErrorMessageResourceType = typeof(TestModel))]
              [Required(ErrorMessageResourceName = nameof(Required), ErrorMessageResourceType = typeof(TestModel))]
              public string Email { get; set; }
      
              // Labels and validation messages: instead of hardcoded you may take it from Dictionary
              public static string FirstNameLabel => "First name";
              public static string LastNameLabel => "Last name";
              public static string EmailLabel => "E-mail";
              public static string Required => "Please enter a value";
              public static string MinimumLength => "Should be at east 3 characters";
              public static string InvalidEmailAddress => "please enter valid email address";
          }
      }
      
      3. Last but not the least, need to decorate POST action with validation action filter attribute, that runs validation prior to executing controller and returns validated view model on failure or runs into controller action if there no errors. So, updated controller action:
              [HttpPost]
              [ValidateModel]
              public ActionResult Test(TestModel loginInfo)
              {
                  // do something on successful form submission
                  return new RedirectResult("/SuccessfulResultPage");
              }
      
      4. And of course, the ValidateModelAttribute class itself:
      using System.Web.Mvc;
      
      namespace YourSolution.Feature.Test.Attributes
      {
          public class ValidateModelAttribute : ActionFilterAttribute
          {
              public override void OnActionExecuting(ActionExecutingContext filterContext)
              {
                  var viewData = filterContext.Controller.ViewData;
      
                  if (!viewData.ModelState.IsValid)
                  {
                      filterContext.Result = new ViewResult
                      {
                          ViewData = viewData,
                          TempData = filterContext.Controller.TempData
                      };
                  }
              }
          }
      }
      

      5. After running gulp commands for updating views and DLL libraries, an updated page would do validation similar to:


      That's it! Finally, our feature project named Test would have the following structure in Solution Explorer:



      Thanks for reading!

      Helix project, MVC routing and the form posting back to controller. Part 2 - Creating a form

      In previous part, we created a test feature, as a part of a Helix-based solution. We also created a page and rendering and wired it together. So, now it's time to create a form.

      As a start, let's create a controller, as it is referenced from rendering definition item:

      namespace YourSolution.Feature.Test.Controllers
      {
          public class TestController : Controller
          {
              public ActionResult Test()
              {
                  return View();
              }
          }
      }
      

      Then create corresponding Razor view..

      @using Sitecore.Mvc.Configuration
      @model YourSolution.Feature.Test.Models.TestModel
      
      @using (Html.BeginRouteForm(MvcSettings.SitecoreRouteName, FormMethod.Post))
      {
          @Html.LabelFor(x => x.FirstName)
          @Html.TextBoxFor(x => x.FirstName)
          
          @Html.LabelFor(x => x.LastName)
          @Html.TextBoxFor(x => x.LastName)
      
          @Html.LabelFor(x => x.Email)
          @Html.TextBoxFor(x => x.Email)
      }
      
      
      
      

      .. and model that represents our form, to be passed between controller and view:

      namespace YourSolution.Feature.Test.Models
      {
          public class TestModel
          {
              public string FirstName { get; set; }
              public string LastName { get; set; }
              public string Email { get; set; }
          }
      }
      

      As we are working with a project outside of webroot, we need to use gulp in order to copy this view into corresponding folder on the website (or you may copy that manually), same for the feature DLL.

      After refreshing browser you will see the view. Obviously, when trying to hit "Submit" button - nothing happens as there's no POST controller action method. So let's add the one:

      namespace YourSolution.Feature.Test.Controllers
      {
          public class TestController : Controller
          {
              public ActionResult Test()
              {
                  return View();
              }
      
              [HttpPost]
              public ActionResult Test(TestModel loginInfo)
              {
                  // do something
              }
          }
      }
      

      Still not there... why? Let's troubleshoot that. First thing to do is to find out where form does POST to. In webinspector, it shows:

        <form action="/test" method="post"> ... </form>
      

      So far so good - that looks correct (form POSTs to itself). But how /test URL corresponds to YourSolution.Feature.Test.Controllers.Test() method of YourSolution.Feature.Test feature project?

      That's where routing comes into a play. As you know, feature is just a single project that is built into individual DLL and is deployed into /bin folder of project webroot along with other libraries. So how do we wire up /test with that particular method?

      We apply config patch Feature.Test.config in order to add a pipeline processor right before the one that initializes MVC routes.

      
      
      
      
      
            
          
        
      
      

      After running gulp configuration task this patch will be put into YourSolution/Website/App_Config/Include/Features folder along with custom configuration include file patches for other feature modules. Corresponding processor will be the following:

      using System.Web.Mvc;
      using System.Web.Routing;
      using Sitecore.Pipelines;
      
      namespace YourSolution.Feature.Test.Pipelines
      {
          public class RegisterWebApiRoutes
          {
              public void Process(PipelineArgs args)
              {
                  RouteTable.Routes.MapRoute("Feature.Test.Api", "api/test/{action}", new { controller = "Test" });
              }
          }
      }
      

      Finally the last bit to make routing work - modify _ViewStart.schtml view file on the project level. Here is it:

      @{
          Layout = (this.ViewContext.IsChildAction) || (this.ViewContext.RouteData.Values.ContainsKey("scIsFallThrough") && 
              Convert.ToBoolean(this.ViewContext.RouteData.Values["scIsFallThrough"])) ? null : "~/Views/Shared/_Layout.cshtml";
      }
      

      If you have done everything correctly, after gulp'ing your assets into web folder and refreshing the page in browser, you'll see a form similar to the one below:


      That's it! Now the form is fully functional and POSTs to a controller, however in order to make things even better - let's apply form validation.

      References:

      Sitecore Helix Documentation