✅ Similar to Blazor WebAssembly
Content projection in Blazor is the ability to create generic components that allow for the specification of one or more UI parts by the user at runtime. This means that you can design a component that handles common logic, while deferring specific logic to other components. Examples of where Content Projection is useful include a component that displays child components or HTML tags in a right-to-left format, or a tab container component that handles all the logic for tabs without knowledge of the specific tabs. In this tutorial, you will learn more about Content Projection and its various uses in Blazor:
You can download the example code used in this topic on GitHub.
Content projection is a technique that allows you to build a component that handles common logic and delegates specific logic to the component where it is used. For example, if you are building a website for an animal clinic, you may have different types of animals like dogs, cats, and turtles. Each animal has a web page to display information, and all animals share some common information such as name and age. However, some animals also have specific information such as skin cycle time or hibernation cycle. If you were to create a separate component for each species, you would end up with hundreds of components and would have to handle common information like name and age repeatedly. Content projection is a solution to this problem. It allows you to create a component that can display all common information of an animal and delegate specific information to another component. This technique is known as content projection.
A RenderFragment
in Blazor is a placeholder for projected UI. It allows components to accept and display content passed to them from outside. Assuming you have the following Tab
component.
@code { [Parameter] public RenderFragment? Title { get; set; } [Parameter] public RenderFragment? Content { get; set; } = (__builder) => { <div>Default content</div> }; }
In the example provided, the component Tab
has two parameters defined - Title
and Content
- both of which are of type RenderFragment
. The Content
parameter also has a default value assigned to it, which will be displayed if no content is projected onto it. When this component is used, the projected content is placed within the Title
and Content
slots, as shown in the example.
<Tab> <Title>Blazor School</Title> <Content>Blazor Tutorial for .NET 7</Content> </Tab>
When creating a projectable component that only requires a single slot, it can be beneficial to avoid naming the RenderFragment
parameter with a specific name. This can lead to unnecessary redundancy in the component's usage. Instead, you can use ChildContent
as a special name for the RenderFragment
that catches all the content passed to it.
For example, in the code below, the component uses a RenderFragment
parameter named Content
which is not necessary.
@* Button Component *@ @code { [Parameter] public RenderFragment? Content { get; set; } }
@* When it is used *@ <Button> <Content>Submit</Content> </Button>
However, when you use ChildContent
, it eliminates the need to specify the name of the RenderFragment
when the component is used.
@* Button Component *@ @code { [Parameter] public RenderFragment? ChildContent { get; set; } }
@* When it is used *@ <Button>Submit</Button>
This way, you can use the component simply by passing content within its opening and closing tags, making it more readable and less verbose.
When working with dynamic and generic data, it can be beneficial to use generic content projection. This allows you to process and display the data in a more efficient and readable way. One of the main advantages of this approach is the ability to reduce redundant code. Take List<T>
as an example. List<T>
where T
is a generic class, you can order the list, find items and more without writing any code.
For example, when displaying a list of animals, you might be tempted to use a foreach
loop with multiple if-else statements to determine the type of animal and display the appropriate content. However, this approach can lead to a lot of repetitive code, making it difficult to read and maintain.
@foreach(var animal in Animals) { @if(animal is Dog) { <div>My name is @animal.Name, I am a dog.</div> } @if(animal is Cat) { <div>My name is @animal.Name, I am a cat.</div> } @if(animal is Bird) { <div>My name is @animal.Name, I am a bird.</div> } }
Instead, you can use a generic content projection component that allows you to specify different templates for different types of animals. This way, you only have to define the templates once and the component takes care of displaying the appropriate content based on the type of animal.
<AnimalDisplay InputData="Animals" Context="animal"> <DogTemplate> <div>My name is @animal.Name, I am a dog.</div> </DogTemplate> <CatTemplate> <div>My name is @animal.Name, I am a cat.</div> </CatTemplate> <BirdTemplate> <div>My name is @animal.Name, I am a bird.</div> </BirdTemplate> </AnimalDisplay>
This approach not only makes the code more readable, but it also allows you to perform additional logic processing on the data, such as sorting the list or updating property values, within the AnimalDisplay
component. Overall, generic content projection is a powerful tool that can help you write more efficient and maintainable code.
T
using the @typeparam
directive. T
can be restricted to an interface or a base class. For example:@typeparam T where T : IAnimal
RenderFragment
. If your component is processing data, declare input parameters and methods to process the data. For example:@code { [Parameter] public T Animal { get; set; } [Parameter] public RenderFragment<string>? ChildContent { get; set; } private string GetExamineDetail(IAnimal animal) => $"The age is: {Animal.Age}."; }
Then you can use the generic content projection component as follows:
@foreach (var animal in Animals) { <AnimalDetail Animal="animal" /> } @code { public List<IAnimal> Animals { get; set; } = new() { new Dog() { Name = "Doggy", Age = 2 }, new Bird() { Name = "Birb", Age = 0 }, new Cat() { Name = "Kitty", Age = 2 } }; }
Or access some processed data:
<AnimalDetail Animal="Stray" Context="examinateDetail"> We have found this stray cat in a bush. @examinateDetail </AnimalDetail> @code { public Cat Stray { get; set; } = new() { Name = "Stray Cat", Age = 3 }; }
In this example, the examinateDetail
is the result of the method GetExamineDetail
we have defined before.