Could not find a Surface controller route in the RouteTable for controller name

Umbraco version 13.8.1

I keep getting the error message in the title when I try to trigger the action. I am trying to programmatically generate a text file and allow the front-end user to download it from their browser. While I can get the download button to render on the page, I get the error if I click it. Here is the controller:

using Microsoft.AspNetCore.Mvc;
using System.Text;

namespace ORG.Core.Controllers
{
    public class DownloadController : Controller
    {
        [HttpPost("download-event-file")]
        public IActionResult DownloadEventFile()
        {

            var textContent = "";

            textContent = textContent + "BEGIN:VCALENDAR" + "/n";
            textContent = textContent + "PRODID:CTUIR.ORG" + "/n";
            textContent = textContent + "VERSION:2.0" + "/n";
            textContent = textContent + "BEGIN:VTIMEZONE" + "/n";
            textContent = textContent + "TZID:America/Los_Angeles" + "/n";
            textContent = textContent + "BEGIN:STANDARD" + "/n";
            textContent = textContent + "DTSTART:20251102T010000" + "/n";
            textContent = textContent + "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11" + "/n";
            textContent = textContent + "TZOFFSETFROM:-0700" + "/n";
            textContent = textContent + "TZOFFSETTO:-0800" + "/n";
            textContent = textContent + "TZNAME:PST" + "/n";
            textContent = textContent + "END:STANDARD" + "/n";
            textContent = textContent + "BEGIN:DAYLIGHT" + "/n";
            textContent = textContent + "DTSTART:20250309T030000" + "/n";
            textContent = textContent + "RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3" + "/n";
            textContent = textContent + "TZOFFSETFROM:-0800" + "/n";
            textContent = textContent + "TZOFFSETTO:-0700" + "/n";
            textContent = textContent + "TZNAME:PDT" + "/n";
            textContent = textContent + "END:DAYLIGHT" + "/n";
            textContent = textContent + "END:VTIMEZONE" + "/n";
            textContent = textContent + "BEGIN:VEVENT" + "/n";
            textContent = textContent + "DTSTAMP:" + "/n";
            textContent = textContent + "STATUS:CONFIRMED" + "/n";
            textContent = textContent + "UID:CTUIRORG" + "/n";
            textContent = textContent + "SEQUENCE:0" + "/n";
            textContent = textContent + "DTSTART;TZID=America/Los_Angeles:@startDateTimeGoogle" + "/n";
            textContent = textContent + "DTEND;TZID=America/Los_Angeles:endDateTimeGoogle" + "/n";
            textContent = textContent + "SUMMARY:" + "/n";
            textContent = textContent + "DESCRIPTION:" + "/n";
            textContent = textContent + "X-ALT-DESC;FMTTYPE=text/html:" + "/n";
            textContent = textContent + "LOCATION:" + "/n";
            textContent = textContent + "TRANSP:OPAQUE" + "/n";
            textContent = textContent + "END:VEVENT" + "/n";
            textContent = textContent + "END:VCALENDAR";

            var fileName = "event-file.ics";
            var contentType = "text/plain";

            var contentBytes = Encoding.UTF8.GetBytes(textContent);
            var memoryStream = new MemoryStream(contentBytes);

            // Return the file for download
            return File(memoryStream, contentType, fileName);
        }
    }
}

And here is the code on the view page to render the button:

@using (Html.BeginUmbracoForm("DownloadEventFile", "DownloadController", FormMethod.Post))
{
    <h4>Click to download:</h4>
    <input type="submit" value="Start Download" />
}

I’ve tried many things (including inheriting from SurfaceController and adding all the required code for doing that, renaming the class to “DownloadSurfaceController”, adding the MVC option to suppress async suffix in action names according to this article “https://docs.umbraco.com/umbraco-cms/13.latest/reference/routing/surface-controllers”) and nothing has resolved the issue yet. Any help would be greatly appreciated!

Have you checked the Umbraco docs for creating forms?

The implementation mostly looks correct, but maybe you have more luck using the strongly typed implementation, like the example they provide.

Which kind of error do you get, a HTTP error, a exception form the controller or something else?

What happens when you call the controller manually using curlor with Postman?

Is the ("download-event-file")part necessary for something we can’t see?
If not then try and remove it as it could interfere with the post request url not registered to the expected name.

I would try this

@using (Html.BeginUmbracoForm("DownloadEventFile", "Download", FormMethod.Post))
{
	<h4>Click to download:</h4>
	<input type="submit" value="Start Download" />
}

(Rename your controller name to Download).

Inherit from SurfaceController instead of the default Controller

public class DownloadController : SurfaceController

Add the required Constructor

public DownloadController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider) : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
{
}

Remove the route as Umbraco would handle this

        [HttpPost]
        public IActionResult DownloadEventFile()

And that should get the dialog box to appear.

1 Like

Yes! This was the fix! Thank you so much for the helpful advice!

Ps you can remove those magic strings…

Html.BeginUmbracoForm<DownloadController >(nameof(DownloadController .DownloadEventFile), FormMethod.Post)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.