Currently, the 4 posts are dummy data we wrote, but normally this isn't done that way. Instead, there should be a button for users to click to increase or decrease the number of posts.
The "Add" button is placed in <Blog>. When clicked, it creates a new Post for the user to input, and then the user can click a "Confirm" button to save the post.
The "Delete" button can also be placed in <Blog>, while a checkbox is added to <Post> so users can select which posts to delete. Alternatively, the delete button can be placed in <Post>, and clicking it deletes that specific post.
Adding a button is straightforward—just attach an @onclick event to <MyButton>. Before starting, let's make some layout adjustments and remove FontSizeStyle at the same time.
Blog.razor
@page "/Blog" @inherits BlogBase @if (Blog == null) {
<p>Loading...</p>
} else {
<div class="container">
<div class="row">
<div class="col pl-0">
<label>博客名称</label>
<input @bind-value="Blog.Name" class="form-control w-25" />
<MyButton
value="Add"
class="btn btn-info my-2"
type="button"
@onclick="Add"
/>
</div>
</div>
<div class="row">
@if (Blog.Posts != null) { foreach (var post in Blog.Posts) {
<div class="col-md-3 border rounded p-3 mr-2 mb-2 w-25">
<CascadingValue Value="ColorStyle" Name="ColorStyle" IsFixed="true">
<Post Post="post" />
</CascadingValue>
</div>
} }
</div>
</div>
}
In the C# part, add the Add() method and remove the dummy posts from LoadData().
BlogBase.razor.cs
using BlazorServer.Models;
using Microsoft.AspNetCore.Components;
namespace BlazorServer.Pages;
public class BlogBase : ComponentBase
{
public BlogModel? Blog { get; set; }
public string? ColorStyle { get; set; } = "color: goldenrod";
public string? FontSizeStyle { get; set; } = "color: goldenrod";
protected override Task OnInitializedAsync()
{
LoadData();
return base.OnInitializedAsync();
}
private void LoadData()
{
Blog = new BlogModel
{
Id = 1,
Name = "我的博客",
Posts = new List<PostModel>(),
CreateDateTime = new DateTime(2021, 12, 14, 23, 46, 59)
};
}
protected void Add()
{
Blog?.Posts?.Add(new PostModel());
}
}
Now, clicking the Add button will increase the number of posts.

Next, implement the Delete functionality. Add the Delete button in Post.razor.

But here's the problem: when I click the Delete button, how does <Blog> know which post is being deleted? This requires a way to identify the post, so we add a private variable _postId, which increments by 1 each time Add() is called. Normally, the PostId should belong to the Post, not be generated by Blog, but since we haven't touched the database yet, we'll make do for now. This will change once we connect to a database.

To verify correctness, remove the commented Post.Id in Post.razor and add the new Post.Id style. You can see it works fine.

Now that we have an identifier, a new problem arises: how does <Blog> receive the Id? Currently, the Id is generated by <Blog>, so there's no issue. Later, when the Id is generated by the database, <Blog> won't know the Id. What we've discussed so far are methods for passing data from parent component to child component. We now need to pass data from child component to parent component. Is there a way to reverse the flow?
Yes, that's EventCallback. However, we need to change Delete to <input> instead of <MyButton> because EventCallback passes from child to parent. If we used <MyButton>, the flow would be: <Blog> => <Post> => <MyButton>, and then back with EventCallback <MyButton> => <Post> => <Blog>, which is too cumbersome.
First, change the Delete button to <input> and add @onclick="ReturnPostId".

Then, in PostBase.razor.cs, define a property of type EventCallback<int> named GetPostId. Remember to add the [Parameter] attribute because it will be used by <Blog>. Then fully implement the ReturnPostId() method, which calls GetPostId.InvokeAsync(Post!.PostId);. When the external GetPostId is triggered, it passes Post.PostId to the parent component, i.e., <Blog>.

Next, in BlogBase.razor.cs, define a method with the same name GetPostId(int id). (The name doesn't have to match; it's just convenient to use the same name here.) This method removes the post whose Id matches the received value.

Finally, in Blog.razor, assign the method just defined to the GetPostId attribute of <Post>.

Let's verify: first add 4 posts, then delete the 2nd one. You can see the post with Id equal to 2 is successfully deleted.

Besides EventCallback, Delegate can also be used, though it has more limitations. Let's try it as well.
First, in PostBase.razor.cs, define a property of type Action<int> named GetPostIdForDelegate. Then change ReturnPostId() to use GetPostIdForDelegate.

Next, in Blog.razor, replace GetPostId in <Post> with GetPostIdForDelegate.

However, after clicking, the post is not deleted. This is because EventCallback monitors the component and triggers re-rendering when there's a change, while delegates do not. With a delegate, you must call StateHasChanged(); in the parent component (BlogBase.razor.cs) to notify the parent that the state has changed.

Additionally, once a delegate is defined in a child component, the parent must invoke it; otherwise, an error will occur. EventCallback does not have this issue.

Editor's note: Of course, using nullable properly can also avoid this exception:

References:
- Blazor EventCallback
- EventCallback
- Blazor Tutorial - Ep11 - EventCallback and how it is different from delegate
Note: The code in this article has been refactored using .NET 6 + Visual Studio 2022. You can click the original link to compare with the refactored code. Thank you for reading, and please support the original author.