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.

Sunday, November 17, 2013

How to Allow a Virtual User to Browse on Pages with Saving Profile Values

There is an ability in Sitecore named Virtual Users. It could be used for many purposes, for example emulating of site view and work for different users. There is one limitation described in Security API Cookbook:
You must log in a virtual user only after you assign Roles and Profile properties to them. The Roles and Profile properties that are assigned after logging in are lost upon subsequent request.

This limitation make work with profiles changes useless: you could get profile values, but you couldn't change profile values. For example, saving values for virtual user on WFFM form will not work.  But this limitation could be overcame.

If Sitecore API cookbook requires login after assigning roles and profile properties, you could add your own processor to httpRequestEnd pipeline.

<httpRequestEnd>
     <processor type=" YourNamespace.Pipelines.HttpRequestEnd.LoginVirtualUser,  YourAssemblyName"
patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.EndDiagnostics, Sitecore.Kernel']" />
</httpRequestEnd>

namespace YourNamespace.Pipelines.HttpRequestEnd
{
    using Sitecore.ExperienceExplorer.Business.Helpers;
    using Sitecore.Pipelines.HttpRequest;
    using Sitecore.Security.Authentication;

    //this processor is used for applying settings to profiles of virtual users
    public class LoginVirtualUser : HttpRequestProcessor
    {
      public override void Process(HttpRequestArgs args)
      {
         if (Context.User.RuntimeSettings.IsVirtual)
         {
             AuthenticationManager.Login(Context.User);
         }
      }
    }
}

Voila, now assigning roles and changing profile properties works also for Sitecore virtual users.