Wednesday, November 12, 2014

Sitecore Latest Log File in One Click

When you are working with Sitecore as developer or quality engineer, you often need to look at log files. There are few ways to do it: you can open files from your Data\Logs\ folder, you can use Log Viewer, you can use Log4Net Dashboard or even you can use any application that works with Log4Net. But for all this situations you need to do many clicks. That is why I decided to write Google Chrome extension that allows to get latest log file in browser in one click.

Google Chrome has limitations for extensions, for example you are not able to read from disk. But you can use some workarounds, for example NPAPI Plugins. It makes some limitation on extension, you can’t upload it to Chrome Web Store and you should install it as unpacked extension.

But it is not problem at all, it just makes installation of extension longer.

Here are steps that you should do:

1) Download extension folder from GitHub.

2) Visit chrome://extensions in your browser (or open up the Chrome menu by clicking the icon to the far right of the Omnibox:  The menu's icon is three horizontal bars.. and select Extensions under the Tools menu to get to the same place).

3) Ensure that the Developer mode checkbox in the top right-hand corner is checked.

4) Click Load unpacked extension… to pop up a file-selection dialog.

5) Navigate to the directory in which you downloaded files from step 1, and select it.

Enjoy getting latest log file in one click!

Screenshot 2014-11-12 15.59.31

Also you are able to configure wwwroot folder where your Sitecore environments live:

Screenshot 2014-11-12 16.02.00

P.S. Bonus: extension also works in Opera, however I do not know who uses Opera for development or testing.

Sunday, October 5, 2014

Sitecore MVC: Globalization for Model Attributes

There is an ability to create fields, fields labels and fields validation basing on model attributes in ASP.Net MVC:

public class SampleModel
{
[StringLength(50)]
[Required(AllowEmptyStrings = false)]
[DisplayName("Test")]
public object Name { get; set; }
}

In this case while you creating page you can use helpers:

@Html.LabelFor(m => m.Name)
@Html.TextBoxFor(m => m.Name)
@Html.ValidationMessageFor(m => m.Name)

You will get HTML form:



Everything will work with Sitecore MVC. However if you need provide translation of labels and messages on you form in Sitecore way you need to customize model attributes.


For DisplayNameAttribute it is easy to achieve with inheriting from standard attribute with adding Translate.Text:

public class DisplayNameAttribute : System.ComponentModel.DisplayNameAttribute
{
public DisplayNameAttribute(string displayName)
: base(displayName)
{
}


public override string DisplayName
{
get
{
return Translate.Text(base.DisplayName);
}
}
}

In this case when you will use new attribute for you model you will receive translated text for your labels. Same thing could be done for ValidationMessageFor, Required and other attributes.

Wednesday, September 17, 2014

Sitecore MVC Request Pipeline Execution Lifecycle

When I looked for description of Sitecore.MVC pipelines I faced with great post of David Morrison where was shown Sitecore MVC request pipeline execution lifecycle. I extended this diagram with Sitecore Analytics pipelines.

Sitecore.Mvc

When you’ll look at your showconfig.aspx you’ll find out that you have more Sitecore Mvc pipeline processors. Diagram does not include Sitecore Mvc pipelines for Experience Editor.

Tuesday, September 9, 2014

SQL Code Snippet for Getting Paths of Sitecore Items

This post is based on my answer of SO question regarding getting Sitecore items paths.

It is rare situation when you need to use recursive SQL queries, I decided to practice.

with Items1 (nm, id1, pathstr)
as (select Name, id, cast('/sitecore' as nvarchar(MAX))
   from Items
   where ParentID = '00000000-0000-0000-0000-000000000000'
union all
   select Items.Name, Items.id, Items1.pathstr +'/'+ Items.Name
   from Items
     inner join Items1 on Items1.id1 = Items.ParentID)
select id1 as ItemId, nm as Name, pathstr as [Path]
from Items1

This query allow you to get ids and paths for all items in your Sitecore master(web, core) database.

Thursday, September 4, 2014

AJAX Lazy Loading with Sitecore MVC

There was request to implement search with lazy loading for Sitecore MVC project. Unfortunately there is no such option in Sitecore out of the box. But I found great article how it could be done on Sitecore project with web forms. That idea works fine for web forms, but it does not work for MVC.

I modified processor proposed in article to work with MVC layouts.

var url = "http://myurl/myItem?UseAjax=true&PresentationId=BACBFEE1-C0BB-4D8E-BA1A-D9F50CCA5B7F";  

$.ajax({
type: 'GET',
url: url
}).done(function (data) {
//Your code
});

We will also need a MVC layout for the ajax queries (in the following code the MY_AJAX_LAYOUT_ID is the id of this layout) and this layout will only contain one placeholder (MY_AJAX_PLACEHOLDER in the code)

Now here is the custom layout resolver (look at the comments into the code):

namespace MyNamespace
{
using System;
using System.Linq;
using System.Web;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Pipelines;
using Sitecore.Mvc.Pipelines.Response.GetPageRendering;
using Sitecore.Mvc.Pipelines.Response.GetRenderer;
using Sitecore.Mvc.Presentation;
using System.Web.Routing;

public class GetAjaxLayoutRendering : GetPageRenderingProcessor

{
public override void Process(GetPageRenderingArgs args)

{
bool useAjax;
//First of all let's check if we are in ajax mode or not if not don't continue
var result = bool.TryParse(HttpContext.Current.Request.Params["UseAjax"], out useAjax);
if (!result || !useAjax)
{
return;
}

//The second parameter we need to pass is the PresentationId if not present the query if not valid -> don't continue
var presentationId = HttpContext.Current.Request.Params["PresentationId"];
if (string.IsNullOrEmpty(presentationId))
{
return;
}

//If the current item is null return
if (Sitecore.Context.Item == null)
{
return;
}

//Let's resolve the rendering
try
{
//Get the list of rendering for the current item
var renderings = args.PageDefinition.Renderings;
//If found
if (renderings != null && renderings.Any())
{
//Get the first rendering corresponding to the requested one
var rendering = renderings.First(r => r.RenderingItem.ID.ToString().Equals(presentationId));

if (rendering != null)
{
args.PageDefinition.Renderings.Remove(rendering);
//Put this rendering into ajax layout
rendering.Placeholder = "MY_AJAX_PLACEHOLDER";

rendering.LayoutId = new Guid(JetstreamShops.Business.Constants.Ids.AjaxLayoutItemId);
var layout = renderings.First(x => x.RenderingType == "Layout");
if (layout != null)
{
args.PageDefinition.Renderings.Remove(layout);
for ( int i=0; i< args.PageDefinition.Renderings.Count; i++)
{
args.PageDefinition.Renderings[i].Placeholder =
args.PageDefinition.Renderings[i].Placeholder.Split('/').Last();
args.PageDefinition.Renderings[i].LayoutId =
new Guid(MY_AJAX_LAYOUT_ID);
}
layout.LayoutId = new Guid(MY_AJAX_LAYOUT_ID);
var getRedererArgs = new GetRendererArgs(new Rendering());
getRedererArgs.LayoutItem =
Sitecore.Context.Database.GetItem(MY_AJAX_LAYOUT_ID);

layout.Renderer = PipelineService.Get().RunPipeline<GetRendererArgs, Renderer>(PipelineNames.GetRenderer, getRedererArgs, a => a.Result);

args.PageDefinition.Renderings.Add(layout);
args.PageDefinition.Renderings.Add(rendering);
}
}
}
}
catch (Exception exception)
{
Log.Warn("Cannot render this!", exception, this);
}
}
}
}

Configuration will look in next way:
<mvc.getPageRendering>
<processor patch:after="*[@type='Sitecore.Mvc.Pipelines.Response.GetPageRendering.GetLayoutRendering, Sitecore.Mvc']" type="MyNamespace.GetAjaxLayoutRendering, MyAssembly"/>
</mvc.getPageRendering>
Many thanks to Vangansewinkel Benjamin whose idea was modified. 

Wednesday, August 27, 2014

Configuration Visibility of Profile Cards Button in Content Editor

Usually visibility of buttons in Sitecore content editor is configured via QueryState of command, but there are few exceptions. One of this exceptions is Profile Cards button that allows you to edit the Profile Cards associated with item.chart_radar

Despite there is command declared for this button:

<command name="item:personalize" type="Sitecore.Shell.Applications.WebEdit.Commands.PersonalizeItem,Sitecore.Client"/>

and there is overridden method QueryState which usually is used to control command visibility.

But this method is not used for showing this button in content editor. Instead of it, button is rendered directly as control by Sitecore.Shell.Applications.ContentManager.Editor.RenderProfileCards . This method checks visibility by calling getItemPersonalizationVisibility pipeline.

So, if you need to show/hide Profile Cards button in Sitecore Content Editor then overriding of QueryState for item:personalize command will not take any effect. You should write you own processor for getItemPersonalizationVisibility pipeline.

Thursday, August 21, 2014

Sitecore MVC and Ajax.BeginForm: How to replace whole view with received HTML code

Ajax.BeginForm easily allows you to work add HTML code returned by view to page. It is easy to do by defining Ajax option UpdateTargetId

UpdateTargetId =
"DivContainer"
But there are only three options how received HTML content should be placed to view (InsertionMode AjaxOption):
  1. InsertionMode.InsertAfter
  2. InsertionMode.InsertBefore
  3. InsertionMode.Replace
Unfortunately, even InsertionMode.Replace mode don’t replace whole HTML element with new code, it replaces only content of this element

$(update).html(data); //jquery.unobstrusive-ajax.js
With ASP.Net MVC it is ok. You can control place where you are rendering your partial view:
<div id="DivContainer">
    @Html.Action("Action","Controller")
</div>
Then you are able to set UpdateTargetId = "DivContainer" and your form will be completely replaced.
BUT! It is not possible to do with Sitecore MVC when your view is added to some placeholder on page. Even if you wrap your view with some element
<div id="DivContainer">
@using (Ajax.BeginForm("Action", "Controller", null, new AjaxOptions {      UpdateTargetId = "DivContainer", InsertionMode = InsertionMode.Replace ….      
</div>
you’ll get a lot of wrapped divs after few updating of form:
<div id="DivContainer">
<div id="DivContainer">
<div id="DivContainer">
@using (Ajax.BeginForm("Action", "Controller", null, new AjaxOptions {      UpdateTargetId = "DivContainer", InsertionMode = InsertionMode.Replace ….      
</div>
</div>
</div>
It can break your design of JavaScript on page.
There is one solution how you are able to avoid it. Insertion HTML received by Ajax is controlled by jquery.unobstrusive-ajax.js. It is JavaScript file and you can easily modify it. For Sitecore MVC views I suggest to add additional insertion mode:
mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase();
        $(element.getAttribute("data-ajax-update")).each(function (i, update) {
            var top;
            switch (mode) {
            case "BEFORE":
                top = update.firstChild;
                $("<div />").html(data).contents().each(function () {
                    update.insertBefore(this, top);
                });
                break;
            case "AFTER":
                $("<div />").html(data).contents().each(function () {
                    update.appendChild(this);
                });
                break;
            case "REPLACEWITH":
                $(update).replaceWith(data);
                break;
            default:
                $(update).html(data);
                break;
            }
        });

It will replace whole HTML element, but not only content of it.
And also you should pass corresponding html attribute to your form:
@using (Ajax.BeginForm("Action", "Controller", null, new AjaxOptions {
UpdateTargetId = "DivContainer"}new { enctype = "multipart/form-data", data_ajax_mode = "replacewith" }
Now you can do not worry about nested containers after Ajax updating of views on your Sitecore MVC application.
For future: I plan to implement my own  Ajax.BeginForm helper with similar abilities targeted on Sitecore.