sitemap.xmlの自動生成

sitemap.xmlって割と重要だと思うのだけど、手動で更新するのは面倒くさいよね。特に日付。

動的生成するライブラリとか間違いなくあるはずだと思って探してみたけど、意外と無い。 とりあえず、外国のサイトでsitemapのクラスを発見したので使ってみよう……と思ったら、XMLの生成部分にバグがあったので修正した。

ページのリンクは紛失してしまった……

   public class XmlSitemapResult : ActionResult {
        private static readonly XNamespace xmlns = "http://www.sitemaps.org/schemas/sitemap/0.9";
        private static readonly XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

        private IEnumerable<ISitemapItem> _items;

        public XmlSitemapResult(IEnumerable<ISitemapItem> items) {
            _items = items;
        }

        public override void ExecuteResult(ControllerContext context) {
            string encoding = context.HttpContext.Response.ContentEncoding.WebName;

            XDocument sitemap = new XDocument(
                new XDeclaration("1.0", encoding, "yes"),
                new XElement("urlset",
                    new XAttribute("xmlns", xmlns),
                    new XAttribute(XNamespace.Xmlns + "xsi", xsi),
                    new XAttribute(xsi + "schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"),
                    from item in _items
                    select CreateItemElement(item)
                )
            );
            var response = context.HttpContext.Response;

            response.ContentType = "text/xml";
            response.ContentEncoding = Encoding.UTF8;

            using (var writer = new XmlTextWriter(response.Output)) {
                writer.Formatting = Formatting.Indented;

                sitemap.WriteTo(writer);
            }
        }

        private XElement CreateItemElement(ISitemapItem item) {
            XElement itemElement = new XElement("url", new XElement("loc", item.Url.ToLower()));

            if (item.LastModified.HasValue)
                itemElement.Add(new XElement("lastmod", item.LastModified.Value.ToString("yyyy-MM-dd")));

            if (item.ChangeFrequency.HasValue)
                itemElement.Add(new XElement("changefreq", item.ChangeFrequency.Value.ToString().ToLower()));

            if (item.Priority.HasValue)
                itemElement.Add(new XElement("priority", item.Priority.Value.ToString(CultureInfo.InvariantCulture)));

            return itemElement;
        }
    }

    public interface ISitemapItem {
        string Url { get; }
        DateTime? LastModified { get; }
        ChangeFrequency? ChangeFrequency { get; }
        float? Priority { get; }
    }

    public class SitemapItem : ISitemapItem {
        public SitemapItem(string url) {
            Url = url;
        }

        public string Url { get; set; }

        public DateTime? LastModified { get; set; }

        public ChangeFrequency? ChangeFrequency { get; set; }

        public float? Priority { get; set; }
    }

    public enum ChangeFrequency {
        Always,
        Hourly,
        Daily,
        Weekly,
        Monthly,
        Yearly,
        Never
    }

こんな感じのサイトマップクラスを適当なとこに置いとく。 使用するときは、こんな風に。

           var items = new List<ISitemapItem>();

            items.Add(new SitemapItem("http://foobar.com/") { LastModified = new DateTime(2015, 9, 25), Priority = 1, ChangeFrequency = ChangeFrequency.Weekly });
            items.Add(new SitemapItem("http://foobar.com/FooBar") { LastModified = foobarDb.FooBar.Max(foobar => foobar.ModifyDate), Priority = 1, ChangeFrequency = ChangeFrequency.Daily });
            items.Add(new SitemapItem("http://foobar.com/Hoge/Fuga") { LastModified = System.IO.File.GetLastWriteTime(Server.MapPath("/") + @"Views\Hoge\Fuga.cshtml"), Priority = 1, ChangeFrequency = ChangeFrequency.Daily });

            return new XmlSitemapResult(items);

上から固定の日付、DBからの取得、cshtmlの更新日時の取得。 最後の更新日時の取得ってもっと良い感じに出来ないのかなー、って思ったけど、あまりこういう事やる人がいないのか、特にこれといった方法は見つからなかった。

あと、こんな取得の仕方が散らばっている構成だとどうしようもなくてitemを一々インスタンス化してるけど、DBにデータをため込んでたりしたらLINQ使ったりしてもっとシンプルに行けるはず。