【问题标题】:Using Razor 2 to write RSS fails on the <link> element使用 Razor 2 在 <link> 元素上编写 RSS 失败
【发布时间】:2012-09-24 13:24:54
【问题描述】:

我昨天升级到 MVC 4,刚刚发现升级引入的一个错误。

我有一个用于生成 RSS 提要的 Razor 视图。它有一些这样的标记(简化):

<item>
    <title>@post.BlogPost.Title</title> 
    <link>@Url.BlogPost(post.BlogPost, isAbsolute: true)</link>
</item>

在 Razor 版本 2 中,special support 对应于 HTML5 void elements。此类 void 元素是自闭合的,并且没有结束标记。

不幸的是,&lt;link&gt; 就是这样一个元素。

这意味着上述 Razor 标记不再有效,并且在运行时失败。删除结束 &lt;/link&gt; 标记会删除解析器错误,但意味着它不再是有效的 RSS。

那么,有没有办法解决这个问题,还是说 Razor 真的只适合生成 HTML5?

【问题讨论】:

  • 这让我对 razor 2 不太兴奋...
  • @boomhauer,我认为将 Razor 2 专门用于 HTML 对于几乎完全与 HTML 模板相关联的标记语言非常有意义。我不确定他们不能将其排他到 XML 的一个松散子集(以允许诸如 &lt;br&gt; 之类的怪事在 XHTML 中无效,但在 HTML5 中有效),因此也许可以将其扩展为支持有效的 XML 不是有效的 HTML5。

标签: .net asp.net-mvc razor asp.net-mvc-4 razor-2


【解决方案1】:

我会这样做:

<item>
   <title>
      @post.BlogPost.Title
   </title>

   @Html.Raw("<link>")
      @Url.BlogPost(post.BlogPost, isAbsolute: true)
   @Html.Raw("</link>")
</item>

生成的源代码如下所示:

<item>
    <title>
        Google
    </title>

     <link>
         http://www.google.se
    </link>
</item>

【讨论】:

  • 此时您必须想知道为什么要使用模板引擎!对 Linq-to-XML 做同样的事情会更清楚,IMO。但很高兴知道您可以在需要时使用Html.Raw 来解决无效标签的问题。
  • 我同意。我喜欢这个的是它至少类似于产生的输出。
  • Drew:我清理了一下。不要认为它太糟糕了,特别是如果用作部分。 RSS 并不复杂,但使用 Razor 标记和部分渲染大型 XSL-FO 文件可能比使用 Linq-to-XML 更甜,即使您必须在某些元素上使用 @Html.Raw。
  • 你的编辑肯定看起来更好。关于您对它类似于生成的输出的评论:至少在 RSS 的情况下,我实际上更喜欢输出的 Linq-to-XML 图片。至少在我看来,它更接近概念模型。如果布局得当,它看起来更像YAML 而不是 XML。
【解决方案2】:

现在我采用这种解决方法:

 @Html.Raw(string.Format(@"<param name=""{0}"">{1}</param>",Name, Value)) 

【讨论】:

  • 非常非常非常感谢!对 Razor 感到非常沮丧! :D
【解决方案3】:

由于 Alexander Taran 已针对此问题开放赏金以寻找明确的答案,我想我会查看 Razor source code on CodePlex 并提供一些详细信息。

首先,看看HtmlMarkupParser。它包含此参考数据:

//From http://dev.w3.org/html5/spec/Overview.html#elements-0
private ISet<string> _voidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
    "area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen",
    "link", "meta", "param", "source", "track", "wbr"
};

这是通过HtmlMarkupParser.VoidElements 公开的,并且该属性的唯一用途是在HtmlMarkupParser.RestOfTag(...) 中。这是一个遍历一系列标记的解析器。相关的sn-p代码为:

if (VoidElements.Contains(tagName))
{
    // Technically, void elements like "meta" are not allowed to have end tags. Just in case they do,
    // we need to look ahead at the next set of tokens. If we see "<", "/", tag name, accept it and the ">" following it
    // Place a bookmark
    int bookmark = CurrentLocation.AbsoluteIndex;

    // Skip whitespace
    IEnumerable<HtmlSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true));

    // Open Angle
    if (At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus))
    {
        HtmlSymbol openAngle = CurrentSymbol;
        NextToken();
        Assert(HtmlSymbolType.Solidus);
        HtmlSymbol solidus = CurrentSymbol;
        NextToken();
        if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase))
        {
            // Accept up to here
            Accept(ws);
            Accept(openAngle);
            Accept(solidus);
            AcceptAndMoveNext();

            // Accept to '>', '<' or EOF
            AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.OpenAngle);
            // Accept the '>' if we saw it. And if we do see it, we're complete
            return Optional(HtmlSymbolType.CloseAngle);
        } // At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase)
    } // At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus)

    // Go back to the bookmark and just finish this tag at the close angle
    Context.Source.Position = bookmark;
    NextToken();
}

这意味着将成功解析以下内容:

<link></link>

但是,前瞻是有限的,这意味着在结束标记之前看到的任何额外标记都会导致它失败:

<link>Some other tokens</link>

在这种情况下,可以扩展前瞻的范围。如果有人热衷,他们可以向 MVC 团队提供拉取请求。

【讨论】:

  • 一个很好的见解,但如果有人会提出拉取请求,应该可以选择使用 razor 创建自定义 xml,而不仅仅是 html
【解决方案4】:

对这个问题的简短回答似乎是 Razor 与第 2 版一样,与 HTML 相关联,排除了 XML。我asked one of the dev's for confirmation,希望他能回来。

我最终将我的方法更改为使用 Linq to XML 和自定义 ActionResult,绕过 Razor 和任何视图引擎:

[HttpGet]
[OutputCache(Duration = 300)]
public ActionResult Feed()
{
    var result = new XmlActionResult(
        new XDocument(
            new XElement("rss",
                new XAttribute("version", "2.0"),
                new XElement("channel",
                    new XElement("title", "My Blog")
                    // snip
                )
            )
        )
    );

    result.MimeType = "application/rss+xml";

    return result;
}

这需要以下自定义ActionResult

public sealed class XmlActionResult : ActionResult
{
    private readonly XDocument _document;

    public Formatting Formatting { get; set; }
    public string MimeType { get; set; }

    public XmlActionResult([NotNull] XDocument document)
    {
        if (document == null)
            throw new ArgumentNullException("document");

        _document = document;

        // Default values
        MimeType = "text/xml";
        Formatting = Formatting.None;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.ContentType = MimeType;

        using (var writer = new XmlTextWriter(context.HttpContext.Response.OutputStream, Encoding.UTF8) { Formatting = Formatting })
            _document.WriteTo(writer);
    }
}

【讨论】:

  • 接受了我自己的答案,因为它似乎不可能做到这一点。如果有人可以演示如何使用 Razor 2 模板发出 XML 元素 &lt;link&gt;content&lt;/link&gt;,我将给予他们答案。
【解决方案5】:

Html5 链接是一种特殊元素,用于样式表等的标题。

你的 Rss 不应该是 Html5,而是类似

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

你可以在你的 rss 提要使用的布局控制器中使用它

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
    @RenderBody()
</rss>

我以前做过的另一种方法是有一个完全空的视图,然后是下面的控制器:

    [NHibernateActionFilter]
    public AtomActionResult Feed()
    {
        var dto = _service.GetThings(NHibernateSession);
        var items = Mapper.Map<List<ThingDto>, List<SyndicationItem>>(dto);
        var url = HttpContextWrapper.Request.UrlReferrer;
        var feed = new SyndicationFeed("MyTitle", "MyByline", url, items)
        {
            Copyright = new TextSyndicationContent("© 2012 SO"),
            Language = "en-IE"
        };
        return new AtomActionResult(feed);
    }

特别值得注意的是System.ServiceModel.Syndication.SyndicationFeed

这是我的自定义结果

 public class AtomActionResult : ActionResult
    {
        readonly SyndicationFeed _feed;

        public AtomActionResult() { }

        public AtomActionResult(SyndicationFeed feed)
        {
            _feed = feed;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            //context.HttpContext.Response.ContentType = "application/atom+xml";
            //chrome does not yet support atom+xml 
            //http://code.google.com/p/chromium/issues/detail?id=104358
            context.HttpContext.Response.ContentType = "application/xml";
            var formatter = new Atom10FeedFormatter(_feed);
            using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
            {
                formatter.WriteTo(writer);
            }
        }
    }

【讨论】:

  • 是的,我的 RSS 不是 HTML5 —— 它只是普通的 XML。然而,新版本的 Razor 似乎与 HTML5 耦合,因此无法生成包含 link 元素和内容的有效 XML 文档。我对是否可以告诉 Razor 解析器忽略围绕 void 元素的规则感兴趣。我还没有找到这样做的方法。
  • 另外,什么是布局控制器?我猜你的意思是某种辅助功能。如果我要这样做,那么我只需使用 Linq-to-XML 在 action 方法中生成整个响应并避免使用 Razor。我最感兴趣的是知道这个新版本的 Razor 是否仅限于 HTML。
  • 我正在使用 razor2 并且没有您的问题。可能是编译或视觉工作室设置对此严格吗?根据您的帖子,您可以将内容类型设置为 xml。我将用我为 Atom 提要所做的更新我的答案。
  • 感谢您的更新,但您没有使用 Razor 呈现 XML 文档的视图。我最感兴趣的是知道这个新版本的 Razor 是否仅限于 HTML。
  • @DrewNoakes 不在我对 Syndication 提要的后一个建议中,但我在第一个建议中,有或没有布局控制器。我不认为它仅限于 Xml,但建议您考虑使用内置的提要生成支持。
【解决方案6】:

你可以这样做:

@("<link>" + Url.BlogPost(post.BlogPost, isAbsolute: true) + "</link>")

简单很多

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-23
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-30
    • 2014-07-03
    相关资源
    最近更新 更多