Dynamically Generating Robots.txt Using ASP.NET MVC

Robots.txt

A robots.txt file is a simple text file you can place at the root of your site at http://example.com/robots.txt to tell search engine robots (also known as web crawlers) how to index your site. The robots know to look for this file at the root of every site before they start indexing the site. If you do not have this file in your site, you will be getting a lot of 404 Not Found errors in your logs.

The robots.txt uses the Robots Exclusion Standard which is a very simple format that can give robots instructions on what to index and what to skip. A very basic robots.txt file looks like this:

# Allow all robots to index this site.
user-agent: *

# Tell all robots not to index any of the pages under the /error path.
disallow: /error/

# Tell all robots to index the under the error/foo path.
allow: /error/foo/

# Add a link to the site-map. Unfortunately this must be an absolute URL.
sitemap: http://example.com/sitemap.xml

In the above code, all comments start with the hash character. It tells all robots that they can index everything on the site except pages under the /error path because we don't want our error pages showing up in peoples search results. The only exception to that rule is to allow the resources under the /error/foo path to be indexed.

The last line is interesting and tells robots where to find an XML file called a site-map. A site-map contains a list of URL's to all the pages in the site and is used to give search engines a list of URL's they can go through to index the entire site. It's a great SEO (Search Engine Optimization) technique to give your site a boost in it's search rankings.

I will discuss creating a dynamic sitemap.xml file for ASP.NET Core in a future post. For now, all you need to know is that the site-map URL has to be an absolute URL according to the specification. This is a pretty terrible decision by whoever created the robots exclusion standard. It's really annoying that when you're creating a site, you have to remember to manually update this URL. If the URL was relative we would not have this problem.

Dynamically Generating a robots.txt File

Fortunately, it's really easy to dynamically create a robots.txt file, which auto-generates the site-map URL using the MVC UrlHelper. Take a look at the code below:

public class HomeController : Controller
{
    [Route("robots.txt", Name = "GetRobotsText"), OutputCache(Duration = 86400)]
    public ContentResult RobotsText()
    {
        StringBuilder stringBuilder = new StringBuilder();
        
        stringBuilder.AppendLine("user-agent: *");
        stringBuilder.AppendLine("disallow: /error/");
        stringBuilder.AppendLine("allow: /error/foo");
        stringBuilder.Append("sitemap: ");
        stringBuilder.AppendLine(this.Url.RouteUrl("GetSitemapXml", null, this.Request.Url.Scheme).TrimEnd('/'));
        
        return this.Content(stringBuilder.ToString(), "text/plain", Encoding.UTF8);
    }
    
    [Route("sitemap.xml", Name = "GetSitemapXml"), OutputCache(Duration = 86400)]
    public ContentResult SitemapXml()
    {
        // I'll talk about this in a later blog post.
    }
}

I set up a route to the robots.txt path at the root of the site in my main HomeController and cached the response for a day for better performance (You can and should probably specify a much longer period of time if you know yours won't change).

I then go on to append my commands to the StringBuilder. The great thing is that I can easily use the UrlHelper to generate a complete absolute URL to the sitemap.xml path which is also dynamically generated in much the same way. Finally, I just return the string as plain text using the UTF-8 encoding.

Creating a route ending with a file extension is not allowed by default in ASP.NET Core. To get around this security restriction, you need to add the following to the Web.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!-- ...Omitted -->
  <system.webServer>
    <!-- ...Omitted -->
    <handlers>
      <!-- ...Omitted -->
      <add name="RobotsText" 
           path="robots.txt" 
           verb="GET" 
           type="System.Web.Handlers.TransferRequestHandler" 
           preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>

Conclusion

Dynamically generating your robots.txt file is pretty easy and only takes as many lines of code as you need to write your robots.txt file anyway. It also means that you don't need to pollute your project structure with yet another file at the root of it (This problem is fixed in MVC Core, where all static files must be added to the wwwroot folder). You can also dynamically generate your site-map URL so you don't need to remember to update it every time you change the domain.

You could argue that performance is an issue when compared to a static robots.txt text file but its a matter of a few bytes and if you cache the response with a sufficient time limit then I think that even that problem goes away.

Once again, you can find a working example of this and much more using the ASP.NET Core Boilerplate project template.

Web Mentions

What's this?

0 Replies

GitHub Comments

RehanSaeed

vlince vlince commented on 2015-08-22 07:48:34

Out of curiosity, what is the added benefit of dynamically creating a robots.txt file versus creating a static one in terms of maintenance?

I guess my real concern, is having to choose between polluting my project structure with another file versus having to redeploy the application each time I need to add, edit or delete stuff inside the robots.txt.
What's the lesser of two evils?

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2015-08-22 08:02:08

Out of curiosity, what is the added benefit of dynamically creating a robots.txt file versus creating a static one in terms of maintenance?

I guess my real concern, is having to choose between polluting my project structure with another file versus having to redeploy the application each time I need to add, edit or delete stuff inside the robots.txt.
What's the lesser of two evils?

Polluting your project structure is a real problem with MVC 5 e.g. if you want to support favicons on all the major platforms then you need to add lots of images to the root or add some meta tags. Thankfully, this problem is solved in MVC 5 with the separate wwwroot folder.

Another problem is that the site-map URL has to be absolute. You have to manually go in and edit this URL. There are just so many little things like this you have to do to get a site up quickly, that I thought it would be best if I made the computer do the work instead. You have to remember your average developer using the ASP.NET Core Boilerplate project template is not always a web expert.

I don't see robots.txt as a file you need to edit very often but you are right, you can no longer just edit the file and it means a deployment. Some developers may be less comfortable with this than others.

RehanSaeed
(edited )

Fabricio Fabricio commented on 2015-09-10 04:18:39

So, how about SitemapXml? it says "I'll talk about this in a later blog post", so...?

RehanSaeed
(edited )

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2015-09-10 20:55:24

So, how about SitemapXml? it says "I'll talk about this in a later blog post", so...?

I've been massively busy lately with a career move. I'll get on it as soon as I can :)

RehanSaeed

Asad Ali Asad Ali commented on 2016-03-28 04:08:55

I have used your sitemap generating algorithms and robotsTXT
they work both fine at localhost:3333/sitemap.xml and /robots.txt

but after publishing and uploading at hosting service - i have to access those at www.hoxat.com/sitemap.xml/
if i don't add the slash then i get HTML 404

i am using shared hosting
Waiting for your kind reply

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2016-03-28 13:57:56

I have used your sitemap generating algorithms and robotsTXT
they work both fine at localhost:3333/sitemap.xml and /robots.txt

but after publishing and uploading at hosting service - i have to access those at www.hoxat.com/sitemap.xml/
if i don't add the slash then i get HTML 404

i am using shared hosting
Waiting for your kind reply

Please raise an issue on the GitHub site listing the version of MVC you are using. The RedirectToCanonicalUrlAttribute is doing the redirecting. As long as you have the NoTrailingSlashAttribute on these actions it should be ok.

RehanSaeed

Arlan Galvez Arlan Galvez commented on 2016-06-30 20:39:29

Hi, I made all you said to make it work , but I had to put a route config rule because always the response was 404.

routes.MapRoute(
    "Robots.txt", 
    "robots.txt",
    new { controller = "Home", action = "RobotsText" },
    new[] { "VisaCuba.Controllers" });

It is mandatory to get the correct response? I'm using MVC 5.
Greetings.

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2016-07-02 11:09:46

Hi, I made all you said to make it work , but I had to put a route config rule because always the response was 404.

routes.MapRoute(
    "Robots.txt", 
    "robots.txt",
    new { controller = "Home", action = "RobotsText" },
    new[] { "VisaCuba.Controllers" });

It is mandatory to get the correct response? I'm using MVC 5.
Greetings.

Looks like you are not using attribute routing, which is why you needed to add that code.

RehanSaeed

Fabricio Gabrielli da Silva Fabricio Gabrielli da Silva commented on 2016-07-04 12:35:28

I've been massively busy lately with a career move. I'll get on it as soon as I can :)

Any update on this?

RehanSaeed

Ian Buchan Ian Buchan commented on 2017-04-05 20:19:59

Super slick trick. I'm working with AWS API Gateway with a .NET Kestrel server living in a Lambda function that's invoked via the incoming web requests. Every day or so, I would end up with a 404 for my robots.txt file. Since it was only an API, I had no file server to return the robots.txt from. Dynamically generating it solved the problem perfectly!

RehanSaeed

Wagner Melo Wagner Melo commented on 2020-05-10 00:51:14

Don't forget to turn on attribute based routing in ASP.NET MVC 5 when you register your routes in RouteConfig.
It's a simple line statement.

public static void RegisterRoutes(RouteCollection routes) {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapMvcAttributeRoutes();

    // ...
}