Appearance
Are you an LLM? You can read better optimized documentation at /core_features/view_components.md for this page in Markdown format
View components
This document provides a technical overview of the View Components used in TheExampleApp. View Components are self-contained, reusable server-side components responsible for rendering chunks of the UI. They encapsulate their own logic and data-fetching, making them ideal for complex UI elements that are used across multiple pages.
For more information on the overall view rendering architecture, see the Views documentation.
View component architecture
In TheExampleApp, View Components follow the standard ASP.NET Core 2.1 pattern. They are designed to be modular and independent, often interacting directly with the Contentful SDK or session state to gather the data they need to render.
The typical structure for a View Component in this project is:
- Component Class: A C# class located in
/ViewComponents/that inherits fromMicrosoft.AspNetCore.Mvc.ViewComponent. InvokeAsyncorInvokeMethod: The entry point for the component's logic. It accepts parameters, performs operations (like API calls), and returns aIViewComponentResult, typically by callingView(model).- Model: A dedicated C# class, often defined within the same file as the component, used to pass strongly-typed data to the corresponding view.
- View: A Razor file located at
/Views/Shared/Components/{ComponentName}/Default.cshtml. This view is responsible for rendering the HTML markup for the component, using the model passed from theInvokeAsyncmethod.
This architecture promotes separation of concerns, allowing complex UI logic to be isolated from the main page views and controllers.
Available components
The application utilizes several View Components for key features, each with a specific responsibility.
EditorialFeaturesViewComponent
This component renders UI elements that provide direct links to edit content within the Contentful web application. These features are only visible when editorial mode is active.
- Purpose: To bridge the gap between the rendered website and the Contentful content editing interface, improving the workflow for content editors.
- Source:
TheExampleApp/ViewComponents/EditorialFeaturesViewComponent.cs - Related Feature: Editorial Features
Implementation Details
The component's visibility is controlled by a session variable. It injects IContentfulOptionsManager to access the current Contentful space configuration.
csharp
// TheExampleApp/ViewComponents/EditorialFeaturesViewComponent.cs
public class EditorialFeaturesViewComponent : ViewComponent
{
private readonly ContentfulOptions _options;
public EditorialFeaturesViewComponent(IContentfulOptionsManager optionsManager)
{
_options = optionsManager.Options;
}
public IViewComponentResult Invoke(IEnumerable<SystemProperties> sys)
{
var model = new EditorialFeaturesModel();
model.Sys = sys;
// The component is only rendered if the "EditorialFeatures" session key is "Enabled".
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
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Parameters
| Parameter | Type | Description |
|---|---|---|
sys | IEnumerable<SystemProperties> | A collection of system properties for the Contentful entries on the page. |
EntryStateViewComponent
This component is responsible for determining and displaying the publication status of Contentful entries, such as "Draft" or "Pending Changes". This is a critical part of the application's preview functionality.
- Purpose: To provide visual feedback to content editors about the state of content when using the Preview API.
- Source:
TheExampleApp/ViewComponents/EntryStateViewComponent.cs - Related Feature: Preview Mode
Implementation Details
The component uses a sophisticated strategy to determine the state of one or more entries. It receives a collection of SystemProperties for entries fetched from the Preview API and compares them against the Delivery API.
- It creates a new, temporary
ContentfulClientinstance configured to always use the Delivery API (usePreviewisfalse). - It attempts to fetch the entries by their IDs from the Delivery API.
- Draft State: If any entry cannot be fetched from the Delivery API, the component sets
Drafttotrue. - Pending Changes State: If entries exist on both APIs, the component compares their
UpdatedAttimestamps. A mismatch for any entry indicates that the version on the Preview API is newer, meaning it hasPendingChanges.
A helper function, TrimMilliseconds, is used to ensure reliable date comparison, avoiding issues with timestamp precision differences between the two APIs.
csharp
// TheExampleApp/ViewComponents/EntryStateViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync(IEnumerable<SystemProperties> sys)
{
// Create a new client with preview set to false to always get the entry from the delivery API.
var client = new ContentfulClient(_httpClient, _options.DeliveryApiKey, _options.PreviewApiKey, _options.SpaceId, false);
IEnumerable<EntryStateModel> entries = null;
var model = new EntryStateModel();
try
{
entries = await client.GetEntries<EntryStateModel>($"?sys.id[in]={string.Join(",", sys.Select(c => c.Id))}");
}
catch { /* Fails silently if entries don't exist on Delivery API */ }
if(entries == null || (entries.Any() == false || sys.Select(c => c.Id).Except(entries.Select(e => e.Sys.Id)).Any()))
{
// One or more of the entries are not published, thus it is in draft mode.
model.Draft = true;
}
if (entries != null && AnySystemPropertiesNotMatching(entries.Select(c => c.Sys),sys))
{
// Entries are published but UpdatedAt dates do not match, thus they have pending changes.
model.PendingChanges = true;
}
return View(model);
// ... helper functions for comparison
}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
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
LocalesViewComponent
This component renders the language selector dropdown, allowing users to switch between the available locales.
- Purpose: To enable multi-language support and user-driven localization.
- Source:
TheExampleApp/ViewComponents/LocalesViewComponent.cs - Related Feature: Localization
Implementation Details
The component fetches the list of available locales directly from the Contentful space settings. It includes robust error handling in case the API call to fetch the space fails (e.g., due to invalid credentials), falling back to a default "en-US" locale to prevent the application from crashing.
The locale selection logic follows this priority:
- Use the locale stored in session (
Startup.LOCALE_KEY) - Fall back to the current culture (
CultureInfo.CurrentCulture) - Use the space's default locale if the selected locale is not available
The component also persists the selected locale back to the session and includes the Preview API state in the model passed to the view.
csharp
// TheExampleApp/ViewComponents/LocalesViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync()
{
Space space = null;
try
{
// Try to get the space, if this fails we're probably in a state of bad credentials.
space = await _client.GetSpace();
}
catch
{
// Getting the space failed, set some default values for resilience.
space = new Space() {
Locales = new List<Locale>() {
new Locale { Code = "en-US", Default = true, Name = "U.S. English" }
}
};
}
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 selected locale to session
HttpContext.Session.SetString(Startup.LOCALE_KEY, localeInfo.SelectedLocale?.Code);
// Include Preview API state in the model
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
34
35
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
MetaTagsViewComponent
A simple but important component for SEO and browser display. It generates the <title> tag and can be extended for other meta tags.
- Purpose: To dynamically set the page title based on the current content, with support for localization.
- Source:
TheExampleApp/ViewComponents/MetaTagsViewComponent.cs
Implementation Details
It injects IViewLocalizer to fetch a localized default site title. If a specific page title is passed as a parameter, it formats it as "{Page Title} — {Default Site Title}". Otherwise, it just uses the default title.
csharp
// TheExampleApp/ViewComponents/MetaTagsViewComponent.cs
public IViewComponentResult Invoke(string title)
{
if (string.IsNullOrEmpty(title))
{
return View("Default", _localizer["defaultTitle"].Value);
}
return View("Default", $"{title} — {_localizer["defaultTitle"].Value}");
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
SelectedApiViewComponent
This component renders the UI for switching between the Contentful Delivery and Preview APIs.
- Purpose: Provides a user-facing control to toggle preview mode for the entire application.
- Source:
TheExampleApp/ViewComponents/SelectedApiViewComponent.cs - Related Feature: Preview Mode
Implementation Details
The component checks the IsPreviewClient property of the injected IContentfulClient to determine the current state. It requires the currentPath of the request to be passed in, which is used to construct the redirect URL so the user stays on the same page after switching APIs.
csharp
// TheExampleApp/ViewComponents/SelectedApiViewComponent.cs
public IViewComponentResult Invoke(string currentPath)
{
// The model is a tuple containing the preview state and the current path for redirection.
return View(Tuple.Create(_client.IsPreviewClient, currentPath));
}1
2
3
4
5
6
7
2
3
4
5
6
7
Component usage
Invoking view components
View Components are invoked from within Razor views (.cshtml files) using the Component.InvokeAsync helper method. You must provide the name of the component (without the "ViewComponent" suffix) and an anonymous object for its parameters.
razor
@* Example: Invoking the MetaTags component in _Layout.cshtml *@
@await Component.InvokeAsync("MetaTags", new { title = ViewBag.Title })1
2
2
Passing parameters
Parameters are passed as an anonymous object where the property names must match the parameter names of the component's Invoke or InvokeAsync method.
csharp
// In the component class:
public IViewComponentResult Invoke(IEnumerable<SystemProperties> sys) { /* ... */ }1
2
2
razor
@* In the Razor view, passing a model property to the 'sys' parameter: *@
@await Component.InvokeAsync("EditorialFeatures", new { sys = Model.AllEntrySystemProperties })1
2
2
Default.cshtml templates
The HTML for a component is rendered by its corresponding view, located at /Views/Shared/Components/{ComponentName}/Default.cshtml. This view receives the model object returned by the component's Invoke method.
For example, the view for MetaTagsViewComponent would be located at /Views/Shared/Components/MetaTags/Default.cshtml and might look like this:
razor
@* File: /Views/Shared/Components/MetaTags/Default.cshtml *@
@model string
@*
This view receives the final title string from the MetaTagsViewComponent's Invoke method.
*@
<title>@Model</title>
<meta property="og:title" content="@Model" />1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
This separation ensures that the C# component class is only concerned with logic and data preparation, while the Razor view is solely responsible for presentation.