ViewModels

A view model is an object that can be rendered in a view - a Razor template. The view model represents an entity such as a page or a paragraph and has been optimized for rendering in front end.

Since view models are objects with strongly typed properties you will be able to use intellisense in e.g. Visual Studio and easily find the values available to you (Figure 1.1).

Figure 1.1 Intellisense

Some of the key advantages to using ViewModels are:

  • Workflow / Intellisense
    Regular razor templates use a key/value collection where the view model templates are served a typed object with properties. The view model will make it possible to get full intellisense when working with templates in Visual Studio. You no longer have to rely as much on documentation or DwTemplateTags/@TemplateTags() to find out what information is available.
  • Feedback
    A regular razor templates will fail silently if you misspell a property, since it is a simple lookup like this: @GetValue(“DwPageName”). In contrast, the rendering engine will give you immediate feedback (an error message) if you misspell the name of a view model property.
  • Performance
    View model templates are easier to optimize, particularly regarding the parsing and rendering process. Therefore you should see some performance improvements when using view model templates, compared to other types of templates.

There are two different base ViewModels available to you:

  • PageViewModel
  • ParagraphViewModel

They inherit a number of other viewmodels, such as the CartViewModel used for rendering shopping cart information. Both of these base ViewModels are described in more detail below.

In order to use a ViewModel, the template must inherit from ViewModelTemplate and should specify a model that is valid in the context where the template is used - e.g. page or paragraph - at the top of the template:

@inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel>

You will then have access to all properties of the ViewModel via @Model - as in the example below.

Please note that - unlike for TemplateTags - you need not remember these properties or look them up anywhere, with a proper setup you will have access to code-hinting and other Intellisense features.

RAZOR
@inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel> @MasterPageFile("Master/Main.cshtml") <!DOCTYPE html> <html> <head> <title>@Model.Title</title> <meta name="description" content="@Model.Description" /> <meta name="keywords" content="@Model.Keywords" /> </head> <body> <h1>@Model.Name</h1> <div> <table> <tr> <th>Model.ID</th> <td>@Model.ID</td> </tr> <tr> <th>Model.Name</th> <td>@Model.Name</td> </tr> <tr> <th>Pageview.ID</th> <td>@Pageview.ID</td> </tr> </table> </div> <h1>Path</h1> <p>Used e.g. for rendering a breadcrumb navigation:</p> <ol> @foreach (var path in Model.Path) { if (Model.Path.IndexOf(path) == 0) { <a href='/Default.aspx?ID=@path.ID'>@path.Name</a> } else { string divider = ">"; @divider <a href='/Default.aspx?ID=@path.ID'>@path.Name</a> } } </ol> <h2>TopPage</h2> <p>Returns the ID and name of the top page for this page.</p> <table> <tr> <th>Model.TopPage.ID</th> <td>@Model.TopPage.ID</td> </tr> <tr> <th>Model.TopPage.Name</th> <td>@Model.TopPage.Name</td> </tr> </table> <h2>Placeholders</h2> <p>Defining content placeholders:</p> <table> <tr> <th>Model.Placeholder("left", "Left content")</th> <td>@Model.Placeholder("left", "Left content")</td> </tr> <tr> <th>Model.Placeholder("Main", "Main content", "default:true;sort:1")</th> <td>@Model.Placeholder("Main", "Main content", "default:true;sort:1")</td> </tr> <tr> <th>Model.Placeholder("footer", "Footer")</th> <td>@Model.Placeholder("footer", "Footer")</td> </tr> </table> <h2>Includes</h2> <table> <tr> <th>Include("/includes/Part1.cshtml")</th> <td>@Include("/includes/Part1.cshtml")</td> </tr> <tr> <th>IncludeFile("/includes/Part2.cshtml")</th> <td>@IncludeFile("/includes/Part2.cshtml")</td> </tr> </table> <h2>Snippets</h2> <p>Snippets are used for rendering markup from any template in any other template.</p> <div style="color:green"> @RenderSnippet("thesnippet") </div> <div style="color:red"> @SnippetStart("thesnippet") <p> This text should be green</p> @SnippetEnd("thesnippet") </div> <h2>Page item</h2> @if (Model.Item != null) { <p>ItemId: @Model.ItemId</p> <p>ItemType: @Model.ItemType</p> <table> @foreach (var field in Model.Item.Fields) { <tr> <th>@field.SystemName</th> <td>@field.GetValue()</td> </tr> } </table> } else { <p>This is not an item-based page</p> } <h2>Page property item</h2> @if (Model.PropertyItem != null) { <table> <tr> <th>Model.PropertyItemId</th> <td>@Model.PropertyItemId</td> </tr> <tr> <th>Model.PropertyItemType</th> <td>@Model.PropertyItemType</td> </tr> </table> <table> @foreach (var field in Model.PropertyItem.Fields) { <tr> <th>@field.SystemName</th> <td>@field.GetValue()</td> </tr> } </table> } else { <p>This page does not have any item-based page properties</p> } <h2>Area item</h2> <p>Model.Area.ItemId: @Model.Area.ItemId</p> <p>Model.Area.ItemType: @Model.Area.ItemType</p> @if (Model.Area.Item != null) { <table> @foreach (var field in Model.Area.Item.Fields) { <tr> <th>@field.SystemName</th> <td>@field.GetValue()</td> </tr> } </table> } else { <p>This website does not use item-based website properties</p> } <h2> Items </h2> <table> <tr> <th>RenderItem(...)</th> <td>@RenderItem(new { ItemType = "MyCustomItem", SourceItemEntry = 2, ItemFields = "*", ListPageSize = 10, ListSourceType = "SelfArea", DetailsTemplate = "ItemPublisher/Details/Details.html" })</td> </tr> <tr> <th>RenderItemList(...)</th> <td>@RenderItemList(new { ItemType = "MyCustomItem", ItemFieldsList = "*", ListPageSize = 10, ListSourceType = "SelfArea", ListTemplate = "ItemPublisher/List/List.html" })</td> </tr> <tr> <th>RenderItemCreationForm(...)</th> <td>@RenderItemCreationForm(new { ItemType = "MyCustomItem" })</td> </tr> </table> <h2>Page languages</h2> <ul> @foreach (var lang in Model.Languages) { <li><a href="/Default.aspx?ID=@lang.Page.ID">@lang.Page.Name</a> (@lang.Culture)</li> } </ul> <h2>Website (Area) languages</h2> <ul> @foreach (var lang in Model.Area.Languages) { <li><a href="/Default.aspx?ID=@lang.FirstActivePage.ID">@lang.Name</a> (@lang.Culture)</li> } </ul> <h2>User</h2> @if (Model.CurrentUser != null) { <table> <tr> <th>Model.CurrentUser.Address</th> <td>@Model.CurrentUser.Address</td> </tr> <tr> <th>Model.CurrentUser.Address2</th> <td>@Model.CurrentUser.Address2</td> </tr> <tr> <th>Model.CurrentUser.City</th> <td>@Model.CurrentUser.City</td> </tr> <tr> <th>Model.CurrentUser.Company</th> <td>@Model.CurrentUser.Company</td> </tr> <tr> <th>Model.CurrentUser.Country</th> <td>@Model.CurrentUser.Country</td> </tr> <tr> <th>Model.CurrentUser.CustomerNumber</th> <td>@Model.CurrentUser.CustomerNumber</td> </tr> <tr> <th>Model.CurrentUser.Department</th> <td>@Model.CurrentUser.Department</td> </tr> <tr> <th>Model.CurrentUser.Email</th> <td>@Model.CurrentUser.Email</td> </tr> <tr> <th>Model.CurrentUser.FirstName</th> <td>@Model.CurrentUser.FirstName</td> </tr> <tr> <th>Model.CurrentUser.HouseNumber</th> <td>@Model.CurrentUser.HouseNumber</td> </tr> <tr> <th>Model.CurrentUser.ID</th> <td>@Model.CurrentUser.ID</td> </tr> <tr> <th>Model.CurrentUser.Image</th> <td>@Model.CurrentUser.Image</td> </tr> <tr> <th>Model.CurrentUser.JobTitle</th> <td>@Model.CurrentUser.JobTitle</td> </tr> <tr> <th>Model.CurrentUser.LastName</th> <td>@Model.CurrentUser.LastName</td> </tr> <tr> <th>Model.CurrentUser.MiddleName</th> <td>@Model.CurrentUser.MiddleName</td> </tr> <tr> <th>Model.CurrentUser.Name</th> <td>@Model.CurrentUser.Name</td> </tr> <tr> <th>Model.CurrentUser.Phone</th> <td>@Model.CurrentUser.Phone</td> </tr> <tr> <th>Model.CurrentUser.PhoneBusiness</th> <td>@Model.CurrentUser.PhoneBusiness</td> </tr> <tr> <th>Model.CurrentUser.PhoneMobile</th> <td>@Model.CurrentUser.PhoneMobile</td> </tr> <tr> <th>Model.CurrentUser.PhonePrivate</th> <td>@Model.CurrentUser.PhonePrivate</td> </tr> <tr> <th>Model.CurrentUser.State</th> <td>@Model.CurrentUser.State</td> </tr> <tr> <th>Model.CurrentUser.Title</th> <td>@Model.CurrentUser.Title</td> </tr> <tr> <th>Model.CurrentUser.UserName</th> <td>@Model.CurrentUser.UserName</td> </tr> <tr> <th>Model.CurrentUser.Zip</th> <td>@Model.CurrentUser.Zip</td> </tr> </table> } else { <p>No user is currently logged in</p> } <h1>Cart model</h1> <table> <tr> <th>Model.Cart.ID</th> <td>@Model.Cart.ID</td> </tr> <tr> <th>Model.Cart.IsEmpty</th> <td>@Model.Cart.IsEmpty</td> </tr> </table> @if (@Model.Cart.IsEmpty == false) { <table> <tr> <th>Model.Cart.ProductsCount</th> <td>@Model.Cart.ProductsCount</td> <td></td> </tr> <tr> <th>Model.Cart.OrderlinesCount</th> <td>@Model.Cart.OrderlinesCount</td> <td></td> </tr> <tr> <th>Model.Cart.TotalDiscountWithoutVAT</th> <td>@Model.Cart.TotalDiscount.PriceWithoutVat.Formatted</td> <td>@Model.Cart.TotalDiscount.PriceWithoutVat.Value</td> </tr> <tr> <th>Model.Cart.TotalDiscountWithVAT</th> <td>@Model.Cart.TotalDiscount.PriceWithVat.Formatted</td> <td>@Model.Cart.TotalDiscount.PriceWithVat.Value</td> </tr> <tr> <th>Model.Cart.TotalPrice w/o Discounts without VAT</th> <td>@Model.Cart.TotalPriceWithoutDiscounts.PriceWithoutVat.Formatted</td> <td>@Model.Cart.TotalPriceWithoutDiscounts.PriceWithoutVat.Value</td> </tr> <tr> <th>Model.Cart.TotalPrice w/o Discounts with VAT</th> <td>@Model.Cart.TotalPriceWithoutDiscounts.PriceWithVat.Formatted</td> <td>@Model.Cart.TotalPriceWithoutDiscounts.PriceWithVat.Value</td> </tr> <tr> <th>Model.Cart.TotalPrice with VAT</th> <td>@Model.Cart.TotalPrice.PriceWithVat.Formatted</td> <td>@Model.Cart.TotalPrice.PriceWithVat.Value</td> </tr> <tr> <th>Model.Cart.TotalPrice without VAT</th> <td>@Model.Cart.TotalPrice.PriceWithoutVat.Formatted</td> <td>@Model.Cart.TotalPrice.PriceWithoutVat.Value</td> </tr> </table> } else { <p>This cart is empty</p> } </body> </html>

When rendering a page layout you must use the PageViewModel:

@inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel>

Content placeholders are defined in the following manner:

@* A content placeholder *@ @Model.Placeholder("footer") @*A content placeholder with a title and a description *@ @Model.Placeholder("main", "Main content", "default:true;sort:1")

A placeholder can take the following parameters:

Parameter

Required

Comments

Id

Yes

Unique id of a layout container.

Title

No

Friendly name of the layout container which is displayed in the administration (defaults to the value of Id).

Settings

No

Additional settings that specifies how the content should be rendered.

 

The placeholder settings available to you are:

Property

Description

Default value

Possible values

Default

Whether new content should be placed in this container by default (page edit).

False

True or False

Sort

Controls the sorting in administration (page edit).

0

1-99

Template

The template that should be used for items in this container.

 

Any valid paragraph template. Must be placed in

/Templates/Paragraph,

/Templates/Designs/Paragraph or

/Templates/Designs/DesignName/Paragraph

A very simple page layout template could look like this:

@inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.PageViewModel> @Title("My view model template") @Description("Description of my view model template") <!DOCTYPE html> <html> <head> <title>@Model.Title</title> <meta name="description" content="@Model.Description" /> <meta name="keywords" content="@Model.Keywords" /> </head> <body> <h1>@Model.Name</h1> <div> @Model.Placeholder("main") </div> </body> </html>

You can explore the PageViewModel further in the API documentation.

When rendering a paragraph you must use the ParagraphViewModel

@inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel>

This view model gives you access to all basic properties of the current paragraph – e.g. the Text, Image, ImageAlt, and any app module output:

RAZOR
@inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> <img src="@Model.Image" alt="@Model.ImageAlt" /> <div> @Model.Text </div> <div> @Model.GetModuleOutput() </div>

You can explore the ParagraphViewModel in more detail in the API documentation.

 

As usual, GetValue() returns the field value as an untyped object which only really advisable if you don't know what you're trying to retrieve, e.g. if you loop through all fields and you don't know which fields will be available.

Under normal circumstances it is advisable to use the appropriate method instead, which alsoallows you to use all the lovely type-appropriate C# methods:

Example: Rendering a Date field using GetValue or GetDateTime

Method

Example

Result

GetValue

@Model.Item.GetValue("Date")

01-11-2016 14:35:09

GetDateTime

@Model.Item.GetDateTime("Date").ToString("s")

2016-11-01T14:35:09

GetDateTime  @Model.Item.GetDateTime("Date").Year 2016

Example: Rendering a File field using GetValue or GetFile

Method

Example

Result

GetValue

<div>@Model.Item.GetValue("File")</div>

<div>Dynamicweb.Frontend.FileViewModel</div>

GetFile

<div>@Model.Item.GetFile("File").Path</div>

<div>/Files/Images/logo.png</div>

Some ViewModels will expose an item property which will give you an instance of the ItemViewModel, e.g.

  • PageViewModel.Item (Item based pages)
  • PageViewModel.PropertyItem (Page Properties items)
  • PageViewModel.Area.Item (Website Properties items)
  • ParagraphViewModel.Item (Item based paragraphs)

The ItemViewModel gives you access to a collection of item fields - and the following methods for retrieving field values directly:

Method

Return type

Use with item field type

Comment

GetBoolean

Boolean

Check box

 

GetDateTime

DateTime

Date, Date and time

 

GetDouble

Double

Decimal number

 

GetInt32

Int32

Integer number

 

GetInt64

Int64

 

 

GetString

String

Text, Rich text, Color

 

GetFile

FileViewModel

File

You use the GetFile() method whenever you’ve used an item field of the type File:

Item.GetFile("Image").Path

GetFiles

IList<FileViewModel>

Folder

 

GetGeolocation

GeolocationViewModel

Geolocation

 

GetItem

ItemViewModel

Item type, Link to item

You have a shared item type called Font which contains all sort of font settings. This item type is used in your website settings – so to retrieve the value from the FontFamily field:

Model.Area.Item.GetItem("Font").GetString("FontFamily”);

GetItems

IList<ItemViewModel>

Item relation list

You use the GetItems() method if you have used an item field of the type Item relation list – this is the only way to retrieve values from item relation lists:

@foreach (var i in Model.Item.GetItems('GalleryImages'))
{
    i.GetString('Image');
}

GetUsers

IList<UserViewModel>

User

 

GetValue

untyped object

Any

 

Examples:

@* Rendering all fields of an item *@ <div> @if (Model.Item != null) { <table> @foreach (var field in Model.Item.Fields) { <tr> <th>@field.Name</th> <td>@field.GetValue()</td> </tr> } </table> } </div> @* Rendering a specific field of an item *@ <div> @{ var myField = Model.Item.GetField("MyField"); if (myField != null) { <p>@myField.Name</p> <p>@myField.GetValue()</p> } } </div> @* Rendering a specific field value of an item *@ <input type="checkbox" checked="@Model.Item.GetBoolean("Checkbox")" />

The following standard template methods and features can be used in ViewModel templates:

Property

Used in

Comments

@ContentPlaceholder()

Master templates

Used to specify where to merge in page template content

@MasterPageFile(“master.cshtml”)

Page templates

Used to specify which Master file to use for this page template

@Include(…)

Everywhere

 

@IncludeFile(…)

Everywhere

 

@GetPageIdByNavigationTag("TagName")

Everywhere

Returns the first area page with the specified navigationtag. If no page is found, 0 is returned

@RenderItem(…)

Everywhere

 

@RenderItemList(…)

Everywhere

 

@RenderItemCreationForm(…)

Everywhere

 

@RenderNavigation(…)

Everywhere

 

@SnippetStart(…)

@SnippetEnd(…)

@RenderSnippet(…)

Everywhere

 

@Translate(…)

Everywhere

 

If the properties of the ViewModel is not enough – if you need more data – you can sometimes use an extension method to retrieve that data. Extension methods are typically available when you occasionally need to fetch complex data but adding the data directly to the ViewModel would lead to too much of a performance hit. In that sense you can think of extension methods as a sort of mid-point between adding more properties to the ViewModel and using the API.

To use an extension method:

  1. Find the extension method using the API documentation
  2. Add the appropriate namespace to the relevant template
  3. Use the extension method(s)

Currently it’s a little bit hard to know which extension methods are available – the only option is to browse the API documentation – but we hope to improve on this in the future.

For example – the NavigationTreeViewModel has an extension method for fetching product group properties. This is useful in edge-cases where you need to fetch data such as group assets, the group description, or the name of a product category applied to the group – things which can be useful in e.g. a mega-menu but not a normal navigation.

To use this extension method:

  1. Open the API documentation and locate the GetProductGroup() method (Dynamicweb.Ecommerce.ProductCatalog.NavigationTreeViewNodeExtensions)
  2. Add @using Dynamicweb.Ecommerce.ProductCatalog to the template where you want to use the method – in this case this is likely a navigation template of some sort
  3. You can now use the extension method in your code, as in the example below:
RAZOR
@using Dynamicweb.Ecommerce.ProductCatalog @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.Navigation.NavigationTreeViewModel> @helper RenderNodes(IEnumerable<Dynamicweb.Frontend.Navigation.NavigationTreeNodeViewModel> nodes) { foreach (var node in nodes) { var productgroup = node.GetProductGroup(); @productgroup.CategoryName @productgroup.Description @productgroup.Id @productgroup.Name @productgroup.Number foreach (var asset in productgroup.Assets) { @asset.DisplayName @asset.Value } } }

The ProductViewModel contains an extension method – IsProductInCart() – which is used to check if the product is in a specific order context cart. This can be useful when you want to check if a product is in for instance a quote cart, or a weekly or monthly cart.

To use this extension method:

  1. Open the API documentation and locate the IsProductInCart() method
  2. Add @using Dynamicweb.Ecommerce.ProductCatalog to the template where you want to use the method – this could be a product details template
  3. You can now use the extension method in your code, as in the example below:
RAZOR
@using Dynamicweb.Ecommerce.ProductCatalog @inherits ViewModelTemplate<ProductViewModel> @{ var isthisincart = Model.IsProductInCart(); } <div><b>IsThisInCart?</b> @isthisincart</div>