Adding multiple Healthchecks in .net 6 with custom IHealthCheck object - c#

For my new requirement, I will have list of URLs(from configuration) and the number of URLs might be more then 10.I need to add IHealthCheck instances for each URL to have health status of all the URLs.
Single URL I can add like below, But how to have Healthcheck for all the URLs
builder.Services.AddHealthChecks().AddCheck<UnknownURIHealthCheck>("ExternalHealthCheck");
IHealthCheck implementation is like
public class UnknownURIHealthCheck : IHealthCheck
{
private readonly int _uri;
public UnknownURIHealthCheck(int uri)
{
_uri = uri;
}
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
if (_uri < 10)
return Task.FromResult(HealthCheckResult.Healthy($"URI Value {_uri}"));
else
return Task.FromResult(HealthCheckResult.Unhealthy($"URI Value {_uri}"));
}
How to create IHealthCheck instance for each URI? Am I trying/looking in a wrong way for my requirement. Can some one help me?
Note: Declared _uri as integer for the purpose of explaining, _uri<10 will be replaced with actual uri's availability logic.

Instead of using the generic version of AddCheck, you can use an overload that takes an instance of the health check as a parameter:
builder.Services
.AddHealthChecks()
.AddCheck(
"ExternalHealthCheck-1",
new UnknownURIHealthCheck("URL1"))
.AddCheck(
"ExternalHealthCheck-2",
new UnknownURIHealthCheck("URL2"));
If you want to add many health checks in a loop, you can use this approach:
var urls = new string[] { "URL1", "URL2" };
var bldr = builder.Services
.AddHealthChecks();
foreach (var url in urls)
{
bldr = bldr.AddCheck(
"ExternalHealthCheck-" + url,
new UnknownURIHealthCheck(url));
}

Related

Using identical query params with QueryHelpers

I'm trying to generate an url like,
www.example.com/Example?someOption=true&anotherOption=false&filter=testFilter&filter=testFilter2&filter=testFilter3
I have been using StringBuilder so far for the task but I'd like to think it's not the appropriate way of doing it. As a result, I came to conclusion that I should be generating this link using Uri class and not StringBuilder or any string extensions. Soon after, I came across QueryHelpers.AddQueryString. The issue with that is, it's using Dictionaries to add query parameters and adding an identical parameter ('filter' in my example) is just not possible.
Just wondering is there any other built in function or library that I can use to cleanly generate my urls with identical query parameters?
One of the overloads to QueryHelpers.AddQueryString takes an enumerable of key/value pairs. This means you can create your own 'dictionary' like this:
var filters = new List<KeyValuePair<string, string>>
{
new KeyValuePair("filter","testFilter1"),
new KeyValuePair("filter","testFilter2"),
new KeyValuePair("filter","testFilter3"),
}
Then you can call
var uri = QueryHelpers.AddQueryString("www.example.com", filters);
A little more elegant:
public async Task<HttpResponseMessage> GetAsync(string url, Action<HttpRequestHeaders> headers, Action<HttpRequestParameters> parameters)
{
using (var client = new HttpClient())
{
var _httpRequestParameters = new HttpRequestParameters();
headers?.Invoke(client.DefaultRequestHeaders);
if (parameters != null)
{
parameters.Invoke(_httpRequestParameters);
var query = _httpRequestParameters.GetQueryString();
url += query;
}
return await client.GetAsync(url);
}
}

How to get file with resourcekey Google Drive API?

I found here
https://stackoverflow.com/questions/68777408/how-to-obtain-a-resourcekey-using-google-drive-api
and
https://developers.google.com/drive/api/v3/resource-keys
I did get resourcekey of folder/file. But how to get copy file or folder with resourcekey?
I use code
DriveService.Files.Get(id).Execute()
It work update before. But now don't. I search many post but don't solve. Sorry my English not good. Thank for read.
Edit: I use C#.
You need to add the X-Goog-Drive-Resource-Keys header in your request. The simplest way to do that in the client libraries is via request interceptors. It's a little clunky, but not actually complicated:
public class HeaderExecuteInterceptor : IHttpExecuteInterceptor
{
private readonly string header;
private readonly string value;
public HeaderExecuteInterceptor(string header, string value)
{
this.header = header;
this.value = value;
}
public Task InterceptAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Headers.Add(header, value);
// The value doesn't matter; Task.CompletedTask is simpler where supported.
return Task.FromResult(true);
}
}
// Where you make the request
const string ResourceKeysHeader = "X-Goog-Drive-Resource-Keys";
var request = service.Files.Get(id);
var interceptor = new HeaderExecuteInterceptor(ResourceKeysHeader, resourceKey);
request.AddExecuteInterceptor(interceptor);
var response = request.Execute();
Alternatively, anyone can use this code.
DriveService.HttpClient.DefaultRequestHeaders.Add("X-Goog-Drive-Resource-Keys", "ID/ResourceKey")
DriveService.Files.Get(id).Execute()
The "/" is required.

WebAPI - Resolve controller and action manually from the request

I wanted to make my WebAPI application change the used SessionStateBehavior based on action attributes like that:
[HttpPost]
[Route("api/test")]
[SetSessionStateBehavior(SessionStateBehavior.Required)] // <--- This modifies the behavior
public async Task<int> Test(){}
It seems, however, that the only place I can change the session behavior is inside my HttpApplication's Application_PostAuthorizeRequest (or in similar places, early in the request lifetime), otherwise I get this error:
'HttpContext.SetSessionStateBehavior' can only be invoked before 'HttpApplication.AcquireRequestState' event is raised.
So, at that point no controller or action resolution is done, so I don't know what action will be called in order to check its attributes.
So, I am thinking of resolving the action manually.
I started with these lines of code to resolve the controller first:
var httpCtx = HttpContext.Current;
var ctrlSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
var actionSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(httpCtx.Request);
But in the last line I can't get the proper HttpRequestMessage from the request.
Any idea ho how get that?
This is not inside a controller, so I don't have it ready there.
Or, is there a better way to do this?
I am trying to see the disassembled code of the framework to copy portions of it, but I am quite lost at this point...
UPDATE:
This is the closest I got to resolving the action manually, but it doesn't work:
I have registered those two services:
container.RegisterType<IHttpControllerSelector, DefaultHttpControllerSelector>();
container.RegisterType<IHttpActionSelector, ApiControllerActionSelector>();
...and try to get the required session behavior like that:
private SessionStateBehavior GetDesiredSessionBehavior(HttpContext httpCtx)
{
var config = GlobalConfiguration.Configuration;
var diResolver = config.Services;
var ctrlSel = diResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
var actionSel = diResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
if (ctrlSel is null || actionSel is null)
{
return DefaultSessionBehavior;
}
var method = new HttpMethod(httpCtx.Request.HttpMethod);
var requestMsg = new HttpRequestMessage(method, httpCtx.Request.Url);
requestMsg.Properties.Add(HttpPropertyKeys.RequestContextKey, httpCtx.Request.RequestContext);
requestMsg.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
httpCtx.Request.Headers.Cast<string>().ForEach(x => requestMsg.Headers.Add(x, httpCtx.Request.Headers[x]));
var httpRouteData = httpCtx.Request.RequestContext.RouteData;
var routeData = config.Routes.GetRouteData(requestMsg);
requestMsg.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
requestMsg.SetRequestContext(new HttpRequestContext(){RouteData = routeData });
requestMsg.SetConfiguration(config);
var route = config.Routes["DefaultApi"];
requestMsg.SetRouteData(routeData ?? route.GetRouteData(config.VirtualPathRoot, requestMsg));
var routeHandler = httpRouteData.RouteHandler ?? new WebApiConfig.SessionStateRouteHandler();
var httpHandler = routeHandler.GetHttpHandler(httpCtx.Request.RequestContext);
if (httpHandler is IHttpAsyncHandler httpAsyncHandler)
{
httpAsyncHandler.BeginProcessRequest(httpCtx, ar => httpAsyncHandler.EndProcessRequest(ar), null);
}
else
{
httpHandler.ProcessRequest(httpCtx);
}
var values = requestMsg.GetRouteData().Values; // Hm this is empty and makes the next call fail...
HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(requestMsg);
IHttpController controller = controllerDescriptor?.CreateController(requestMsg);
if (controller == null)
{
return DefaultSessionBehavior;
}
var ctrlContext = CreateControllerContext(requestMsg, controllerDescriptor, controller);
var actionCtx = actionSel.SelectAction(ctrlContext);
var attr = actionCtx.GetCustomAttributes<ActionSessionStateAttribute>().FirstOrDefault();
return attr?.Behavior ?? DefaultSessionBehavior;
}
I have an alternative hack to make it work (send header values from the client to modify the session behavior), but it would be nice if the version above worked.
UPDATE:
Eventually, I went with setting the session behavior based on a client header value and validating the validity of sending that header based on the action attributes later-on in the request lifetime. If someone can solve the action resolution code I was fighting with above, feel free to post the answer here.
I don't know if this is going to be helpful for you, but I was just following a Pluralsight course (https://app.pluralsight.com/player?course=implementing-restful-aspdotnet-web-api) and in the Versioning chapter the author shows how to implement a controller selector where he does have access to the request.
The controller selector looks like:
public class CountingKsControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
public CountingKsControllerSelector(HttpConfiguration config)
: base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor descriptor;
if (controllers.TryGetValue(controllerName, out descriptor))
{
[...]
return descriptor;
}
return null;
}
}
And it's registered in WebApiConfig with:
config.Services.Replace(typeof(IHttpControllerSelector),
new CountingKsControllerSelector(config));

Web API and HTTP Module

We have an HTTP Module that decodes all encoded requests.
It works great with all WCF requests, but NOT in Web Api requests- in Web Api the request (both POST and GET) gets to the service still encoded
I see that it Hits the HTTP Module but,again,still gets to the service encoded.
How can i fix it? or what am i doing wrong?
i know that its better to work with Message Handlers in Web Api, but HTTP Modules suppose to work too- no?
HTTP Module:
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += context_PreSendRequestContent;
}
void context_PreSendRequestContent(object sender, EventArgs e)
{
string encodedQuerystring = HttpContext.Current.Request.QueryString.ToString();
if (!string.IsNullOrEmpty(encodedQuerystring))
{
System.Collections.Specialized.NameValueCollection col = new System.Collections.Specialized.NameValueCollection();
col.Add("q", encodedQuerystring);
WebFunction.CreateQuerystring(HttpContext.Current, col);
}
}
void context_BeginRequest(object sender, EventArgs e)
{
string encodedQueryString = String.Empty;
if (HttpContext.Current.Request.QueryString.Count > 0 && HttpContext.Current.Request.QueryString["q"] != null)
{
object _p = HttpContext.Current.Request.QueryString;
encodedQueryString = HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.QueryString["q"].ToString());
string originalQueryString = HttpContext.Current.Server.UrlDecode(WebFunction.Base64Decode(encodedQueryString));
if (!string.IsNullOrEmpty(originalQueryString))
{
WebFunction.CreateQuerystring(HttpContext.Current, WebFunction.ConvertQueryToCollection(originalQueryString));
}
}
}
WebFunction:
public static void CreateQuerystring(HttpContext context, System.Collections.Specialized.NameValueCollection nameValueCollection)
{
// reflect to readonly property
PropertyInfo isreadonly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
// make collection editable
isreadonly.SetValue(context.Request.QueryString, false, null);
context.Request.QueryString.Clear();
context.Request.QueryString.Add(nameValueCollection);
// make collection readonly again
isreadonly.SetValue(context.Request.QueryString, true, null);
}
Web Api:
public class NamesController : ApiController
{
[HttpGet]
[ActionName("GET_NAMES")]
public Drugs_ResponseData Get(string q)
{
//need to add the decode function to get it to work
string[] arrAmpersant = Commonnn.DecodeFrom64(q).Split('&');
Names_obj = new Names();
return _obj.GetResult(Convert.ToInt32(Commonnn.GetValFromEqual(arrAmpersant[0])));
}
}
It seems that the Web API doesn't use the QueryString collection in the request, but it parses the URL itself.
See the GetQueryNameValuePairs method in this file - they take the Uri and parse its Query property.
So you have two options to do that:
The dirty one is to change the Uri of the request in your the HTTP module. I don't know whether it's possible, but some reflection could do the trick.
The nicer way would be to use the Web API message handler.
May I suggest you use Context.Items and let the QueryString have the encoded version.
It's a not very well known built in key/value dictionary which last throughout a request where you easily store any object and then share it between module, handlers, etc.
Using this would very like give you a better performance than unlocking the QueryString object, but more importantly, you process the value in one place and reuse it in many, and when needed, you just add a second value, the complete QueryString collection or any other value you want to share across a request.
void context_BeginRequest(object sender, EventArgs e)
{
string encodedQueryString = String.Empty;
if (HttpContext.Current.Request.QueryString.Count > 0 && HttpContext.Current.Request.QueryString["q"] != null)
{
string encodedQueryString = HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.QueryString["q"].ToString());
HttpContext.Current.Items("qs_d") = HttpContext.Current.Server.UrlDecode(WebFunction.Base64Decode(encodedQueryString));
}
}
Web Api:
public class NamesController : ApiController
{
[HttpGet]
[ActionName("GET_NAMES")]
public Drugs_ResponseData Get(string q)
{
string[] arrAmpersant = Commonnn.DecodeFrom64(HttpContext.Current.Items("qs_d").ToString()).Split('&');
Names_obj = new Names();
return _obj.GetResult(Convert.ToInt32(Commonnn.GetValFromEqual(arrAmpersant[0])));
}
}
Side note: I see you call HttpContext.Current.Server.UrlDecode twice. I don't think you need that unless your Base64Decode method encode the value again.
You can handle like this
protected void Application_BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
string path = app.Context.Request.Url.PathAndQuery;
int pos = path.IndexOf("?");
if (pos > -1)
{
string[] array = path.Split('?');
app.Context.RewritePath(array[0]+"?"+ HttpContext.Current.Server.UrlDecode(array[1]));
}
}
Adding on to #Tomáš Herceg 's answer, I would implement a Web Api message handler rather than modifying your HttpModule to accommodate Web Api.
public class DecodeQueryStringMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Get)
{
var originalQueryString = request.RequestUri.Query;
if (!string.IsNullOrEmpty(originalQueryString))
{
var ub = new UriBuilder(request.RequestUri) { Query = HttpUtility.UrlDecode(originalQueryString) };
request.RequestUri = ub.Uri;
}
}
return base.SendAsync(request, cancellationToken);
}
}
It is possible, but you will need reflection, what means that exist a risk here. Please, let me suggest you what I consider to be a more clean solution after the solution.
Solution
if (!string.IsNullOrEmpty(originalQueryString))
{
var request = HttpContext.Current.Request;
request.GetType().InvokeMember("QueryStringText", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, request, new[] { "q=" + originalQueryString });
//WebFunction.CreateQuerystring(HttpContext.Current, WebFunction.ConvertQueryToCollection(originalQueryString));
}
This will update the following properties of the Request:
Request.Param
Request.QueryString
Request.ServerVariables
Request.Url
but will not update the:
Request.RawUrl
Cleaner Solution
IIS URL Rewrite Module
http://www.iis.net/learn/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module

What is the ASP.NET Core MVC equivalent to Request.RequestURI?

I found a blog post that shows how to "shim" familiar things like HttpResponseMessage back into ASP.NET Core MVC, but I want to know what's the new native way to do the same thing as the following code in a REST Post method in a Controller:
// POST audit/values
[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
{
var NewEntity = _repository.InsertFromString(value);
var msg = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + NewEntity.ID.ToString());
return msg;
}
In an ASP.NET Core MVC project, I can't seem to get Request.RequestUri.
I tried inspecting Request, and I was able to make a function like this:
private string UriStr(HttpRequest Request)
{
return Request.Scheme + "://" + Request.Host + Request.Path; // Request.Path has leading /
}
So I could write UriStr(Request) instead. But I'm not sure that's right. I feel like I'm hacking my way around, and not using this correctly.
A related question for earlier non-Core ASP.NET MVC versions asks how to get the base url of the site.
Personally, I use :
new Uri(request.GetDisplayUrl())
GetDisplayUrl fully un-escaped form (except for the QueryString)
GetEncodedUrl - fully escaped form suitable for use in HTTP headers
These are extension method from the following namespace : Microsoft.AspNetCore.Http.Extensions
A cleaner way would be to use a UriBuilder:
private static Uri GetUri(HttpRequest request)
{
var builder = new UriBuilder();
builder.Scheme = request.Scheme;
builder.Host = request.Host.Value;
builder.Path = request.Path;
builder.Query = request.QueryString.ToUriComponent();
return builder.Uri;
}
(not tested, the code might require a few adjustments)
Here's a working code. This is based off #Thomas Levesque answer which didn't work well when the request is from a custom port.
public static class HttpRequestExtensions
{
public static Uri ToUri(this HttpRequest request)
{
var hostComponents = request.Host.ToUriComponent().Split(':');
var builder = new UriBuilder
{
Scheme = request.Scheme,
Host = hostComponents[0],
Path = request.Path,
Query = request.QueryString.ToUriComponent()
};
if (hostComponents.Length == 2)
{
builder.Port = Convert.ToInt32(hostComponents[1]);
}
return builder.Uri;
}
}

Categories

Resources