ASP.NET Core hapily serves up files from the wwwroot folder based on the mime type of the file. But how do I get it serve up a file with no extension?
As an example, Apple require that you have an endpoint in your app /apple-app-site-association for some app-intergration. If you add a text file called apple-app-site-association into your wwwroot it won't work.
Some things I've tried:
1) Provide a mapping for when there's no extension:
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[""] = "text/plain";
app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = provider
});
2) Adding an app rewrite:
var options = new RewriteOptions()
.AddRewrite("^apple-app-site-association","/apple-app-site-association.txt", false)
Neither work, the only thing that does work is a .AddRedirect which I'd rather not use if possible.
Adding an alternative solution. You have to set ServeUnknownFileTypes to true and after that set the default content type.
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "text/plain"
});
Rather than fighting with static files, I think you'd be better off just creating a controller for it:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using System.IO;
namespace MyApp.Controllers {
[Route("apple-app-site-association")]
public class AppleController : Controller {
private IHostingEnvironment _hostingEnvironment;
public AppleController(IHostingEnvironment environment) {
_hostingEnvironment = environment;
}
[HttpGet]
public async Task<IActionResult> Index() {
return Content(
await File.ReadAllTextAsync(Path.Combine(_hostingEnvironment.WebRootPath, "apple-app-site-association")),
"text/plain"
);
}
}
}
This assumes your apple-app-site-association file is in your wwwroot folder.
An easier option may be to put a file with a proper extension on the server, and then use URL rewrite as follows.
app.UseRewriter(new RewriteOptions()
.AddRewrite("(.*)/apple-app-site-association", "$1/apple-app-site-association.json", true));
I think the easiest way is to add the apple-app-site-association file in a .well-known folder in the root folder of the application as described here: [https://developer.apple.com/documentation/safariservices/supporting_associated_domains] and then allow access to it from your code, like this (Startup.cs):
// to allow access to apple-app-site-association file
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, #".well-known")),
RequestPath = new PathString("/.well-known"),
DefaultContentType = "application/json",
ServeUnknownFileTypes = true,
});
Tested in AWS Serverless Application (.NET Core 3.1)
Related
Introduction
I'm configuring MIMEs for static files as usual, something like this (and that works fine, keep reading so you get to the actual question):
var defaultStaticFileProvider = new PhysicalFileProvider(Path.Combine(webHostEnvironment.ContentRootPath, "content"));
var contentTypeProvider = new FileExtensionContentTypeProvider();
var defaultStaticFilesRequestPath = "/content";
// This serves static files from the 'content' directory.
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = defaultStaticFileProvider,
ServeUnknownFileTypes = false,
RequestPath = defaultStaticFilesRequestPath,
ContentTypeProvider = contentTypeProvider
});
The previous code is mapping the .json extension to application/json by default. Works fine.
Question
What I want is to change that mapping to application/manifest+json but only for one file: manifest.json
So, I tried to add another configuration like this (not working):
// Add custom options for manifest.json only.
var manifestContentTypeProvider = new FileExtensionContentTypeProvider();
manifestContentTypeProvider.Mappings.Clear();
manifestContentTypeProvider.Mappings.Add(".json", "application/manifest+json");
var manifestStaticFileProvider = new PhysicalFileProvider(Path.Combine(webHostEnvironment.ContentRootPath, "content/en/app"));
var manifestStaticFileRequestPath = "/content/en/app/manifest.json";
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = manifestStaticFileProvider,
ServeUnknownFileTypes = false,
RequestPath = manifestStaticFileRequestPath,
ContentTypeProvider = manifestContentTypeProvider
});
Just to clarify, I have added the above code right after the previous one.
Hope the question is clear enough, I'll be checking for comments proposing editing tips to make it better anyways.
The StaticFileOptions class has an OnPrepareResponse property to which you can assign an Action in order to change the HTTP response headers.
From the documentation
Called after the status code and headers have been set, but before the body has been written. This can be used to add or change the response headers.
In that Action you check for the manifest.json file and set/change the content-type header accordingly. That action has an StaticFileResponseContext input argument with access to the HttpContext and File.
var options = new StaticFileOptions
{
OnPrepareResponse = staticFileResponseContext =>
{
var httpContext = staticFileResponseContext.Context;
// Request path check:
if (httpContext.Request.Path.Equals("/content/en/app/manifest.json", StringComparison.OrdinalIgnoreCase))
// or file name only check via:
// if (staticFileResponseContext.File.Name.Equals("manifest.json", StringComparison.OrdinalIgnoreCase))
{
httpContext.Response.ContentType = "application/manifest+json"
}
},
// Your other custom configuration
FileProvider = defaultStaticFileProvider,
ServeUnknownFileTypes = false,
RequestPath = defaultStaticFilesRequestPath
};
app.UseStaticFiles(options);
I am trying to download a .html file that I placed in the wwwroot.
This is the function:
public FileResult Download()
{
var file = Path.Combine(System.IO.Directory.GetCurrentDirectory(),"EmailTemplate","base.html");
var x = System.IO.File.Exists(file);
var result = File(file, MediaTypeNames.Text.Html);
return result;
}
I tried to check if the file exists but it returns false. The file variable shows the path to the file which is:
wwwroot\EmailTemplate\base.html
I'm unsure of what else I need to do.
EDIT:
app.UseStaticFiles(
new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "EmailTemplate")),
RequestPath = "/EmailTemplate"
}
);
This is what I added to my Startup.cs
Make sure you have app.UseStaticFiles(); in your Configure method in the Startup file.
In your controllers you can inject an IHostingEnvironment interface and use it to retrieve your wwwroot path like:
_hostingEnvironment.WebRootPath;
I want to get access to multiple folder locations from my web api to display image.
I can't change the folder locations (depending on devices on which I don't have right to modify anything).
for one of the folders I did:
string FolderToListen = Configuration["xxx:yyy"];
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(FolderToListen),
});
and now I would like to do:
string FolderToListen2= Configuration["xxx2:yyy2"];
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(FolderToListen),
FileProvider = new PhysicalFileProvider(FolderToListen2),
});
but it doesn't work.
Is there any way to pass multiple folders to UseStaticFiles ?
You can register UseStaticFolder twice:
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(FolderToListen),
});
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(FolderToListen2),
});
This answer is wonderful, localhost'ly, but when I push an .NET Core 3.1 app into Azure Web Apps it completely goes 500 on me.
Looking around the web it appears that duplicate entries are not allowed ... although this seems more like in Startup services.
I think I too would prefer a solution like,
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(FolderToListen),
FileProvider = new PhysicalFileProvider(FolderToListen2),
});
not that its possible, of course, but the duplicate entries simply don't seem to work.
I currently have a generated index.html, js and other static files living in a folder and I'm marking that folder as a static folder (by adding the following in the Configure method in Startup.cs:
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new Path.Combine(env.ContentRootPath, #"../build")),
RequestPath = new PathString("/app/")
});
Is there a way to set index.html as the default response for this */app route? Because right now localhost:5000/app/ returns a 404 while localhost:5000/app/index.html returns the index.html.
EDIT: I missed to mention that I did try using app.UseDefaultFiles() like mentioned in docs but it does not work for me. The server still returns a 404
One of the comments in that docs has clarified it:
Kieren_Johnstone
Featured May 22, 2017
The section, "Serving a default document" misses some vital
information. If you configure your UseStaticFiles to work off a
non-root RequestPath, you need to pass the same FileProvider and
RequestPath into both UseDefaultFiles and UseStaticFiles, it seems.
You can't always just call it as stated in the section.
So that means, you should write something like this to enable your specified folder to provide a default page:
app.UseDefaultFiles(new DefaultFilesOptions()
{
FileProvider = Path.Combine(env.ContentRootPath, #"../build")),
RequestPath = new PathString("/app/")
});
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = Path.Combine(env.ContentRootPath, #"../build")),
RequestPath = new PathString("/app/")
});
Use this:
public void Configure(IApplicationBuilder app)
{
// Serve my app-specific default file, if present.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);
app.UseStaticFiles();
}
For more details follow this link:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files
and go to section: "Serving a default document"
from documentation:
Setting a default home page gives site visitors a place to start when
visiting your site. In order for your Web app to serve a default page
without the user having to fully qualify the URI, call the
UseDefaultFiles extension method from Startup.Configure as follows.
public void Configure(IApplicationBuilder app)
{
app.UseDefaultFiles();
app.UseStaticFiles(); // For the wwwroot folder
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), #"build")),
RequestPath = new PathString("/app")
});
}
UseDefaultFiles must be called before UseStaticFiles to serve the
default file.
I have a custom route that reads URLs from a no-SQL database (MongoDB) and add them to the route pipeline at moment that the application starts, which is "pretty standard"
something like this (in my startup.cs file):
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
routes.Routes.Add(
new LandingPageRouter(routes, webrequest, memoryCache, Configuration));
// this custom routes adds URLs from database
}
the issue is that if I add another route to the database after the application has started I basically get a 404 since the routing system isn't aware of this new route, I think that what I need is add the missing routes at runtime or (less convenient) restart the application pool from another web application (which has been developed on framework 4.5 and obviously it runs on a different pool)
Any other thoughts on this?
thanks.
The first question is what does database mean when you say: I add another route to the database and wether you can keep your routes in a JSON, XML or INI file.
If you can, for example, keep the routes in a JSON file, then there is possible for the routes to be dynamically available on runtime (as you can read in the ASP.NET Core Documentation)
You can find a full blog post about this here.
Assuming that routes.json is a JSON file with structure similar to the following, and is in the same folder as Startup:
{
"route": "This is a route",
"another-route": "This is another route",
"default": "This is default!"
}
You can configure the Startup class in the following way:
Note that this example does not use MVC but the separate routing package, although I assume you can transpose it to work with MVC.
public class Startup
{
public IConfiguration Configuration {get;set;}
public Startup(IHostingEnvironment env)
{
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("routes.json", optional: false, reloadOnChange: true);
Configuration = configurationBuilder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
}
public void Configure(IApplicationBuilder app)
{
var routeBuilder = new RouteBuilder(app);
routeBuilder.MapGet("{route}", context =>
{
var routeMessage = Configuration.AsEnumerable()
.FirstOrDefault(r => r.Key == context.GetRouteValue("route")
.ToString())
.Value;
var defaultMessage = Configuration.AsEnumerable()
.FirstOrDefault(r => r.Key == "default")
.Value;
var response = (routeMessage != null) ? routeMessage : defaultMessage;
return context.Response.WriteAsync(response);
});
app.UseRouter(routeBuilder.Build());
}
}
At this point, while the application is running, you can modify the JSON file, save it, then because of the reloadOnChange: true parameter, the framework will reinject the new configuration into the Configuration property.
The implementation of the reload is based on a file watcher, so if you want to use a database for this - a SQL Server, then I think you have to implement this yourself.
A (not pretty at all and reminded here just for completeness) solution could be to create a console application that adds database changes in a file, then use that file in the application configuration.
Best regards!
In ASP.NET Core 3 you can use Dynamic Routing. In Startup.cs add:
app.UseEndpoints(endpoints =>
{
endpoints.MapDynamicControllerRoute<SearchValueTransformer>("{**url}");
});
And create new class SearchValueTransformer
class SearchValueTransformer : DynamicRouteValueTransformer
{
public override async ValueTask<RouteValueDictionary> TransformAsync(HttpContext httpContext, RouteValueDictionary values)
{
var url = values["url"] as string;
if (String.IsNullOrEmpty(url))
return values;
values["controller"] = "Controller";
values["action"] = "Action";
values["name"] = url;
return values;
}
}
Also in method TransformAsync you can search in your MongoDB for proper Controller, Action and Name values. More info: https://weblogs.asp.net/ricardoperes/dynamic-routing-in-asp-net-core-3