-
Notifications
You must be signed in to change notification settings - Fork 118
/
Copy pathActionContextExtensions.cs
132 lines (119 loc) · 5.3 KB
/
ActionContextExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Extensions.Logging;
using System;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Diagnostics.Monitoring.WebApi
{
public sealed class ExecutionResult<T>
{
private static readonly ExecutionResult<T> _empty = new ExecutionResult<T>();
public Exception Exception { get; private set; }
public T Result { get; private set; }
public ProblemDetails ProblemDetails { get; private set; }
private ExecutionResult() { }
public static ExecutionResult<T> Succeeded(T result) => new ExecutionResult<T> { Result = result };
public static ExecutionResult<T> Failed(Exception exception) =>
new ExecutionResult<T> { Exception = exception, ProblemDetails = exception.ToProblemDetails((int)HttpStatusCode.BadRequest) };
public static ExecutionResult<T> Empty() => _empty;
}
internal static class ExecutionHelper
{
public static async Task<ExecutionResult<T>> InvokeAsync<T>(Func<CancellationToken, Task<ExecutionResult<T>>> action, ILogger logger,
CancellationToken token)
{
// Exceptions are logged in the "when" clause in order to preview the exception
// from the point of where it was thrown. This allows capturing of the log scopes
// that were active when the exception was thrown. Waiting to log during the exception
// handler will miss any scopes that were added during invocation of action.
try
{
return await action(token);
}
catch (ArgumentException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (DiagnosticsClientException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (InvalidOperationException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (OperationCanceledException ex) when (token.IsCancellationRequested && LogInformation(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (OperationCanceledException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (MonitoringException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (ValidationException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
catch (UnauthorizedAccessException ex) when (LogError(logger, ex))
{
return ExecutionResult<T>.Failed(ex);
}
}
private static bool LogError(ILogger logger, Exception ex)
{
logger.RequestFailed(ex);
return true;
}
private static bool LogInformation(ILogger logger, Exception ex)
{
logger.RequestCanceled();
return true;
}
}
internal static class ActionContextExtensions
{
public static Task ProblemAsync(this ActionContext context, BadRequestObjectResult result)
{
if (context.HttpContext.Features.Get<IHttpResponseFeature>().HasStarted)
{
// If already started writing response, do not rewrite
// as this will throw an InvalidOperationException.
return Task.CompletedTask;
}
else
{
// Need to manually add this here because this ObjectResult is not processed by the filter pipeline,
// which would look up the applicable content types and apply them before executing the result.
result.ContentTypes.Add(ContentTypes.ApplicationProblemJson);
return result.ExecuteResultAsync(context);
}
}
public static async Task InvokeAsync(this ActionContext context, Func<CancellationToken, Task> action, ILogger logger)
{
//We want to handle two scenarios:
//1) These functions are part of an ExecuteResultAsync implementation, in which case
//they don't need to return anything. (The delegate below handles this case).
//2) They are part of deferred processing and return a result.
Func<CancellationToken, Task<ExecutionResult<object>>> innerAction = async (CancellationToken token) =>
{
await action(token);
return ExecutionResult<object>.Empty();
};
ExecutionResult<object> result = await ExecutionHelper.InvokeAsync(innerAction, logger, context.HttpContext.RequestAborted);
if (result.ProblemDetails != null)
{
await context.ProblemAsync(new BadRequestObjectResult(result.ProblemDetails));
}
}
}
}