I´ve got a Middleware for my ASP.NET WebApi, which should Log the Request in case its something we cannot work with. There for I´ve invented the following Attribute to declare which Methods uses the Middleware:
public class UseMiddlewareAttribute : Attribute
{
public Type MiddlewareType { get; }
public UseMiddlewareAttribute(Type middlewareType)
{
MiddlewareType = middlewareType;
}
}
And a example controller:
[HttpPost]
[DisableRequestSizeLimit]
[UseMiddleware(typeof(PlausibilityMiddleware))]
public async Task<ActionResult> ImportPersonsAsync(List<Person> persons)
{
return Ok(persons);
}
And here the middleware:
internal class PlausibilityMiddleware
{
#region Fields
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private readonly RequestDelegate _next;
private readonly IServiceProvider _provider;
#endregion
#region Constructor
public PlausibilityMiddleware(RequestDelegate next, IServiceProvider provider)
{
_next = next;
_provider = provider;
}
#endregion
public async Task InvokeAsync(HttpContext context)
{
try
{
if (!CanProcess(context))
{
await _next(context);
return;
}
var requestBody = DoSomeMagic();
Log.Trace(requestBody);
}
catch (Exception ex)
{
// do not skip _next(context) ---> This method calls the Controller behind
Log.Error("Unknown error occured while validate pre import");
Log.Error(ex);
}
await _next(context);
}
private bool CanProcess(HttpContext context)
{
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<UseMiddlewareAttribute>();
return attribute != null && attribute.MiddlewareType.Equals(GetType());
}
Firstly the context.Features.Get<IEndpointFeature>()?.Endpoint returns null, so I cannot access the attributes, also I don´t know how to read Request-Body.
To access the given Attribute I needed to configure the app, it´s neccessary to Add the Middleware between UseRouting and UseEndpoint:
app
.UseRouting()
.UseMiddleware<PlausibilityMiddleware>()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
})
To read the Request-Body I need to EnableBuffering and use a StreamReader. But then the StreamReader disposes the underlying Stream, so the Controller cannot access the Stream anymore. You can use CopyTo and use another MemoryStream but that is overhead. StreamReader has a Consturctor parameter leaveOpen finaly:
private static async Task<string> DoSomeMagic(HttpContext context)
{
context.Request.EnableBuffering();
using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
var result = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
return result;
}
Also its nice to know you cannot access the stream as synchronously read ReadToEnd() if you want to, you need to set AllowSynchronousIO = true
var syncIOFeature = httpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature is not null)
syncIOFeature.AllowSynchronousIO = true;
Then you can ReadToEnd as Synchronously method.
Related
So currently I need to implement try-catches, but everywhere I look the try-catch can be relevant. How do you determine where to use the try-catches?
Is there also a generalized way how to implement the try-catches? For example Is there some way how an empty class can be used in Faulthandling as indicated below?
public class FailedRoleManager : IRoleManager { }
Depending on the type of project you are doing, it is possible to make a middleware that handles all the exceptions that occurred in the application. In this link you can see an example of an implementation of global error capture in a .net core api:
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception error)
{
var response = context.Response;
response.ContentType = "application/json";
response.StatusCode = (int)HttpStatusCode.InternalServerError;
var result = JsonSerializer.Serialize(new { message = error?.Message });
await response.WriteAsync(result);
}
}
}
https://jasonwatmore.com/post/2020/10/02/aspnet-core-31-global-error-handler-tutorial#:~:text=The%20global%20error%20handler%20middleware%20is%20used%20catch%20all%20exceptions,Configure%20method%20of%20the%20Startup.
I am exploring Azure Function running on .net 5 and I found out about the new middleware capabilities.
I have built a dummy middleware like this one:
public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ExceptionLoggingMiddleware> m_logger;
public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
{
m_logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception unhandledException)
{
m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
}
}
}
In my use case, the Azure Function is an HTTP triggered function:
public sealed class StorageAccountsFunction
{
private readonly ILogger<StorageAccountsFunction> m_logger;
public StorageAccountsFunction
(
ILogger<StorageAccountsFunction> logger
)
{
m_logger = logger;
}
[Function("v1-post-storage-account")]
public async Task<HttpResponseData> CreateAsync
(
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")]
HttpRequestData httpRequestData,
FunctionContext context
)
{
m_logger.LogInformation("Processing a request to create a new storage account");
throw new Exception("Oh no! Oh well..");
}
}
In my Function App running in-process on .net core 3.1, each Function had the responsibility of catching the unhandled exception (via a base class) and returned the appropriate HTTP status code.
I would like to have that logic sit in a middleware instead to have it centralized and avoid any future mistakes.
Question
The exception is caught by the middleware properly. However, I do not see how I can alter the response and return something more appropriate, instead of a 500 Internal Server Error that I get right now?
According to this issue, there is currently no official implementation regarding this, but they also mention a "hacky workaround" until the proper functionality is implemented directly into Azure functions
We created an extension method for FunctionContext:
internal static class FunctionUtilities
{
internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
result.SetValue(functionBindingsFeature, response);
}
}
The usage in the middleware looks like this:
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
if (ex.InnerException is *NameOfExceptionYouNeed* e)
{
var req = context.GetHttpRequestData();
var res = await req.ErrorResponseAsync(e.Message);
context.InvokeResult(res);
return;
}
throw;
}
}
This is natively supported now as of version 1.8.0 of Microsoft.Azure.Functions.Worker.
The FunctionContextHttpRequestExtensions class was introduced so now you can just
using Microsoft.Azure.Functions.Worker;
public class MyMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// To access the RequestData
var req = await context.GetHttpRequestDataAsync();
// To set the ResponseData
var res = req!.CreateResponse();
await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
context.GetInvocationResult().Value = res;
}
}
This code works for me. It is based on the example here: https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// Simple example which always fails. Use the following in an error condition
var httpReqData = await context.GetHttpRequestDataAsync();
if (httpReqData != null)
{
var newHttpResponse = httpReqData.CreateResponse(HttpStatusCode.InternalServerError);
await newHttpResponse.WriteAsJsonAsync(new { ResponseStatus = "Invocation failed!" }, newHttpResponse.StatusCode);
context.GetInvocationResult().Value = newHttpResponse;
}
}
First of all, I can access localstorage data in .razor pages. I mean I cannot access localstorage data in .cs files. How can I access?
_Imports.razor:
#using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
#inject ProtectedLocalStorage protectedLocalStorage
anyone .razor file:
await protectedLocalStorage.SetAsync(key, JsonSerializer.Serialize(instance));
Above code works for me but I want to call protectedLocalStorage from .cs files additionally.
P.S sorry for grammar mistakes
Edit:
I am using IHttpClientFactory in startup.cs and I want to add token as a header before api request.
startup.cs
services.AddHttpClient("api", hc =>
{
hc.BaseAddress = new Uri("http://localhost:5000/");
string tokenVal = tokenService.GetToken();
if (!String.IsNullOrEmpty(tokenVal))
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenVal);
});
I want to take token value from local storage from this .cs file
public class TokenService : ITokenService
{
private IHttpContextAccessor httpContextAccessor;
public TokenService(IHttpContextAccessor HttpContextAccessor, IProtected) => httpContextAccessor = HttpContextAccessor;
public string GetToken()
{
return "";
}
}
How do I access browser local storage from .cs files in blazor?
ASP.NET supports injection in most constructors. Expanding OP's example:
// Startup.cs -> ConfigureServices(IServiceCollection services)
// Probably not necessary in your case but, to be thorough:
services.AddScoped<ProtectedLocalStorage>();
// SomeFile.cs
public class TokenService : ITokenService
{
// Ignore for the moment that these are being used in the same context
private IHttpContextAccessor httpContextAccessor;
private readonly ProtectedBrowserStorage _storage;
// Injection can happen here in ASP.NET
public TokenService(
IHttpContextAccessor HttpContextAccessor,
ProtectedBrowserStorage storage)
{
httpContextAccessor = HttpContextAccessor;
// injection works but the PBS service might not: see below
_storage = storage;
}
//..
}
However, I don't recommend this for ProtectedBrowserStorage, since it uses IJSRuntime under the hood. If you try to use this in a non-javascript aware context (e.g. during Startup.Configure where the client is still awaiting a response and there is no way to execute javascript), you will run into errors. In Blazor, ProtectedBrowserStorage should only be called - directly or indirectly - from a Blazor component; to keep it simple, wrap it in a class you only use with components, or keep it in the component itself.
Thus, if you are trying to do this:
I am using IHttpClientFactory in startup.cs and I want to add token as a header before api request.
ProtectedBrowserStorage is not the tool for you. Use cookies or another web server technology.
How I solved in the end:
I have created custom authentication class inherited AuthenticationStateProvider. Then I designed all of check processes to be solved on ProtectedLocalStorage.
AuthenticationService
public class AuthenticationService : AuthenticationStateProvider
{
private const string USER_SESSION_OBJECT_KEY = "user_session_obj";
private const string ACCESS_TOKEN = "accesstoken";
private const string USER_PERMISSIONS = "userpermissions";
private readonly ProtectedLocalStorage _protectedLocalStorage;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthenticationService(ProtectedLocalStorage protectedSessionStore, IHttpContextAccessor httpContextAccessor)
{
_protectedLocalStorage = protectedSessionStore;
_httpContextAccessor = httpContextAccessor;
}
public string IpAddress => _httpContextAccessor?.HttpContext?.Connection?.RemoteIpAddress?.ToString() ?? string.Empty;
private User User { get; set; }
private List<UserPermission> UserPermissionList { get; set; }
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
User userSession = await GetUserSession();
List<UserPermission> userPermissions = await GetUserPermission();
if (userSession != null)
return await GenerateAuthenticationState(userSession, userPermissions);
return await GenerateEmptyAuthenticationState();
}
catch
{
await LogoutAsync();
return null;
}
}
public async Task LoginAsync(User user,List<UserPermission> userPermissions)
{
await SetUserSession(user);
await SetUserPermissionSession(userPermissions);
NotifyAuthenticationStateChanged(GenerateAuthenticationState(user, userPermissions));
}
public async Task LogoutAsync()
{
//await SetUserSession(null);
RefreshUserSession(null);
await _protectedLocalStorage.DeleteAsync(USER_SESSION_OBJECT_KEY);
await _protectedLocalStorage.DeleteAsync(ACCESS_TOKEN);
await _protectedLocalStorage.DeleteAsync(USER_PERMISSIONS);
NotifyAuthenticationStateChanged(GenerateEmptyAuthenticationState());
}
public async Task<User> GetUserSession()
{
if (User != null)
return User;
//TODO burda localUserJson get yaparken hata alıyor. try catch işi çözmezse buraya tekrardan bakılacak.
try
{
var localUserJson = await _protectedLocalStorage.GetAsync<string>(USER_SESSION_OBJECT_KEY);
if (string.IsNullOrEmpty(localUserJson.Value))
return null;
return RefreshUserSession(JsonConvert.DeserializeObject<User>(localUserJson.Value));
}
catch
{
await LogoutAsync();
return null;
}
}
public async Task<List<UserPermission>> GetUserPermission()
{
if (UserPermissionList != null)
return UserPermissionList;
try
{
var localUserPermissionJson = await _protectedLocalStorage.GetAsync<string>(USER_PERMISSIONS);
if (string.IsNullOrEmpty(localUserPermissionJson.Value))
return null;
return RefreshUserPermissionSession(JsonConvert.DeserializeObject<List<UserPermission>>(localUserPermissionJson.Value));
}
catch
{
await LogoutAsync();
return null;
}
}
private async Task SetUserSession(User user)
{
RefreshUserSession(user);
await _protectedLocalStorage.SetAsync(USER_SESSION_OBJECT_KEY, JsonConvert.SerializeObject(user));
}
private async Task SetUserPermissionSession(List<UserPermission> userPermissions)
{
RefreshUserPermissionSession(userPermissions);
await _protectedLocalStorage.SetAsync(USER_PERMISSIONS, JsonConvert.SerializeObject(userPermissions));
}
private User RefreshUserSession(User user) => User = user;
private List<UserPermission> RefreshUserPermissionSession(List<UserPermission> userPermission) => UserPermissionList = userPermission;
private Task<AuthenticationState> GenerateAuthenticationState(User user, List<UserPermission> userPermission)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Id.ToString()),
new Claim(ClaimTypes.Role, userPermission.ToString()),
}, "auth");
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
return Task.FromResult(new AuthenticationState(claimsPrincipal));
}
private Task<AuthenticationState> GenerateEmptyAuthenticationState() => Task.FromResult(new AuthenticationState(new ClaimsPrincipal()));
}
Then I registered this class in startup.cs
Startup
services.AddScoped<AuthenticationStateProvider, AuthenticationService>();
During changing page, authentication system interrupt showing page to check if it is authenticate or not thanks to below code.
_Imports
#attribute [Authorize]
*You can set localstorage at login page. You can create your way to check thanks to this way.
I have a Controller, which has 2 functions and a middleware.
My middleware
try
{
var request = httpContext.Request;
if (/* some logic here */)
{
await _next(httpContext);
// Some logic here
var someReturnValue = someFunction();
// Need to call the Controller Method here
// call SaveStaging(someReturnValue);
}
else
{
await _next(httpContext);
}
}
catch (Exception ex)
{
await _next(httpContext);
}
In my Controller
[Authorize(AuthenticationSchemes = "APIKeyAuthentication")]
[LogAction]
[ApiController]
[Route("api/[controller]/[action]")]
public class MyController : ControllerBase
{
private IHttpContextAccessor _accessor;
public MyController(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
[HttpPost]
public async Task<ActionResult<ServiceResult>> PostData([FromBody] DataObj request)
{
ServiceResult result = new ServiceResult();
result.isSuccessful = true;
return Ok(result);
}
public async Task SaveStaging(int logNumber)
{
}
}
So, initially, user will call the PostData endpoint. In this method, it will return that it accepts the data posted by user.
Then in the middleware, I will need to do some things, and finally, I would like to call the SaveStaging method in the Controller. I want this SaveStaging method to be only accessible for the middleware, and should not be called directly by the user.
So my question:
How to call this method from the middleware?
How to limit SaveStaging to be accessible only to the middleware?
You should consider wrapping SaveStaging into a service because it appears to be a cross-cutting concern. Beyond that, it's likely wise to implement a layered architecture as a matter of refactoring.
Since this is .NET Core, I would recommend fully embracing dependency injection and injecting the service into your middleware.
public interface IStagingService
{
Task Save(int logNumber);
}
public class MyController : ControllerBase
{
private readonly IStagingService _service;
private readonly IHttpContextAccessor _accessor;
public MyController(IStagingService service, IHttpContextAccessor accessor)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
_accessor = accessor ?? throw new ArgumentNullException(nameof(accessor));;
}
//....
}
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly IStagingService _service;
public RequestCultureMiddleware(RequestDelegate next, IStagingService service)
{
_next = next;
_service = service ?? throw new ArgumentNullException(nameof(service));
}
public async Task InvokeAsync(HttpContext context)
{
// ...
}
In the beginning, your service's will likely start out small and simple. In the event the app grows, the complexity of this layer will likely increase, spawning the need for some facade services (i.e. services composed of services) and maybe some providers for thing like data access.
I'm creating ASP.Net Core Web API using .Net Core 2.1 and I need to create custom middleware for global exception handling. What I am trying to do is capture JSON request when exception happened in my application. And since I'm using custom middleware, I want the JSON request available in my middleware. How I can accomplish this?
I've tried creating custom middleware for centralized exception handling by following an article from Marinko Spasojevic and slightly modified it to capture the json request. It seems the request already unavailable since the exception occurred inside controllers action (not in the middleware). Here is my code:
Here is my Error Log model
public class ErrorLog
{
public DateTime LogDate { get; set; }
public string URL { get; set; }
public string Request { get; set; }
public string Source { get; set; }
public string Message { get; set; }
}
Here is standard response model used in my project
public class BaseResponse<T> : IBaseResponse where T : class
{
public bool Status { get; set; }
public string Message { get; set; }
public IEnumerable<T> Data { get; set; }
}
Here is my custom exception middleware
public class GlobalException
{
private readonly RequestDelegate _next;
private readonly ICustomLogger _logger;
public GlobalException(RequestDelegate next, ICustomLogger logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
ErrorLog log = new ErrorLog();
log = await HandleLogError(httpContext, ex);
_logger.LogError(log); // Custom build logger
await HandleExceptionAsync(httpContext, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
BaseResponse<object> response = new BaseResponse<object>();
response.Status = false;
response.Message = "There is an exception occured.";
response.Data = new List<object>();
await context.Response.WriteAsync(response.Serialize());
}
private static async Task<ErrorLog> HandleLogError(HttpContext context, Exception exception)
{
Stream body = context.Request.Body;
context.Request.EnableRewind();
byte[] buffer = new byte[Convert.ToInt32(context.Request.ContentLength)];
await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
string requestText = Encoding.UTF8.GetString(buffer);
context.Request.Body = body;
ErrorLog log = new ErrorLog();
UriBuilder builder = new UriBuilder();
builder.Scheme = context.Request.Scheme;
builder.Host = context.Request.Host.Host;
builder.Path = context.Request.Path.ToString();
builder.Query = context.Request.QueryString.ToString();
log.LogDate = DateTime.Now;
log.URL = builder.Uri.ToString();
log.Request = requestText;
log.Source = exception.Source;
log.Message = exception.Message;
return log;
}
}
And finnally register the middleware
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMiddleware<GlobalException>();
...
}
So, anyone can give me an enlightenment? Any help would be appreciated and thank you before.
It seems the request already unavailable since the exception occurred inside controllers action (not in the middleware).
Firstly, you can get the root error by ex.InnerException if this is what you wanna get.
What I am trying to do is capture JSON request when exception happened in my application.
Also, you can read and log requests & responses (in this case I assume it is serialized to json) as shown below in your error handling middleware.
public async Task InvokeAsync(HttpContext context)
{
var requestAsString = await FormatRequest(context.Request);
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
var responseString = await FormatResponse(context.Response);
await responseBody.CopyToAsync(originalBodyStream);
}
}
private async Task<string> FormatRequest(HttpRequest request)
{
var body = request.Body;
request.EnableRewind();
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body = body;
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
var text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
return $"Response {text}";
}
Btw, I have made small changes to make it fit to your problem properly, but the credits goes to this gist page. Hope this solves your problem.
Apparently this part of code causing the request cannot retrieved
...
try
{
await _next(httpContext); // this will change the httpContext contents
}
...
Here is my final code
public class GlobalException
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public GlobalException(RequestDelegate next, ILogger logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
HttpContext tempCtx = context; // had to contain the http context
string request = await FormatRequest(context.Request);
try
{
await _next(context);
}
catch (Exception ex)
{
ErrorLog log = new ErrorLog();
UriBuilder builder = new UriBuilder();
builder.Scheme = tempCtx.Request.Scheme;
builder.Host = tempCtx.Request.Host.Host;
builder.Path = tempCtx.Request.Path.ToString();
builder.Query = tempCtx.Request.QueryString.ToString();
log.LogDate = DateTime.Now;
log.URL = builder.Uri.ToString();
log.Request = request;
log.Source = ex.Source;
log.Message = ex.Message;
await _logger.LogError(log); // custom logger
await HandleExceptionAsync(context);
}
}
private async Task<string> FormatRequest(HttpRequest request)
{
request.EnableRewind();
var body = request.Body;
byte[] buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
string requestBody = Encoding.UTF8.GetString(buffer);
body.Seek(0, SeekOrigin.Begin);
request.Body = body;
return requestBody;
}
private async Task HandleExceptionAsync(HttpContext context)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
BaseResponse<object> response = new BaseResponse<object>();
response.Status = false;
response.Message = "There is an exception occured.";
response.Data = new List<object>();
await context.Response.WriteAsync(response.Serialize());
}
}
Great thanks to Hasan for giving me an enlightenment.