Appearance
Are you an LLM? You can read better optimized documentation at /api_reference/view_components.md for this page in Markdown format
View components reference
This document provides a detailed technical reference for the View Components used in TheExampleApp. It is intended for developers responsible for maintaining and extending the application's user interface. For a higher-level overview of the role of View Components in this project, please see the Core Features: View Components documentation.
View components API
In our ASP.NET Core 2.1 application, View Components are self-contained, reusable rendering units that encapsulate server-side logic and UI markup. They are analogous to partial views but are more powerful, as they are fully self-contained and do not rely on the controller's model. This makes them ideal for rendering pieces of UI that have their own data requirements, such as navigation menus, login panels, or, in our case, UI elements tied directly to our headless CMS, Contentful.
Each View Component consists of two main parts:
- A C# class, inheriting from
ViewComponent, which contains the logic to prepare a model. This class must implement anInvokeorInvokeAsyncmethod. - A Razor view (
.cshtml) located in/Views/Shared/Components/{ComponentName}/Default.cshtml, which defines the markup to be rendered.
This separation allows for clean, testable, and maintainable UI code.
Available view components
The following sections detail the implementation and purpose of each View Component available in the application.
MetaTagsViewComponent
This component is responsible for generating the HTML <title> tag for each page, incorporating SEO best practices and internationalization.
Purpose: To render a dynamic and localized page title.
Implementation Details: The component injects IViewLocalizer to fetch localized strings. Its logic determines the final title based on whether a specific page title is provided.
- If
titleis provided: It concatenates the specific title with the default site title (e.g., "Our Awesome Course — The Example App"). - If
titleis null or empty: It falls back to using only the default site title fetched from localization resources (e.g., "The Example App").
This ensures brand consistency across all pages while allowing for specific, SEO-friendly titles.
Parameters:
| Parameter | Type | Description |
|---|---|---|
title | string | The optional, specific title for the current page. |
Source Snippet (MetaTagsViewComponent.cs):
csharp
// The Invoke method contains the core logic for title generation.
// It uses the injected IViewLocalizer to support multiple languages.
public IViewComponentResult Invoke(string title)
{
if (string.IsNullOrEmpty(title))
{
// If no specific title is passed, use the default from localization resources.
return View("Default", _localizer["defaultTitle"].Value);
}
// If a title is passed, combine it with the default title.
return View("Default", $"{title} — {_localizer["defaultTitle"].Value}");
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
EditorialFeaturesViewComponent
This component renders UI elements that provide editors with contextual information and links directly within the web application, bridging the gap between the live site and the Contentful web app. For more context, see the Editorial Features documentation.
Purpose: To display a contextual toolbar for content editors when editorial features are enabled.
Implementation Details: The component's visibility is controlled by a session variable (EditorialFeatures). It gathers necessary data from the Contentful entry's system properties and the application's configuration to build links that take an editor directly to the corresponding entry in the Contentful UI.
The component uses a dedicated view model, EditorialFeaturesModel, to pass a structured set of data to its view.
Parameters:
| Parameter | Type | Description |
|---|---|---|
sys | IEnumerable<SystemProperties> | A collection of SystemProperties objects for the Contentful entries rendered on the page. |
View Model (EditorialFeaturesModel):
| Property | Type | Description |
|---|---|---|
Sys | IEnumerable<SystemProperties> | The system properties of the entry. |
FeaturesEnabled | bool | Flag indicating if the feature is enabled in the current user session. |
UsePreviewApi | bool | Flag indicating if the app is currently using the Contentful Preview API. |
SpaceId | string | The ID of the current Contentful space. |
Source Snippet (EditorialFeaturesViewComponent.cs):
csharp
// The component depends on IContentfulOptionsManager to access configuration.
public EditorialFeaturesViewComponent(IContentfulOptionsManager optionsManager)
{
_options = optionsManager.Options;
}
// The Invoke method builds the model for the view.
public IViewComponentResult Invoke(IEnumerable<SystemProperties> sys)
{
var model = new EditorialFeaturesModel();
model.Sys = sys;
// The feature's visibility is controlled by a session string.
model.FeaturesEnabled = HttpContext.Session.GetString("EditorialFeatures") == "Enabled";
model.SpaceId = _options.SpaceId;
model.UsePreviewApi = _options.UsePreviewApi;
return View(model);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
EntryStateViewComponent
This is a critical component for the editorial experience. It determines and displays the publication status of a Contentful entry (Draft, Published, or Pending Changes).
Purpose: To show the live publication status of a content entry, even when viewing the site via the Preview API.
Implementation Details: This component performs a complex state analysis. It receives the SystemProperties of an entry, which may come from the Preview API. To determine the true publication status, it creates a separate, temporary ContentfulClient that is hard-coded to use the Delivery API.
- It queries the Delivery API for the entry IDs it received.
- Draft Status: If an entry ID from the preview context cannot be found on the Delivery API, the entry is considered a Draft.
- Pending Changes Status: If an entry exists on both APIs, the component compares their
UpdatedAttimestamps. A mismatch indicates the preview version is newer, meaning the entry has Pending Changes. - Published Status: If an entry exists on both APIs and the timestamps match, it is considered Published.
Gotcha: A helper function, TrimMilliseconds, is used during the timestamp comparison. This is crucial to prevent false positives caused by precision differences between the .NET DateTime object and the timestamp returned by the Contentful API.
Parameters:
| Parameter | Type | Description |
|---|---|---|
sys | IEnumerable<SystemProperties> | A collection of SystemProperties from the entries whose status needs to be checked. |
View Model (EntryStateModel):
| Property | Type | Description |
|---|---|---|
Sys | SystemProperties | The system properties of the entry. |
Draft | bool | Flag indicating if the entry is a draft (not published). |
PendingChanges | bool | Flag indicating if the entry has pending changes. |
Source Snippet (EntryStateViewComponent.cs):
csharp
public async Task<IViewComponentResult> InvokeAsync(IEnumerable<SystemProperties> sys)
{
// Create a new client hardcoded to the Delivery API to get the public state.
var client = new ContentfulClient(_httpClient, _options.DeliveryApiKey, _options.PreviewApiKey, _options.SpaceId, false);
IEnumerable<EntryStateModel> entries = null;
var model = new EntryStateModel();
try
{
// Fetch the same entries from the Delivery API.
entries = await client.GetEntries<EntryStateModel>($"?sys.id[in]={string.Join(",", sys.Select(c => c.Id))}");
}
catch { /* Gracefully fail if the API call throws */ }
// If an entry from the preview context is not found in the delivery response, it's a draft.
if(entries == null || (entries.Any() == false || sys.Select(c => c.Id).Except(entries.Select(e => e.Sys.Id)).Any()))
{
model.Draft = true;
}
// If timestamps don't match between preview and delivery versions, there are pending changes.
if (entries != null && AnySystemPropertiesNotMatching(entries.Select(c => c.Sys),sys))
{
model.PendingChanges = true;
}
return View(model);
// Helper to check if any system properties don't match between published and preview.
bool AnySystemPropertiesNotMatching(IEnumerable<SystemProperties> published, IEnumerable<SystemProperties> preview)
{
foreach (var publishedEntry in published)
{
var previewEntry = preview.FirstOrDefault(c => c.Id == publishedEntry.Id);
if(TrimMilliseconds(publishedEntry.UpdatedAt) != TrimMilliseconds(previewEntry?.UpdatedAt))
{
return true;
}
}
return false;
}
// Helper to compare timestamps, ignoring millisecond differences.
DateTime? TrimMilliseconds(DateTime? dateTime)
{
if (!dateTime.HasValue) return dateTime;
return new DateTime(dateTime.Value.Year, dateTime.Value.Month, dateTime.Value.Day, dateTime.Value.Hour, dateTime.Value.Minute, dateTime.Value.Second, 0);
}
}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
40
41
42
43
44
45
46
47
48
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
40
41
42
43
44
45
46
47
48
LocalesViewComponent
This component renders a dropdown menu allowing users to switch between the available languages for the site.
Purpose: To display a language selector and manage the current locale state.
Implementation Details: The component fetches the list of available locales directly from the Contentful space settings via _client.GetSpace(). It determines the currently selected locale by checking the user's session (HttpContext.Session). If no locale is set in the session, it defaults to the browser's culture (CultureInfo.CurrentCulture). The resolved locale is then persisted back into the session.
Error Handling: The component includes a try-catch block around the API call. If fetching the space fails (e.g., due to invalid credentials), it gracefully falls back to a default model containing only "U.S. English" (en-US) to prevent the application from crashing.
Parameters: None.
View Model (LocalesInfo):
| Property | Type | Description |
|---|---|---|
Locales | List<Locale> | The list of all locales available in the Contentful space. |
SelectedLocale | Locale | The currently active locale. |
UsePreviewApi | bool | Flag indicating if the app is currently using the Contentful Preview API. |
Source Snippet (LocalesViewComponent.cs):
csharp
public async Task<IViewComponentResult> InvokeAsync()
{
Space space = null;
try
{
// Fetch space details to get the list of configured locales.
space = await _client.GetSpace();
}
catch
{
// Graceful fallback in case of API/credential failure.
space = new Space() {
Locales = new List<Locale>() {
new Locale { Code = "en-US", Default = true, Name = "U.S. English" }
}
};
}
// Read the selected locale from the session, or default to the current culture.
var selectedLocale = HttpContext.Session.GetString(Startup.LOCALE_KEY) ?? CultureInfo.CurrentCulture.ToString();
var localeInfo = new LocalesInfo
{
Locales = space.Locales,
SelectedLocale = space?.Locales.FirstOrDefault(c => c.Code == selectedLocale) ?? space?.Locales.Single(c => c.Default),
};
// Persist the resolved locale back to the session.
HttpContext.Session.SetString(Startup.LOCALE_KEY, localeInfo.SelectedLocale?.Code);
localeInfo.UsePreviewApi = _client.IsPreviewClient;
return View(localeInfo);
}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
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
SelectedApiViewComponent
This component renders a UI element for switching between the Contentful Delivery and Preview APIs.
Purpose: To display the current API mode (Delivery/Preview) and provide a mechanism to toggle it.
Implementation Details: The component checks _client.IsPreviewClient to determine the current mode. It passes this boolean along with the current request path (currentPath) to its view. The currentPath is essential for redirecting the user back to the same page after the API mode is changed, preserving the user's context.
Design Note: The component passes its data to the view using a Tuple<bool, string>. While functional, for more complex data structures, a dedicated view model class (like those used in the other components) is the recommended pattern for maintainability and clarity.
Parameters:
| Parameter | Type | Description |
|---|---|---|
currentPath | string | The path of the current request, used for redirection after a state change. |
Source Snippet (SelectedApiViewComponent.cs):
csharp
public class SelectedApiViewComponent : ViewComponent
{
private readonly IContentfulClient _client;
public SelectedApiViewComponent(IContentfulClient client)
{
_client = client;
}
// The Invoke method passes the API state and current path to the view.
public IViewComponentResult Invoke(string currentPath)
{
// The tuple contains:
// Item1 (bool): True if using the Preview API.
// Item2 (string): The current URL path for redirection.
return View(Tuple.Create(_client.IsPreviewClient, currentPath));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Invocation syntax
View Components can be invoked from any Razor view (.cshtml) using either the Component.InvokeAsync method or the more concise Tag Helper syntax. The Tag Helper syntax is generally preferred for its readability. For more information on Tag Helpers, see the Tag Helpers API Reference.
To enable View Component Tag Helpers, ensure the following line is present in /Views/_ViewImports.cshtml:
csharp
@addTagHelper *, TheExampleApp1
The following table provides invocation examples for each component.
| Component Name | InvokeAsync Syntax | Tag Helper Syntax |
|---|---|---|
| MetaTags | @await Component.InvokeAsync("MetaTags", new { title = Model.Title }) | <vc:meta-tags title="@Model.Title"></vc:meta-tags> |
| EditorialFeatures | @await Component.InvokeAsync("EditorialFeatures", new { sys = Model.SysProps }) | <vc:editorial-features sys="Model.SysProps"></vc:editorial-features> |
| EntryState | @await Component.InvokeAsync("EntryState", new { sys = Model.SysProps }) | <vc:entry-state sys="Model.SysProps"></vc:entry-state> |
| Locales | @await Component.InvokeAsync("Locales") | <vc:locales></vc:locales> |
| SelectedApi | @await Component.InvokeAsync("SelectedApi", new { currentPath = Context.Request.Path }) | <vc:selected-api current-path="@Context.Request.Path"></vc:selected-api> |