I am trying to host an ASP.NET 5.0 (beta 4) website on Ubuntu. I have configured Kestrel with nginx as a reverse proxy, but there are a couple of problems that prevent this from being used for a production site:
HTTP 404 error pages are blank - is there a way to configure either ASP.NET/Kestrel or nginx to send a custom page instead of a blank page?
How can I configure URL rewriting - for example, I have some static .htm pages in addition to the ASP.NET content, and I would like to rewrite these to serve them without the .htm extension
Thanks to the suggestion from Matt DeKrey, I got this working using two pieces of middleware.
For custom 404 error pages, I used:
public class CustomErrorMiddleware
{
private readonly RequestDelegate next;
public CustomErrorMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.StatusCode = 404;
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync("/errors/404.html");
}
}
While for URL rewriting I used:
public class UrlRewriteMiddleware
{
private readonly RequestDelegate next;
public UrlRewriteMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
// Redirect from /some/page.htm to /some/page
Regex r1 = new Regex("^/some/[a-zA-Z0-9]+\\.htm$");
if (r1.IsMatch(context.Request.Path.Value))
{
context.Response.Redirect(context.Request.Path.Value.Substring(0, context.Request.Path.Value.Length - 4));
return;
}
// Rewrite from /some/page to /some/page.htm
Regex r2 = new Regex("^/some/[a-zA-Z0-9]+$");
if (r2.IsMatch(context.Request.Path.Value))
context.Request.Path = new PathString(context.Request.Path.Value + ".htm");
await next(context);
}
}
Then Startup.cs is modified to use each of these. Middleware is run in the order it is specified in, so the URL rewriting needs to be first to modify the requests as they are received. The custom 404 error middleware needs to be last to catch any requests not handled by any other middleware. For example:
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<UrlRewriteMiddleware>();
app.UseStaticFiles();
app.UseMvc();
app.UseMiddleware<CustomErrorMiddleware>();
}
Related
Previously in ASP.Net I would have written a Generic Http Handler to do this but it looks like in ASP.Net Core you write Middleware.
I am trying to get my Middleware to be called when the page GetUser is requested.
I've tried to understand how from this page https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0 - but have only got so far.
The Middleware code is as follows;
public class GetUser
{
public GetUser(RequestDelegate next)
{
// note this is a handler so no need to store the next
}
public async Task Invoke(HttpContext context)
{
String response = String.Empty;
try
{
response = GenerateResponse(context);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = 200;
await context.Response.WriteAsync(response);
}
catch ( System.Exception ex)
{
response = ex.Message;
}
}
public String GenerateResponse(HttpContext context)
{
String response = "";
response = "a response";
return response;
}
}
public static class GetUserExtension
{
public static IApplicationBuilder UseGetUser(this IApplicationBuilder builder)
{
return builder.UseMiddleware<GetUser>();
}
}
In Program.cs I have added the following line;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
app.Map("/GetUser",GetUserExtension.UseGetUser(builder));
Which fails because builder is a WebApplicationBuilder and not IApplicationBuilder.
So how can I use app.Map so that it calls GetUserExtension.UseGetUser when the GetUser Page is requested.
Make sure your middleware is correctly placed in the program.cs file, which when it is called contains all the resources that it needs to return the request.
This is not much but hopes this helps.
I want to log every SQL request in a request to the database with a middleware. so far I have only managed to create an empty middleware that is hit by every request but I have no idea how to get the SQL queries from that request.
I have read a few articles (1) about how to log requests and responses with middleware but they are getting the whole request (the whole HTML page) and I only want the SQL queries. so how can I get those queries?
public class LoggingEveryRequestToLogDataBase
{
private readonly RequestDelegate _next;
public LoggingEveryRequestToLogDataBase(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, ILogDbContext dbContext)
{
var request = new
{
ipAddress = context.Connection.RemoteIpAddress.ToString(),
path = context.Request.Host + context.Request.Path + context.Request.QueryString,
};
await _next(context);
}
}
public static class LoggingEveryRequestToLogDataBaseMiddleware
{
public static IApplicationBuilder LoggingEveryRequestToLogDataBase(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<LoggingEveryRequestToLogDataBase>();
}
}
I have the following code:
public class CookieCheckMiddleware
{
private readonly RequestDelegate _next;
public CookieCheckMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if(httpContext.Request.Cookies["MyCookie"] == null && httpContext.Request.Path != "/WhereIShouldGo")
{
httpContext.Response.Redirect("/WhereIShouldGo");
}
await _next(httpContext); // calling next middleware
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CookieCheckMiddlewareExtensions
{
public static IApplicationBuilder UseCookieCheckMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CookieCheckMiddleware>();
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseCookieCheckMiddleware();
...
}
It basically redirects to the captive portal if no cookie is set. Now I need to up a level - I need to somehow save the httpContext.Request.Path and forward to it RIGHT AFTER the user accepted the cookies. So setting a cookie beforehand is not an option, since the user hasn't accepted it yet... How could I accomplish that?
The solution was giving the request URL via the redirect like this:
httpContext.Response.Redirect("/Cookies?q="+ httpContext.Request.Path);
and then via JavaScript getting the GET parameter and redirecting after clicking on the button.
Case closed :-)
For my ASP.NET MVC application I'm using ADFS authentication. It is set up in the following manner
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
MetadataAddress = ConfigurationManager.AppSettings.Get("MetadataAddress"),
Wtrealm = ConfigurationManager.AppSettings.Get("Realm"),
});
Due to something beyond my control, occasionally the metadata at the MetadataAddress is unreachable. In situations like that, I would like to redirect users to a custom view rather than the default error view. How would one accomplish this?
What I've ended up doing is creating Owin Middleware that captures the error that is thrown by invalid metadata that looks like the following and redirects the user to a route describing the issue:
public class LoggingMiddleware : OwinMiddleware
{
public LoggingMiddleware(OwinMiddleware next)
: base(next)
{
}
public async override Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
} catch(Exception e)
{
Logger.Error($"Encountered error in authenticating {e.ToString()}");
if(e.Source.Equals("Microsoft.IdentityModel.Protocols"))
{
context.Response.Redirect("/Home/OwinError");
} else
{
throw e;
}
}
}
}
The middleware can simply be added in the startup.cs file with the following line:
app.Use(typeof(LoggingMiddleware));
I am learning ASP.NET Core and for that I am trying to implement a simple proxy server.
Setup idea:
I will set the proxy settings of my browser to point to my server (currently localhost). The server will duplicate the http request and send to the intended server and receive the request. Then the original response header will be edited to match the received header from the actual request and sent to the client.
example: I type http://bing.com in my browser --> the proxy settings will take this request to my server --> I will create a http request called tempRequest with values from the original request --> the tempRequest is sent and a tempResponse is received from http://bing.com --> I will edit the original Http response to match tempResponse --> send the modified response to the browser.
I am trying to achieve this using middleware.
So far, I think I am successfully getting the response back from the original server (http://bing.com). I am having some trouble sending this received response to the browser.
Startup class:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseProxyServer();
}
}
Middleware implementation:
public static class ProxyMiddlewareExtensions
{
public static IApplicationBuilder UseProxyServer(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyMiddleware>();
}
}
public class ProxyMiddleware
{
private readonly RequestDelegate _next;
public ProxyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalRequest = context.Request;
var absoluteUri = string.Concat(
originalRequest.Scheme,
"://",
originalRequest.Host.ToUriComponent(),
originalRequest.PathBase.ToUriComponent(),
originalRequest.Path.ToUriComponent(),
originalRequest.QueryString.ToUriComponent());
if(!absoluteUri.Contains("localhost"))
{
HttpWebRequest newRequest = WebRequest.CreateHttp(absoluteUri);
//Make a copy of the original request
CopyRequestTo(context.Request, newRequest);
//Get a response using the copied http request.
var response = (HttpWebResponse)await newRequest.GetResponseAsync();
//Modify the response going back to the browser to match the response
//received from the intended server.
CopyResponseTo(response, context);
}
await this._next(context);
}
private void CopyResponseTo(HttpWebResponse source, HttpContext destination)
{
destination.Response.OnStarting(async() =>
{
await source.GetResponseStream().CopyToAsync(destination.Response.Body);
foreach (string headerKey in source.Headers.AllKeys)
{
//============Exception Here=========
destination.Response.Headers[headerKey] = source.Headers.Get(headerKey);
}
destination.Response.StatusCode = (int)source.StatusCode;
});
}
public static void CopyRequestTo(HttpRequest source, HttpWebRequest destination)
{
destination.Method = source.Method;
foreach (var headerKey in source.Headers.Keys)
{
destination.Headers[headerKey] = source.Headers[headerKey];
}
destination.ContentType = source.ContentType;
if (source.Method != "GET" && source.Method != "HEAD" && source.ContentLength > 0)
{
var destinationStream = destination.GetRequestStream();
source.Body.CopyTo(destinationStream);
destinationStream.Close();
}
}
}
But while changing the response I get this exception:
Headers are read only, response has already started
Why this exception even when I am using Response.OnStarting ?
I am running the application from console ( not using IIS )