레이블이 Sitecore인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Sitecore인 게시물을 표시합니다. 모든 게시물 표시

2020년 1월 23일 목요일

사이트코어 SXA 구조

2020년도 첫 포스트는 SXA로 시작을 해보자. 사이트코어 플랫폼에서는 새로운 웹사이트를 쉽게 생성하고 관리하는 SXA의 비중이 점점(?) 중요해지고 있다. 이는 최근 업데이트 및 차후 업그레이드 될 플랫폼의 로드맵을 보면 SXA는 꾸준히 업그레이되고 있으며, Sitecore 9.3부터는 CLI를 통하여 새로운 사이트 생성 및 관리를 할 수 있도록 아주 진보(?)적이다.

그래서 필자는 이번 포스트에서 Habitat을 기준으로 SXA 사이트 생성 및 컨텐츠, Theme 그리고 템플릿 Structure에 대하여 설명하고자 한다.

Tenant:

SXA는 Tenant 아이템을 생성하고 그 하위 아이템으로 Site 및 Tenant Folder를 생성할 수 있다. Tenant 아이템 아래에 추가적으로 Tenant Folder를 생성하면 유저는 세부적으로 웹사이트의 카테고리를 구분하여 Multiple 웹사이트를 관리할 수 있다. 아래는 Multiple Tenant를 생성하였고, 각각 Tenant에 Global 사이트를 생성하고 해당 Tenant안의 많은 웹사이트를 Standardize 할 수 있도록 계획을 세웠다. 글로벌 사이트를 생성하는 것은 Option으로써, 만약 Tenant안에 소수의 웹사이트가 존재한다면, 굳이 글로버 사이트를 생성할 필요은 없으며, 사이트들 중 하나의 웹사이트가 표준이 될 수도 있다. 이는 개발자 및 설계자의 재량으로써 비지니스의 컨셉 및 플랜에 따라 언제든지 바꿔질 수 있다.




Template:

Template 스트럭쳐는 쉽게 생각할 수 있지만 많은 수의 및 다양한 옵션을 요구하는 웹사이트에서는 아주 중요하다고 할 수 있다. 기본적으로 SXA Template은 Tenant 아래 하나의 사이트를 생성함과 동시에 만들어 지는데 템플릿은 "template/{tenant name}"에 존재하며, 해당 Tenant에서 사이트를 생성할 때 마다 모든 사이트는 Tenant 템플릿을 사용되도록 세팅이 되어있다. SXA의 장점중의 하나로 웹사이트는 Page Design 및 Partial Design으로 쉽게 디자인을 변경하고 추가할 수 있도록 잘 짜여져 있지만, 어떤 특정 사이트를 개발하고 그 사이트가 템플릿에서 새로운 필드를 필요로 한다면 필드는 Tenant 템플릿에 추가되어야 한다.

새로운 필드는 컴포넌트와 함께 연동 될 수 있으며, 만약 많은 웹사이트가 Tenant 템플릿을 사용 중이라면 분명히 통합하는 과정에서 해당 필드 및 템플릿 업데이트가 다른 사이트에는 문제가 되지 않는지 짚고 넘어가야 한다. 이런 부분을 해소하기 위하여 아래처럼 Tenant 폴더에 Global 템플릿을 기본 템플릿으로 설정하고 특정한 사이트만의 템플릿을 만들어 Global 템플릿에 상속되도록 설정할 수가 있다.

모든 페이지 및 웹사이트가 하나의 템플릿에서 Page Design과 Partial Design에 의존을 하는지 아니면, 사이트만의 템플릿과 함께 Page Design 및 Partial Design이 연동되는지는 개발자 및 설개자의 재량이다. 만약 Sitecore 사용자가 사이트 특정의 Workflow를 사용할 계획이 있다면 사이트 특정 템플릿을 만드는것이 하나의 장점일뿐더러 차후 웹사이트만의 퍼브리슁을 관리하고 매니지하는 부분에 있어서는 좋은 옵션이라고도 볼 수 있다.




Theme:

SXA Theme은 SASS를 통하여 CSS를 생성하고 사이트코어에 업로드 된 CSS는 플랫폼 차체 프로세서를 통하여 Optimize/minify 된 CSS 파일을 생성한다. 사이트 Theme에는 사이트를 생성할 때 선택되어진 모듈/컨포넌트의 SASS/CSS를 포함하고 있는데 이 모든 CSS는 통합되어지고 SXA 사이트에서는 "optimized-min.css"만 불러들임으로써 스타일 및 다지인을 렌더링 할 수 있다. SXA 사이트를 생성할때 사이트만의 Theme를 새로 생성할 수 있으나 이미 똑같은 디자인 및 theme이 존재하다면 굳이 새로운 사이트 Theme를 생성할 필요는 없다. 아래처럼, 필요자는 몇 개의 Theme을 정하고 Theme이 재사용 되어지도록 리스트 하였다. Theme의 이름은 사이트 이름 및 Tenant 이름과 반드시 매치될 필요는 없다.




Media:

SXA 사이트 컨텐츠 트리는 웹 페이지 아이템뿐만 아니라 사이트 세팅, 컴포넌트 로컬 데이타소스의 Data폴더 및 디자인을 위한 프리젠테이션 등 하나의 패키지형식으로 사이트 아이템이 구성되어져있다. 여기서 추가적으로 Media 폴더가 있는데 폴더 하위에는 사이트 이름의 폴더와 "Shared" 라는 폴더가 기본으로 존재한다. 만약, 이미지 및 Digital Assets을 Tenant안의 다른 모든 사이트와 공유를 하고 싶다면 "shared" 폴더에 이미지 및 디지털 파일을 저장하고 그 Digital Assets이 오직 해당 사이트에서만 사용되어진다면 사이트 폴더에 저장하면 된다. 해당 Media 폴더에 업로드된 아이템은 사이트코어의 Media Library에 동등한 사이트 경로 "/sitecore/media library/Project/{tenant 이름}/{사이트 이름}/{파일 이름}" 에 저장이 되며, 사이트 내의 이미지들은 자동으로 매핑이 된다.




이번 포스트에서는 SXA의 세부(?) Structure에 대하여 알아봤으므로 다음에는 어떻게 Page Design 및 Partial Design을 페이지에 적용시키는지 알아보도록 하자.





2019년 11월 16일 토요일

Sitecore Symposium 2019 올랜도 - 후기

사이트코어 심포지엄 2019 (Sitecore Symposium 2019)에서의 메인 Keynote는 Sitecore SaaS (Software as a Service) 발표이다. 공개적으로 어떻게 SaaS의 아키텍쳐 및 SaaS가 Microsoft Azure의 기준으로 개발되는지 아니면 AWS에서도 똑같은 기준으로 서포트를 하고 이용할 수 있는지는 발표되어지 않았다. 아래는 이번 Symposium에서의 Keynote 및 기준으로 리스트를 만들었다.

Sitecore 9.3 Winter 2019 Release

  • ContentHub 3.3 새로운 버전 출시 및 CMP (Content Marketing Platform) 은 Salesforce Marketing Cloud와 연동되어 쉽게 마케팅 컨텐츠를 관리하고 매니지 할수있다.
  • 3rd Party 서치엔진인 Coveo와 부분적으로 연동하기 시작한다.
  • Horizon이라는 새로운 에디터 인터페이스를 적용한다. 이는 기존의 Content Editor와 Experience Editor의 구분없이 한 UI에서 컨테츠를 관리하며 디자인 뷰를 제공한다.

  • SXA (Sitecore Experience Accelerator) 9.3 은 Front-End 개발 Workflow를 강화하는 동시에 추가적으로 새로운 2개의 Search Facet Filter를 추가한다.
  • 기존 SXA Theme은 Sitecore의 UI로만 제공되었으나, SXA 9.3에서부터 SXA CLI를 이용하여 Site Theme을 생성할 수가 있다. 
  • SXA 9.3는 Scriban 이라는 Text Templating Engine을 Rendering Variants를 적용하는데 이용할 수 있도록 한다.

  • Auto Personalization이라는 새로운 옵션을 적용하여 개별적인 아이템 Personalization 설정없이 Sitecore AI가 컨테츠를 체크하고 자동으로 적용을 시킨다.

Summer 2020

  • Sitecore SaaS 릴리즈 
  • ContentHub – DAM (Digital Asset Management)에 Video AI 적용시키며 Open API도 제공한다.
  • Experience Platform에 Container (예, Dockers)를 추가하고 쉽게 Instance 및 설정을 이미지화 할수있다. 이는 SaaS 이용과 동시에 큰 변화라 예상한다.
  • 새로운 개발 및 컨텐츠 에디터의 환경 변화가 있을꺼라 예상한다.

내년 Sitecore Symposium 2020은 시카고에서 10월 26일부터 10월 29일까지 진행되며, Sitecore 9.3의 Update 버전과 동시에 SaaS대한 많은 설명이 있을거라 예상한다.







2019년 3월 22일 금요일

API를 사용하여 Datasource를 생성하고 페이지 레이아웃 업데이트하기 [Part 2]

이전 포스트의 API를 사용하여 데이타소스(DS) 아이템을 만드는 방법에 이어서 이번에는 어떻게 API를 컴포넌트에 적용시키는지 알아보도록 하자. 필자는 아주 심플한 RichText Editor 컴포넌트를 만들었고, 컴포넌트 내에 API를 XMLHttpRequest를 통하여 Ajax Call을 실행하였다.

페이지 아이템의 xEditor에서 API를 이용하여 DS를 생성할시 아이템은 아래의 스크린샷처럼 하위경로의 DS폴더안에 생성된다.


해당 DS를 다른 페이지에서 재사용 할 경우 페이지 구조상으로 문제가 될수 있지만, 필자는 API를 컴포넌트에서 쉽게 사용하는 방법의 목적의 예제이므로 컨턴츠 구조 문제는 다루지 않겠다. 아래는 Controller Rendering의 RichText Editor 컴포넌트이다.

CustomComponentController.cs (Controller)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Sitecore.Mvc.Controllers;
using Sitecore.Mvc.Presentation;
using System;
using System.Web.Mvc;
using Sitecore.Data;

namespace Sitecore.Custom.Component.RichTextEditor
{
    public class CustomComponentController : SitecoreController
    {
        /// <summary>
        /// RichText Editor의 Action Method
        /// </summary>
        /// <returns>ActionResult</returns>
        public ActionResult RichTextEditor()
        {
            Models.RichTextEditor rte = new Models.RichTextEditor();
            rte.PageItem = PageContext.Current.Item;
            rte.Rendering = RenderingContext.CurrentOrNull.Rendering;
            rte.RenderingDatasource = rte.Rendering.DataSource;

            // 레이아웃에 적용되어진 RTE 컨트롤에 데이타소스가 적용되었는지 확인한다.
            rte.IsDatasourceSet = (!String.IsNullOrEmpty(rte.RenderingDatasource) ? true : false);

            // 새로운 데이타소스 아이템의 이름을 선업한다.
            rte.NewItemName = rte.PageItem.Name + "_" + rte.DatasourceItemSuffix;

            // RTE DS의 템플릿을 선언한다.
            rte.RteTemplate = rte.PageItem.Database.GetTemplate(new ID("{0B57D07C-F406-4700-9CAC-D4C4B84D1B78}"));

            return PartialView(rte);
        }
    }
}

RichTextEditor.cs (Model)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using Sitecore.Data.Items;
using Sitecore.Mvc.Presentation;

namespace Sitecore.Custom.Component.Models
{
    public class RichTextEditor
    {
        // Initialize component's rendering info
        public Item Item { get; set; }
        public Item ResourceFolder { get; set; }
        public Rendering Rendering { get; set; }
        public TemplateItem RteTemplate { get; set; }
        public string NewItemName { get; set; }
        public string DatasourceItemSuffix { get { return "RTE"; } }
        public string RenderingDatasource { get; set; }
        public Item PageItem { get; set; }
        public bool IsDatasourceSet { get; set; }
    }
}

RichTextEditor.cshtml (Views)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@model Sitecore.Custom.Component.Models.RichTextEditor
@if (!Model.IsDatasourceSet && Sitecore.Context.PageMode.IsExperienceEditor)
{
    <div style="padding:20px 0;" class="border border-success">
        <h4>RichText Editor Added</h4>
        <button type="button" class="btn btn-success" onclick="SendAjaxPOST(); return false;">Create Rich Text Editor Datasource</button>
        <script>
            function SendAjaxPOST() {
                // 현재 페이지에서 다른 아이템 및 필드가 수정되었는지 확인한다.
                if (Sitecore.PageModes.PageEditor.isModified()) {
                    alert("There are unsaved items on this page.\rPlease save the page, then create a new datasource.");
                    return false;
                }

                // XHR 시작
                var xhr = new XMLHttpRequest();
                xhr.open("POST", "http://sc910.sc/customapi/createitem/@Model.PageItem.ID.Guid.ToString()");
                xhr.setRequestHeader("Content-Type", "application/json");
                xhr.send("{\"itemName\": \"@Model.NewItemName\", \"templateGuid\": \"@Model.RteTemplate.ID.Guid.ToString()\", \"isDatasourceItem\": \"1\", \"renderingUid\": \"@Model.Rendering.UniqueId.ToString()\"}");
                xhr.onreadystatechange = function () {
                    if (this.readyState == 4) {
                        if (this.status === 200) {
                            window.onbeforeunload = null;
                            this.returnValue = true;

                            setTimeout(function () { location.reload() }, 500);
                        } else {
                            console.log('Error Status: ' + this.status + '\nHeaders: ' + JSON.stringify(this.getAllResponseHeaders()) + '\nBody: ' + this.responseText);
                        }
                    }
                };
            }
        </script>
    </div>
}
else
{
    <div>@Html.Sitecore().Field("Content")</div>
}

해당 컴포넌트를 사이트코어 페이지에 적용하고 실행하면 ContentEditor 와 xEditor의 페이지 이동 및 확인없이 쉽게 데이타소스를 생성할수있다. 이는 Non-Technical 유저 입장에서 쉽게 페이지를 생성하고 데이터소스를 다이나믹하게 생성하는데 도움이된다.





2019년 1월 15일 화요일

사이트코어 Federated Authentication - Azure AD Workflow

이전 포스트에서는 Sitecore Federated Authentication을 사용하여 어떻게 Azure AD를 컨넥하고 유저정보를 인증하는지 그리고 어떻게 Customize한 코드를 적용하는지에 대하여 알아보았다.
이번에는 조금 더 디테일하게 접근하기위하여 필자는 유저의 이벤트 Request부터 시작하여 어떻게 Azure AD를 통하여 사이트코어 로그인을 성공하는지에 대하여 Workflow를 그려보았다.

순서부터 설명하자면,

  1. 유저는 Authentication 버튼이 생성되어진 로그인 페이지로 이동한다.
  2. 개발자의 의해 Customize되어진 사이트코어 Pipleline을 통하여 등록되어진 Microsoft Identity Server로 이동한다.
  3. MS 로그인페이지에서 유저 이메일 및 암호를 입력하고 로그인을 시도한다.
  4. MS Identity Server는 사이트코어 Pipeline를 통하여 받은 정보와 유저의 로그인 정보를 통하여 Azure AD로 이동한다.
  5. 유저정보가 Azure AD에 등록 또는 이용가능한 정보인지 검증한다.
  6. 검증이 확인되면 다시 MS Identity Server로 이동 후, 유정정보를 이용하여 Unique한 Token을 생성한다.
  7. 사이트코어는 생성된 Token과 함께 유저정보를 사이트코어 Pipeline에 보낸다.
  8. 사이트코어 Pipeline 및 설정에 등록된 Claims에 따라 유저정보 및 프로파일을 업데이트 한다.
  9. 유저는 사이트코어 Dashboard로 이동하여 사이트코어 CMS를 이용한다.




2018년 11월 19일 월요일

사이트코어의 새로운 MVC Route 적용하기

이번 블로그에서는 새로운 MVC Route를 사이트코어에 적용하는 방법을 알아보자.
사이트코어의 기본 MVC Route는 "/api/sitecore/{controller}/{action}"으로써 Sitecore 9 이상의 버전에서는 Sitecore.Mvc.config, 그리고 Sitecore 9 하위 버전에서는 Sitecore.Speak.Mvc.Config 파일에서 확인할 수가 있다.
Sitecore의 자체적인 MVC Application의 컨트롤는 "/sitecore/layout/controllers" 아이템 트리에 등록되어진 Application 렌더링 아이템을 작동시키지만, 간혹 아이템의 등록없이 새로운 API를 만들어 외부 소스를 Ajax Call로 불러들여 원하는 기능을 작동시킬수가 있다.

이를 위하여, 새로운 MVC Route를 만듬으로써 기본 API 경로에 구애받지 않고 사이트코어 내에 다른 어플리케이션을 독립적으로 수행할수가 있다. 지금부터 어플리케이션을 만들고 적용시키는 방법을 알아보자.

  1. Visual Studio에서 새로운 Empty MVC 프로젝트를 만든다.

  2. App_Start 폴더에 RouteConfig.cs을 새로운 Route를 레지스터하기 위하여 수정한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    using System.Web.Mvc;
    using System.Web.Routing;
    
    namespace SitecoreSpaceNewRoute
    {
        public class RouteConfig
        {
            public static void RegisterRoutes(RouteCollection routes)
            {
                //// 
                //// 기본 MVC Route
                ////
                //routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
                //routes.MapRoute(
                //    name: "Default",
                //    url: "{controller}/{action}/{id}",
                //    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
                //);
    
    
                ////
                //// 사이트코어 스페이스의 새로운 MVC Route
                ////
                routes.MapRoute(
                    name: "SitecpreSpaceRouting",
                    url: "new/sitecore/space/routing/{controller}/{action}/{id}",
                    defaults: new { controller = "SitecoreSpace", action = "MyView", id = UrlParameter.Optional }
                );
            }
        }
    }
    


  3. 새로운 폴더 "CustomRouting" 이라는 폴더를 만들고, Sitecore.Kernel assembly를 Reference에 추가하고 사이트코어의 Bin 폴더의 System.Web.Mvc를 사용하도록 프로젝트에 업데이트한다. 레지스터한 Route를 Sitecore.Pipeline의 프로세스에 적용을 시킨다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    using System.Web.Routing;
    using Sitecore.Pipelines;
    
    namespace SitecoreSpaceNewRoute.CustomRouting
    {
        public class InitializeRoutes
        {
            /// <summary>
            /// 새로 등록한 Route는 Sitecore Pipeline에 적용시킨다.
            /// </summary>
            /// <param name="args"></param>
            public void Process(PipelineArgs args)
            {
                RouteConfig.RegisterRoutes(RouteTable.Routes);
            }
        }
    }
    


  4. 간단하게 새로운 Route의 레지스트를 마쳤으며, 이젠 Simple MVC을 만들자.

    Controller (SitecoreSpaceController.cs)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace SitecoreSpaceNewRoute.Controllers
    {
        public class SitecoreSpaceController : Controller
        {
            // GET: SitecoreSpace
            public ActionResult MyView()
            {
                Models.SitecoreSpace ssnr = new Models.SitecoreSpace();
    
                // Valuable 값을 다른 소스 (예, 데이타베이스)에서 불러들일수 있지만,
                // 이번은 예제이므로, Static 값을 메뉴얼로 적용하겠다.
                ssnr.Subject = "사이트코어의 새로운 MVC Routing";
                
                ssnr.Description = "사이트코어 스페이스의 새로운 MVC Routing으로써, 기존 (Default)의 Routing 제한을 받지않고, 임의의 새로운 Route를 적용시켜 독립적인 MVC 어플리케이션을 Sitecore XP내에서 작동시킬수 있다.";
                ssnr.CurrentDateTime = DateTime.UtcNow;
    
                return View(ssnr);
            }
    
            // Redirect가 아니 다른 View rendering으로 사용할수가 있다.
            public ActionResult RedirectToBlog()
            {
                Models.SitecoreSpace ssnr = new Models.SitecoreSpace();
                ssnr.BlogUrl = "https://SitecoreSpace.Blogspot.com";
    
                return Redirect(ssnr.BlogUrl);
            }
        }
    }
    


    Model (SitecoreSpace.cs)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    using System;
    namespace SitecoreSpaceNewRoute.Models
    {
        public class SitecoreSpace
        {
            public string FullBlogName { get; set; }
            public string BlogUrl { get; set; }
            public string Subject { get; set; }
            public string Description { get; set; }
            public DateTime CurrentDateTime { get; set; }
        }
    }
    


    View (MyView.cshtml)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @model SitecoreSpaceNewRoute.Models.SitecoreSpace
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>MyView</title>
    </head>
    <body>
        <div class="form-horizontal">
            <h4>SitecoreSpace</h4>
            <hr />
            <div class="col-md-10">
                @Html.DisplayTextFor(model => model.Subject)
            </div>
            <br />
            <div class="col-md-10">
                @Html.DisplayTextFor(model => model.FullBlogName)
            </div>
            <br />
            <div class="col-md-10">
                @Html.DisplayTextFor(model => model.Description)
            </div>
            <br />
            <div class="col-md-10">
                링크: @Html.ActionLink("Sitecore Space Blog", "RedirectToBlog", null, new { target = "_blank" })
            </div>
            <br />
            <div class="col-md-10">
                현재시간: @Model.CurrentDateTime.ToString("yyyy-MM-dd h:mm:ss tt")
            </div>
        </div>
    </body>
    </html>
    


    View (RedirectToBlog.cshtml)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>RedirectToBlog</title>
    </head>
    <body>
        <div> 
            추가 extra 페이지
        </div>
    </body>
    </html>
    


  5. 새로운 패치파일을 만들어 사이트코어 파일시스템의 "/App_config/Include/" 폴더에 z.Sitecore.Space.Route.config을 만든다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <?xml version="1.0" encoding="utf-8"?>
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
      <sitecore>
        <pipelines>
          <initialize>
            <processor type="SitecoreSpaceNewRoute.CustomRouting.InitializeRoutes, SitecoreSpaceNewRoute" 
                       patch:after="processor[@type='Sitecore.Pipelines.Loader.EnsureAnonymousUsers, Sitecore.Kernel']" />
          </initialize>
        </pipelines>
      </sitecore>
    </configuration>
    


이젠 브라우저에서 패치되어진 API 경로를 이동을 해본다.








2018년 10월 26일 금요일

Sitecore Symposium 2018 in Orlando FL, USA - 사이트코어 9.1 Overview

조금 늦었지만, 이번 달에 있었던 Sitecore Symposium에 대하여 이야기하도록 하겠다.
이번 Symposium은 작년에 출시된 Sitecore 9.0의 섭버전으로써, 9.1에서는 Performace 업그레이드를 중점을 두었다.

아래는 9.1에서 새로 공개된 기능들이다.
  • JSS - JavaScript Service로써, Headless CMS이다. 이는 굳이 Presentation Layer를 걸치지 않고, 자바스크립트 라이브러리를 이용하여 사이트코어 어플리케이션을 개발 및 Sitecore CMS로 퍼블리쉬할수가 있다. 참고로, JSS를 사이트코어에서 사용하기 위해서는 사이트코어 Subscription 버전의 라이센스를 사용하여야지 이용 및 개발이 가능하다. 이번에 업그레이드 되어진 버전은 xEditor 및 Personalization and Tracking 기능과 통합되었다.

  • XM Performance - 아래의 테이블처럼 컨텐츠 로딩 시간이 현저히 줄어들었고, 컨텐츠 서치 기능을 향상시켰다.

  • Forms - Forms기능은 9.0에서 새로 소개되었으며, 이번 버전에는 Form Condition기능을 추가하여 유저의 선택사항에 따라 다른 필드 옵션을 제공한다.

  • Update Center - 컨트롤 패널 또는 어드민 페이지에서 Sitecore를 업그레이드한것과 달리 Speak3 어플리케이션을 사용하여 새로운 UI에서 시스템을 업그레이드한다. 또한, 외부 소스를 쉽게 불러들일 수 있다. 업그레이드가 진행중일때는 사이트코어의 모듈 및 Enviornment 레이어가 Disable되고 사이트코어 업그레이 모드로 전환이된다.

  • Sitecore Identity - 9.0에서 소개된 Federated Authentication되었는데, 이번에는 추가적으로 SSO (Single Sign On)을 제공한다. 만약, 멀티플 사이트코어를 관리해야한다면, 굳이 추가적인 로그인없이 XM, XP, xCommerce 등 쉽게 접속할수가 있다.

  • Universal Tracker - Web/REST API를 기반으로 트래킹하는 프레임워크로 Collection(수집), Storing(저장), Process(진행)순으로 Stage가 진행된다. Website뿐만 아니라 App, AR/VR 등 다양한 플랫폼으로부터 정보받아 Sitecore xDB와 연결을한다.

  • Horizon - 이는 작년에 이미 소개되었지만, 사이트코어 CMS의 새로운 UI/UX이다. Horizon의 새로운 시뮬레이션 기능과 유저 프로파일가능들은 충분히 발전된 기능이라고 할수있다. 아직 오피셜하게 공개되어지지 않은 관계로 Performance에 대하여 말하기는 어려운것같다.

  • Sitecore Host - Sitecore .Net Core로써, 이미 Sitecore Commerce에 도입하였지만, 이번에는 Sitecore Identity, Horizon, Universal Tracker에 사용되어진다. .Net Core을 도입함으로써 다른 OS Platform과 쉽게(?) 호환이 가능하다.



  • Cortex - 머신러닝(ML)로 다양한 디바이스 및 컨텐츠를 통합, 분석 및 테스트를 하며, 결과물을 유저에 맞춤형 서비스를 제공하고 데이타를 Personalization한다.

  • Cortex Tagging - Open Calais의 솔루션을 이용하여 SEO 및 컨텐츠를 서비하는데에 도움(?)을 주며, 마케팅 오토메이션과 유용하게 사용되어질수있다.


아래는 Sitecore 9.1 Overview에 사용되어진 슬라이드이다.





2018년 8월 21일 화요일

Sitecore Migration - Data Migration Assistant 사용하기

이전에 포스트 하였던 Sitecore Migration의 두번째로 필자가 경험하였던 문제에 대하여 이야기를 하고자한다.

Sitecore Migration은 사이트코어의 새로운 버전이 나올때마다, 개발자 또는 설계자들은 "어떻게 하면 가장 쉽고 안정적인 방식으로 데이타의 손실없이 Migration을 할수가 있을까?"가 첫번째의 과제이다. 그 다음으로는 기존에 사용하였던 방식 또는 설정들이 얼마나 새로운 버전에서 잘 호환되고 작동되는지를 눈여겨 봐야한다.

이번 포스트는 필자가 생각하는 데이터의 손실없이 가장 손 쉬운 방법으로 Migration하는것에 대하여 알아보도록 하겠다.

먼저, 이전에 사용해보았던, Sitecore Azure ToolKit은 Sitecore Azure Deployment의 목정으로써, Sitecore On-Prem Instance를 Azure Cloud로 Deploy를 할수가 있다. 패키지를 Powershell Script을 통하여 만드는데, 작업 시스템의 환경에 땨라 제한이 있을수 있다. (필자의 경우, 시스템에서 2GB의 패키지 제한이 있어, 기존 데이타를 포함 약 6GB에 해당되는 Sitecore Instance를 패키지하기에는 한계가 있었다.) 이로, 새로운 방법으로 Sitecore에서 추천한 SSMS (SQL Server Management Studio)을 사용하는것이다. SSMS에서 Azure SQL를 컨넥하고, 백업된 DB를 Import하는 방식인데, 이 역시 기존 Master DB의 테이블들을 백업하는데 무리가 있었다.

그래서 결론은 Data Migration Assistant (DMA).

DMA는 Microsoft에서 오피셜로 제공하며, 이는 기존 DB (Source)를 다른 DB (Target)으로 분석 및 검증과 함께 데이타베이스를 Migration할수가 있다. 아래는 필자가 DMA를 사용하여 Sitecore Migration을 한 진행과정을 나열하였다.
  1. 로컬머신에 Azure에 존재하는 같은 버전의 Sitecore Instance를 설치한다.
  2. SSMS에서 Azure SQL의 Master DB를 컨넥하고, 아래의 Statement를 적용하여 해당 테이블의 데이터를 모두 삭제한다. (반드시, 기존의 데이터는 백업을 미리해둔다.)

    truncate table AccessControl;
    truncate table Archive;
    truncate table ArchivedFields;
    truncate table ArchivedVersions;
    truncate table ArchivedItems;
    truncate table Blobs;
    truncate table ClientData;
    truncate table Descendants;
    truncate table EventQueue;
    truncate table History;
    truncate table IDTable;
    truncate table Links;
    truncate table Notifications;
    truncate table Properties;
    truncate table PublishQueue;
    truncate table SharedFields;
    truncate table Tasks;
    truncate table UnversionedFields;
    truncate table VersionedFields;
    truncate table WorkflowHistory;
    truncate table Items;
    
  3. DBA에서 소스/타켓 데이타베이스를 정하고, Migration을 시작한다.





  4. FTP를 통하여 Azure App Service에 연결하고, Custom 어플리케이션 또는 Config 파일들을 매뉴얼로 복사한다. (CMS / CDS if needs)
  5. Azure에서 App Service를 Restart한다.
  6. Sitecore CMS에 로그인하여 잘 작동(?)되는지 확인한다.
  7. Master DB만 Migration을 한 관계로, 혹 Core DB에서 Custom 업데이트가 있다면 사이트코어 CMS에서 패키지를 만들어 인스톨한다.
  8. 사이트코어 컨트롤 패널에서 "Clean up Databases"를 실행한다.
  9. 사이트코어 컨트롤 패널에서 "Rebuild Link DB"를 실행한다.
  10. 사이트코어 컨트롤 패널에서 "Rebuild Indexes"를 실행한다.
  11. 사이트코어 컨트롤 패널에서 "Deploy Marketing Definitions"을 실행한다.
  12. 사이트코어 컨트롤 패널에서 "Rebuild Link DB"를 실행한다.
  13. "Publish the site"를 통하여 Web DB로 컨텐츠를 Publish 한다.

DBA를 통하여 사이트코어 Migration을 하는데 아무런 문제없이 잘 진행될수있었다.



2018년 7월 17일 화요일

사이트코어 Copy vs Duplicate vs Clone

사이트코어를 사용하다보면 기본의 아이템 (item)과 그의 섭아이템 (subitem)을 복제하여야 하는경우가 많이 있다.
이번에는 사이트코어의 기본 기능중에 복사, 복제, 클론에 대하여 알아보도록 하자.

Copy (복사)

  • 선택되어진 아이템 (Source "/sitecore/homepage/source-item-page")과 그의 섭아이템을 다른 경로 (Destination "/sitecore/anotherpage/copied-item-page")로 복사를 한다.
  • Source경로에서 복사되어질 아이템의 프리젠테이션 레이아웃 디테일을 복사하고, 복사되어진 새로운 경로의 Datasource 경로는 업데이트 하지 않는다.
Source 경로: /sitecore/homepage/source-item-page/resource folder/body content
Destination 경로: /sitecore/anotherpage/copied-item-page/resource folder/body content

Duplicate (복제)

  • 선택되어진 아이템(Source, "/sitecore/homepage/source-item-page")과 그의 섭아이템을 같은 경로(Destination, "/sitecore/homepage/duplicated-item-page")로 복제를 한다.
  • Source경로에서 복사되어질 아이템의 프리젠테이션 레이아웃 디테일을 복사하고, 파이널 레이아웃의 Datasource 경로만 업데이트를 한다. Shared Layout의 컨트롤의 Datasource 경로는 변경하지 않는다.
  • 복제되어진 아이템을 다른 경로로 이동을 하면 Final Layout의 Datasource 경로도 새로운 경로와 함께 업데이트 된다.
Source 경로: /sitecore/homepage/source-item-page/resource folder/body content
Destination 경로: /sitecore/homepage/duplicated-item-page/resource folder/body content

Clone (클론)

  • 선택되어진 아이템(Source, "/sitecore/homepage/source-item-page")과 그의 섭아이템을 다른 경로(Destination, "/sitecore/anotherpage/cloned-item-page")로 복제를 한다.
  • Final Layout의 정보를 Reset 한다.
Source 경로: /sitecore/homepage/source-item-page/resource folder/body content
Destination 경로: /sitecore/anotherpage/cloned-item-page/resource folder/body content


2018년 7월 16일 월요일

사이트코어 심포지엄 - Sitecore Symposium 2018, 올랜도 플리리다

지난 해의 Sitecore Symposium 처럼 이번해에도 Sitecore는 플로리다 올랜도에서 Sitecore Symposium을 개최한다. 이번에도 역시 새로 적용된 기술을 소개할것이고, 조금 더 집중적으로 Cortext/ML/AI 및 Horizon & Zenith (UI/UX)에 관련하여 설명할것으로 예상된다.

현재까지 사이트코어 최신버전 9.0 Update 2이지만, 이번 Symposium 이후, 버전 9.1을 릴리즈 할것으로 예상되며, 새로운 기술뿐만 아니라 Keynote 및 Customer Event를 통하여 참석자들에게 다양한 경험을 제공할것이다. 참고로, 이번에 게스트 스피커로써 헐리우드 배우 윌 스미스 (Will Smith)가 참석한다.


Sitecore Symposium: 2018년 10월 8일 ~ 11일
Location: Walt Disney World Swan & Dolphin Resort, 올랜도 플로리다






2018년 6월 18일 월요일

사이트코어 내의 URL 마스크 (Alias) 설정하기

사이트코어 CMS에는 많은 기능이 포함되어있다. 그 중의 하나가 바로 Alias. 이 기능은 흔희 말하는 URL Mask 또는 cloaked URL이라고 불리며, 원래의 URL 링크주소를 Mask하여 다른 이름으로 바꿔주는 것이다.

보통 이 기능은 웹서버  (Rewrite Module) 또는 프로그래밍 상에서 Redirect Method (C#)를 사용하여 URL을 변경하지만, 사이트코어의 Aliases 기능을 통하여 쉽게 변경할수가 있다.

"http(s)://yoursite/blog/2018/11/25/My-Blog"를 "http(s)://mysite/blog/My-Blog"으로 변경하는것을 예로 들어 순서대로 세팅을 해보겠다.

  1. 해당 URL은 사이트 홈 (시작페이지) 아래의 "Blog" 아이템에서 URL이 시작하므로, "Blog" 아이템을 선택한다.
  2. Content Editor의 Presentation 탭 또는 Experience Editor의 Advance 탬에서 "Aliases"를 클릭한다.
  3. Name 필드에 "/blog" 입력하고, "Add" 버턴을 눌러 Aliases를 추가한다.

  4. 이젠, "My-Blog" 아이템을 눌러주고 같은 방법으로 Name 필드에 "/blog/My-Blog"를 추가한다.

  5. 위 두개의 아이템의 Aliases를 세팅함으로써 "/sitecore/system/Aliases/"에 "blog/My-Blog" 라는 아이템이 Linked 아이템과 함께 생성되었다.

  6. 원래 생성한 아이템 페이지로 가서 Related Item과 함께 아이템을 Publish하면, Alias가 적용된 URL로 페이지를 렌더링 할 수가 있다.
    http(s)://mysite/blog/My-Blog


* 참고로, 사이트코어의 Alias 기능은 Temporary Redirection 으로써 보통 Major Search Engine의 검색 결과에서는 기존의 URL (http(s)://mysite/blog/2018/11/25/My-Blog)로 결과물이 나타난다.

* 이번 예제는 Multi-Site가 아닌, 사이코어내의 기본 사이트 세팅에만 적용되는것이며, 한 Instance에 Multiple 사이트가 존재한다면, 아래의 링크를 통하여 Pipeline을 업데이트하길 바란다.
How to configure site-specific aliases (https://kb.sitecore.net/articles/565325)







2018년 5월 4일 금요일

Rich Text Editor (RTE)에 YouTube 버튼 추가하기

사이트코어는 Telerik 에디터를 기본 Rich Text Editor (RTE)로 사용하며, Core DB에서 HTML Profile 세팅을 통하여 CMS Author들에게 툴 사용 제한을 둘수가 있다.

최근 회사에서 블로그 페이지 및 툴을 새롭게 만들었으며, 블로그 Author로부터 YouTube 링크를 RTE 디자인 모드에서 추가하고 싶어한다. 이번에는 어떻게 새로운 버턴을 만들고 커맨드를 형성 시키는지 알아보도록하자.
  1. Core DB로 이동 후, 새로운 버턴을 추가하고 싶어하는 Tool에서 "__Html Editor Button"의 템플릿으로 새로운 아이템을 추가한다.



  2. 새로운 아이템의의 속성을 아래와 같이 변경해주며, 아이템 아이콘, 디스플레이 이름도 변경한다.



  3.  "/WebSite/sitecore/shell/Controls/Rich Text Editor/RichText Commands.js" 파일로 이동 후, 새로운 "RadEditorCommandList"를 추가하고, "Regular Expression"을 통하여, 유튜브 비디오 아이디만을 가져온다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    RadEditorCommandList["InsertYouTube"] = function (commandName, editor, args) {
        var d = Telerik.Web.UI.Editor.CommandList._getLinkArgument(editor);
        Telerik.Web.UI.Editor.CommandList._getDialogArguments(d, "A", editor, "DocumentManager");
    
        // Highlight 되어진 텍스트만 가져온다.
        var html = editor.getSelectionHtml();
        if (getId(html)) {
            var videoId = getId(html);
            scEditor = editor;
            scEditor.pasteHtml('<div class="blog-rte-youtube"><iframe width="560" height="315" src="//www.youtube.com/embed/' + videoId + '" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe></div>');
        } else {
            // Highlight 되어진 텍스트가 YouTube 아이디와 매치가 되지 않을 경우, 메세지를 보낸다.
            alert("Please highlight YouTube link URL.");
        }
    };
    
    //Get YouTube video ID
    function getId(url) {
        var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
        var match = url.match(regExp);
    
        if (match && match[2].length == 11) {
            return match[2];
        } else {
            return false;
        }
    }
    


  4. Master DB로 이동 후, RichText Editor를 사용하고 페이지에서 Editor Popup을 실행한다.



YouTube 링크 버턴










2016년 7월 31일 일요일

사이트코어에 새로운 아이콘 추가하기

#Icon #NewIcon #사이트코어 #아이콘

 이번에는 Sitecore 아이콘에 대하여 설명하여 보겠다.

사이트코어에서 아이템의 타입을 쉬게 표시하기 위하여, 아이콘을 사용한다. Content Editor 상단의 툴바에서 "Configuration" 탭을 선택하면, Icon 메뉴가 있다. Icon 메뉴를 선택하면 최근 사용한 Icon 목록과 카테코리별로 나눠져있는 Icon리스트를 볼수있다.

  • Applications 
  • Business 
  • Controls 
  • Core 1 
  • Core 2 
  • Core 3 
  • Database 
  • Flags 
  • Imaging
  • Multimedia
  • Network 
  • Other 
  • People 
  • Software 
  • Word Processing

사이트코어는 기본적으로 .config 파일에서 "UseZippedIcons"을 "true" 세팅이 되어져있어, "/WebSite/Sitecore/Shell/Themes/Standard" ZIP file 형식으로 카테고리별로 저장이 되어있다. 만약, 새로운 아이템을 추가하고 싶다면 해당 카테고리를 unzip 하고, 새로운 아이콘을 사이즈별로 저장을 한다.

  • 16x16
  • 24x24
  • 32x32
  • 48x48
  • 128x128
기본적인 아이콘의 경로는 "/ZipFileName/Size/IconName.png" 이다.

2016년 3월 9일 수요일

Name Value List 타입을 이용하여 데이타소스 만들기

#HowTo #Datasource #GetItem

사이트코어에서 탬플릿을 만들다보면 다양한 템프릿 필드를 선택할수가 있다. 그 중에서 Key와 Value 개념인 "Name Value List" 타입에 대하여 알아보도록 하겠다.

간혹, 데이타소스를 만들는데 있어서, 똑같은 데이타 타입임에도 불구하여, 데이타의 수 만큼 데이타 이이템을 만들어야 하는경우가 있다.

사이트 이름사이트 URL
구글http://www.google.com
야후http://www.yahoo.com
레딧http://www.reddit.com
사이트코어http://www.sitecore.com
......


이럴경우 사이트 각각의 아이템을 만들 필요없이, 템플릿의 "Name Value List"을 선택함으로로써 하나의 데이타소스 아이템에 여러게의 값을 저장할수가 있다. 프로그래밍에서 Array 또는 List와 똑같은 개념이라고 할수있다.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var db = Sitecore.Configuration.Factory.GetDatabase("master");
// NameValueType의 필드를 가지고 있는 데이타소스 아이템의 ID 값을 입력하다.
var item = db.GetItem("{8A8E427B-9162-4680-AE69-239217F5B9AB}");
 
//데이타를 초기화하고,
var data = new NameValueCollection();
// 필요한만큼 데이타를 추가한다.
data.Add("name1", "value1");
data.Add("name2", "value2");
data.Add("name3", "value3");
data.Add("name4", "value4");
     
using (new Sitecore.SecurityModel.SecurityDisabler())
{
    item.Editing.BeginEdit();
    try
    {
        //데이타값은 필드 이름을 입력한다.
        //'&'는 값들의 Divider이다.
        item["NameList"] = StringUtil.NameValuesToString(data, "&");
    }
    finally
    {
        item.Editing.EndEdit();
    }
}

2015년 7월 30일 목요일

MVC Field Update in Page Editor (xEditor)

#HowTo #MVC #Ajax

 Sitecore CMS에는 Page Editor (xEditor) 그리고 Content Editor가 있다. CMS 사용자는 Page Editor에서 해당 페이지의 컨텐츠를 수정하며, 필요한 Rendering또는 Sublayout을 추가하는데, 사용자의 편의에 따라 Page Editor에서 해당 페이지의 템플릿 필드항목을 수정할때가 있다.

Sitecore는 XSLT extensions은 RenderField Pipeline를 기반으로 적용되지만, MVC에서는 "Sitecore.Web.UI.WebControls.FieldRenderer" 에서 "Render" 메써드를 지원하여, 이 메써드를 통하여, 템플릿을 Data Type을 내부적으로 캐치한다.

그러므로, MVC에서는 굳이 XSLT에서의 <sc:text ../>, <sc:image .../> 등등 Element를 선언할 필요가 없다.

아래의 코드는 Page Editor에 해당 Rendering Component의 체크박스 필드를  Droplist (Enabled/Disable options)로 구현하였으며, DropList에서 Enable이 선택되면 컴포넌트 아이템의 해당 체크박스 필드가 자동으로 체크가 되며, 반대로 Disable경우 해당 필드의 체크가 없어진다.

Model


public class ComponentModel : IRenderingModel
{
    public string Title { get; set; }
    public Item Item { get; set; }
    public Item PageItem { get; set; }
    public Sitecore.Data.Fields.CheckboxField chBox { get; set; }
    ... some other declared data types based on templates if you want ...

    public void Initialize(Sitecore.Mvc.Presentation.Rendering rendering)
    {
        Rendering = rendering;
        Item = rendering.Item;
        PageItem = Sitecore.Mvc.Presentation.PageContext.Current.Item;

        Title = FieldRenderer.Render(Item, "Title");
        ... more if you want ...
    }
}

Controller


public class Components : Controller
{
    //
    // POST: /Components/

    public ActionResult ComponentView(string changedValue, string fieldName)
    {

        ComponentModel ss = new ComponentModel();
        ss.Initialize(RenderingContext.Current.Rendering);
        Item item = ss.Item;

        if (!String.IsNullOrEmpty(changedValue) && !String.IsNullOrEmpty(fieldName))
        {
            ss.Item.Editing.BeginEdit();
            using (new SecurityDisabler())
            {
                switch (fieldName)
                {
                    ... conditions ...
                }
            }
            ss.Item.Editing.EndEdit();
        }
        return PartialView(ss);
    }
}

View


@model yournamespace.ComponentModel
@using Sitecore.Mvc

@if (Sitecore.Context.PageMode.IsPageEditor)
{  
    if (!@Sitecore.Data.ID.IsID(Model.Rendering.DataSource))
    {
        <div>No Associated Datasource.<br />Please Create New Datasource</div><br />
    }
    else
    {
        <div class="newdata">
            <h3>This is page editor</h3>
            Title: @Html.Raw(Model.Title) <br />

            DropList: <select name="list" id="fieldName" onclick="javascript:dlOnChangeUpdate('fieldName');">
                <option value="True" @Html.Raw((Model.chBox.Checked) ? "selected" : "")>Enable</option>
                <option value="False" @Html.Raw((!Model.chBox.Checked) ? "selected" : "")>Disable</option>
            </select><br />

            <script type="text/javascript">
                function dlOnChangeUpdate(fieldName)
                {
                    $("#" + fieldName).on('change', function () {
                        var changedValue = $("#" + fieldName).val();

                        $.ajax({
                            url: '@Url.Action("ComponentModel","Components")',
                            type: "POST",
                            data: { "changedValue": changedValue, "fieldName": fieldName },
                            context: this,
                            success: function (data) {
                                Sitecore.PageModes.PageEditor.postRequest('webedit:save()');
                                console.log("success", data);
                            },
                            error: function (data) {
                                alert('error: ' + data);
                                console.log("error", data);
                            }
                        });
                    });
                }
            </script>
        </div>
    }
}
else
{
    <div id="info">
        <h3>This is preview</h3>
    </div>
}