Razor 中优雅的 foreach - else 构造

发布于 2024-12-11 04:24:40 字数 824 浏览 0 评论 0原文

许多模板引擎都有一种特殊的语法,它是 foreachelse 的组合。基本上,当 foreach 循环没有任何迭代时,就会执行 else 子句。如果您想显示某种列表中没有项目的后备,这会很有用。

例如,在 Twig 中,for 循环 可以如下所示

{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no user found</em></li>
{% endfor %}

使用 Razor View Engine,模板会像这样,涉及对项目数量的额外检查在集合中:

@foreach (var user in users) {
    <li>@user.UserName</li>
}
@if (!users.Any()) {
    <li><em>no user found</em></li>
}

所以我的问题是:可以我们使用 Razor View Engine 以某种方式实现了类似的优雅。

A lot of templating engines have a special kind of syntax that is a combination of foreach and else. Basically the else clause is executed when the foreach loop doesn't have any iterations. This can be useful if you want to display some kind of no items in the list fallback.

In Twig for example, the for loop can look like this

{% for user in users %}
    <li>{{ user.username|e }}</li>
{% else %}
    <li><em>no user found</em></li>
{% endfor %}

Using the Razor View Engine, the template would like like this, involving an additional check on the number of items in the collection:

@foreach (var user in users) {
    <li>@user.UserName</li>
}
@if (!users.Any()) {
    <li><em>no user found</em></li>
}

So my questions is: can we achieve a similar elegance some way or another using the Razor View Engine.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

风向决定发型 2024-12-18 04:24:40

巩固杰米克和马丁·布斯的答案。我创建了以下扩展方法。它采用 IEnumerable 作为第一个参数,然后使用两个委托来呈现文本。在 Razor 视图中,我们可以传入模板化委托两个这样的参数。简而言之,这意味着您可以提供模板。下面是扩展方法以及如何调用它:

    public static HelperResult Each<TItem>(this IEnumerable<TItem> items, 
        Func<TItem, HelperResult> eachTemplate, 
        Func<dynamic, HelperResult> other)
    {
        return new HelperResult(writer =>
        {
            foreach (var item in items)
            {
                var result = eachTemplate(item);
                result.WriteTo(writer);
            }

            if (!items.Any())
            {
                var otherResult = other(new ExpandoObject());
                // var otherResult = other(default(TItem));
                otherResult.WriteTo(writer);
            }
        });
    }

在 Razor 视图中:

@Model.Users.Each(
    @<li>@item.Name</li>,
    @<li>
        <b>No Items</b>
     </li>
)

总而言之,非常干净。

更新实施评论中提出的建议。此扩展方法采用一个参数来循环遍历集合中的项目并返回一个自定义 HelperResult。在该 helperresult 上,可以调用 Else 方法来传入模板委托,以防找不到任何项目。

public static class HtmlHelpers
{
    public static ElseHelperResult<TItem> Each<TItem>(this IEnumerable<TItem> items, 
        Func<TItem, HelperResult> eachTemplate)
    {
        return ElseHelperResult<TItem>.Create(items, eachTemplate);
    }
}

public class ElseHelperResult<T> : HelperResult
{
    private class Data
    {
        public IEnumerable<T> Items { get; set; }
        public Func<T, HelperResult> EachTemplate { get; set; }
        public Func<dynamic, HelperResult> ElseTemplate { get; set; }

        public Data(IEnumerable<T> items, Func<T, HelperResult> eachTemplate)
        {
            Items = items;
            EachTemplate = eachTemplate;
        }

        public void Render(TextWriter writer)
        {
            foreach (var item in Items)
            {
                var result = EachTemplate(item);
                result.WriteTo(writer);
            }

            if (!Items.Any() && ElseTemplate != null)
            {
                var otherResult = ElseTemplate(new ExpandoObject());
                // var otherResult = other(default(TItem));
                otherResult.WriteTo(writer);
            }
        }
    }

    public ElseHelperResult<T> Else(Func<dynamic, HelperResult> elseTemplate)
    {
        RenderingData.ElseTemplate = elseTemplate;
        return this;
    }

    public static ElseHelperResult<T> Create(IEnumerable<T> items, Func<T, HelperResult> eachTemplate)
    {
        var data = new Data(items, eachTemplate);
        return new ElseHelperResult<T>(data);
    }

    private ElseHelperResult(Data data)
        : base(data.Render)
    {
        RenderingData = data;
    }

    private Data RenderingData { get; set; }
}

然后可以这样调用:

@(Model.Users
   .Each(@<li>@item.Name</li>)
   .Else(
        @<li>
            <b>No Users</b>
         </li>
        )
)

Consolidating the answers of Jamiec and Martin Booth. I created the following extension method. It takes an IEnumerable as first argument, and then two delegates for rendering the text. In the Razor Views we can pass in Templated Delegates two these parameters. In short this means that you can give in templates. So here is the extension method and how you can call it:

    public static HelperResult Each<TItem>(this IEnumerable<TItem> items, 
        Func<TItem, HelperResult> eachTemplate, 
        Func<dynamic, HelperResult> other)
    {
        return new HelperResult(writer =>
        {
            foreach (var item in items)
            {
                var result = eachTemplate(item);
                result.WriteTo(writer);
            }

            if (!items.Any())
            {
                var otherResult = other(new ExpandoObject());
                // var otherResult = other(default(TItem));
                otherResult.WriteTo(writer);
            }
        });
    }

And in the Razor views:

@Model.Users.Each(
    @<li>@item.Name</li>,
    @<li>
        <b>No Items</b>
     </li>
)

All in all, pretty clean.

UPDATE implementing the suggestions made in the comments. This extension method takes one argument to loop over the items in the collection and returns a custom HelperResult. On that helperresult, one can call the Else method to pass in a template delegate in case no items are found.

public static class HtmlHelpers
{
    public static ElseHelperResult<TItem> Each<TItem>(this IEnumerable<TItem> items, 
        Func<TItem, HelperResult> eachTemplate)
    {
        return ElseHelperResult<TItem>.Create(items, eachTemplate);
    }
}

public class ElseHelperResult<T> : HelperResult
{
    private class Data
    {
        public IEnumerable<T> Items { get; set; }
        public Func<T, HelperResult> EachTemplate { get; set; }
        public Func<dynamic, HelperResult> ElseTemplate { get; set; }

        public Data(IEnumerable<T> items, Func<T, HelperResult> eachTemplate)
        {
            Items = items;
            EachTemplate = eachTemplate;
        }

        public void Render(TextWriter writer)
        {
            foreach (var item in Items)
            {
                var result = EachTemplate(item);
                result.WriteTo(writer);
            }

            if (!Items.Any() && ElseTemplate != null)
            {
                var otherResult = ElseTemplate(new ExpandoObject());
                // var otherResult = other(default(TItem));
                otherResult.WriteTo(writer);
            }
        }
    }

    public ElseHelperResult<T> Else(Func<dynamic, HelperResult> elseTemplate)
    {
        RenderingData.ElseTemplate = elseTemplate;
        return this;
    }

    public static ElseHelperResult<T> Create(IEnumerable<T> items, Func<T, HelperResult> eachTemplate)
    {
        var data = new Data(items, eachTemplate);
        return new ElseHelperResult<T>(data);
    }

    private ElseHelperResult(Data data)
        : base(data.Render)
    {
        RenderingData = data;
    }

    private Data RenderingData { get; set; }
}

This can then be called as follows:

@(Model.Users
   .Each(@<li>@item.Name</li>)
   .Else(
        @<li>
            <b>No Users</b>
         </li>
        )
)
抱猫软卧 2024-12-18 04:24:40

我认为实现这样的目标的唯一方法是对 IEnumerable 进行一些扩展:

public static class IEnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
    {
       foreach(T item in enumerable)
           action(item);

        return enumerable;
    }

    public static IEnumerable<T> WhenEmpty<T>(this IEnumerable<T> enumerable, Action action)
    {
       if(!enumerable.Any())
           action();
        return enumerable;
    }
}

这使您能够将 2 个调用相互链接,如本实例所示:http://rextester.com/runco​​de?code=AEBQ75190 使用以下代码:

var listWithItems = new int[] {1,2,3};
var emptyList = new int[]{};

listWithItems.ForEach(i => Console.WriteLine(i))
    .WhenEmpty( () => Console.WriteLine("This should never display"));

emptyList.ForEach(i => Console.WriteLine(i))
    .WhenEmpty( () => Console.WriteLine("This list was empty"));

我仍然不确定这将如何与 Razor 模板相适应……但这也许会给您一些继续的机会。

The only way I could think to achieve something like this is with a couple of extensions to IEnumerable<T>:

public static class IEnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
    {
       foreach(T item in enumerable)
           action(item);

        return enumerable;
    }

    public static IEnumerable<T> WhenEmpty<T>(this IEnumerable<T> enumerable, Action action)
    {
       if(!enumerable.Any())
           action();
        return enumerable;
    }
}

This enables you to chain 2 calls onto each other as demonstarted by this live example: http://rextester.com/runcode?code=AEBQ75190 which uses the following code:

var listWithItems = new int[] {1,2,3};
var emptyList = new int[]{};

listWithItems.ForEach(i => Console.WriteLine(i))
    .WhenEmpty( () => Console.WriteLine("This should never display"));

emptyList.ForEach(i => Console.WriteLine(i))
    .WhenEmpty( () => Console.WriteLine("This list was empty"));

Quite how this would fit in with a Razor template im still unsure of.... but maybe this gives you something to go on.

◇流星雨 2024-12-18 04:24:40

据我所知,没有内置任何内容,但您可以扩展它以满足您的需求:

http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx

我稍后可能会提供帮助如果你仍然没有答案,我不会使用我的手机

Nothing built in afaik, but you could probably extend this to suit your needs:

http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx

I might be able to help later when I'm not using my phone if you still don't have an answer

丑丑阿 2024-12-18 04:24:40

也许在提出问题时这是不可能的,但我刚刚实现了这样的目标:

@if (Model.EmailAddress.Count() > 0)
{
    foreach (var emailAddress in Model.EmailAddress)
    {
        <div>@emailAddress.EmailAddress</div>
    }
} else { <span>No email addresses to display</span>  }

Maybe this wasn't possible when the question was posed, but I've just achieved this like this:

@if (Model.EmailAddress.Count() > 0)
{
    foreach (var emailAddress in Model.EmailAddress)
    {
        <div>@emailAddress.EmailAddress</div>
    }
} else { <span>No email addresses to display</span>  }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文