Check if request is made to Razor Page - asp.net-core

How can I check within middleware code if current request is made to Razor Page not to any other resource (static file, or API)?
All my APIs are located within api folder, so if (!context.Request.Path.StartsWithSegments("/api")) {} filters out APIs, but that will not work for static content as these files and libraries are placed within number of folders what results in number of URL segments.
Could not find any relevant property in context.

First step - place the middleware after app.UseRouting(). Any request for a static file will never reach your middleware because the static files middleware will short circuit the request. Also, after this point, the routing middleware will have selected the endpoint and populated the endpoint metadata. Then you can test the endpoint metadata collection to see if it includes PageRouteMetaData, which tells you that this is a Razor page route:
app.Use((context, next) => {
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
foreach(var md in endpoint.Metadata)
{
if( md is PageRouteMetadata)
{
// this is a page route
}
}
}
return next(context);
});

Related

ASP.NET Core Endpoint - route all calls to specific route first

What do I need to change this to route all requests to /api/ShibAuth?
endpoints.MapGet("/", async context =>
{
context.Response.Redirect("/api/ShibAuth");
});
The code above obviously routes any calls to root URL and I've already tried what I though was appropriate wildcard.
What do I need to change this to route all requests to /api/ShibAuth?
Well, if I correctly understand the requirement, you would like all of your request to redirect to this /api/ShibAuth route at the begining.
Certainly, we can implement above scenario using UriBuilder class which provides the functionality to modify HttpRequest.Path. Finally, rebuild the request URI and redirect to your expected path. You can do as following
Solution:
app.MapGet("/", async context =>
{
var originalUrl = context.Request.GetDisplayUrl();
var routeToCallFirst = "api/ShibAuth";
var updatedUrl = (new UriBuilder(originalUrl) { Host = context.Request.Host.Host, Path = routeToCallFirst }).Uri;
context.Response.Redirect(updatedUrl.AbsoluteUri);
});
Output:
Note: Here, I am redirecting the all landing request to /api/ShibAuth controller from the middleware.

Passing Controller Action output as SupplyData in UseSpaPrerendering of .Net Core

In .Net Core application, I have below code in Configure method of Startup.cs file.
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.UseSpaPrerendering(options =>
{
options.BootModulePath = $"{spa.Options.SourcePath}/dist-server/main.js";
options.BootModuleBuilder = env.IsDevelopment() ? new AngularCliBuilder(npmScript: "build:ssr") : null;
options.ExcludeUrls = new[] { "/sockjs-node" };
});
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
UseSpaPrerendering has an option to provide SupplyData callback which lets you pass arbitrary, per-request, JSON-serializable data.
In my case there are pages in my Angular application which makes http requests to fetch data. Since these requests are made to the same application. I see a potential of optimization i.e. if we could just call the corresponding Controller Action method and supply its data to Angular, so that we dont have to make an http request for SSR.
Can anyone please guide how to achieve this.
I know that below is how we pass data using SupplyData
options.SupplyData = (context, data) =>
{
// Creates a new value called isHttpsRequest that's passed to TypeScript code
data["isHttpsRequest"] = context.Request.IsHttps;
};
But how to we pass the results/output of a Controller Actions (which returns json).
I wrote a package to determine the currently activated SPA route from the supplydata delegate.
https://github.com/MusicDemons/AspNetSpaPrerendering
You have to define all your SPA routes using the SpaRouteBuilder and then you can check which route was activated and get the route data (like an id). Based on that you get data from your database through your repositories and add this data to the array. A complete example is included.

.NET Core serving HTML file outside wwwroot can't load JS files

I am experimenting with different ways to secure an Angular CLI app with .NET Core Authorization.
To make it as secure as possible, I would like to keep all of the Angular CLI output files from being publicly available and keep them in the default "dist" folder preconfigured by the CLI.
I can load the index.html from an authorized controller by returning a PhysicalFileResult...
public IActionResult Index()
{
return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "dist", "index.html"),"text/HTML");
}
But I get 404s on all of the bundle.js files when the page loads.
Is it possible to serve the app this way without involving the static file middleware or making the files publicly available (preferably without having to manually change the src for each bundled js file in index.html)?
Take a look at this article from the asp.net core docs (excerpt included below): https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files#static-file-authorization
Just place your authorization middleware before the static one.
// has to be first so user gets authenticated before the static middleware is called
app.UseIdentity();
app.Use(async (context, next) =>
{
// for pathes which begin with "app" check if user is logged in
if(context.Request.Path.StartsWith("app") && httpContext.User==null)
{
// return "Unauthorized"
context.Response.StatusCode = 401;
return;
}
// If user is logged in, call next middleware
await next.Invoke();
});
app.UseStaticFiles();

Filter for static file middleware

Is there a way to intercept the request before it got serve, so I can edit a picture or create dynamic pdf on demand? I tried using MapArea and redirect the request to a controller, but when I use staticfiles middleware, it catch the request, and my controller wont handle the request.
If your static files does not exist and you want generate them on-thy-fly - it's better to create your own middelware and register it before UseStaticFiles.
If files exist, but you want "slightly" modify response (for different users for example) - you may use OnPrepareResponse handler in static file options:
var staticFileOptions = new StaticFileOptions
{
OnPrepareResponse = (context) =>
{
var fn = context.File.Name.ToLowerInvariant();
if (fn.EndsWith(".pdf"))
{
SomeService.LogPdfDownload(context.Context.Response);
}
else
{
context.Context.Response.Headers.Add("Cache-Control", "public, max-age=15552000"); // 180 days
}
}
};
app.UseStaticFiles(staticFileOptions);
From docs: OnPrepareResponse is called after the status code and headers have been set, but before the body has been written
Is there a way to intercept the request before it got serve
Yes. You can write your own middleware and add it to IApplicationBuilder before you call UseStaticFiles. See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware#ordering
See also https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing. You may also be able to solve this problem by writing routes instead of middleware.

URL rewrite in ASP.NET 4.5 and Web API

We've got a long-running ASP.NET web-forms application that was born in the .NET 1.1/IIS6 days. We're now on .NET4.5/IIS7 but we've done nothing with MVC.
We provide a catalog to customers and give them a URL they can use:
www.ourhost.com/customername
Using a custom IHttpModule we developed we pull 'customername' out of the URL to find the customer in the database. That customer's ID is then stored in the page's context* and used by virtually all the pages on the site to customize content for that customer. After this process, the above URL would be rewritten and processed as
www.ourhost.com/index.aspx
with index.aspx having access to the customer's ID via its context and it can do its thing.
This works great and we support several thousand customers with it. the rewriting logic is fairly complex because it validates customer accounts, redirects to a 'uh oh' page if the customer is invalid and to a different 'find a dealer' page if the customer has not paid, etc. etc.
Now I'd like to build some Web API controllers and MVC-style rewriting has me worried. I see many examples where rewriting happens to make URL's like this work:
www.ourhost.com/api/{controller}
but I still need these web api 'calls' to happen in the context of a customer. Our pages are getting more sophisticated with JSON/AJAX async calls but in answering those calls I still need customer context. I would like the URL's to be
www.ourhost.com/customername/api/{controller}
But I am stumped as to how to configure routing to do this and have it play nicely with our IHttpModule.
Is this even possible?
*UPDATE: When I say 'stored in the page context' I mean the HttpContext associated with each web request that includes a dictionary where I can store some page/request-specific data.
There are two parts of the answer to your issue that I can see.
Maintaining the User Info across multiple requests
Generally an MVC API application will be stateless, that is you do not retain the current users session state between requests. Well that is what I have learned or been preached many times when writing RESTFul APIs.
That been said, you can enable session state in MVC Web API by adding the following to your global.asax.cs
protected void Application_PostAuthorizeRequest()
{
// To enable session state in the WebAPI.
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Authorising A Customer in the Request
As you have shown in the Request URL you could add the customer name, then capture that and pass it to the same routine that your current http module calls to authorise on request. You could do this with an MVC Filter.
First do a similar URL Pattern to capture your customers name in the WebApiConfig.cs, something like so;
config.Routes.MapHttpRoute(
name: "WithCustomerApi",
routeTemplate: "api/{customername}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then add an ActionFilter to your API Controller which processes each request, checks current session info and if needed calls your authorise/customer lookup code and then saves to session state for later use. Or if no good info from customer can send to a new MVC route
So you will add an attribute something like so;
[WebApiAuthentication]
public class BaseApiController : ApiController
{
}
Then create an action filter that might look like this (note I have not tested this, just done for a pattern of how to).
public class WebApiAuthenticationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var routeData = actionContext.ControllerContext.Request.GetRouteData();
var currentContext = HttpContext.Current;
if (routeData.Route.RouteTemplate.Contains("customername"))
{
try
{
var authenticated = currentContext.Request.IsAuthenticated;
if (!authenticated)
{
var customer = routeData.Values["customername"];
// do something with customer here and then put into session or cache
currentContext.Session.Add("CustomerName", customer);
}
}
catch (Exception exception)
{
var error = exception.Message;
// We dont like the request
actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
else
{
// No customer name specified, send bad request, not found, what have you ... you *could* potentially redirect but we are in API so it probably a service request rather than a user
actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
}
If you create a new MVC 5 Web API Application and add in these extras and put the filter on the default values controller like so you should be able to see this running as demo of a possible solution.
This will echo the customer name back if all works ok.
[WebApiAuthentication]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var session = HttpContext.Current.Session;
if (session != null)
{
return new string[] {"session is present", "customer is", session["CustomerName"].ToString()};
}
return new string[] { "value1", "value2" };
}
}
I offer this up as a possible solution as I say, there are religious arguments about storing session and authorising in an API but those are not the question.
Hope that helps,
Steve