Developer forum

Forum » Dynamicweb 10 » Serving unknown extensions

Serving unknown extensions

Imar Spaanjaars Dynamicweb Employee
Imar Spaanjaars
Reply

Hi there,

In DW 9 when I needed to let the user download files with an unknown extension, I'd create a mime-type mapping in IIS.

In core, this is now done with AddStaticFiles. Where in DW can I configure which extensions to allow? I am looking for the equivalent of this where I can register .step (Auto Cad) files:

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = new FileExtensionContentTypeProvider
    {
        Mappings =
        {
            [".step"] = "application/step"
        }
    }
});

I searched the source code but can't find anything that I can use.


Replies

 
Nicolai Pedersen Dynamicweb Employee
Nicolai Pedersen
Reply

From what I can figure out, you cannot do this in DW10... I will have to ask someone smarter than me!

 
Jeppe Eriksson Agger Dynamicweb Employee
Jeppe Eriksson Agger
Reply

Hi all,

While there isn't a simple solution at the moment, you can accomplish this. You need to implement a IPipeline that has a middleware that can append the MIME type you need. I've implemented a working example. You may need to adapt it to only react to specific paths, but I'll leave that to you.

Going forward we could open up for customizations to the default application configuration, but that you include more than MIME types and it would entail a larger discussion as to the limits of those possibilities. That's why it's not been done yet when there are workarounds.

I hope this helps :)

- Jeppe

Implementation:

internal sealed class StaticFileMiddleware(RequestDelegate next)
{
    private static readonly FileExtensionContentTypeProvider ContentTypeProvider = new();
    private static readonly Dictionary<string, string> CustomMimeTypes = new()
    {
        // Custom MIME types can be added here
        [".step"] = "application/step"
    };

    // Do the work
    public async Task Invoke(HttpContext ctx)
    {
        var path = FilePathHelper.GetAbsolutePath(ctx.Request.Path);
        if (ShouldHandleFile(path))
        {
            ctx.Response.ContentType = GetMimeType(path);

            await ctx.Response.SendFileAsync(path);
            return;
        }

        await next(ctx);
    }

    // Custom logic to determine if the file should be handled
    private static bool ShouldHandleFile(string path) => File.Exists(path);

    // Calculate MIME type with custom types support
    private static string GetMimeType(string filePath)
    {
        if (ContentTypeProvider.TryGetContentType(filePath, out var mimeType))
            return mimeType;

        var extension = Path.GetExtension(filePath);
        if (CustomMimeTypes.TryGetValue(extension, out mimeType))
            return mimeType;

        return "text/plain";
    }
}

 
Imar Spaanjaars Dynamicweb Employee
Imar Spaanjaars
Reply

Thanks Jeppe. I ended up with this which has two  functional changes compared to your code:

1. Only consider .step files (or any other added to CustomMimeTypes)
2. Force download, rather than opening the file in the browser using Content-Disposition

For anyone else implementing this, here's the code. Note also the use of HttpUtility.UrlDecode to get a path without URL encoded values like %20. You'll need to register this middleware using an IPipeline.

 public async Task Invoke(HttpContext ctx)
 {
   try
   {
     var path = FilePathHelper.GetAbsolutePath(HttpUtility.UrlDecode(ctx.Request.Path));
     if (ShouldHandleFile(path))
     {
       ctx.Response.Headers["Content-Disposition"] = $"attachment; filename=\"{Path.GetFileName(path)}\"";
       await ctx.Response.SendFileAsync(path);
       return;
     }
   }
   catch (Exception ex)
   {
     Logger.Error($"Error handling static file: {ctx.Request.Path}", ex);
   }
   await next(ctx);
 }
  /// <summary>
  /// Returns true when the file has one of the custom extensions and exists.
  /// </summary>
  /// <param name="path">The path to test.</param>
  private static bool ShouldHandleFile(string path)
  {
    var extension = Path.GetExtension(path);
    return CustomMimeTypes.Any(x => x.Key.Equals(extension, StringComparison.OrdinalIgnoreCase)) && File.Exists(path);
  }

Is there any configuration mechanism available during the IPipeline process that I can use to register my supported extensions so I don't have to hardcode them? Or is it too early for that?

Imar

 

 
Cecilia Sørensen Vejle
Cecilia Sørensen Vejle
Reply

Hej,

Følger lige lidt med her, da jeg også skal have opsat noget mimetype for en kunde, men er lidt i tvivl om, hvordan den skal registers i en IPipeline.

Har i evt. lyst til, at dele et eks.? :)

 
Imar Spaanjaars Dynamicweb Employee
Imar Spaanjaars
Reply

Hi Cecilia,

FYI: English is the preferred language on this forum :-)

That said, here's what is needed to get this working:

1. In a Class Library targeting .NET 8 and referencing DW 10, create a folder called Middleware.

2. Add to that folder the following class:

using Dynamicweb.Host.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Your.Company.Middleware;

public sealed class RegisterCustomMiddleware : IPipeline
{
  public void RegisterServices(IServiceCollection services, IMvcCoreBuilder mvcBuilder)
  {
    
  }
  public void RegisterApplicationComponents(IApplicationBuilder app)
  {
    app.UseStaticFilesForUnknownExtensions();
  }
  public void RunInitializers()
  {

  }
  public int Rank => 200; // High number so we go after DW built-in stuff
}

This is the IPipeline implementation to register your own stuff. DW will find the class itself (because it implements IPipeline) and then calls methods like RegisterApplicationComponents where you can register your own middleware. In my case, I call UseStaticFilesForUnknownExtensions which is defined in another class you need to add to the middleware folder:

3. Add a class called StaticFileUnknownExtensionsMiddlewareExtensions with the following code:

using Microsoft.AspNetCore.Builder;

namespace Your.Company.Middleware;

internal static class StaticFileUnknownExtensionsMiddlewareExtensions
{
  public static IApplicationBuilder UseStaticFilesForUnknownExtensions(this IApplicationBuilder app)
  {
    return app.UseWhen(
    ctx =>
    {
      var path = ctx.Request.Path.Value;
      if (string.IsNullOrEmpty(path))
        return false;

      var ext = Path.GetExtension(path);
      return StaticFileUnknownExtensionsMiddleware.CustomMimeTypes.ContainsKey(ext);
    },
    branch => branch.UseMiddleware<StaticFileUnknownExtensionsMiddleware>()
    );
  }
}

This sets up the middleware onditionally. When the current request ends a with a file extension defined in StaticFileUnknownExtensionsMiddleware.CustomMimeTypes, it uses the middleware StaticFileUnknownExtensionsMiddleware which is the final class you need to add:

4. Add StaticFileUnknownExtensionsMiddleware with the following code:

using Dynamicweb.Core.Helpers;
using Microsoft.AspNetCore.Http;
using System.Web;

namespace Your.Company.Middleware;

internal sealed class StaticFileUnknownExtensionsMiddleware(RequestDelegate next)
{
  internal static readonly Dictionary<string, string> CustomMimeTypes = new(StringComparer.InvariantCultureIgnoreCase)
  {
    // Custom MIME types can be added here 
    [".step"] = "application/step",
    [".stp"] = "application/step"
  };

  public async Task Invoke(HttpContext ctx)
  {
    try
    {
      var path = FilePathHelper.GetAbsolutePath(HttpUtility.UrlDecode(ctx.Request.Path));
      ctx.Response.Headers.ContentDisposition = $"attachment; filename=\"{Path.GetFileName(path)}\""; 
      await ctx.Response.SendFileAsync(path);
      return;
    }
    catch (Exception ex)
    {
      // When there's a failure, ignore and let DW handle the request.
      // Todo: Log error
    }
    await next(ctx);
  }
}

This is the actual implementation that serves the physical file from disk using SendFileAsync. In CustomMimeTypes  you register the custom extensions you like to suport. The code also sets the proper mime type which may help the browser to know what to do with it.

Hope this helps,

Imar

 

 

You must be logged in to post in the forum