2

I have a .NET 7 web app, where I have a controller that results in a sitemap.xml file. When I run the application locally, I get an XML file as a result with this content:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>

And it looks like this:

enter image description here

However, when this is pushed to production (everything is hosted as a web app on Azure), the same endpoint returns nothing. It does recognize the endpoint and looks like this:

enter image description here

My code to generate this, is shown below:

    [Route("/sitemap.xml")]
    public async Task SitemapXml()
    {
        var countries = await _countryService.GetBySpecificationAsync(new CountrySpecification()
        {
            Take = int.MaxValue
        });
        
        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.WriteEndElement();
        }
    }

My question: I am completely lost. At first I thought it was because I didn't add support for static files and this is considered a static file, but I do have:

app.UseStaticFiles();

In the Program.cs.

Any hints where I should be starting?

Lars Holdgaard
  • 9,496
  • 26
  • 102
  • 182

2 Answers2

4

I spent some time this week wanting to answer this question, and I have time now.

The main issue with your attempt is you are not returning XML results. To do so I suggest using IActionResult interface.

Now time to create sitemap.xml. IMO there are 2 ways to go from here, either using a library OR writing your own sitemap method.

I will start with a library. For instance, there is a very simple library (NuGet) called SimpleMvcSitemap.Core. Install it in your project, and in your controller insert the following code:

[Route("/sitemap.xml")]
public async Task<IActionResult> SitemapXml()
{

    // your await call etc

    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));
}

Btw for this test, I created an asp.net MVC .net 7 project.

I have deployed the solution to azure and it works both on local development and on azure. Here is the result:

enter image description here

If you do want to do it manually, you can do following

var listUrls = new List<string>
{
    Url.Action("Index", "Home"),
    Url.Action("About", "Home")
};

return new SitemapResult(listUrls);

And here is the implementation:

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

    public SitemapResult(IEnumerable<string> 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);
            writer.WriteEndElement();

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

The manual way is also deployed on azure and works, but in the manual way you need to do a lot of work that is already done in a library. To be fair both above outcome is inspired form the question How to dynamically create a sitemap.xml in .NET core 2?.

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
2

from this msdn magazine: "A controller that returns void will produce an EmptyResult." I assume this holds true also for Task.

So maybe you need to change your return type of your method from Task to Task<IActionResult> (or whatever suits you most) and return the content with any of these availablle methods.

Then though, I cannot understand why without these mods is currently working locally.

david-ao
  • 3,000
  • 5
  • 12