Adding headers to every response of an Blazor WASM in .NET 6 - http-headers

I created a simple (so far) Blazor WebAssembly application in .NET 6.
I'm currently adding additional HTTP requests to every response of the application and wanted to add an X-FRAME-OPTIONS header, but when searching on how to do it, I realized I don't know how to approach it.
For starters here's my Program.cs file:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MyApplicationNamespace;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
When reading this webpage I learned about using middleware inside
app.Use(async (context, next) =>
{
context.Response.Headers.Add("x-my-custom-header", "middleware response");
await next();
});
I do understand from this site that in order to use the Use function I can do this:
var app = builder.Build();
app.Use();
Or that I can just pass a delegate function
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
Point is, in Blazor WASM I don't have a Run method, and RunAsync does not take parameters.
I'm not sure where to go from here to add a header?
Am I missing a NuGet passage?

From what I've learned from this person on Twitter:
The Blazor WASM application is the client. It lives exclusively within the browser. It is receiving responses from a web server, and not "returning" anything. X-Frame-Options headers need to be set on the server, not by the application in the Browser.
Do you mean that the web server should add these headers when it's delivering the (static) files of your Blazor application to the browser before it starts being executed there? You need to configure your web server (whatever it is) to send these headers then.
Since I deployed my application as an Azure App Service I used Advanced Tools to edit out web.config inspired by this site.

Related

ASP.NET Core URL rewriting with query parameters

I have a LitElement based SPA with an ASP.NET Core backend which hosts the static files and a REST API for loading data into the SPA.
The user starts with /index.html and the client-side router brings him to, f.e.,
/data-analysis or /dashboard.
When the user now presses the browser refresh button she gets 404 which is to be expected since the server does not know any of these subpaths.
I read elsewhere that I have to take care of this on the server side, so I came up with this middleware in my Startup.cs Configure method:
app.Use(async (c, next) =>
{
//Identify subpaths...
c.Request.Path = "/index.html";
await next();
});
For all subpaths it brings the user back to index.html which is nice. Better yet would be to let the client side know which subpath to restore. For this I added the following lines to the code above:
var url = c.Request.Path.Value;
c.Request.QueryString = c.Request.QueryString.Add("page", $"{url.Substring(1)}");
I expected the client to see a window.location of, f.e.
.../index.html?page=data-analysis
but the query string never arrives, window.location.search is always empty on the client side.
Is this possible at all or am I misunderstanding something here?
Nicolas
Studying Microsofts documentation about the URL rewriting middleware led me to the conclusion that I should redirect and not rewrite! The following code produces the desired result. Note that the 'return' statement is critical, because no further middleware should be called!
app.Use(async (c, next) =>
{
var url = c.Request.Path.Value;
//Identify subpaths...
if(... should redirect ...)
{
c.Response.Redirect($"/index.html?page={url.Substring(1)}");
return;
}
await next();
});

Different mechanism of HttpClient in Blazor server side and Web assembly

I want to get data from ASP.net Core API with HttpClient Factory.
I use Microsoft.Extensions.Http package like this :
// Register service in IOC containter
builder.Services.AddHttpClient<IProductService, ProductService>(option =>
{
option.BaseAddress = new Uri(""/*Base url*/);
});
// Use in service
var stream = await _httpClient.GetStreamAsync("");
When I use code in Blazor server side and works correctly. But when I use code in Blazor wasm throw Exception
Access to fetch at 'http client factory base url' from origin 'blazor wasm app url' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
No change in ASP.net core API and different results.
I use .net core 3.1 in all apps
Thanks
I activate CORS in my API Like this
// In ConfigureServices method
options.AddPolicy("OpenCors", builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
;
});
// In Configure method
app.UseCors("OpenCors");

URL Rewrite exceptions for Blazor WebAssembly Hosted deployment

During development, i have used Swagger on the server side of my Blazor WebAssembly App. Always launching (debug) using kestrel instead of IIS Express.
Routing worked as expected, all my component routed properly and if i manually typed /swagger, i got to the swagger page. All good.
We have deployed under IIS on our pre-prod servers, the Server side and Blazor WebAssembly App (client) work as expected and are usable, however, my /swagger url gets rewritten (I assume) to go somewhere in my App instead of letting it go to Swagger, obviously there isn't any component that answers to /swagger.
My only guess is that, when hosted on IIS, the aspnet core app takes care of telling IIS what to rewrite and how (similar to the configs that could be provided thru a web.config for a "Standalone" deployment.)
I can't find how to specify exceptions, I've been following the doc at
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/blazor/webassembly?view=aspnetcore-3.1#iis
Any idea how i could add an exception for /swagger ?
EDIT:
Turns out it works without issues in Chrome, only Firefox has the unwanted behavior. If i clear my cache, or use Incognito mode, the issue does not happen in Firefox. So, it seems that Firefox caches some stuff and tries to send my URL input to the Blazor Wasm instead of going thru to the server. I will debug some more with the dev tools and fiddler open to try and figure it out, will report back.
Turns out there this is part of the service-worker.js file that is published. It is different in dev than what gets published (which makes sense).
During my debugging i was able to reproduce the issue on all browsers (Edge, Chrome and Firefox), regardless of being in Incognito/Private mode or not.
Once the service-worker is running, it handles serving requests from cache/index.html of the Blazor WebAssembly app.
If you go into your Blazor WebAssembly Client "wwwroot" folder, you'll find a service-worker.js and a service-worker.published.js. In the service-worker.published.js, you will find a function that looks like this :
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
// For all navigation requests, try to serve index.html from cache
// If you need some URLs to be server-rendered, edit the following check to exclude those URLs
const shouldServeIndexHtml = event.request.mode === 'navigate'
&& !event.request.url.includes('/connect/')
&& !event.request.url.includes('/Identity/');
const request = shouldServeIndexHtml ? 'index.html' : event.request;
const cache = await caches.open(cacheName);
cachedResponse = await cache.match(request);
}
return cachedResponse || fetch(event.request);
}
Simply following the instructions found in the code comments is gonna fix the issue. So we ended up adding an exclusion for "/swagger" like so :
&& !event.request.url.includes('/swagger')
Hopefully this post is useful for people who are gonna want to serve things outside of the service worker, not only Swagger.
Do you have UseSwagger first in your Startup.Configure method?
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint("/swagger/v1/swagger.json", "YourAppName V1")
);
In Startup.ConfigureServices I have the Swagger code last.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSwaggerGen(c =>
c.SwaggerDoc(
name: "v1",
info: new OpenApiInfo
{
Title = "YourAppName",
Version = "V1",
}));
}
This is working just fine for us.
Note: You must navigate to https://yourdomain/swagger/index.html

How to cache static content using ASP.NET 5 and MVC 6?

This was previously achieved by adding some configuration to the web.config file, but now this file is to be extinguished.
I was expecting to find some methods or properties in the middleware declaration, but I haven't found:
app.UseStaticFiles();
So, which is now the procedure to cache static content as images, scripts, etc.?
Is there another middleware to do this or is this feature not implemented yet in MVC 6?
I'm looking for a way to add the cache-control, expires, etc. headers to the static content.
It is all about Middleware with AspNet Core;
Add the following to your Configure method in the Startup.cs file
app.Use(async (context, next) =>
{
context.Response.Headers.Add("Content-encoding", "gzip");
context.Response.Body = new System.IO.Compression.GZipStream(context.Response.Body,
System.IO.Compression.CompressionMode.Compress);
await next();
await context.Response.Body.FlushAsync();
});
By the way for caching you would add this to the ConfigureServices method
services.AddMvc(options =>
{
options.CacheProfiles.Add("Default",
new CacheProfile()
{
Duration = 60
});
options.CacheProfiles.Add("Never",
new CacheProfile()
{
Location = ResponseCacheLocation.None,
NoStore = true
});
});
And decorate the control with
[ResponseCache(CacheProfileName = "Default")]
public class HomeController : Controller
{
...
Your title says compress, but your question body says cache. I'll assume you mean both.
Minification of css/javascript is already handled by the grunt task runner on publish. Caching and compression outside this seem like something a webserver is more suited to, rather than the application layer, so here's a great article that details the config for nginx to manage caching and compression for kestrel.
If you're using IIS, you can configure caching and compression directly on it, here's a tutorial. Considering the previous versions of MVC configured this functionality in web.config\system.Webserver which basically sets IIS config values, you can likely still use a web.config for the purposes of configuring IIS (only).

page Redirect in ASP.Net MVC + Web Api + AngularJs

I am building a ASP.Net MVC application that can work both in Web and JQuery mobile. So i am creating a seperate view for Web and JQuery mobile application. I have placed all my primary business logic services as a Web Api calls which are called by both the clients using the AngularJs which is working fine so far.
Now I was looking to introduce the security in to the application, and realized that Basic authentication is the quickest way to get going and when I looked around I found very nice posts that helped me build the same with minimal effort. Here are 3 links that I primarily used:
For the Client Side
HTTP Auth Interceptor Module : a nice way to look for 401 error and bring up the login page and after that proceed from where you left out.
Implementing basic HTTP authentication for HTTP requests in AngularJS : This is required to ensure that I am able reuse the user credentials with the subsequent requests. which is catched in the $http.
On the Server Side :
Basic Authentication with Asp.Net WebAPI
So far so good, all my WebApi calls are working as expected,
but the issue starts when I have to make calls to the MVC controllers,
if I try to [Authorize] the methods/controllers, it throws up the forms Authentication view again on MVC even though the API has already set the Authentication Header.
So I have 2 Questions:
Can We get the WebApi and MVC to share the same data in the header? in there a way in the AngularJS i can make MVC controller calls that can pass the same header information with authorization block that is set in the $http and decode it in the server side to generate my own Authentication and set the Custom.
In case the above is not possible, I was trying to make a call to a WebApi controller to redirect to a proper view which then loads the data using the bunch of WebApi calls so that user is not asked to enter the details again.
I have decorated it with the following attribute "[ActionName("MyWorkspace")] [HttpGet]"
public HttpResponseMessage GotoMyWorkspace(string data)
{
var redirectUrl = "/";
if (System.Threading.Thread.CurrentPrincipal.IsInRole("shipper"))
{
redirectUrl = "/shipper";
}
else if (System.Threading.Thread.CurrentPrincipal.IsInRole("transporter"))
{
redirectUrl = "/transporter";
}
var response = Request.CreateResponse(HttpStatusCode.MovedPermanently);
string fullyQualifiedUrl = redirectUrl;
response.Headers.Location = new Uri(fullyQualifiedUrl, UriKind.Relative);
return response;
}
and on my meny click i invoke a angular JS function
$scope.enterWorkspace = function(){
$http.get('/api/execute/Registration/MyWorkspace?data=""')
.then(
// success callback
function(response) {
console.log('redirect Route Received:', response);
},
// error callback
function(response) {
console.log('Error retrieving the Redirect path:',response);
}
);
}
i see in the chrome developer tool that it gets redirected and gets a 200 OK status but the view is not refreshed.
is there any way we can at least get this redirect to work in case its not possible to share the WebApi and MVC authentications.
EDIT
Followed Kaido's advice and found another blog that explained how to create a custom CustomBasicAuthorizeAttribute.
Now I am able to call the method on the Home controller below: decorated with '[HttpPost][CustomBasicAuthorize]'
public ActionResult MyWorkspace()
{
var redirectUrl = "/";
if (System.Threading.Thread.CurrentPrincipal.IsInRole("shipper"))
{
redirectUrl = "/shipper/";
}
else if(System.Threading.Thread.CurrentPrincipal.IsInRole("transporter"))
{
redirectUrl = "/transporter/";
}
return RedirectToLocal(redirectUrl);
}
Again, it works to an extent, i.e. to say, when the first call is made, it gets in to my method above that redirects, but when the redirected call comes back its missing the header again!
is there anything I can do to ensure the redirected call also gets the correct header set?
BTW now my menu click looks like below:
$scope.enterMyWorkspace = function(){
$http.post('/Home/MyWorkspace')
.then(
// success callback
function(response) {
console.log('redirect Route Received:', response);
},
// error callback
function(response) {
console.log('Error retrieving the Redirect path:',response);
}
);
}
this finally settles down to the following URL: http://127.0.0.1:81/Account/Login?ReturnUrl=%2fshipper%2f
Regards
Kiran
The [Authorize] attribute uses forms authentication, however it is easy to create your own
BasicAuthenticationAttribute as in your third link.
Then put [BasicAuthentication] on the MVC controllers instead of [Authorize].