Quick Links

One of the many great features of frameworks like Blazor and ASP.NET (which it runs on) is the ability to serve dynamic content at whatever endpoint your application needs. If you'd like to serve downloads of generated files, it's easy to do with a little configuration.

Why Serve Dynamic Files?

Basically, you have two options as a web server: respond to a request with static content, like an HTML page or JPG file, or generate a custom response to send to the user. Blazor runs on ASP.NET, so the built-in HTTP server supports a wide array of options and allows for great flexibility.

For example, perhaps you want to host an JSON file at

        /images/pathname.json
    

. This doesn't have to be a literal file on disk; the server can interpret this request and respond with any type of content, including something unexpected like a PNG file. You could respond to this request by fetching some results from an API, constructing a response, and sending the string back to the user.

Or perhaps you'd like to generate the actual file on the fly. For example, there are many graphics libraries used for drawing custom images. You could use one of these to generate an image and respond to the user request with the data, all in memory.

In the latter case, it may make sense to cache the response by saving it on disk and replying with a real file most of the time. This can be useful for resource-intensive file generations that don't change that often.

Setting It Up

Serving files like this is built-in and pretty simple to do. You'll need to create a new Razor page, which is what Blazor runs on. You can do this by right-clicking in Visual Studio and selecting Add > Razor Page.

Doing this creates two files that are linked with each other in the hierarchy:

        Name.cshtml
    

, which handles the HTML side of things, and

        Name.cshtml.cs
    

, which handles the model and the actual code. Since this isn't going to be an actual web page, just a file, you can ignore the first one for the most part.

You will however need to set the

        @page
    

 attribute to match where you'd like this file to be hosted. You'll probably want to include some wildcards, which you do with brackets.

In the

        Name.cshtml.cs
    

 file, you'll see some actual code extending

        PageModel
    

. The main function here is

        OnGet()
    

, which you will probably want to change to OnGetAsync() if you're doing any asynchronous processing.

You have three main options from this function. First, is returning a PhysicalFile, which literally reads a file on disk given a path, and sends it to the user with a type, optionally with a separate download name than the actual path.

While you're probably here to generate something dynamically, this can be very useful for caching. If you have your processing function save the result to a file, you can check if that file exists before processing it again, and if it does, simply return the cached response.

The next option is returning a virtual file, given a byte array. This is what you'll want to use for most applications, as it operates entirely in memory and should be very fast.

Depending on what encoding you're trying to use, you may want to convert a string to a byte array using the

        Encoding
    

helper class.

Encoding.UTF8.GetBytes(string);

Lastly, you can return a content string directly, which is what you should use if you want to display the content to the user rather than triggering a download in their browser.

There are other options than these three, but the rest involve replying with status codes, redirecting, unauthorized responses, and rendering the page itself.

Serving Files Based On Routes & Parameters

Of course, none of this is useful if you can't respond to requests based on user input. The two forms of input are both in the URL: routing parameters and URL parameters. Routing parameters are what you specified using wildcards in the page itself, and are the actual path to the file. URL parameters are optional.

Figuring this out can be a bit of a pain, but luckily you have a debugger at your side, so you can set a breakpoint in OnGetAsync() and view the entire local variable tree.

You'll find, under this.PageContext.RouteData, there's a RouteValueDictionary<string, object> which stores all the routes. Note that this includes the page route itself, so if you used /Download/{param} as the route, the param will be the second option.

The best way to fetch the parameters is to look them up by key:

Similarly, the query parameters are also available, though from a different object. You'll need to access HttpContext.Request.Query, which is a QueryValueDictionary containing the URL parameters, and works in much the same way as the route one.

You are then free to use these parameters to perform lookups or affect logic. Though, if you are caching responses, you will want to make sure your cache conditions and lookups are affected by these parameters as well, or you could run into problems with unexpected caching behaviors.