在.NET core 2中如何动态创建sitemap.xml?

15

有人能告诉我如何在.NET Core 2中创建网站地图吗?

这些文章/备用链接在.NET Core 2中不起作用。

11个回答

28
我在一个样例Web应用程序中找到了解决您问题的方法。感谢Mads Kristensen。这是您要查找的非常简化版本。将此代码放入控制器类中,例如HomeController,与添加操作方法的方式相同。
以下是返回XML的方法:
[Route("/sitemap.xml")]
public void SitemapXml()
{
     string host = Request.Scheme + "://" + Request.Host;

     Response.ContentType = "application/xml";

     using (var xml = XmlWriter.Create(Response.Body, new XmlWriterSettings { Indent = true }))
     {
          xml.WriteStartDocument();
          xml.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");

          xml.WriteStartElement("url");
          xml.WriteElementString("loc", host);
          xml.WriteEndElement();

          xml.WriteEndElement();
     }
}

当你输入 http://www.example.com/sitemap.xml 时,会产生以下结果:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
     <url>
          <loc>http://www.example.com/</loc>
     </url>
</urlset>

我希望这可以帮助您。如果您也发现了解决方案,请将其作为有关问题的更新发布。


1
很好的解决方案,没有任何包。也适用于带有.NET Framework的ASP.NET Core MVC。 - everydayXpert
4
请注意,根据 .NET Core 版本的不同,AllowSynchronousIO 功能可能会默认禁用。您可以使用以下代码针对每个请求进行覆盖: var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>(); if (syncIOFeature != null) { syncIOFeature.AllowSynchronousIO = true; } 更多信息请参见 https://learn.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.0 - user3019725
如果能够声明函数方法为async就好了。当你将其设置为async void时,Response对象在函数完成之前就被处理掉了。如果能够将其与异步的Task<IActionResult>结合起来,以流的形式输出文件,那就太棒了。 - Steve Wranovsky

14

实际上,我更喜欢使用Razor在模板文件中编写。假设你只有一个页面,.NET Core 3.1中的示例代码如下(.NET core 2的代码不会有太大区别):

<!-- XmlSitemap.cshtml -->
@page "/sitemap.xml"
@using Microsoft.AspNetCore.Http
@{
    var pages = new List<dynamic>
    {
        new { Url = "http://example.com/", LastUpdated = DateTime.Now }
    };
    Layout = null;
    Response.ContentType = "text/xml";
    await Response.WriteAsync("<?xml version='1.0' encoding='UTF-8' ?>");
}

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    @foreach (var page in pages)
    {
        <url>
            <loc>@page.Url</loc>
            <lastmod>@page.LastUpdated.ToString("yyyy-MM-dd")</lastmod>
        </url>
    }
</urlset>

如果您正在使用MVC,那么这是一个很好的解决方案。您可以创建功能来将页面列表作为视图模型传递到此Razor视图,然后按需生成它。您还可以在后端缓存它以提高性能。 - Eric Conklin
你不能在 .Net 5 的 Razor 中使用 @page,所以我会将其修改为不同的变量名,否则看起来很好。 - Eric Conklin

7

幸运的是,已经有一些预先构建的库列表可供使用。安装这个工具 https://github.com/uhaciogullari/SimpleMvcSitemap

然后像这样创建一个新控制器(在 github 上还有更多示例):

public class SitemapController : Controller
{
    public ActionResult Index()
    {
        List<SitemapNode> nodes = new List<SitemapNode>
        {
            new SitemapNode(Url.Action("Index","Home")),
            new SitemapNode(Url.Action("About","Home")),
            //other nodes
        };

        return new SitemapProvider().CreateSitemap(new SitemapModel(nodes));
    }
}

5
中间件正常运作,但需要进行微小修复。
if (context.Request.Path.Value.Equals("/sitemap.xml", StringComparison.OrdinalIgnoreCase))
{
    // Implementation
}
else
    await _next(context);

我创建了一个新项目,然后添加了中间件并运行后,我在浏览器中输入http://localhost:64522/sitemap.xml,得到以下结果:

<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://localhost:64522/home/index</loc>
    <lastmod>2018-05-13</lastmod>
  </url>
  <url>
    <loc>http://localhost:64522/home/about</loc>
    <lastmod>2018-05-13</lastmod>
  </url>
  <url>
    <loc>http://localhost:64522/home/contact</loc>
    <lastmod>2018-05-13</lastmod>
  </url>
  <url>
    <loc>http://localhost:64522/home/privacy</loc>
    <lastmod>2018-05-13</lastmod>
  </url>
  <url>
    <loc>http://localhost:64522/home/error</loc>
    <lastmod>2018-05-13</lastmod>
  </url>
</urlset>

2

SEOHelper是一个用于SEO管理的nuget库。

Install-Package AspNetCore.SEOHelper

SEOHelper包提供了SitemapNode类来设置URL,以及CreateSitemapXML方法来创建sitemap.xml。

var list = new List<SitemapNode>();  
list.Add(new SitemapNode { LastModified = DateTime.UtcNow, Priority = 0.8, Url = "https://www.example.com/page 1", Frequency = SitemapFrequency.Daily });  
list.Add(new SitemapNode { LastModified = DateTime.UtcNow, Priority = 0.9, Url = "https://www.example.com/page2", Frequency = SitemapFrequency.Yearly });  
new SitemapDocument().CreateSitemapXML(list, _env.ContentRootPath);  

2
以下代码适用于ASP.NET Core 2.2。
 public class SitemapUrl
    {
        public string Page { get; set; }

        public DateTime? LastModifyed { get; set; }

        /*
            always
            hourly
            daily
            weekly
            monthly
            yearly
            never
        */
        public string ChangeFreq { get; set; }

        public float Priority { get; set; } = 0.5f;
    }

    public class SitemapResult : ActionResult
    {
        private readonly IEnumerable<SitemapUrl> _urls;

        public SitemapResult(IEnumerable<SitemapUrl> urls)
        {
            _urls = urls;
        }

        public override async Task ExecuteResultAsync(ActionContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            var response = context.HttpContext.Response;
            response.ContentType = "application/xml; charset=utf-8";

            var settings = new XmlWriterSettings() { Async = true, Encoding = Encoding.UTF8, Indent = false };
            using (var writer = XmlWriter.Create(response.Body, settings))
            {
                WriteToXML(writer);
                await writer.FlushAsync();
            }
        }

        private void WriteToXML(XmlWriter writer)
        {
            writer.WriteStartDocument();
            // Write the urlset.
            writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
            // url element
            foreach (var item in _urls)
            {
                writer.WriteStartElement("url");
                // loc
                writer.WriteStartElement("loc");
                writer.WriteValue(item.Page);
                writer.WriteEndElement();
                // changefreq
                if (!string.IsNullOrEmpty(item.ChangeFreq))
                {
                    writer.WriteStartElement("changefreq");
                    writer.WriteValue(item.ChangeFreq);
                    writer.WriteEndElement();
                }
                // lastmod
                if (item.LastModifyed.HasValue)
                {
                    writer.WriteStartElement("lastmod");
                    writer.WriteValue(item.LastModifyed.Value.ToString("yyyy-MM-dd"));
                    writer.WriteEndElement();
                }

                // priority
                writer.WriteStartElement("priority");
                writer.WriteValue(item.Priority);
                writer.WriteEndElement();

                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            writer.WriteEndDocument();
        }
    }

然后在MVC的控制器类中调用SitemapResult

public IActionResult Sitemap(){
    return new SitemapResult(new SitemapUrl[] { new SitemapUrl() { } });
}

在ASP.NET 3.0+中,只需删除XmlWriterSettingsasync操作或在所有服务器上禁用AllowSynchronousIO即可。
我使用SitemapHub网站地图工具为我的多个网站构建XML网站地图,无需编码,节省时间。

2

ASP.NET Core 3.1的动态站点地图“ sitemap-blog.xml ”适用于博客部分并具有24小时缓存。

  • sitemap.xml 存在于wwwroot中(由xml-sitemaps.com或其他方式生成)。
  • sitemap-blog.xml 动态生成。

robots.txt

User-agent: *
Disallow: /Admin/
Disallow: /Identity/
Sitemap: https://example.com/sitemap.xml
Sitemap: https://example.com/sitemap-blog.xml

Startup.cs

services.AddMemoryCache();

HomeController.cs

namespace MT.Controllers
{
    public class HomeController : Controller
    {
        private readonly ApplicationDbContext _context;
        private readonly IMemoryCache _cache;

        public HomeController(
            ApplicationDbContext context,
            IMemoryCache cache)
        {
            _context = context;
            _cache = cache;

        }

        [Route("/sitemap-blog.xml")]
        public async Task<IActionResult> SitemapBlog()
        {
            string baseUrl = $"{Request.Scheme}://{Request.Host}{Request.PathBase}";
            string segment = "blog";
            string contentType = "application/xml";

            string cacheKey = "sitemap-blog.xml";

            // For showing in browser (Without download)
            var cd = new System.Net.Mime.ContentDisposition
            {
                FileName = cacheKey,
                Inline = true,
            };
            Response.Headers.Append("Content-Disposition", cd.ToString());

            // Cache
            var bytes = _cache.Get<byte[]>(cacheKey);
            if (bytes != null)
                return File(bytes, contentType);

            var blogs = await _context.Blogs.ToListAsync();

            var sb = new StringBuilder();
            sb.AppendLine($"<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            sb.AppendLine($"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"");
            sb.AppendLine($"   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
            sb.AppendLine($"   xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\">");

            foreach (var m in blogs)
            {
                var dt = m.LastModified;
                string lastmod = $"{dt.Year}-{dt.Month.ToString("00")}-{dt.Day.ToString("00")}";

                sb.AppendLine($"    <url>");

                sb.AppendLine($"        <loc>{baseUrl}/{segment}/{m.Slug}</loc>");
                sb.AppendLine($"        <lastmod>{lastmod}</lastmod>");
                sb.AppendLine($"        <changefreq>daily</changefreq>");
                sb.AppendLine($"        <priority>0.8</priority>");

                sb.AppendLine($"    </url>");
            }

            sb.AppendLine($"</urlset>");

            bytes = Encoding.UTF8.GetBytes(sb.ToString());

            _cache.Set(cacheKey, bytes, TimeSpan.FromHours(24));
            return File(bytes, contentType);
        }
    }
}

有一个大的站点地图,我看到的唯一缺点是整个站点地图必须在响应返回之前全部存储在内存中,如果可以在创建时“流式”输出xml文件,那就太好了。 - Steve Wranovsky

1

让我们采取两种方法来实现所需的结果。

首先,这些文章使用中间件方法。使用控制器和SimpleMvcSitemap nuget包有一个简单的解决方案。

public class SitemapController : Controller
{
    public ActionResult Index()
    {
        List<SitemapNode> nodes = new List<SitemapNode>
        {
            new SitemapNode(Url.Action("Index","Home")),
            new SitemapNode(Url.Action("About","Home")),
            //other nodes
        };

        return new SitemapProvider().CreateSitemap(new SitemapModel(nodes));
    }
}

第二部分是使用反射动态获取所有控制器和操作。以 iaspnetcore 为例,以下是获取控制器和操作列表的方法。

            // get available controllers
            var controllers = Assembly.GetExecutingAssembly().GetTypes()
                .Where(type => typeof(Controller).IsAssignableFrom(type)
                || type.Name.EndsWith("controller")).ToList();

            foreach (var controller in controllers)
            {
                var controllerName = controller.Name.Replace("Controller", "");

                // get available methods  in controller
                var methods = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                    .Where(method => typeof(IActionResult).IsAssignableFrom(method.ReturnType));
                foreach (var method in methods)
                {
                    var myRoute = Url.Action(method.Name, controllerName);
                }
            }

把所有东西放在一起,我们就有了这段代码。
/// <summary>
/// Base URL Provider for sitemap. Replace with your domain
/// </summary>
public class BaseUrlProvider : IBaseUrlProvider
{
    public Uri BaseUrl => new Uri("https://example.com");
}

public class SitemapController : Controller
{

    [Route("sitemap.xml")]
    public ActionResult Index()
    {
        List<SitemapNode> nodes = new List<SitemapNode>();


        // get available contrtollers
        var controllers = Assembly.GetExecutingAssembly().GetTypes()
                .Where(type => typeof(Controller).IsAssignableFrom(type)
                || type.Name.EndsWith("controller")).ToList();

        foreach (var controller in controllers)
        {
            // get available methods
            var methods = controller.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                .Where(method => typeof(IActionResult).IsAssignableFrom(method.ReturnType));

            foreach (var method in methods)
            {
                // add route name in sitemap
                nodes.Add(new SitemapNode(Url.Action(method.Name, controllerName)));
            }
        }

        return new SitemapProvider(new BaseUrlProvider()).CreateSitemap(new SitemapModel(nodes));
    }
}

使用列表:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using SimpleMvcSitemap;
using SimpleMvcSitemap.Routing;

最后只需打开路由,例如:

https://localhost:44312/sitemap.xml

1

创业公司

 services.AddMvcCore(options =>
        {
            options.OutputFormatters.Clear(); // Remove json for simplicity
            options.OutputFormatters.Add(new MyCustomXmlSerializerOutputFormatter());
        });
app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
       name: "sitemapxml",
       pattern: "/sitemap.xml",
       defaults: new { controller = "Home", action = "SitemapXML" }
       );

自定义格式化程序

  public class MyCustomXmlSerializerOutputFormatter : XmlSerializerOutputFormatter
{
    protected override void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value)
    {

        xmlSerializer = new XmlSerializer(typeof(List<url>), new XmlRootAttribute("urlset"));

        xmlSerializer.Serialize(xmlWriter, value);
    }
}

urlclass

public class url
{
    

    public string changefreq { get; set; }
    public DateTime? lastModified { get; set; }
    public double? priority { get; set; }
    public string loc { get; set; }
}

控制器
    [HttpGet]
    [Produces("application/xml")]
    public ActionResult<List<url>> SitemapXML() 
    {
        var list = new List<url>();

        var dokumanlar = _dokumanCategoryService.GetAll().Where(i => i.Yatirimci == 1).ToList();


        

        foreach (var dokuman in dokumanlar)
        {
            var dokumanurl = dokuman.SeoUrl;

            var culture = dokuman.Culture;

            if (dokuman.Culture == "tr")
            {
                list.Add(new url { lastModified = DateTime.UtcNow, priority = 0.8, loc = $"https://example.com/yatirimci-iliskileri/{dokumanurl}", changefreq = "always" });
            }
            else
            {
                list.Add(new url { lastModified = DateTime.UtcNow, priority = 0.8, loc = $"https://example.com/{culture}/investor-relations/{dokumanurl}", changefreq = "always" });
            }


        }


        

        return list;
    }

0

迄今为止,我发现在 .Net Core 3.x 中最优雅的方法是使用 ParkSquare.AspNetCore.Sitemap。这将根据您定义的路由动态创建 sitemap.xml 和 robots.txt。

在 startup.cs 中注册中间件:

app.UseSitemap();

为了排除任何内容,你可以装饰控制器类以排除该控制器中的所有内容,或是特定路由:

// All routes in this controller will be ignored
[SitemapExclude]
public class BlahController : Controller
{
    [Route("some-route")]
    public IActionResult Something()
    {
        return View();
    }
}

public class BlahController : Controller
{
    [SitemapExclude]
    [Route("some-route")]
    public IActionResult Ignored()
    {
        return View();
    }

    [Route("some-other-route")]
    public IActionResult NotIgnored()
    {  
        return View();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接