CshtmlComponent - ASP.NET Core MVC and Razor Pages Component V4.1.0

Using components in ASP.NET Core MVC or Razor Pages, out of the box, is annoying to say the least (read: a real PITA). Tag Helpers do not support Razor syntax, View Components can not access nested child content. Razor Components do not support runtime compilation and do not work too well in standard MVC or Razor Page projects. CshtmlComponent, from the perspective of an MVC or Razor Pages app, combines the best features of these technologies.

Note: This document assumes that you have a good understanding of C#, Razor markup and ASP.NET Core.
Install the Nuget package.
See more detailed Usage instructions.

CshtmlComponent

  • Razor Syntax
  • Nested Child Content
  • Runtime Compilation
  • MVC & Razor Pages
  • Lenient File Structure
  • Named Slots
  • Reference Capturing
  • One-Off Content [1]
  • Head Content Injection [2]
  • Body Content Injection [3]
  • Render to IHtmlContent
  • Render to String

Tag Helper

  • Razor Syntax
  • Nested Child Content
  • Runtime Compilation
  • MVC & Razor Pages
  • Lenient File Structure
  • Named Slots
  • Reference Capturing
  • One-Off Content [1]
  • Head Content Injection [2]
  • Body Content Injection [3]
  • Render to IHtmlContent
  • Render to String

View Component

  • Razor Syntax
  • Nested Child Content
  • Runtime Compilation
  • MVC & Razor Pages
  • Lenient File Structure
  • Named Slots
  • Reference Capturing
  • One-Off Content [1]
  • Head Content Injection [2]
  • Body Content Injection [3]
  • Render to IHtmlContent
  • Render to String

Razor Component

  • Razor Syntax
  • Nested Child Content
  • Runtime Compilation
  • MVC & Razor Pages
  • Lenient File Structure
  • Named Slots
  • Reference Capturing
  • One-Off Content [1]
  • Head Content Injection [2]
  • Body Content Injection [3]
  • Render to IHtmlContent
  • Render to String

✓ Feature Supported ◻ Feature Partially Supported ✕ Feature Not Supported

[1] One-Off Content

Component content that is rendered only for the first instantiated component.

[2] Head Content Injection

Inject HTML content to the head tag from a component.

[3] Body Content Injection

Inject HTML content to the body tag from a component.

Video Demonstration

Examples

This section contains examples of how CshtmlComponents can be coded.

Basic Example

A basic CshtmlComponent that features attributes and child content.

C# Code (ExampleComponent.cshtml.cs)

Cshtml Code (ExampleComponent.cshtml)

Component Instantiation

Advanced Example

An advanced CshtmlComponent that features attributes, child content, named slots, typed slots, reference capturing, head content injection, body content injection, one-off content and model mutation.

C# Code (AdvancedExampleComponent.cshtml.cs)

Cshtml Code (AdvancedExampleComponent.cshtml)

Component Instantiation

Core

This section documents the main built in classes and components of CshtmlComponent.

CshtmlComponentBase

CshtmlComponentBase is the base class which all components should inherit. The class inherits TagHelper and all of its functionality. See the documentation for TagHelper here.

Properties

The properties of CshtmlComponentBase (not including inherited).

HtmlHelper

Type: IHtmlHelper

Default Value: [Dependency Injected in Constructor]

Description: The IHtmlHelper the component was initialized by. Generally, dependency injected by ASP.NET Core.

ViewContext

Type: ViewContext

Default Value: [Auto Generated]

Description: The current ViewContext. Automatically set by ASP.NET Core.

PartialViewName

Type: string?

Default Value: null

Description: The path to the associated .cshtml file. The contents of this file will be rendered.

OutputTagName

Type: string?

Default Value: null

Description: Describes within which tag the component output should be wrapped. Default value null means that no extra wrapping component is created.

OutputTagMode

Type: TagMode

Default Value: TagMode.StartTagAndEndTag

Description: Describes how the resulting component should be closed. Mainly related to the optional OutputTagName.

ChildContent

Type: string

Default Value: [Auto Generated]

Description: The child content of the component.

NamedSlots

Type: Dictionary<string, string>

Default Value: [Empty Dictionary]

Description: The content of the optional named slots.

Constructors

The constructors of CshtmlComponentBase, their arguments and the key properties that they set.

public CshtmlComponentBase(IHtmlHelper htmlHelper)

HtmlHelper: htmlHelper

PartialViewName: The path to the associated .cshtml file is auto generated by analyzing the namespace and the class name of the component. For example, if the namespace is "SampleRazorPagesApplication.Pages.Components.Example" and the class name is "ExampleComponent", then PartialViewName will be "/Pages/Components/Example/ExampleComponent.cshtml".

OutputTagName: null

OutputTagMode: TagMode.StartTagAndEndTag

public CshtmlComponentBase(IHtmlHelper htmlHelper, string? partialViewName)

HtmlHelper: htmlHelper

PartialViewName: partialViewName

OutputTagName: null

OutputTagMode: TagMode.StartTagAndEndTag

public CshtmlComponentBase(IHtmlHelper htmlHelper, string? partialViewName, string? outputTagName = null)

HtmlHelper: htmlHelper

OutputTagName: outputTagName

OutputTagMode: TagMode.StartTagAndEndTag

public CshtmlComponentBase(IHtmlHIHtmlHelper htmlHelper, string? partialViewName, string? outputTagName = null, TagMode outputTagMode = TagMode.StartTagAndEndTag)

HtmlHelper: htmlHelper

PartialViewName: partialViewName

OutputTagName: outputTagName

OutputTagMode: outputTagMode

Methods

The methods of CshtmlComponentBase and what they do and return (not including inherited).

public IHtmlContent Render()

Renders and returns the content of the component.

public Task<IHtmlContent> RenderAsync()

Renders and returns the content of the component asynchronously.

public string RenderToString(bool preserveLineBreaks = true)

Renders and returns the content of the component. If preserveLineBreaks = true, then line breaks are preserved, otherwise they are not.

public async Task<string> RenderToStringAsync(bool preserveLineBreaks = true)

Renders and returns the content of the component asynchronously. If preserveLineBreaks = true, then line breaks are preserved, otherwise they are not.

protected virtual Task ProcessComponent(TagHelperContext context, TagHelperOutput output)

A method that is executed just before a component is rendered. By default the method does nothing, but by overriding it you may control properties and other things. Properties may generally not be controlled in the constructor, due to the fact that they may not be initialized. When this method is called, all properties that have explicitly been defined will have been set. Note that CshtmlComponent does not differentiate between required and non-required properties. Ff this is important for you, then you should implement checking logic here.

CshtmlComponentReferenceableBase<ReferencableComponentType>

CshtmlComponentReferenceableBase is the base class which components that require reference capturing should inherit. This is not the default behavior due to the slightly unorthodox inheritance syntax. For example, if you define the component "Box", then in the class declaration you must use this syntax:

public class Box : CshtmlComponentReferenceableBase<Box>
The class inherits CshtmlComponentBase and all of its functionality.

Properties

The properties of CshtmlComponentReferenceableBase<ReferencableComponentType> (not including inherited).

Reference

Type: CshtmlComponentReferenceableBase<ReferencableComponentType>

Default Value: null

Description: A property that allows you to capture the reference to a component from a .cshtml file. Useful if, for example, one must access an id that another component defines. See the Advanced Example for how to accomplish this.

Constructors

The constructors of CshtmlComponentReferenceableBase<ReferencableComponentType> are exactly the same as those of CshtmlComponentBase.

CshtmlHead

CshtmlHead allows one to inject content in to the head tag of a document. By default, only one CshtmlHead tag is supported per component, but this can be overridden with the use of attributes. Note that instantiating multiple components of the same type will not render the content of a CshtmlHead tag multiple times, unless explicitly specified.

Attributes

The attributes of CshtmlHead.

Key

Type: string

Default Value: ""

Description: Sets a key for the tag. Multiple CshtmlHead tags with unique keys are supported per component. Only the first CshtmlHead tag with a unique key is rendered (within the context of a component).

Multiple

Type: bool

Default Value: false

Description: If true, then renders the content of a CshtmlHead tag, even if the key is not unique. Additionally, if true, then instantiating multiple components of the same type in a request will render the content of a CshtmlHead tag multiple times. This feature should almost never be used.

ContentOrder

Type: int

Default Value: int.MaxValue

Description: Sets the order of CshtmlHead tags. The content of CshtmlHead tags with negative values are prepended to the document head element, so that the most negative ones are rendered first. The content of CshtmlHead tags with positive values (including zero) are appended to the document head element, so that the least positive ones are rendered first. The default behavior will append everything to the head element. Useful if certain components require, for example, the JS of other components. One component should in most cases use only one ContentOrder.

CshtmlHead Ordering Example

An example that shows how CshtmlHead affects the content of the head element.

CshtmlBody

CshtmlBody allows one to inject content in to the body tag of a document. By default, only one CshtmlBody tag is supported per component, but this can be overridden with the use of attributes. Note that instantiating multiple components of the same type will not render the content of a CshtmlBody tag multiple times, unless explicitly specified.

Attributes

The attributes of CshtmlBody.

Key

Type: string

Default Value: ""

Description: Sets a key for the tag. Multiple CshtmlBody tags with unique keys are supported per component. Only the first CshtmlBody tag with a unique key is rendered (within the context of a component).

Multiple

Type: bool

Default Value: false

Description: If true, then renders the content of a CshtmlBody tag, even if the key is not unique. Additionally, if true, then instantiating multiple components of the same type in a request will render the content of a CshtmlBody tag multiple times. This feature should almost never be used.

ContentOrder

Type: int

Default Value: int.MaxValue

Description: Sets the order of CshtmlBody tags. The content of CshtmlBody tags with negative values are prepended to the document body element, so that the most negative ones are rendered first. The content of CshtmlBody tags with positive values (including zero) are appended to the document body element, so that the least positive ones are rendered first. The default behavior will append everything to the body element. Useful if certain components require, for example, the JS of other components. One component should in most cases use only one ContentOrder.

CshtmlBody Ordering Example

An example that shows how CshtmlBody affects the content of the head element.

CshtmlInitial

CshtmlInitial allows one to inject one-off content in to a component. By default, only one CshtmlInitial tag is supported per component, but this can be overridden with the use of attributes. Note that instantiating multiple components of the same type will not render the content of a CshtmlInitial tag multiple times, unless explicitly specified.

Attributes

The attributes of CshtmlInitial.

Key

Type: string

Default Value: ""

Description: Sets a key for the tag. Multiple CshtmlInitial tags with unique keys are supported per component. Only the first CshtmlInitial tag with a unique key is rendered (within the context of a component).

Multiple

Type: bool

Default Value: false

Description: If true, then renders the content of a CshtmlInitial tag, even if the key is not unique. Additionally, if true, then instantiating multiple components of the same type in a request will render the content of a CshtmlInitial tag multiple times. This feature should almost never be used.

CshtmlSlot

CshtmlSlot allows one to specify named slots. The parent component then decides where this content is rendered. Can be inherited to create typed slots. Typed slots do not require the specification of the Name attribute, which improves maintenance.

Attributes

The attributes of CshtmlSlot.

Name

Type: string

Default Value: ""

Description: The name of the slot. The content of the slot is stored in a dictionary, where Name is the key. Components that implement slots, should then render this content in an appropriate place.

Usage

This section documents how CshtmlComponent should be installed and how to author custom components.

Installation & Initialization

  1. Install the Nuget package.
  2. Dependency inject some classes in Startup.cs:
  3. In an appropriate _ViewImports.cshtml (usually located directly under the Pages directory) add the following:

Authoring Custom CshtmlComponents

  1. Create the file ExampleComponent.cshtml.cs somewhere under your ASP.NET Core Project (usually under the Pages or View directory, but anything should work).
  2. Create the file ExampleComponent.cshtml in the same location.
  3. In ExampleComponent.cs:
    1. Inherit CshtmlComponentBase. If you want to capture the reference to this component, then you should instead inherit CshtmlComponentReferenceableBase<ExampleComponent>. No other changes necessary.
    2. Implement one of the constructor so that all arguments are dependency injected arguments. In most cases, it is enough that IHtmlHelper htmlHelper is the only argument.
    3. Add [HtmlTargetElement("ExampleComponentTag")] to the component class, where ExampleComponentTag is the tag that the component will be associated with.
    4. Optionally, specify how the tag should be closed in HtmlTargetElement, see more about how this is achieved in the TagHelper docs.
    5. List all component attributes as C# properties. ASP.NET Core will translate the properties to their kebabcased variants, unless otherwise specified with [HtmlAttributeName("AttributeName")], where AttributeName is the name of the attribute.
    6. Create fields or properties for all other values you wish to use in ExampleComponent.cshtml.
    7. Optionally, override protected virtual Task ProcessComponent(), which is called before ExampleComponent.cshtml is executed. Here you can extract information from properties and access other fields etc. This can not be done in the constructor, since any provided properties will not have been set yet. The nested child content can be accessed in this method by accessing ChildContent. The named slots can be accessed by accessing NamedSlots
  4. In ExampleComponent.cshtml:
    1. Set the model to ExampleComponent with @model ExampleComponent. You may need to add appropriate using statements.
    2. Component properties and field are accessible with Model.PropertyName.
    3. The nested child content can be accessed with Model.ChildContent. To render the child content, you can use Html.Raw(Model.ChildContent), but note that this does not encode the HTML, which means that XSS attacks are possible if non-validated user input is used as child content.
    4. Any named slots can be accessed with Model.NamedSlots. To render the slot content, you can use Html.Raw(Model.NamedSlots["SlotName"]), but note that this does not encode the HTML, which means that XSS attacks are possible if non-validated user input is used as child content. The named slots have to be defined, in the initializing context, with <CshtmlSlot Name="SlotName">[CONTENT HERE]<CshtmlSlot> or by using typed slots. See the advanced example in Examples.
    5. Use <CshtmlHead>[CONTENT HERE]<CshtmlHead>, <CshtmlBody>[CONTENT HERE]<CshtmlBody> and <CshtmlInitial>[CONTENT HERE]<CshtmlInitial> to render or inject content according to the rules in Components.
    6. Render your markup and use .cshtml files as you wish.

Notes

  • See the sample project in the CshtmlComponent GitHub repository for concrete examples.
  • A CshtmlComponent is just a TagHelper with some "magic". In practice, everything that applies to ASP.NET Core TagHelpers apply to CshtmlComponents.
  • The entire CshtmlComponent is passed as Model to the .cshtml file. This includes properties inherited from TagHelper.

Changelog

V4.1.0

V4.1.0 Documentation

Published: Dec 23, 2020

Exposed the IHtmlHelper that is dependency injected into each component with the public property HtmlHelper.

V4.0.0

V4.0.0 Documentation

Published: Dec 20, 2020

Updated to .NET 5.0. Additionally, added the methods Render, RenderAsync, RenderToString and RenderToStringAsync with which a component can manually be rendered. Manual rendering may be useful in, for example, communicating with JavaScript.

V3.1.0

V3.1.0 Documentation

Published: Oct 30, 2020

Added constructors to CshtmlComponentBase and CshtmlComponentReferenceableBase to make development more convenient. The main change was that from now on the path to the associated .cshtml file can be automatically generated by analyzing the namespace and class name. This improves code maintainability and reduces unnecessary bugs. The path to the associated .csthml file can still be defined manually.

V3.0.0

V3.0.0 Documentation

Published: Oct 11, 2020

Breaking changes. Added support for head and body content injection with CshtmlHead and CshtmlBody. Rewrote CshtmlInitial to no longer depend on the obsolete Context attribute. Upgrading from a previous version should only minimally affect previous usage of CshtmlInitial (might even in most cases work without any changes). Additionally, slight changes in dependency injection (Startup.cs).

V2.2.0

V2.2.0 Documentation

Published: Sep 26, 2020

Added support component reference capturing. This grants access to component properties outside the components own context. See the Razor code in Example for an example of how this is done. Note: This feature must be enabled at a component level.

V2.1.0

V2.1.0 Documentation

Published: Sep 20, 2020

Added support for CshtmlInitial, which allows component code be rendered once per request, regardless of the number of instantiated components.

V2.0.0

V2.0.0 Documentation

Published: Sep 19, 2020

Breaking changes. Added support for named slots and added tag helper context and output as arguments to ProcessComponent.

V1.1.0

V1.1.0 Documentation

Published: Sep 17, 2020

Added support for TagMode, which provides some extra customizability.

V1.0.0

V1.0.0 Documentation

Published: Sep 15, 2020

The initial release.

Credits

CshtmlComponent was developed by Acmion (GitHub).

Contribute to CshtmlComponent in it's GitHub repository.