Использование Razor 2 для записи RSS не выполняется с помощью элемента <link>
Я вчера обновился до MVC 4 и только что обнаружил ошибку, которая была обновлена.
У меня есть представление Razor, которое используется для создания RSS-канала. У этого есть такая разметка (упрощенная):
<item>
<title>@post.BlogPost.Title</title>
<link>@Url.BlogPost(post.BlogPost, isAbsolute: true)</link>
</item>
В версии Razor две, специальная поддержка для элементов HTML5 void. Такие недействительные элементы само закрываются и не имеют закрывающего тега.
К сожалению, <link>
- один из таких элементов.
Это означает, что вышеуказанная разметка Razor больше не действительна и не работает во время выполнения. Удаление закрывающего тега </link>
устраняет ошибку анализатора, но означает, что он больше не действителен RSS.
Итак, есть ли способ обойти это, или Razor действительно подходит для генерации HTML5?
Ответы
Ответ 1
Короткий ответ на этот вопрос, похоже, заключается в том, что Razor, как и версия 2, привязан к HTML, исключая XML. Я спросил одного из разработчиков для подтверждения, поэтому, надеюсь, он вернется.
В итоге я изменил свой метод, чтобы использовать Linq для 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);
}
}
Ответ 2
Я бы сделал это вот так:
<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>
Ответ 3
Теперь я перехожу к этому обходному пути:
@Html.Raw(string.Format(@"<param name=""{0}"">{1}</param>",Name, Value))
Ответ 4
Поскольку Александр Таран открыл щедрость по этому вопросу в поисках окончательного ответа на этот вопрос, я подумал, что я проведу Исходный код 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.
Ответ 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);
}
}
}
Ответ 6
Что вы можете сделать, так это:
@("<link>" + Url.BlogPost(post.BlogPost, isAbsolute: true) + "</link>")
намного проще