ABP Framework - how to intercept all the requests - c#

I am new to this framework and i have to intercept all the requests. Is there a way to use AbpModule or ApplicationService to check all the requests?
Thanks.

You can do this in the "OnApplicationInitialization" method of your module by using this code.
// All requests blocked by custom code
app.Use( (context, next) =>
{
// custom code
var request = context.Request;
var #continue = false;
if(#continue)
{
return next();
}
else
{
return Task.CompletedTask;
}
});

Related

Adding Middleware to Pipe in ASP.Net Core

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.

Return custom status code on authentication failure .Net Core

I have an custom authentication scheme in .Net core where I want to return a specific response for some specific kinds of authentication failures (as opposed to just returning a 401). My current approach looks something like this:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = GetToken();
if (string.IsNullOrWhiteSpace(token))
{
return AuthenticateResult.NoResult();
}
var validationResult = _tokenContextProvider.Validate(); // validate
if (validationResult != TokenValidationResult.Ok)
{
await updateResponseCode(Context, validationResult);
return AuthenticateResult.Fail("Token invalid");
}
//success
var userContext = tokenContextProvider.GetUserContext();
var ticket = GetAuthenticationTicket(userContext);
return AuthenticateResult.Success(ticket);
}
private static async Task updateResponseCode(HttpContext context, TokenValidationResult validationResult)
{
if (!context.Response.HasStarted)
{
if (validationResult == TokenValidationResult.SpecialError)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
var errorMessage = JsonConvert.SerializeObject(new
{
error = new
{
message = validationResult.Message
}
});
context.Response.Headers["Content-Type"] = "application/json";
await context.Response.WriteAsync(errorMessage, Encoding.UTF8);
}
}
}
This is fine and returns the response code I want. However, another bit of middleware (I'm assuming) is then trying to set the StatusCode after and resulting in some exceptions being logged.
Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext in ThrowResponseAlreadyStartedException
System.InvalidOperationException: StatusCode cannot be set because the response has already started.
I can just "ignore" the exception but I would rather find some way to do this that is supported a bit more cleanly so I don't clutter the logs with these exceptions.
Is there some other way to change the response returned after a failed authentication?
In case someone runs into a similar problem, I essentially took a different approach. Instead of attempting to force the authentication handler to do something it wasn't built to do, I pulled the StatusCode setting logic into its own middleware.
During the authentication process, when the event I care about happens, I add an item to the HttpContext like this
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = GetToken();
...
if (validationResult != TokenValidationResult.Ok)
{
Context.Items.Add("TokenValidationResult", validationResult);
return Task.FromResult(AuthenticateResult.Fail("Token invalid"));
}
The middleware then has a chance to run after the other handlers run
public async Task Invoke(HttpContext httpContext)
{
await _next(httpContext);
// context Item ready here
var tokenValidationResult = getTokenValidationResult(httpContext);
if (tokenValidationResult != null && tokenValidationResult != TokenValidationResult.Ok)
{
await updateResponseCode(httpContext, tokenValidationResult);
}
}
private static async Task updateResponseCode(HttpContext context, TokenValidationResult validationResult)
{
if (!context.Response.HasStarted)
{
if (validationResult == TokenValidationResult.SpecialError)
{
await setErrorMessageAndCode(context, HttpStatusCode.Gone, validationResult);
}
else
{
await setErrorMessageAndCode(context, HttpStatusCode.Forbidden, validationResult);
}
}
}
The check for !context.Response.HasStarted is still required, but now I can update the status code dynamically without having downstream impact. I registered the middleware to run before the authentication one (so it runs after on the way back out)
app.UseCustomJwtStatusCodes();
app.UseAuthentication();
app.UseAuthorization();
That did the trick for me and allows me to set the error code without interfering with any other middleware.

.Net Core: Return IActionResult from a custom Exception Middleware

I have created a new Exception middleware in my .Net Core application. All the exceptions throughout the application are captured and logged here. What I want is to return a IActionResult type like InternalServerError() or NotFound() from the Exception Middleware and not do response.WriteAsync as below.
Controller Method:
public async Task<IActionResult> Post()
{
//Do Something
return Ok();
}
Middleware:
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var response = context.Response;
var statusCode = (int)HttpStatusCode.InternalServerError;
var message = exception.Message;
var description = exception.Message;
response.ContentType = "application/json";
response.StatusCode = statusCode;
await response.WriteAsync(JsonConvert.SerializeObject(new ErrorResponse
{
Message = message,
Description = description
}));
}
}
IActionResult is a thing from MVC, so it is only available within the MVC pipeline (including Razor Pages). Just before the MVC middleware terminates, it will execute those action results using ExecuteResultAsync. That method is then responsible of writing that response to HttpContext.Response.
So in custom middleware, you cannot just set an action result and have it executed, since you are not running within the MVC pipeline. However, with that knowledge, you can simply execute the result yourself.
Let’s say you want to execute a NotFoundResult which is what Controller.NotFound() creates. So you create that result and call ExecuteResultAsync with an . That executor will be able to execute that result object and write to the response:
var result = new NotFoundResult();
await result.ExecuteResultAsync(new ActionContext
{
HttpContext = context
});
That's not really possible due to where IActionResult and middleware sit in relation to one another in the architecture. Middleware sits much lower, and so it can't reach further up the stack to IActionResult. Here's an answer that talks more about it: https://stackoverflow.com/a/43111292/12431728
What you're trying to do can be done by simply adding this line:
app.UseStatusCodePagesWithReExecute("/Public/Error", "?statusCode={0}");
to the Configure method in the Startup.cs. Then you can create your Public Controller with Error method that does the following:
[AllowAnonymous]
public IActionResult Error(int? statusCode = null)
{
// Retrieve error information in case of internal errors.
var error = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
var path = HttpContext.Features.Get<IExceptionHandlerPathFeature>()?.Path;
// TODO: Redirect here based on your status code or perhaps just render different views for different status codes.
}
There is also another middleware that allows you to do a similar thing:
app.UseStatusCodePages(async context =>
{
if (context.HttpContext.Response.StatusCode == 401)
{
context.HttpContext.Response.Redirect("Errors/Unauthorized/");
}
else if (context.HttpContext.Response.StatusCode == 500)
{
// TODO: Redirect for 500 and so on...
}
});

Breakpoint not getting hit in AuthorizationFilterAttribute

I have this attribute:
public class ValidateCertAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext context)
{
// Read certificate from the HTTP Request and
// check its various attributes against known
// values from a config file.
if (true) // certificate is invalid
{
context.Response = new HttpResponseMessage(HttpStatusCode.Forbidden)
{
ReasonPhrase = "Invalid certificate"
};
}
else
{
base.OnAuthorization(context);
}
}
}
and this action:
[HttpGet]
[Route("TestAuth")]
[ValidateCert]
public HttpResponseMessage TestAuth()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
ReasonPhrase = "In Test method without any authorization."
};
}
This action is inside a .NET Core Web Api controller:
[ApiController]
public class TestAuthController : ControllerBase
On the other hand, Startup.cs contains:
app.UseMvc();
so that seems odd, since this should just be a Web Api controlller, and not an MVC web application. Apparently, though, that's just for routing, but I thought it was worth mentioning.
I've set a breakpoint on the first line of the attribute code, but it's not getting hit. I believe it should get hit before the action executes and that the action should never execute because I'm setting the Response inside the attribute. Why isn't the attribute executing?
This can be accomplished using policy-based authorization.
The idea essentially is that you have a requirement that needs to be met (valid certificate), a handler for the requirement (how to validate the certificate) and a policy that enforces this requirement and is applied during authorization.
If your code is simple enough, you can just provide a Func<AuthorizationHandlerContext, bool> to the policy that is applied for evaluation. Here is how to setup the policy (in Startup.cs, ConfigureServices()):
services.AddAuthorization(options =>
{
options.AddPolicy("ValidateCertificate", policy =>
policy.RequireAssertion(context =>
{
var filterContext = (AuthorizationFilterContext)context.Resource;
var Response = filterContext.HttpContext.Response;
var message = Encoding.UTF8.GetBytes("Invalid certificate");
Response.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await Response.Body.WriteAsync(message, 0, message.Length);
});
return false;
}));
});
This will do what you want.
Now, if you want to go the more structured route, you can implement each piece:
First, create your requirement (more like a marker for reference, really):
public class ValidCertificateRequirement : IAuthorizationRequirement
{
}
Then setup the policy that needs to be applied (Startup.cs, ConfigureServices()):
services.AddAuthorization(options =>
{
options.AddPolicy("ValidateCertificate", policy =>
{
policy.Requirements.Add(new ValidCertificateRequirement());
});
});
Now you need to create your requirement handler:
public class ValidCertificateHandler : AuthorizationHandler<ValidCertificateRequirement>
{
public ValidCertificateHandler()
{
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidCertificateRequirement requirement)
{
var filterContext = (AuthorizationFilterContext)context.Resource;
var Response = filterContext.HttpContext.Response;
var message = Encoding.UTF8.GetBytes("Invalid certificate");
Response.OnStarting(async () =>
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await Response.Body.WriteAsync(message, 0, message.Length);
});
context.Fail();
return Task.CompletedTask;
}
}
The handler then needs to be registered on startup (in ConfigureServices()):
//Register handler
services.AddSingleton<IAuthorizationHandler, ValidCertificateHandler>();
Lastly, for either approach (assertion or implementation), apply the Authorize attribute to your actions, indicating the policy to apply:
[Authorize(Policy = "ValidateCertificate")]
public HttpResponseMessage TestAuth()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
ReasonPhrase = "In Test method without any authorization."
};
}
You can read more about it here:
Policy-based authorization in ASP.NET Core

How to add custom header to ASP.NET Core Web API response

I am porting my API from Web API 2 to ASP.NET Core Web API. I used to be able to add a custom header in the following manner:
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Headers.Add("X-Total-Count", count.ToString());
return ResponseMessage(response);
How does one add a custom header in ASP.NET Core Web API?
You can just hi-jack the HttpContext from the incoming Http Request and add your own custom headers to the Response object before calling return.
If you want your custom header to persist and be added in all API requests across multiple controllers, you should then consider making a Middleware component that does this for you and then add it in the Http Request Pipeline in Startup.cs
public IActionResult SendResponse()
{
Response.Headers.Add("X-Total-Count", "20");
return Ok();
}
There is an example for simple GET action which returns top X records from some list as well as the count in the response header X-Total-Count:
using System;
using System.Linq;
using System.Net;
using Microsoft.AspNetCore.Mvc;
namespace WebApplication.Controllers
{
[Route("api")]
public class ValuesController : Controller
{
[HttpGet]
[Route("values/{top}")]
public IActionResult Get(int top)
{
// Generate dummy values
var list = Enumerable.Range(0, DateTime.Now.Second)
.Select(i => $"Value {i}")
.ToList();
list.Reverse();
var result = new ObjectResult(list.Take(top))
{
StatusCode = (int)HttpStatusCode.OK
};
Response.Headers.Add("X-Total-Count", list.Count.ToString());
return result;
}
}
}
URL looks like http://localhost:3377/api/values/5 and results (for 19 dummy records generated, so X-Total-Count value will be 19) are like:
["Value 18","Value 17","Value 16","Value 15","Value 14"]
For anyone who want to add custom header to all requests, middleware is the best way.
make some change in startup.cs like this:
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Developed-By", "Your Name");
await next.Invoke();
});
Good luck.
A custom attribute can be a good way.
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;
public AddHeaderAttribute(string name, string value)
{
_name = name;
_value = value;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
base.OnResultExecuting(context);
}
}
Then use it like this on your API method
[AddHeader("X-MyHeader", "123")]
If you have a common header you can just extend this class :
public class MySpecialHeaderAttribute : AddHeaderAttribute
{
public MySpecialHeaderAttribute() : base("X-MyHeader", "true")
{
}
}
I agree with #Ho3Ein that
if you want to add a custom header to all requests, middleware is the best way
but modifying Resposne directly in middleware is discouraged. From Microsoft Doc.
Changes to HttpResponse after the response has started, throw an exception. For example, changes such as setting headers and a status code throw an exception.
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
So the better way to add a custom header in middleware is to use Response.OnStarting callback like below:
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
context.Response.Headers.Add("X-Developed-By", "Your Name");
return Task.FromResult(0);
});
await next();
}
);
Other middleware might clear out headers after you set them. To make sure your headers are added, add them just before the response is sent.
app.Use(async (context, next) => {
context.Response.OnStarting(() => {
context.Response.Headers.Add("X-Developed-By", "Your Name");
return Task.FromResult(0);
});
await next();
});
Or in a real middleware
public class AddHeadersMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
context.Response.OnStarting(() => {
context.Response.Headers.Add("X-Developed-By", "Your Name");
return Task.FromResult(0);
});
await next();
}
}
The selected answer is OK but if you want to add AES-like encoded values on headers you will get an Error:
Invalid non-ASCII or control character in header
One way to pass is encoding the value one more time with URL encoding. To do it:
string urlEncodedValue = WebUtility.UrlEncode(value);
Vice versa to decode it:
string value = WebUtility.UrlDecode(urlEncodedValue);
FWIW, if you have an ApiController, instead of a Controller, here is how you can do it:
public class InfoController : ApiController
{
// Without custom header
public IHttpActionResult MyMethod(..)
{
var myObject= GetMyResult();
return Ok(myObject);
}
// With custom header
public IHttpActionResult MyMethod(..)
{
var myObject = GetMyResult();
// inspired from https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/content-negotiation#how-content-negotiation-works
var negotiator = Configuration.Services.GetContentNegotiator();
var result = negotiator.Negotiate(typeof(TypeOfMyObject), Request, Configuration.Formatters);
var msg = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<TypeOfMyObject>(myObject, result.Formatter,result.MediaType.MediaType)
};
msg.Headers.Add("MyCustomHeader", "MyCustomHeaderValue");
return ResponseMessage(msg);
}
}

Categories

Resources