如何从 C# 方法添加 Razor 标记?
How can I add Razor Markup from a C# Method?
Objective
我的页面上有一个表单,当用户单击按钮发送表单时,该表单将调用方法 OnFinish
。在那种方法中,我想向页面添加新标记。 (表格代码不相关,这里就不加了)
问题
似乎没有从 C# 代码添加 razor 标记的简单方法(如果有的话)。
我试过的
我尝试的第一件事是在 C# 代码中直接标记。看起来像这样:
private void OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
这确实 - 当然 - 不起作用,因为我是在 .cs 文件中编写的。
在 .razor 文件中,我尝试执行相同的操作:
@code
{
private void OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
}
编译器也不喜欢这样并引发了以下错误:
The name '__builder' does not exist in the current context
我很快从 this 问题中了解到,标记不能写在 @code
块中。
根据 this 官方 ASP.NET 博客 post,在 @functions
块中编写标记应该可行:
@functions
{
private void OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
}
不幸的是,这与使用 @code
块的结果相同。
编辑:再次阅读博客 post 后,我意识到,该功能仅适用于 .cshtml
文件。我对此进行了测试,并且确实可以在 .cshtml
文件中使用。但是这些文件不能作为组件使用,所以对我来说终究是行不通的。
我尝试在方法中使用 RenderTreeBuilder __builder
作为参数, 答案中建议这样做。
此解决方案的问题是,__builder
参数只能在标记代码中使用,如下所示:
<a>@__builder</a>
而不是来自 C# 代码(这就是使用参数的原因)。
接下来我发现的是 @helper
块,this 答案中提到了它。我将它应用到我的代码中,如下所示:
@helper OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
这也不起作用,因为编译器会引发以下两个错误:
The helper directive is not supported.
The name 'helper' does not exist in the current context
结论
现在,搜索到此结束。我花了很多时间研究这个,我想相信可能根本没有任何解决方案。我错过了什么?
编辑
有些人提到了将标记放在 if 中的想法,而当某些条件成立时 if 会被激活(例如,我有一个布尔值,我将其设置为 true 或我更改了一个变量,并且 if 识别出).
可以在当前线程的任一 other question or 答案中看到该解决方案。
该解决方案的代码如下所示:
if (renderNewMarkup)
{
<a>Placeholder</a>
}
@functions
{
private void OnFinish(EditContext editContext)
{
renderNewMarkup = true;
}
}
这确实有效,但我认为它非常不愉快。我遇到的问题是变量的使用。
在我的程序中,我从 UI 中检索了一些数据并将其进一步传递给当时创建的标记。我可以举个例子:
private void OnFinish(EditContext editContext)
{
Dictionary<string, string> dict = new()
{
["Key1"] = boundVariable,
["Key2"] = otherBoundVariable,
["Key3"] = anotherBoundVariable
}
// Create markup with dict variable
}
我想添加到 HTML 的标记是具有 Dictionary<string, string>
参数的自定义剃须刀组件。
您可以看到,我现在不能简单地在那里创建标记。我必须实现我想要的效果如下所示:
@if (renderNewCustomComponent)
{
<Custom DictParameter="@dict" />
}
@code
{
private Dictionary<string, string> dict;
private void OnFinish(EditContext editContext)
{
dict = new()
{
["Key1"] = boundVariable,
["Key2"] = otherBoundVariable,
["Key3"] = anotherBoundVariable
}
renderNewCustomComponent = true;
}
}
希望你明白我的意思。
那么这个解决方案还有一个更大的问题。当我想调用一个方法时,例如AddElementToHTML()
,这将始终向 HTML 添加一些元素,这是不可能的。是的,通过一些复杂的方式,使用 @foreach
之类的东西,这是可能的,但为什么语言一开始就不允许这样做呢?
Razor 代码使用 if
、foreach
等控制结构。您可以根据需要构建 HTML 字符串,并使用条件让 Blazor 知道何时显示它。
@if (CustomHtml is not null) {
<div>@((MarkupString)CustomHtml)</div>
}
@code
{
string CustomHtml; // starts as null
private void OnFinish(EditContext editContext)
{
CustomHtml = "<b>You are now registered!</b>";
}
}
通常,要添加控件,您会在条件逻辑中包含一个 .razor 组件。组件可以嵌套,因此您可以构建的内容真的没有限制。
在函数中,您必须操纵 editContext 或 return 某些东西。
带有return的伪代码示例:
@functions
{
private string OnFinish(EditContext editContext)
{
return "<a>Placeholder</a>";
}
}
带有操作的伪代码示例:
@functions
{
private void OnFinish(EditContext editContext)
{
editContext.change("<a>Placeholder</a>");
}
}
这只是我认为的部分答案。但是函数中的内联标记 是 可能的,只是因为额外的 {}
有点尴尬。您可以将其与 @if()
或其他控制结构合并:
@{ void F1() { <p>Inline Markup (local fn)</p> } }
@{ F1(); }
@if (2 > 1)
{
F1();
F2(__builder);
}
@code {
void F2(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
<p>Inline Markup </p>
}
}
考虑将页面的标记部分编译为 override void BuildRenderTree(builder) {...}
。
您可以添加本地函数 (F1) 或从您的 class (F2) 调用其他方法。
这并不能解决您的 OnFinish(editContext) 问题,因为它是在渲染代码路径之外调用的。
所以你仍然需要 @if()
.
原答案
这向您展示了如何从标记构建和显示简单的呈现片段。可能会有帮助。
@page "/Test"
<h3>Test8</h3>
<input @bind-value="content" class="form-control" />
<button class="btn btn-dark" @onclick="RenderSomeContent">Render Content</button>
<button class="btn btn-danger ml-2" @onclick="ClearContent">Clear Content</button>
@Placeholder
@code {
RenderFragment Placeholder { get; set; }
string content = "<div class='m-3 bg-success'>Hello</div>";
void RenderSomeContent()
{
Placeholder = (builder) =>
{
builder.AddMarkupContent(0, content);
};
}
void ClearContent()
{
Placeholder = null;
}
}
评论更新
二进制逻辑控件
@if (this.Show)
{
if (this.ChildContent != null)
{
@this.ChildContent
}
else
{
@((MarkupString)this.Body)
}
}
@code {
[Parameter] public bool Show { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Body { get; set; }
}
测试页面
@page "/Test"
<button class="btn btn-secondary" @onclick="DoIt">Toggle content</button>
<BinaryLogicControl Show="show">
<div class='m-3 bg-success'>Hello</div>
</BinaryLogicControl>
<BinaryLogicControl Show="show" Body="@Display" />
@code {
string Display = string.Empty;
bool show;
void DoIt()
{
show = !show;
Display = "<div class='m-5 p-3 bg-danger'>Hello</div>";
}
}
基于渲染组件的更新
根据评论,您想要的是 Net 6 中的 DynamicComponent
。目前您必须构建自己的(更简单的)版本。下面的代码显示了原始答案的更新版本,它构建了用于呈现的 BinaryLogicControl
而不是简单的 Markupstring
.
@page "/Test"
<button class="btn btn-primary" @onclick="RenderMe">Render Control</button>
@Placeholder
@code {
RenderFragment Placeholder { get; set; }
void RenderMe()
{
RenderSomeContent(typeof(BinaryLogicControl), new Dictionary<string, object> {
{ "Show", true },
{ "Body", "<div class='m-3 bg-warning'>Hello</div>" }
});
}
void RenderSomeContent(Type componentType, Dictionary<string, object> parameters)
{
Placeholder = (builder) =>
{
builder.OpenComponent(0, componentType);
foreach (var parameter in parameters)
{
builder.AddAttribute(1, parameter.Key, parameter.Value);
}
builder.CloseComponent();
};
}
Objective
我的页面上有一个表单,当用户单击按钮发送表单时,该表单将调用方法 OnFinish
。在那种方法中,我想向页面添加新标记。 (表格代码不相关,这里就不加了)
问题
似乎没有从 C# 代码添加 razor 标记的简单方法(如果有的话)。
我试过的
我尝试的第一件事是在 C# 代码中直接标记。看起来像这样:
private void OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
这确实 - 当然 - 不起作用,因为我是在 .cs 文件中编写的。
在 .razor 文件中,我尝试执行相同的操作:
@code
{
private void OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
}
编译器也不喜欢这样并引发了以下错误:
The name '__builder' does not exist in the current context
我很快从 this 问题中了解到,标记不能写在 @code
块中。
根据 this 官方 ASP.NET 博客 post,在 @functions
块中编写标记应该可行:
@functions
{
private void OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
}
不幸的是,这与使用 @code
块的结果相同。
编辑:再次阅读博客 post 后,我意识到,该功能仅适用于 .cshtml
文件。我对此进行了测试,并且确实可以在 .cshtml
文件中使用。但是这些文件不能作为组件使用,所以对我来说终究是行不通的。
我尝试在方法中使用 RenderTreeBuilder __builder
作为参数,
此解决方案的问题是,__builder
参数只能在标记代码中使用,如下所示:
<a>@__builder</a>
而不是来自 C# 代码(这就是使用参数的原因)。
接下来我发现的是 @helper
块,this 答案中提到了它。我将它应用到我的代码中,如下所示:
@helper OnFinish(EditContext editContext)
{
<a>Placeholder</a>
}
这也不起作用,因为编译器会引发以下两个错误:
The helper directive is not supported.
The name 'helper' does not exist in the current context
结论
现在,搜索到此结束。我花了很多时间研究这个,我想相信可能根本没有任何解决方案。我错过了什么?
编辑
有些人提到了将标记放在 if 中的想法,而当某些条件成立时 if 会被激活(例如,我有一个布尔值,我将其设置为 true 或我更改了一个变量,并且 if 识别出).
可以在当前线程的任一
该解决方案的代码如下所示:
if (renderNewMarkup)
{
<a>Placeholder</a>
}
@functions
{
private void OnFinish(EditContext editContext)
{
renderNewMarkup = true;
}
}
这确实有效,但我认为它非常不愉快。我遇到的问题是变量的使用。
在我的程序中,我从 UI 中检索了一些数据并将其进一步传递给当时创建的标记。我可以举个例子:
private void OnFinish(EditContext editContext)
{
Dictionary<string, string> dict = new()
{
["Key1"] = boundVariable,
["Key2"] = otherBoundVariable,
["Key3"] = anotherBoundVariable
}
// Create markup with dict variable
}
我想添加到 HTML 的标记是具有 Dictionary<string, string>
参数的自定义剃须刀组件。
您可以看到,我现在不能简单地在那里创建标记。我必须实现我想要的效果如下所示:
@if (renderNewCustomComponent)
{
<Custom DictParameter="@dict" />
}
@code
{
private Dictionary<string, string> dict;
private void OnFinish(EditContext editContext)
{
dict = new()
{
["Key1"] = boundVariable,
["Key2"] = otherBoundVariable,
["Key3"] = anotherBoundVariable
}
renderNewCustomComponent = true;
}
}
希望你明白我的意思。
那么这个解决方案还有一个更大的问题。当我想调用一个方法时,例如AddElementToHTML()
,这将始终向 HTML 添加一些元素,这是不可能的。是的,通过一些复杂的方式,使用 @foreach
之类的东西,这是可能的,但为什么语言一开始就不允许这样做呢?
Razor 代码使用 if
、foreach
等控制结构。您可以根据需要构建 HTML 字符串,并使用条件让 Blazor 知道何时显示它。
@if (CustomHtml is not null) {
<div>@((MarkupString)CustomHtml)</div>
}
@code
{
string CustomHtml; // starts as null
private void OnFinish(EditContext editContext)
{
CustomHtml = "<b>You are now registered!</b>";
}
}
通常,要添加控件,您会在条件逻辑中包含一个 .razor 组件。组件可以嵌套,因此您可以构建的内容真的没有限制。
在函数中,您必须操纵 editContext 或 return 某些东西。
带有return的伪代码示例:
@functions
{
private string OnFinish(EditContext editContext)
{
return "<a>Placeholder</a>";
}
}
带有操作的伪代码示例:
@functions
{
private void OnFinish(EditContext editContext)
{
editContext.change("<a>Placeholder</a>");
}
}
这只是我认为的部分答案。但是函数中的内联标记 是 可能的,只是因为额外的 {}
有点尴尬。您可以将其与 @if()
或其他控制结构合并:
@{ void F1() { <p>Inline Markup (local fn)</p> } }
@{ F1(); }
@if (2 > 1)
{
F1();
F2(__builder);
}
@code {
void F2(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
<p>Inline Markup </p>
}
}
考虑将页面的标记部分编译为 override void BuildRenderTree(builder) {...}
。
您可以添加本地函数 (F1) 或从您的 class (F2) 调用其他方法。
这并不能解决您的 OnFinish(editContext) 问题,因为它是在渲染代码路径之外调用的。
所以你仍然需要 @if()
.
原答案
这向您展示了如何从标记构建和显示简单的呈现片段。可能会有帮助。
@page "/Test"
<h3>Test8</h3>
<input @bind-value="content" class="form-control" />
<button class="btn btn-dark" @onclick="RenderSomeContent">Render Content</button>
<button class="btn btn-danger ml-2" @onclick="ClearContent">Clear Content</button>
@Placeholder
@code {
RenderFragment Placeholder { get; set; }
string content = "<div class='m-3 bg-success'>Hello</div>";
void RenderSomeContent()
{
Placeholder = (builder) =>
{
builder.AddMarkupContent(0, content);
};
}
void ClearContent()
{
Placeholder = null;
}
}
评论更新
二进制逻辑控件
@if (this.Show)
{
if (this.ChildContent != null)
{
@this.ChildContent
}
else
{
@((MarkupString)this.Body)
}
}
@code {
[Parameter] public bool Show { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Body { get; set; }
}
测试页面
@page "/Test"
<button class="btn btn-secondary" @onclick="DoIt">Toggle content</button>
<BinaryLogicControl Show="show">
<div class='m-3 bg-success'>Hello</div>
</BinaryLogicControl>
<BinaryLogicControl Show="show" Body="@Display" />
@code {
string Display = string.Empty;
bool show;
void DoIt()
{
show = !show;
Display = "<div class='m-5 p-3 bg-danger'>Hello</div>";
}
}
基于渲染组件的更新
根据评论,您想要的是 Net 6 中的 DynamicComponent
。目前您必须构建自己的(更简单的)版本。下面的代码显示了原始答案的更新版本,它构建了用于呈现的 BinaryLogicControl
而不是简单的 Markupstring
.
@page "/Test"
<button class="btn btn-primary" @onclick="RenderMe">Render Control</button>
@Placeholder
@code {
RenderFragment Placeholder { get; set; }
void RenderMe()
{
RenderSomeContent(typeof(BinaryLogicControl), new Dictionary<string, object> {
{ "Show", true },
{ "Body", "<div class='m-3 bg-warning'>Hello</div>" }
});
}
void RenderSomeContent(Type componentType, Dictionary<string, object> parameters)
{
Placeholder = (builder) =>
{
builder.OpenComponent(0, componentType);
foreach (var parameter in parameters)
{
builder.AddAttribute(1, parameter.Key, parameter.Value);
}
builder.CloseComponent();
};
}