Is there a way to access the request and response object in an azure middle ware.
Using a tutorial for a logging middleware I already got this far:
public class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
// Code before function execution here
await next(context);
// Code after function execution here
}
catch (Exception ex)
{
var log = context.GetLogger<ExceptionLoggingMiddleware>();
log.LogWarning(ex, string.Empty);
}
}
}
but I want to access the response and request object too. Like status code, body parameters, query parameters etc. Is this possible?
While there is no direct way to do this, but there is a workaround for accessing HttpRequestData (Not the best solution but it should work until there is a fix.):
public static class FunctionContextExtensions
{
public static HttpRequestData GetHttpRequestData(this FunctionContext functionContext)
{
try
{
KeyValuePair<Type, object> keyValuePair = functionContext.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
object functionBindingsFeature = keyValuePair.Value;
Type 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;
}
catch
{
return null;
}
}
}
And you can use it like this:
public class CustomMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
HttpRequestData httpRequestData = context.GetHttpRequestData();
// do something with httpRequestData
await next(context);
}
}
Check out this for more details.
For Http Response, there is no workaround AFAIK. Further, check out GH Issue#530, that says that documentation for this will be added soon. This capability looks like a popular demand and expected to be fixed soon (at the time of writing this).
Related
I'm using ASP, CQRS + MediatR and fluent validation. I want to implement user role validation, but I don't want to mix it with business logic validation. Do you have any idea how to implement this?
I mean a specific validator must be executed for a specific request.
Something tells me the solution lies in IEnumerable< IValidator>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToArray();
if (failures.Any())
{
var errors = failures
.Select(x => new Error(x.ErrorMessage, x.ErrorCode))
.ToArray();
throw new ValidationException(errors);
}
}
return await next();
}
}
I see your concern, I also found myself in this situation. I wanted to separate my validators from handlers while also keeping them in the domain/business project. Also I didn't want to throw exceptions just to handle bad request or any other custom business exception.
You have the right idea by
I mean a specific validator must be executed for a specific request
For this, you need to set up a mediator pipeline, so for every Command you can find the appropriate the appropriate validator, validate and decide whether to execute the command or return a failed result.
First, create an interface(although not necessary but it is how I did it) of ICommand like this:
public interface ICommand<TResponse>: IRequest<TResponse>
{
}
And, ICommandHandler like:
public interface ICommandHandler<in TCommand, TResponse>: IRequestHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse>
{
}
This way we can only apply validation to commands. Instead of iheriting IRequest<MyOutputDTO> and IRequestHandler<MyCommand, MyOutputDTO> you inherit from ICommand and ICommandHandler.
Now create a ValidationBehaviour for the mediator as we agreed before.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : class, ICommand<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (!_validators.Any())
return await next();
var validationContext = new ValidationContext<TRequest>(request);
var errors = (await Task.WhenAll(_validators
.Select(async x => await x.ValidateAsync(validationContext))))
.SelectMany(x => x.Errors)
.Where(x => x != null)
.Select(x => x.CustomState)
.Cast<TResponse>();
//TResponse should be of type Result<T>
if (errors.Any())
return errors.First();
try
{
return await next();
}
catch(Exception e)
{
//most likely internal server error
//better retain error as an inner exception for debugging
//but also return that an error occurred
return Result<TResponse>.Failure(new InternalServerException(e));
}
}
}
This code simply, excepts all the validators in the constructor, because you register all your validator from assembly for your DI container to inject them.
It waits for all validations to validate async(because my validations mostly require calls to db itself such as getting user roles etc).
Then check for errors and return the error(here I have created a DTO to wrap my error and value to get consistent results).
If there were no errors simply let the handler do it's work return await next();
Now you have to register this pipeline behavior and all the validators.
I use autofac so I can do it easily by
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(typeof(IValidator<>))
.AsImplementedInterfaces();
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestExceptionHandler<,,>),
typeof(IRequestExceptionAction<,>),
typeof(INotificationHandler<>),
typeof(IPipelineBehavior<,>)
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
If you use Microsoft DI, you can:
services.AddMediatR(typeof(Application.AssemblyReference).Assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddValidatorsFromAssembly(typeof(Application.AssemblyReference).Assembly); //to add validators
Example usage:
My generic DTO Wrapper
public class Result<T>: IResult<T>
{
public Result(T? value, bool isSuccess, Exception? error)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
}
public bool IsSuccess { get; set; }
public T? Value { get; set; }
public Exception? Error { get; set; }
public static Result<T> Success(T value) => new (value, true, null);
public static Result<T> Failure(Exception error) => new (default, false, error);
}
A sample Command:
public record CreateNewRecordCommand(int UserId, string record) : ICommand<Result<bool>>;
Validator for it:
public class CreateNewRecordCommandValidator : AbstractValidator<CreateNewRecordCommand>
{
public CreateNewVoucherCommandValidator(DbContext _context, IMediator mediator) //will be injected by out DI container
{
RuleFor(x => x.record)
.NotEmpty()
.WithState(x => Result<bool>.Failure(new Exception("Empty record")));
//.WithName("record") if your validation a property in array or something and can't find appropriate property name
RuleFor(x => x.UserId)
.MustAsync(async(id, cToken) =>
{
//var roles = await mediator.send(new GetUserRolesQuery(id, cToken));
//var roles = (await context.Set<User>.FirstAsync(user => user.id == id)).roles
//return roles.Contains(MyRolesEnum.CanCreateRecordRole);
}
)
.WithState(x => Result<bool>.Failure(new MyCustomForbiddenRequestException(id)))
}
}
This way you always get a result object, you can check if error is null or !IsSuccess and then create a custom HandleResult(result) method in your Controller base which can switch on the exception to return BadReuqestObjectResult(result) or ForbiddenObjectResult(result).
If you prefer to throw, catch and handle the exceptions in the pipeline or you wan't non-async implementation, read this https://code-maze.com/cqrs-mediatr-fluentvalidation/
This way all your validations are very far from your handler while maintaining consistent results.
I think that your initial approach its right. When you say that you want to keep the auth validations apart from the other business validation, do you mean like returning a http error like 403 and 401 right?
If thats the case try marking the auth validations with and interface to identify they, and do not run all the validations at once. Search first in the collection for a validation with that interface, and if it fails send a custom exception that you can identity in a IActionFilter to set the wanted result. This code does not do that exactly but you can make an idea.
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
private ISystemLogger _logger;
public HttpResponseExceptionFilter()
{
}
public int Order { get; } = int.MaxValue - 10;
public void OnActionExecuting(ActionExecutingContext context) { }
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception is PipelineValidationException exception)
{
context.Result = new ObjectResult(new Response(false, exception.ValidationErrors.FirstOrDefault()?.ErrorMessage ?? I18n.UnknownError));
context.ExceptionHandled = true;
}
else if (context.Exception != null)
{
_logger ??= (ISystemLogger)context.HttpContext.RequestServices.GetService(typeof(ISystemLogger));
_logger?.LogException(this, context.Exception, methodName: context.HttpContext.Request.Method);
context.Result = new ObjectResult(new Response(false, I18n.UnknownError));
context.ExceptionHandled = true;
}
}
}
I'm trying to extend the HttpClient with an EventHandler.
Is this possible?
I have an Extension on HttpClient as follows:
public static class HttpClientExtensions
{
public async static Task<T> GetSomthingSpecialAsync<T>(this HttpClient client, string url)
{
using var response = await client.GetAsync(url);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
//I have an error and want to raise the HttpClientEventError
HttpClientErrorEvent(null, new HttpClientErrorEventArgs()
{
StatusCode = response.StatusCode,
Message = $"{response.StatusCode } {(int)response.StatusCode } "
});
return default(T);
}
response.EnsureSuccessStatusCode();
[... ]
}
}
public class HttpClientErrorEventArgs : EventArgs
{
public System.Net.HttpStatusCode StatusCode { get; set; }
public string Message { get; set; }
}
But how do I define the HttpClientErrorEvent?
I tried the following but it is not an extension to a specific HttpClient:
public static event EventHandler<HttpClientErrorEventArgs> HttpClientErrorEvent = delegate { };
Don't use an event to return errors. For starters, how are you going to identify which request raised which error? You'd have to register and unregister event handlers around each call but how would you handle concurrent calls? How would you compose multiple such calls?
Errors aren't events anyway. At best, you'd have to handle the event as if it was a callback - in which case why not use an actual callback?
public async static Task<T> GetSomethingSpecialAsync<T>(this HttpClient client, string url,Action<(HttpStatusCode Status,string Message)> onError)
{
...
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
onError(response.Status,....);
return default;
}
}
...
var value=await client.GetSomethingSpesialAsync(url,
(status,msg)=>{Console.WriteLine($"Calling {url} Failed with {status}:{msg}");}
);
async/await was created so people can get rid of callbacks and events though. It's almost impossible to compose multiple async calls with events, and hard enough to do so with callbacks. That's why a lot of languages (C#, JavaScript, Dart, even C++ in a way ) introduced promises and async/await to get rid of both the success and error callback.
Instead of calling a callback you can actually return either a result or an error from your function. This is a functional way embedded in eg F#, Rust and Go (through tuples). There are a lot of ways to do this in C#:
Return a tuple with the value and error, eg (T? value, string? error)
Create a record with the value and error
Create separate Success and Error classes that share a common IResult<T> interface
Pattern matching can be used with any option to retrieve either the error or value without a ton of if statements.
Let's say we have a specific error type, HttpError.:
record HttpError(HttpStatusCode Status,string Message);
Using tuples, the method becomes:
public async static Task<(T value,HttpError error> GetSomethingSpecialAsync<T>(this HttpClient client,string url)
{
...
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
return (default,new HttpError(response.Status,....);
}
}
And called :
var (value,error)=await client.GetSomethingSpecialAsync(url);
if(error!=null)
{
var (status,msg)=error;
Console.WriteLine($"Calling {url} Failed with {status}:{msg}");
...
}
Instead of a tuple, we can create a Result record:
record Result<T>(T? Value,HttpError? Error);
Or separate classes:
interface IResult<T>
{
bool IsSuccess{get;}
}
record Success<T>(T Value):IResult<T>
{
public bool IsSuccess=>true;
}
record Error<T>(HttpError Error):IResult<T>
{
public bool IsSuccess => false;
}
public async static Task<IResult<T>> GetSomethingSpecialAsync<T>(this HttpClient client,string url){...}
var result=await client.GetSomethingSpecialAsync(url);
In all cases pattern matching can be used to simplify handling the result, eg:
var result=await client.GetSomethingSpecialAsync<T>(url);
switch (result)
{
case Error<T> (status,message):
Console.WriteLine($"Calling {url} Failed with {Status}:{Message}");
break;
case Success<T> (value):
...
break;
}
Having a specific Result<T> or IResult<T> type makes it easy to write generic methods to handle success, errors or compose a chain of functions. For example, the following could be used to call the "next" function if the previous one succeeded, otherwise just propagate the "error" :
IResult<T> ThenIfOk(this IResult<T> previous,Func<T,IResult<T>> func)
{
return previous switch
{
Error<T> error=>error,
Success<T> ok=>func(ok.Value)
}
}
This would allow creating a pipeline of calls :
var finalResult=doSomething(url)
.ThenIfOk(value=>somethingElse(value))
.ThenIfOk(....);
This style is called Railway oriented programming and is very common in functional and dataflow (pipeline) programming
You could store the handlers in your extension class and do something like this ? Please note this code is not thread safe and need to be synchronized around dictionary and list access !
public static class HttpClientExtensions
{
private static Dictionary<HttpClient, List<Action<HttpClientErrorEventArgs>>> Handlers { get; set; }
static HttpClientExtensions()
{
Handlers = new Dictionary<HttpClient, List<Action<HttpClientErrorEventArgs>>>();
}
public async static Task<T> GetSomthingSpecialAsync<T>(this HttpClient client, string url)
{
////code ....
//I have an error and want to raise the HttpClientEventError
HttpClientErrorEventArgs args = null;
client.RaiseEvent(args);
return default(T);
////code
}
public static void AddHandler(this HttpClient client, Action<HttpClientErrorEventArgs> handler)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (!found)
{
handlers = new List<Action<HttpClientErrorEventArgs>>();
Handlers[client] = handlers;
}
handlers.Add(handler);
}
public static void RemoveHandler(this HttpClient client, Action<HttpClientErrorEventArgs> handler)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (found)
{
handlers.Remove(handler);
if (handlers.Count == 0)
{
Handlers.Remove(client);
}
}
}
private static void RaiseEvent(this HttpClient client, HttpClientErrorEventArgs args)
{
var found = Handlers.TryGetValue(client, out var handlers);
if (found)
{
foreach (var handler in handlers)
{
handler.Invoke(args);
}
}
}
}
I have ASP.NET Web API controller with some actions (methods). Let's say something like this:
[HttpPost]
public async Task<ActionResult> SavePerson([FromBody]PersonDto person)
{
await _mediatr.Send(new SavePerson.Command(person));
return Ok();
}
and the PersonDto looks something like this:
public record PersonDto([Required, MinLength(3)]string Name, int? Age);
When I call my Web API action 'SavePerson' with invalid person data (Name.Length < 3 and etc...), ASP.NET Core model binding validation interrupts the execution and returns 400 (Bad Request) as it should. When I pass valid person data, it works fine.
My questions are:
How can I catch this model binding validation result (400 Bad Request) and transform it into different format, so our front-end developers will be happy?
Should I validate my DTOs (PersonDto) in Web API layer or it's better to validate it in MediatR command handler? I'm trying to adhere Uncle Bob's Clean Architecture. I have Domain, Application, Infrastructure, Web API. And my MediatR CQRS handlers are placed in the Application layer.
Automatic 400 bad request responses is enabled by default. To disable it use the following code in Startup ConfigureServices method:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
Then you can handle invalid model states manually like this:
[HttpPost]
public async Task<ActionResult> SavePerson([FromBody]PersonDto person)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);// or what ever you want
await _mediatr.Send(new SavePerson.Command(person));
return Ok();
}
You can use Jason Taylor's Clean Architecture approach. Instead of using attribute validation, use FluentValidation:
public class CreatePersonCommandValidator : AbstractValidator<SavePerson.Command>
{
public CreatePersonCommandValidator()
{
RuleFor(v => v.Title)
.NotEmpty().WithMessage("Title is required.")
.MinimujLength(200).WithMessage("Title at least should have 3 characters.");
}
}
Use MediatR behavior to perform validation and translate errors into validation exception:
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}
Validation exception:
public class ValidationException : Exception
{
public ValidationException()
: base("One or more validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}
public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}
public IDictionary<string, string[]> Errors { get; }
}
And finally, implement an exception filter or exception handling middleware to catch that exception and return desired response:
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;
public ApiExceptionFilterAttribute()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(ValidationException), HandleValidationException }
};
}
public override void OnException(ExceptionContext context)
{
HandleException(context);
base.OnException(context);
}
private void HandleException(ExceptionContext context)
{
Type type = context.Exception.GetType();
if (_exceptionHandlers.ContainsKey(type))
{
_exceptionHandlers[type].Invoke(context);
return;
}
if (!context.ModelState.IsValid)
{
HandleInvalidModelStateException(context);
return;
}
HandleUnknownException(context);
}
private void HandleValidationException(ExceptionContext context)
{
var exception = context.Exception as ValidationException;
//var details = new ValidationProblemDetails(exception.Errors)
//{
//Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
//};
context.Result = Returns your response type //new BadRequestObjectResult(details);
context.ExceptionHandled = true;
}
}
You can perform ModelState.isValid() in the biginig of your Api method and return a BadRequestResult() if the model is invalid. You can can return the validation errors along with BadRequestResult.
You need to get validation errors from model state and fill your custom error object. This way your customers can see more meaningful errors.
I have seen some of the existing questions regarding async waiting for completion , However for me none of the solution work.
I am using a C# wrapper for connecting to sales force https://github.com/developerforce/Force.com-Toolkit-for-NET/
In the below method i want to wait for the method UsernamePasswordAsync to complete execution so that i can get the values from the auth object.
public async Task<Token> GetTokenForSalesForce()
{
Token token = null;
try
{
var auth = new AuthenticationClient();
await auth.UsernamePasswordAsync(configuration.Value.ClientId, configuration.Value.ClientSecert,
configuration.Value.SFUsername, configuration.Value.SFPassword,
configuration.Value.SFBaseUrl);
if (!string.IsNullOrEmpty(auth.AccessToken) && !string.IsNullOrEmpty(auth.InstanceUrl))
{
token = new Token
{
BearerToken = auth.AccessToken,
InstanceURL = auth.InstanceUrl,
ApiVersion = auth.ApiVersion
};
}
}
catch (Exception ex)
{
throw ex;
}
return token;
}
public async Task<List<SFDashboardResponse>> GetOrderCountFromSalesForce(Token token)
{
List<SFDashboardResponse> sFDashboardResponses = new List<SFDashboardResponse>();
try
{
var client = new ForceClient(token.InstanceURL, token.BearerToken, token.ApiVersion);
var response = await client.QueryAsync<SFDashboardResponse>("SELECT something ");
var records = response.Records;
}
catch(Exception e)
{
}
return sFDashboardResponses;
}
The signature in the library is
public async Task WebServerAsync(string clientId, string clientSecret, string redirectUri, string code, string tokenRequestEndpointUrl)
{
}
The problem is while the method wait for await to be first execute another thread executes the other part of the orignal caller.
I call it from here
public IActionResult post()
{
var authtoken = _salesForceService.GetTokenForSalesForce();
var response = _salesForceService.GetOrderCountFromSalesForce(authtoken.Result);
DashboardModel dashboardModel = null;
if (authtoken.Status == TaskStatus.RanToCompletion)
{
fill the object
}
return Ok(dashboardModel);
}
You can wrap the IActionResult with a Task and await on the tasks below.
public async Task<IActionResult> post()
{
var authtoken = await _salesForceService.GetTokenForSalesForce();
var response = await _salesForceService.GetOrderCountFromSalesForce(authtoken);
DashboardModel dashboardModel = //fill the object
return Ok(dashboardModel);
}
At least this is what you are asking for as far as I understand, if its another problem let me know.
EDIT 1:
This is just my suggestion/opinion.
Personally I dont really like having the code wrapped in try-catch everywhere, this way the code can be hard to read and maintain. You really should consider centralizing exception handling in one place, you could have a base controller or just a middleware like this one:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context, ILogger logger)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, logger);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
{
logger.Log(exception);
//do something
return context.Response.WriteAsync(... something ...); //Maybe some JSON message or something
}
}
The you just register it as a middleware in the Configure method like below:
app.UseMiddleware<ErrorHandlingMiddleware>();
I need to audit log calls to my Web API, ideally I'd like to use an Attribute, something like:
[HttpPost, Auditing]
public dynamic MyAPICall()
The Attribute should be able to intercept the API call before and after execution in order to log the parameters and also, how long the API call took to run.
With MVC I could create an ActionFilterAttribute derivative and override OnActionExecuted and OnActionExecuting.
Is the equivalent possible in the Web API world?
Http message handler should be a good extensible point for such purposes. Be careful though, there can be some issues with concurrent request content reading. For instance, Model Binder may try to read request content while LoggingHandler is reading it and fail to deserialize a model. To prevent such issues just add Wait call to the LogRequestLoggingInfo method.
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Log the request information
LogRequestLoggingInfo(request);
// Execute the request
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
// Extract the response logging info then persist the information
LogResponseLoggingInfo(response);
return response;
});
}
private void LogRequestLoggingInfo(HttpRequestMessage request)
{
if (request.Content != null)
{
request.Content.ReadAsByteArrayAsync()
.ContinueWith(task =>
{
var result = Encoding.UTF8.GetString(task.Result);
// Log it somewhere
}).Wait(); // !!! Here is the fix !!!
}
}
private void LogResponseLoggingInfo(HttpResponseMessage response)
{
if (response.Content != null)
{
response.Content.ReadAsByteArrayAsync()
.ContinueWith(task =>
{
var responseMsg = Encoding.UTF8.GetString(task.Result);
// Log it somewhere
});
}
}
}
You can read more about it here.
I would use a message handler rather than attributes.
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
LogRequest(request);
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
LogResponse(response);
return response;
});
}
private void LogRequest(HttpRequestMessage request)
{
(request.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
Logger.Info("{4:yyyy-MM-dd HH:mm:ss} {5} {0} request [{1}]{2} - {3}", request.GetCorrelationId(), request.Method, request.RequestUri, x.Result, DateTime.Now, Username(request));
});
}
private void LogResponse(HttpResponseMessage response)
{
var request = response.RequestMessage;
(response.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
Logger.Info("{3:yyyy-MM-dd HH:mm:ss} {4} {0} response [{1}] - {2}", request.GetCorrelationId(), response.StatusCode, x.Result, DateTime.Now, Username(request));
});
}
private string Username(HttpRequestMessage request)
{
var values = new List<string>().AsEnumerable();
if (request.Headers.TryGetValues("my-custom-header-for-current-user", out values) == false) return "<anonymous>";
return values.First();
}
}
I think you will be interested to take a look at Web API tracing http://www.asp.net/web-api/overview/testing-and-debugging/tracing-in-aspnet-web-api. It allows you to look into the internal mechanism of Web API.
In your case, I assume you're particularly interested in what's the input and output of actions. So you can right your TraceWriter like following sample to filter out the redundant information:
public class ActionAuditor : ITraceWriter
{
private const string TargetOperation = "ExecuteAsync";
private const string TargetOpeartor = "ReflectedHttpActionDescriptor";
public void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)
{
var rec = new TraceRecord(request, category, level);
traceAction(rec);
if (rec.Operation == TargetOperation && rec.Operator == TargetOpeartor)
{
if (rec.Kind == TraceKind.Begin)
{
// log the input of the action
}
else
{
// log the output of the action
}
}
}
}
I've worked on a library that allows you to log interactions with ASP.NET Web API Controllers by using Action Filters.
It can record action method calls with caller info, arguments, output, duration, exceptions and more.
Take a look at Audit.WebApi.
You can quickly create a sample project that uses this library with the following commands:
> dotnet new -i Audit.WebApi.Template
> dotnet new webapiaudit