Agility Developer Workshop

 

Before We Start

  • Must have an Agility Developer Sandbox
     
  • Visual Studio 2015/2017 Community or higher installed
     
  • AgilityCMS Model Updater VS extension installed
     
  • Grab a coffee, water, or tea (common area)
     

By - James Vidler

Director of Technology

About Me

  • Former head of professional services team
  • 8 years development experience
  • Partner and Developer advocate

Ask me About:

  • Development
  • Architecture
  • Training + Docs
  • Feature Requests
  • Azure

Contact me:

Email

Facebook

LinkedIn

 

 

Workshop Tips

  • Open this presentation on your laptop: bit.ly/agl-ws-sl
     
  • Follow-along on Labs
     
  • Time for catching up during break
     
  • Presentation includes step-by-step instructions for each lab
     
  • Ask for help if stuck
     

Agenda

  • Agility Overview
     
  • Building a Blog
     
  • BREAK - 15 min​s
  • Deployments
     
  • Custom Fields
     
  • Form Builder

Agility Architecture

True Cloud

  • Multi-tenanted, cloud hosted

Hybrid

  • Traditional with the Agility.Web SDK, Headless with the REST API, or both!

Agility

Agility Architecture

Web

Content Management

WCM: Unlimited Creative Freedom

  1. Build your bespoke schema in the cloud.
     
  2. You define the Pages, Page Templates, Modules, and Content
     
  3. Output code on your website using your front-end framework of choice

WCM: Demonstration

  • Pages
     
  • Shared Content
     
  • Media & Documents
     
  • Settings
     
  • Preview & Publishing

WCM: What's in a Page?

Page

Page Template

Content Zones

Modules

A

B

WCM: What's in a Page? (...)

Page Template

Content Zone A

Content Zone B

Module A

Module B

WCM: Content Defintions

  • Define structured content definitions such as a Blog Post, Product, etc.
     
  • Create content items and lists based on the definition
     
  • Content can be scoped to a specific page
     
  • Content can be 'Shared' in Shared Content and accessible from any Page

WCM: Developer Sandbox Overview

+

.NET MVC + Agility (...)

  • Follows MVC pattern
     
  • Agility.Web nuget package
     
  • AgilityWebsiteService.svc endpoint for syncing content
     
  • Customizable routing
     
  • Can be setup in an existing MVC application

.NET MVC + Agility

  • Page Template Output = Razor Partial View
     
  • Module Output = ControllerActionResult or Razor Partial View
     
  • Agility provides  auto-generated, C# API classes representing ALL content and module definitions

Agility.Web

  • Page routing
  • Page Template execution
  • Module execution
  • Content access and deserialization
  • Url Redirections
  • Output Caching
  • Core assembly of Agility SDK
  • Dependency on MVC
  • Handles syncing
  • Available on nuget
  • Preview Mode
  • Development Mode
  • Live Mode
  • Language Context Management
  • Channel Management

Does a lot of other stuff too:

And more...

HTTP(s) Request
~/about-us

RouteConfig.cs

Matches Agility route constraint

Agility.Web.dll
Renders Page Template and each Module

RenderBody()

Rendered HTML served in Layout file

Agility Page Render Lifecycle

Lab: Running an Agility MVC Website Locally

Steps to Run a Project Locally

  1. Locate and open the .sln file
     
  2. Go to "Build" > "Start without Debugging"
     
  3. Will build and download missing nuget packages
     
  4. Websites loads over IIS express on localhost:{random-port}

Now that we understand some basic concepts, we are going to build a blog ...

In the end, we should have a simple blog template, a list of posts, and a module to output the post details.

Lab: Create a new Page Template

Create a new Page Template

What we are going to do:

  1. Create a new Page Template definition called Blog Template with a single Main Content Zone
     
  2. Add code for our page template output
     
  3. Create a page using our new page template
     
  4. Verify the results

 

  1. In the Manager, go to Settings > Page Templates and click New to create a new Page template
     
  2. For New Page Template, use the following values:

    Name = Blog Template
    Digital Channel Type = Website
    Output Template = Relative URL
    Relative URL = ~/Views/Templates/BlogTemplate.cshtml

Create Page Template Definition

  1. Under Module Zones, click the + icon to create a module zone
     
  2. For Module Zone Details, enter the following values:

    Display Name = Main Content Zone
    Reference Name = [auto-populated]

Create Page Template Definition (...)

  1. ​Click Save to apply the module zone
     
  2. Click Save and Close  to finish the Page Template creation

Create Page Template Definition (...)

Create Page Template Definition Result

Add Partial View for Page Template

  1. In Visual Studio, create a new Partial View for the template using the Relative URL value you set for the Page Template
     
  2. Add the following code:
<!-- Normally, you'd but this in a CSS file -->
<style>
    .blog-template__wrapper {
        max-width: 730px;
        margin: 0 auto;
    }
</style>

<!-- Page Template Code -->
<div class="blog-template container contentWrap" role="main">
    <div class="blog-template__wrapper">
        @{Html.RenderContentZone("MainContentZone");}
    </div>
</div>

Create a Page using the Page Template

  1. In the Content Manager, go to Pages  and create a new Page, using your new Blog Template
     
  2. In the Main Content Zone of the page, add a Rich Text Area and input some sample content to test your template
     
  3. On your localhost, navigate to the page you just created and verify the output

Blog Page Template Result

Lab: Create a Module

Create a Module

What we are going to do:

  1. Create a new Module Definition called Blog Post Details
     
  2. Download and refresh the C# API
     
  3. Add code for our module output
     
  4. Add our module to a page a test the output
     

 

Create a new Module Definition

  1. In the Content Manager, navigate to Settings > Module Definitions and click New+ to open the New Module Definition fly-out
     
  2. In the Definition Details tab, enter the following values:

    Title = Blog Post Details
    Reference Name = [auto-populate]
    Description = Will output the details of a Blog Post

Create a new Module Definition (...)

Create a new Module Definition (...)

  1. On the Form Builder tab, add the following field types:

    Title - Text - [Required]*
    Details - HTML - [Required]*
    Image - Image - [Required]*

Create a new Module Definition (...)

  1. On the Output Template tab, select MVC Controller Action and set the following values:

    Controller = Blog
    Action = BlogPostDetails

Create a new Module Definition (...)

  1. Click Save & Close  to complete the creation of the module definition
     

Refresh C# Classes

  1. Need to download and refresh C# API for definitions
     
  2. Includes all module and content definitions as partial classes
     
  3. Take full advantage of intellisense and compile-time error checking
     
  4. Two ways to do this:
    - Download the file from the Content Manager
    - Refresh the file from within Visual Studio

Refresh C# Classes - From Content Manager

  1. In Content Manager, go to Settings > Module Definitions, click the Download API button
     
  2. Open the file and copy EVERYTHING below the namespace declaration

    Note: Namespace may not match project

Refresh C# Classes - From Content Manager (...)

  1. Find the existing file in your project - in the sandbox, it can be found in Models > MVCSampleSite_API.cs
     
  2. Replace EVERYTHING below the namespace, save and Build your project

Refresh C# Classes - From Visual Studio

  1. Requires AgilityCMS Model Updater Visual Studio extension
     
  2. Find the existing file in your project - in the sandbox, it can be found in Models > MVCSampleSite_API.cs
     
  3. Right-click the file in Visual Studio and select AgilityCMS  from the context menu

Refresh C# Classes - From Visual Studio (...)

  1. Select Update Model and enter your Agility Username and Password and click OK
     
  2. Confirm your Namespace, by default it will maintain your current Namespace - click OK  to update the file

Add Code for Module Output

  1. In Visual Studio, add a new Controller called "Blog" - filename will be BlogController
     
  2. Set the following code in the BlogController:
using MVC4SampleSite.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MVC4SampleSite.Controllers
{
    public class BlogController : Controller
    {
        public ActionResult BlogPostDetails(Module_BlogPostDetails module)
        {
            return PartialView("~/Views/Blog/BlogPostDetails.cshtml", module); 
        }

    }
}

Add Code for Module Output (...)

  1. In Visual Studio, create a new folder under Views called "Blog"
     
  2. Add a new PartialView called "BlogPostDetails" so the location of the file becomes "~/Views/Blog/BlogPostDetails.cshtml"

Add Code for Module Output (...)

  1. In Visual Studio, add the following code to the "BlogPostDetailis.cshtml" PartialView:
@model MVC4SampleSite.Models.Module_BlogPostDetails

<div class="blog-post-details">
    @if(Model.Image != null)
    {
        <!-- This img is being thumbnailed to 730px -->
        <img class="img-responsive" src="@Model.Image.URL?w=730" alt="@Model.Image.Label" />
    }

    <h1>@Model.Title</h1>

    @Html.Raw(Model.Details)

</div>

Add Module to a Page and set Content

  1. In Content Manager, go to Pages and add the Blog Post Details module to the page you created earlier with the Blog Template
     
  2. Set some sample values in the input form for the module, filling out the Title, Details, and Image fields
     
  3. Click Save & Close to save the module to the page
     
  4. Next, in Visual Studio, click Build, and navigate on your localhost to the page you added the module to

Verify Module Output

Content Architecture Challenge

Do you think our current Blog Post architecture is scalable? Why not?
 

How can it be improved?

Lab: Moving Module content to Shared Content

Moving Module content to Shared Content

What we are going to do:

  1. Create a new Content Definition representing our Blog Post schema
     
  2. Create a new Blog Post list in Shared Content
     
  3. Update our Blog Post Details module to allow editors to select a Blog Post
     
  4. Update our Blog Post Details module code

Create a new Content Definition

  1. In the Content Manager, go to Settings > Content Definitions and click on New+ to launch the New Content Definition fly-out
     
  2. In the Definition Details tab, enter the following values:

    Type = Content List
    Title = Blog Post
    Reference Name  = [auto-generated]
    Description = This is the definition for a Blog Post

Create a new Content Definition (...)

  1. On the Form Builder tab, add the following field types:

    Title - Text - [Required]*
    Details - HTML - [Required]*
    Image - Image - [Required]*

    Note: this is the exact same fields you had in your Blog Post Details module

     
  2. Click Save & Close to create the content definition

Create a new Content List in Shared Content

  1. In the Content Manager, go to Shared Content found in the main navigation menu
     
  2. Click on the + icon to launch the New Content List fly-out
     
  3. Set the following values:

    Content Definition = Blog Post
    Display Name = Blog Posts
    Reference Name = [auto-populate]
     
  4. Click Save to create the Shared Content list

Updating a Module Definition to use Linked Content

In the previous steps, we've replicated our Blog Post Details module schema to a Shared Content List called Blog Posts. Now we'll need to modify the module to be able to link to the Blogs Posts list.

 

Updating a Module Definition to use Linked Content (...)

  1. In the Content Manager, go to Settings > Module Definitions and select Blog Post Details
     
  2. Click on the Form Builder tab, and remove all existing fields by clicking the trash-can icon
     
  3. Create a new field - enter the following values:

    Field Label = Blog Post ID
    Field Name = [auto-generated]
    Hide Field From Input Form = true
    Field Type = Number
     
  4. Click Save & Close to create the field

Updating a Module Definition to use Linked Content (...)

  1. Create a new field - enter the following values:

    Field Label = Blog Post
    Field Name = [auto-generated]
    Field Type = Linked Content
    Content Definition = Blog Post
    Content View = Shared Content
    Shared Content = Blog Posts
    Render As = Dropdownlist
    Save Value To Field = Blog Post ID

    Note: Leave everything else as default values

Updating a Module Definition to use Linked Content (...)

  1. Click Save & Close to create the field
     
  2. Click Save & Close to save the changes to the module definition
     
  3. You will be prompted that your changes to your definition are in Staging Mode - address this by selecting the checkbox next Blog Post Details from the list and click Publish

Create a Blog Post Content Item in the List

  1. Go to Shared Content > Blog Posts
     
  2. Create a new Blog Post by clicking New+ and entering content for the fields: Title, Details, and Image
     
  3. Click Save & Close to save the content item

Select your Content Item from your Module

  1. Go to Pages and select the page you had previously added the Blog Post Details to and open the details of the module - you'll notice the old fields are gone and the new linked content dropdown is shown

  2. Click on the Blog Post linked content dropdown and select the content item you just created

  3. Click Save & Close  to save the new module values

Modify the Module ControllerActionResult

  1. Refresh your C# API either through the Content Manager or from Visual Studio
     
  2. In Visual Studio, open the file BlogController.cs and update the code:
using MVC4SampleSite.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Agility.Web.Extensions;

namespace MVC4SampleSite.Controllers
{
    public class BlogController : Controller
    {
        public ActionResult BlogPostDetails(Module_BlogPostDetails module)
        {
            BlogPost blogPost = module.BlogPost.GetByID(module.BlogPostID);
            return PartialView("~/Views/Blog/BlogPostDetails.cshtml", blogPost); 
        }

    }
}

Modify the Module PartialView

  1. In Visual Studio, open the file BlogPostDetails.cshtml and update the code:
@model MVC4SampleSite.Models.BlogPost

<div class="blog-post-details">
    @if(Model.Image != null)
    {
        <!-- This img is being thumbnailed to 730px -->
        <img class="img-responsive" src="@Model.Image.URL?w=730" alt="@Model.Image.Label" />
    }

    <h1>@Model.Title</h1>

    @Html.Raw(Model.Details)

</div>

Note: The only thing that has changed here is the ViewModel declaration at the top

Verify the Module Ouput

  1. Build your project and browse on your localhost to your page to test your updates
     
  2. The result on the website should be exactly the same  - since we only changed the content architecture

Content Architecture Challenge

Our Blogs are now in Shared Content, and can be reference by a Module to output the details.

However, this still presents some architectural issues.

 

What limitations do we have with this implementation?

Lab: Implementing Dynamic Pages

Implementing Dynamic Pages

What we are going to do:

  1. Upgrade our existing Blog Posts list in Shared Content to a Dynamic Page List
     
  2. Create a new Dynamic Page in Pages
     
  3. Update our Blog Post Details module to not use Linked Content anymore
     
  4. Update our Blog Post Details module code to retrieve the current Dynamic Page Item

Upgrading a Standard Shared Content List to a Dynamic Page List

  1. In the Content Manager, go to Shared Content and select Blog Posts
     
  2. Click on the Settings tab, and set the Content Type value to Dynamic Page List
     
  3. Open an existing content item in the list and note how there are now page level tabs called SEO, and Scripts

Creating a Dynamic Page in your Page Tree

  1. In the Content Manager, go to Pages and from the Page Tree, select the page you where previously testing which has the Blog Post Details module
     
  2. Within the Page Tree, click the + button to create a new page underneath your current page
     
  3. In the Add a New Page fly-out, click the Dynamic Page tab

Creating a Dynamic Page in your Page Tree (...)

  1. In the Add a New Dynamic Page tab, enter the following values:

    Page Template = Blog Template
    Build Pages From = Blog Posts
    Sitemap Label = post-dynamic
    Page Path Formula = ##Title##
    Page Title Formula  = ##Title##
    Menu Text Formula  = ##Title##
     
  2. Click Save to create the dynamic page

Creating a Dynamic Page in your Page Tree (...)

Note: Dynamic Pages CANNOT be created in the page root and must be under a page or folder

Clean-up the Parent Page

  1. Click on the page this is now the parent of the post-dynamic page
     
  2. Remove the Blog Post Details module from the Main Content Zone
     
  3. Click on the SEO tab, and rename the page:

    Page Title = Blog
    Page Name = blog
    Menu Text  = Blog

Note: This page will be empty for demonstration purposes, but normally this would be your root listing page that lists links to all your dynamic page items

Remove Linked Content field from Module Definition

  1. In the Content Manager, go to Settings > Module Definitions and select the Blog Post Details module
     
  2. In the Edit Module Definition fly-out, click on the Form Builder tab and remove ALL fields
     
  3. Click Save & Close to save the module definition, then select the definition from the grid using the checkbox and click Publish to publish the definition

Add the Module to the Dynamic Page in the Page Tree

  1. In the Content Manager, go to Pages and select the dynamic page you created
     
  2. Add the Blog Post Details module to the dynamic page's Main Content Zone
     
  3. Click Save & Close to save the module

 

Note: You will notice the module itself no longer has any fields, because the Blog Post will be dynamically resolved at run-time based on the Page Path Formula

Modify the Module Code to Resolve the Dynamic Page Item

  1. In Visual Studio, open the BlogController.cs file and update the code:
using MVC4SampleSite.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Agility.Web.Extensions;
using Agility.Web;

namespace MVC4SampleSite.Controllers
{
    public class BlogController : Controller
    {
        public ActionResult BlogPostDetails(Module_BlogPostDetails module)
        {
            BlogPost blogPost = AgilityContext.GetDynamicPageItem<BlogPost>();
            if(blogPost == null)
            {
                throw new HttpException(404, "No blog post found matching the Page Path Formula.");
            }
            return PartialView("~/Views/Blog/BlogPostDetails.cshtml", blogPost); 
        }

    }
}

Verify Module Output

  1. In Visual Studio, build your project and run on your localhost
     
  2. In your browser, enter a URL like this:

    http://localhost:{port}/blog/{some-post-title}

    Where {port} is the auto-determined port by your IIS Express and {some-post-title} represents the Title field of one of your Blog Posts

Note: Agility.Web will automatically convert a standard string like Title to strip out whitespaces and special characters and force a friendly, lowercase URL

Verify Module Output (...)

[TODO] Blog Listing Discussion

BREAK - 15 mins

Lab: Deploying your Website

Deploying your Website

What we are going to do:

  1. Import Publish Profile into the Visual Studio website project
     
  2. Verify web.config values
     
  3. Deploy the website using Web Deploy from Visual Studio
     
  4. Verify deployment

Import Publish Profile

  1. In Visual Studio, right-click on the project and click Publish 
     
  2. Select Import Profile, then click Create Profile
     
  3. Find and select the publish profile to import the credentials -  found in the root source code directory:
    "s/sandbox-{random-str}.publishsettings"

Verify Stage/Release Web.Config

  1. Ensure that DevelopmentMode is set to False
     
  2. Ensure the TraceLevel is set to Warning
<agility.web>
    <settings applicationName="Agility Sample MVC4 (Live)"
 developmentMode="false" contentCacheFilePath="d:\home\AgilityContent\" xdt:Transform="SetAttributes">
      <trace traceLevel="Warning" emailErrors="true" logFilePath="d:\home\AgilityLogs\SampleMVC4.log"
 xdt:Transform="SetAttributes" />
    </settings>
  </agility.web>

Publish Website

  1. In Visual Studio, right-click on the project and click Publish 
     
  2. Select "sandbox-{random-str} - Web Deploy" as the publish option
     
  3. Click Publish

Verify Deployment

  1. If successful, Visual Studio will launch a new browser window of your deployed website
     
  2. Verify your latest code has been updated
     
  3. You will need to Publish Pages, and Content Items in order for them to appear

Deployment Best Practices

  • Use a Continuous Integration and Deployment where possible
     
  • Always deploy to a UAT/Stage website before deploying to production

Custom Fields

Custom Fields Overview

  • Build your own re-usable field types
     
  • Compatible with any Agility instance, module or content input form
     
  • Use HTML, CSS and JS
     
  • Content Manager Development Mode for testing
     
  • Integrate with CMS UI
     
  • Code hosted within Agility or externally

Some Custom Field Examples

  • A field that auto-populates itself based on the value of another field within the input form
     
  • A search field that allows an editor to search via a third-party API and save the ID of an entity
     
  • A search field that allows an editor to search across multiple Content Lists in Agility at once and select items

Some Custom Field Examples (...)

  • A color-picker field that allows an editor to select a color from a color wheel and save the rgba/hex value
     
  • A Vimeo custom field that allows an editor to enter an ID, the field calls the Vimeo API, gets the embed code, and thumbnail and saves it all as JSON

Color Picker Example

In the Content Manager, go to Shared Content > Colors and create or open an existing item

Documentation and Examples available on GitHub

Lab: Installing a Custom Field

Installing a Custom Field

What we are going to do:

  1. Use the example Friendly URL field on GitHub
     
  2. Using the code from the repo, install using Inline Code
     
  3. Update the Blog Post definition to use the new Friendly URL field as the Dynamic Page Path Formula

Get the Custom Field Code

  1. Go to the GitHub repo for the Friendly URL field
     
  2. Open the html/friendly-url-template.html from the repo - you will need this later
     
  3. Open the js/friendly-url-init.js from the repo - you will need this later

Installing the Friendly URL Custom Field

  1. In Content Manager, go to Settings > Inline Code
     
  2. Add a new Template called "Friendly URL Template" and append the contents of the friendly-url-template.html file - make note of the Reference Name

Installing the Friendly URL Custom Field (...)

  1. Open the Custom Fields Live JS Inline Code file and append the contents of the friendly-url-init.js file
     
  2. Ensure the Template variable in the JS declaration function is referencing the inline code html template you just created by Reference Name
     
  3. Refresh the Content Manager

Using a Custom Field in a Definition

  1. Go to Settings > Content Definitions  and select Blog Post
     
  2. In Form Builder, click to add a new field, then select the Custom Field tab
     
  3. Name the field "URL Slug", and select "Friendly URL" as the Field Type

Testing your Custom Field

  1. Go to Shared Content and select Blog Posts
     
  2. Create or edit an existing item
     
  3. Note the URL Slug field being rendered - on new items it is auto-populated by the Title field - on existing fields it allows you to re-generated the value or manually update it

Using a Custom Field as a Dynamic Page Path Formula

  1. Go to your Dynamic Page that you created previously
     
  2. Update the Page Path Formula to reference the URL Slug field

Custom Input Form Events

Custom Input Form Events

  • Run custom JS on events in a content/module input form
     
  • Use cases include:
    • Advanced input validation
    • Third-party API integrations

Custom Input Form Events

Custom Input Form Events

Form Builder + Webhook Demo

Form Builder + Webhook Demo

  1. Add a Form using Form Builder
     
  2. Send submission data via Webhook
     
  3. Collect, and integrate data with virtually ANYTHING without writing code

Integration Services

In Preview + Roadmap

In Preview: Agility Search

  • Designed to replace Google Site Search and alternative to Bing Site Search
     
  • Frequent crawls
     
  • Control over index
     
  • JS widget + intellisense for auto-complete

In Preview: Agility Personalization

  • A/B/n tests
     
  • Personas
     
  • Content Experiments
     
  • Analytics

Roadmap: Custom Fields

  • Ability to 'package' and one-click install custom fields
     
  • Custom field 'parameters' that can be set when adding a field to a defintion
     
  • Documentation for Content Manager UI elements such as message dialogues, and fly-outs

Roadmap: Custom Components

  • Ability to build custom applications and register them within the Content Manager
     
  • Sample use cases:
    • Tessitura plug-in
    • Shopify plug-in
    • AR/VR plug-in
    • Advanced reporting

Roadmap: SPA Starter Templates

  • React
     
  • Vue.js
     
  • KnockoutJS
     
  • Shared on GitHub

Roadmap: .NET Core

Upcoming Developer Events

  • More core Agility workshops
     
  • React Developer Workshop - Oct 2018
     
  • Ecommerce Workshop - January 2018
     
  • Personalization Workshop - March 2018

Burning Questions...

Agility Developer Workshop Extended Version

By James Vidler

Agility Developer Workshop Extended Version

  • 926