Table of Contents

Custom Data Models in the Strong Typed APIs in 2sxc 17+

The Copilot Data Model Generator will generate classes for each Content-Type in the AppCode.Data namespace. The details are documented there. This guide focuses on how to use these classes in your Razor and C# code, and how to extend them to best fit your needs.

Setup for this Guide

This guide assumes you have the following Content-Types in your app:

  1. Product with fields like Title, Teaser, Packshot (image), OnSale (bool), Categories (of type Category), Company (type Company) and Description (HTML)
  2. Company with fields like Name, Logo (image), Description and Website (URL)
  3. Category with fields like Name, Description and Parent (type Category)

We have used Copilot to generate the files, so our folder structure looks like this:

  • /AppCode/Data/Product.Generated.cs
  • /AppCode/Data/Company.Generated.cs
  • /AppCode/Data/Category.Generated.cs
Tip

The auto-generated files should never be modified, as you will want to re-generate and overwrite them again.

What's in the Auto-Generated Classes?

The auto-generated files contain two classes: one is the auto-generated class, and the other is the base class. There is some extensive reasoning to this, best check out the Copilot documentation for details. For example, the Product.Generated.cs contains the following classes:

  • AppCode.Data.AutoGenerated.ZagProduct - this one is specially named, so you won't accidentally use it in IntelliSense and so the property names never conflict with the class name.
  • AppCode.Data.Product - this one is partial so you can extend it in a separate file later on.

Internally, the ZagProduct has a lot of code which looks like this:

/// <summary>
/// Title as string. <br/>
/// For advanced manipulation like scrubHtml, use .String("Title", scrubHtml: true) etc.
/// </summary>
public bool OnSale => _item.Bool("OnSale");

/// <summary>
/// Packshot as link (url). <br/>
/// To get the underlying value like 'file:72' use String("Packshot")
/// </summary>
public string Packshot => _item.Url("Packshot");

/// <summary>
/// Get the file object for Packshot - or null if it's empty or not referencing a file.
/// </summary>
public IFile PackshotFile => _item.File("Packshot");

/// <summary>
/// Get the folder object for Packshot.
/// </summary>
public IFolder PackshotFolder => _item.Folder("Packshot");

/// <summary>
/// Title as string. <br/>
/// For advanced manipulation like scrubHtml, use .String("Title", scrubHtml: true) etc.
/// </summary>
public new string Title => _item.String("Title", fallback: "");

/// <summary>
/// Fields as list of Category.
/// </summary>
/// <remarks>
/// Generated to return child-list child because field settings had Multi-Value=true. The type Category was specified in the field settings.
/// </remarks>
/// <returns>
/// An IEnumerable of specified type, but can be empty.
/// </returns>
public IEnumerable<Category> Categories => _categories ??= _item.Children<Category>("Categories");
private IEnumerable<Category> _categories;

Using the Strong Typed Classes in Razor or C# Files

TODO:

Modify the Strong Typed Classes

As mentioned before, you don't want to touch the generated files. Instead, you should create a new file with the same name, but without the .Generated part. Here's an example of a /AppCode/Data/Product.cs file which extends the auto-generated class Product with some custom properties and methods:

namespace AppCode.Data
{
  public partial class Product
  {
    // Add your own properties and methods here
  }
}

The compiler will then combine the auto-generated Product with yours.

Example: Add a Main Category Property

Now let's say that the first category is used a lot, so you want a MainCategory property which is just a shortcut to the first category. This is how you would do it:

namespace AppCode.Data
{
  public partial class Product
  {
    public Category MainCategory => Categories.FirstOrDefault();
  }
}

That's it! You could of course optimize it, so that it only does this once, and add some comments, like this:

namespace AppCode.Data
{
  public partial class Product
  {
    /// <summary>
    /// The main / primary category
    /// </summary>
    public Category MainCategory => _mainCategory ??= Categories.FirstOrDefault();
    private Category _mainCategory;
  }
}

Doing this is a bit better for performance, and gives you intellisense when you hover over MainCategory in your Razor files.

Example: Add a CssClasses property

Now let's say you want to add a CssClasses property which should be the same in many Razor views, but change based on the OnSale status. This is how you would do it:

namespace AppCode.Data
{
  public partial class Product
  {
    public string CssClasses => OnSale ? "on-sale" : "";
  }
}

Example: Override the Title Property

Imagine you want the title to always have a 🌟 emoji at the end, when it's on sale:

namespace AppCode.Data
{
  public partial class Product
  {
    public new string Title => base.Title + (OnSale ? " 🌟" : "");
  }
}

Note the word new - this is because the Title property is already defined in the auto-generated class, and we're just extending it. It's not necessary, but it's a good practice to make it clear that you're replacing the Title.

Example: Add a Typed Presentation property

Every Typed-Item has a Presentation property which is an ITypedItem - and it can also be null.

But let's imagine that you always use Product together with ProductPresentation so you want to make it typed as well.

This is how you would do it:

namespace AppCode.Data
{
  public partial class Product
  {
    public new ProductPresentation Presentation => _presentation ??= As<ProductPresentation>(base.Presentation);
    private ProductPresentation _presentation;
  }
}

Note the new - as were replacing the Presentation property, and base.Presentation - without the base prefix, you would create an infinite loop.

Super-Extending the Strong Typed Classes

The previous examples all worked because every new property we created used existing data to determine the new value. In some cases, you would like to add a property which is not based on existing data, but on other data. For example, you may want to add a IsRecommended based on the current User.

This is a bit more tricky, because your classes are based on CustomItem and does not know about the current user. Our recommended way is to do the following:

  1. Add a property for the external information - eg. User - to be passed in from the Razor file
  2. Use this property to calculate the new property

Here's an example:

namespace AppCode.Data
{
  public partial class Product
  {
    public ICmsUser User { get; set; }
    public bool IsRecommended => User.IsSystemAdmin;
  }
}

When converting the data to a strong-typed object, you would then pass in the user like this:

@{
  var product = AsList<Product>(MyItems)
      .Select(i => {
        i.User = MyUser;  // Set this property, so it can be used in calculated properties of Link
        return i;
      })
      .ToList();
}

APIs you should Know About

All your custom data objects will be based on CustomItem which has a lot of methods to get data. These are some you will need to know about:

  • _item is the underlying object which is being wrapped. You can always access it's methods such as _item.String("Teaser") or _item.Children("Categories")
  • As<T>(object) will convert an item to a strong-typed object
  • AsList<T>(object) will convert a list of items to a list of strong-typed objects
  • Child<T>(name) will get a child item and convert it to a strong-typed object
  • Children<T>(name) will get a list of child items and convert them to a list of strong-typed objects
  • Parent<T>(type: name, field: name) will get the parent item (or the first parent in all the matching parents) and convert it to a strong-typed object
  • Parents<T>(type: name, field: name) will get the list of parent items and convert them to a list of strong-typed objects

History

  • HotBuild introduced in v17.00 but was enhanced a lot in each additional version
  • Copilot was introduced in 17.03 and is still getting improved
  • AppCode was introduced as ThisApp but renamed to AppCode in 17.02