Documentation

RazorEngine development examples

To get started using RazorEngine templates for custom card development see "Create or edit a custom card". The examples here will be placed in the Server (C#) area of the custom card editor.

For detailed examples and instructions see Starting with Razor Syntax. We will be covering more ThoughtFarmer specific examples here. In particular we will make use of the IPageContext Context object available through the Custom portlet view model
 

Simple variables and Html

Variables in custom card templates are referenced by the @ symbol. This will also signify to the RazorEngine when you are switching between Html and c# code blocks. 
 
@{
     var currentDate = DateTime.Now.ToLongDateString();
}

<div> <h4>Hello @this.Model.Context.User.FullName</h4> <p> It is @currentDate </p> </div>


Note above the use of this.Model.Context.User. This represents the current logged in user viewing the page. This is an instance of IUser with other methods and members described in our Object model documentation.
 

If statements and boolean logic

You may wish to show content only under certain conditions. Or you may want to show different content depending on the current page context and circumstance. Razor templates make this very easy.
@if(this.Model.CurrentUserCanEditThisContent)
{
     <h4>Edit user controls</h4>
     <div class="custom-user-controls">
          <button class="tf-button" id="performActionButton">Custom action</button>
          <button class="tf-button tf-text-button" id="cancelActionButton">Cancel</button>
     </div>
}
else
{
     <p>You do not have access to the custom controls.</p>
}
The value this.Model.CurrentUserCanEditThisContent is part of the custom card view model. Also note the use of the ThoughtFarmer classes on the buttons. You can use any ThoughtFarmer classes to create custom content that matches default page components. Just use your browser's dev tools to inspect the CSS for any page. You can also use your own classes and custom CSS on the appropriate tab in the custom card editor. 
 

Loops and iterators

You can use common C# loops and iterators to insert repeated content easily. 
@{
     var treePath = this.Model.Context.Page.Node.TreePath; // returns a Collection<int> of content Ids

 &lt;p&gt;Parent pages
      &lt;ul&gt;
      @foreach(var contentId in treePath)
      {
           var page = this.Api.Content.Get(contentId);
           &lt;li&gt;&lt;a href="/content/@page.Node.ContentId"&gt;@page.Title.Get(this.Model.Context.Culture, this.Model.Context)&lt;/a&gt;&lt;/li&gt;
      }
      &lt;/ul&gt;
 &lt;/p&gt;

}

Please note the helper method LocalizedSortedList.Get(string culture, IPageContext context) used to get the title in the page above (@page.Title.Get). This will get the proper title depending on the current user's preferred language and if there is one available for the page. It will fall back to site defaults if not.
 

Helper functions

Helper functions are great for displaying more complex and repeated portions of Html that may be based on variables. It helps make your code cleaner and easier to read. Here is an example that will get the child pages for a specific section. It will use LINQ to filter the list to the latest published 5. This will work for any content type; news, blog, forum, section, etc...
 
@helper DisplayPage(IPage page)
{
     var owner = this.Api.User.Get(page.Audit.OwnerId);
     <div class="custom-page-container">
          <h5><a href="/content/@page.Node.ContentId">@page.Title.Get(this.Model.Context.Culture, this.Model.Context)</a></h5>
          <div class="custom-page-info">
               <strong>Owned by:</strong> <a href="/content/@owner.ContentId">@owner.FullName</a><br />
               <strong>Created:</strong> @page.Audit.PublishedDate.ToLongDateString()
          </div>
     </div>
}

@{ var childPages = this.Api.Content.GetChildren(1234); // Just using a hard coded content ID for sample var filteredChildPages = childPages.OrderByDescending(page => page.Audit.PublishedDate).Take(5);

 foreach(var page in filteredChildPages)
 {
      &lt;text&gt;@this.DisplayPage(page)&lt;/text&gt;
 }

}


You can reference helper functions using the same @ syntax as before. Note the use of <text> above. Without it you will get an error as RazorEngine has a problem realizing it should be parsing Html and not C#.
 

Functions

The key difference between helper functions and regular functions in Razor is that helper functions are just meant as display helpers. Functions have a specified return value and can be more universally used to create more complex customizations. 

In the next example we will get all child pages from a section and filter them based on a function we create. 
@functions{

 //stale if published more than 90 days ago and this user is the owner
 private bool ShowStaleMessage(IPage page) 
 {
      var staleDate = DateTime.Now.AddDays(-90);
      return page.Audit.OwnerId == this.Model.Context.User.UserId &amp;&amp; page.Audit.PublishedDate &lt; staleDate;
 }

}

@{ var contentId = 1234; // Just using a hard coded content ID for sample var sectionPage = this.Api.Content.Get(contentId); var childPages = this.Api.Content.GetChildren(1234);
var filteredChildPages = childPages.OrderByDescending(page => page.Audit.PublishedDate).Where(this.ShowStaleMessage); <h4>You have @filteredChildPages.Count stale pages in the <a href="/content/@sectionPage.Node.ContentId">@sectionPage.Title.Get(this.Model.Context.Culture, this.Model.Context)</a> section</h4> foreach(var page in filteredChildPages) { // Using the helper method from the previous example <text>@this.DisplayPage(page)</text> } }

 

Script validation

When creating the custom cards you will see the "Verify script" button available. Clicking this will send the contents of the Server (c#) code to the application to be parsed by RazorEngine. There are some caveats when working with this mechanism.
 
  1. If validation shows errors, you should see red X symbols on the lines where there is a problem. If you do not see this try switching to the JavaScript tab, then back to the Server tab. There is an outstanding bug where the indicator does not always display right away.
  2. If you are using code that depends on this.Model.Context.Page be sure to write defensive code that will not execute if Page is null or Page.Node.ContentId is equal to zero. You may get runtime errors validating the code as this is the context of the edit card page, and not the page the card will eventually end up on. You can use "Preview" to set up a proper page context using a valid contentID.
  3. If you see MVC runtime compatibility errors it is likely that a variable within your code needs to be declared more specifically, or cast.