Использование Razor 2 для записи RSS не работает в элементе <link>

StackOverflow https://stackoverflow.com//questions/12711507

Вопрос

Вчера я обновился до MVC 4 и только что обнаружил ошибку, возникшую в результате обновления.

У меня есть представление Razor, которое используется для создания RSS-канала.У него есть такая разметка (упрощенная):

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

Во второй версии Razor есть специальная поддержка для Недействительные элементы HTML5.Такие пустые элементы являются самозакрывающимися и не имеют закрывающего тега.

К сожалению, <link> является одним из таких элементов.

Это означает, что приведенная выше разметка Razor больше недействительна и не работает во время выполнения.Удаление закрытия </link> удаляет ошибку синтаксического анализатора, но означает, что он больше не является действительным RSS.

Итак, есть ли способ обойти это, или Razor действительно подходит только для генерации HTML5?

Это было полезно?

Решение 4

Краткий ответ на этот вопрос заключается в том, что Razor, как и версия 2, привязан к HTML, исключая XML.я попросил подтверждения у одного из разработчиков, так что надеюсь, он вернется.

В итоге я изменил свой метод, чтобы использовать 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);
    }
}

Другие советы

Я бы сделал это так:

<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>

На данный момент я использую этот обходной путь:

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

Поскольку Александр Таран открыл награду за этот вопрос в поисках окончательного ответа на этот вопрос, я решил проверить Исходный код Razor на 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(...).Это анализатор, который обрабатывает последовательность токенов.Соответствующий фрагмент кода:

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.

Ссылка 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);
            }
        }
    }

Что вы можете сделать, это:

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

намного проще

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top