Getting Cannot access a disposed object. Object name: 'SocketsHttpHandler' exception in .Net 6 Application - asp.net

In one of my Azure Function app(.Net 6 isolated process) and I am making some http requests with a client certificate. I'm registering my services in the Program.cs like this,
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
services.AddHttpClient().Configure<HttpClientFactoryOptions>(
"myClient", options =>
options.HttpMessageHandlerBuilderActions.Add(builder =>
builder.PrimaryHandler = handler));
services.AddTransient<IMyCustomClient, MyCustomClient>(provider =>
new MyCustomClient(provider.GetService<IHttpClientFactory>(),
cutomParameter1, cutomParameter2));
services.AddSingleton<IMyCustomService, MyCustomService>();
And injecting MyCustomClient in MyCustomService constructor
private readonly IMyCustomClient _myCustomClient;
public PlatformEventManagementService(IMyCustomClient myCustomClient)
{
_myCustomClient = myCustomClient;
}
var result = await _myCustomClient.GetResponse();
It works fine for some time and getting the below exception after sending many requests.
Cannot access a disposed object. Object name: 'SocketsHttpHandler'.

You are supplying the factory with a single instance of HttpClientHandler to use in all clients. Once the default HandlerLifetime has elapsed (2 minutes) it will be marked for disposal, with the actual disposal occurring after all existing HttpClients referencing it are disposed.
All clients created after the handler is marked continue to be supplied the soon-to-be disposed handler, leaving them in an invalid state once the disposal is actioned.
To fix this, the factory should be configured to create a new handler for each client. You may wish to use the simpler syntax shown in the MS documentation.
// Existing syntax
services.AddHttpClient().Configure<HttpClientFactoryOptions>(
"myClient", options =>
options.HttpMessageHandlerBuilderActions.Add(builder =>
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
builder.PrimaryHandler = handler;
}));
// MS extension method syntax
services
.AddHttpClient("myClient")
// Lambda could be static if clientCertificate can be retrieved from static scope
.ConfigurePrimaryHttpMessageHandler(_ =>
{
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
return handler;
});

Related

How to fire a function on every request in .net core minimal API

Just starting out with .net core minimal API and trying to solve all the little issues that will have to work before it's usable in my situation.
This API will be accessible from multiple tenants so I need to check the calling domain on every request (including when authenticating via user/pass) and then set some variables that need to be accessible from all routes.
Can anyone point me in the right direction because I can't find any information on how this is achieved.
Thanks
UPDATE - some examples of where the middleware works and doesnt
So this is the middleware
app.Use((context, next) =>
{
// Lets assume the domain info is in the query string
context.Items["domain"] = context.Request.Query["domain"];
return next();
});
and this code works just fine
app.MapGet("/", async handler =>
{
// Get the domain and then return back the formatted string with the domain name
var domain = handler.Items["domain"];
await handler.Response.WriteAsJsonAsync($"Hello World! {domain}");
});
When I have an endpoint decorated with [AlowAnonymous] I have to put handler in brackets like this
app.MapGet("/whatever",
[AllowAnonymous] async (handler) =>
{
// Get the domain and then return back the formatted string with the domain name
var domain = handler.Items["domain"];
await handler.Response.WriteAsJsonAsync($"Hello World! {domain}");
});
If I have multiple class objects it borks (this has the httprequest, my db class and a login class). The error is Delegate 'RequestDelegate' does not take 4 arguements.
app.MapGet("/whatever2",
[AllowAnonymous] async (handler, HttpRequest request, SqlConnection db, Login login) =>
{
// Get the domain and then return back the formatted string with the domain name
var domain = handler.Items["domain"];
await handler.Response.WriteAsJsonAsync($"Hello World! {domain}");
});
Thanks
You can add a piece of middleware in the request pipeline to fetch any details needed for mapped APIs. For example...
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use((context, next) =>
{
// Lets assume the domain info is in the query string
context.Items["domain"] = context.Request.Query["domain"];
return next();
});
app.MapGet("/", async handler =>
{
// Get the domain and then return back the formatted string with the domain name
var domain = handler.Items["domain"];
await handler.Response.WriteAsJsonAsync($"Hello World! {domain}");
});
app.Run();
The above adds middleware that runs first before attempting to map to any of the endpoints.

Changing Request Path in .Net Core 3.1

Prior to 3.0, I could change the path of a request (without any form of browser redirection) by just accessing the HttpRequest property of the HttpContext and then changed the value of the Path.
As an example, to display a page for a user who needed to change his/her password (irrespective of the page the user intended to visit), I extended the HttpContext
public static void ChangeDefaultPassword(this HttpContext context)
=> context.Request.Path = "/Account/ChangePassword";
This piece of code takes the user to the action method ChangePassword in the AccountController without executing the action method the user intends to visit.
Then enters dotnet core 3.1.
In 3.1, the extension method changes the path. However, it never executes the action method. It ignores the updated path.
I am aware this is due to the changes in the routing.The endpoint can now be accessed with the extension method HttpContext.GetEndpoint(). There is also an extension method HttpContext.SetEndpoint which seems to be the right way to set a new endpoint. However, there is no sample of how to accomplish this.
The Question
How do I change the request path, without executing the original path?
What I Have Tried
I tried changing the path. It seems routing in dotnet core 3.1 ignores the value of the HttpRequest path value.
I tried redirecting with context.Response.Redirect("/Account/ChangePassword");. This worked but it first executed the original action method requested by the user. This behavior defeated the purpose.
I tried using the extension method HttpContext.SetEndpoint, but there was no example available to work with.
The way I worked around this issue is to use EndpointDataSource directly, which is a singleton service that is available from DI as long as you have the routing services registered. It works as long as you can provide the controller name and the action name, which you can specify at compile time. This negates the need to use IActionDescriptorCollectionProvider or build the endpoint object or request delegate by yourself (which is pretty complicated...):
public static void RerouteToActionMethod(this HttpContext context, EndpointDataSource endpointDataSource, string controllerName, string actionName)
{
var endpoint = endpointDataSource.Endpoints.FirstOrDefault(e =>
{
var descriptor = e.Metadata.GetMetadata<ControllerActionDescriptor>();
// you can add more constraints if you wish, e.g. based on HTTP method, etc
return descriptor != null
&& actionName.Equals(descriptor.ActionName, StringComparison.OrdinalIgnoreCase)
&& controllerName.Equals(descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
});
if (endpoint == null)
{
throw new Exception("No valid endpoint found.");
}
context.SetEndpoint(endpoint);
}
I was able to find a working solution. My solution works by manually setting a new endpoint with the SetEndpoint extension method.
Here is an extension method I created to resolve this issue.
private static void RedirectToPath(this HttpContext context, string controllerName, string actionName )
{
// Get the old endpoint to extract the RequestDelegate
var currentEndpoint = context.GetEndpoint();
// Get access to the action descriptor collection
var actionDescriptorsProvider =
context.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
// Get the controller aqction with the action name and the controller name.
// You should be redirecting to a GET action method anyways. Anyone can provide a better way of achieving this.
var controllerActionDescriptor = actionDescriptorsProvider.ActionDescriptors.Items
.Where(s => s is ControllerActionDescriptor bb
&& bb.ActionName == actionName
&& bb.ControllerName == controllerName
&& (bb.ActionConstraints == null
|| (bb.ActionConstraints != null
&& bb.ActionConstraints.Any(x => x is HttpMethodActionConstraint cc
&& cc.HttpMethods.Contains(HttpMethods.Get)))))
.Select(s => s as ControllerActionDescriptor)
.FirstOrDefault();
if (controllerActionDescriptor is null) throw new Exception($"You were supposed to be redirected to {actionName} but the action descriptor could not be found.");
// Create a new route endpoint
// The route pattern is not needed but MUST be present.
var routeEndpoint = new RouteEndpoint(currentEndpoint.RequestDelegate, RoutePatternFactory.Parse(""), 1, new EndpointMetadataCollection(new object[] { controllerActionDescriptor }), controllerActionDescriptor.DisplayName);
// set the new endpoint. You are assured that the previous endpoint will never execute.
context.SetEndpoint(routeEndpoint);
}
Important
You must make the view of the action method available by placing it in the Shared folder. Alternatively, you may decide to provide a custom implementation of IViewLocationExpander
Before accessing the endpoint, the routing middleware must have executed.
USAGE
public static void ChangeDefaultPassword(this HttpContext context)
=> context.RedirectToPath("Account","ChangePassword");
Check your middleware order.
The middleware exposed by .UseRouting() is what's responsible for deciding which endpoint to hit based on the incoming request path. If your path rewrite middleware comes later in the pipeline (like mine was), it'll be too late and the routing decision has been made.
Moving my custom middleware before UseRouting() ensured that the path was set as I needed it before the routing middleware had been hit.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration telemetryConfig)
{
//snip
app.UseMiddleware<PathRewritingMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//snip
}
I had a similar reroute issue. In my case, I want to reroute users to a "you don't have permissions" view when an AuthorationHandler fails. I applied the following code, notably (httpContext.Response.Redirect(...)) in (.Net Core 3.1) to route me to a NoPermissions action on a Home Controller.
In the handler class:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FooBarRequirement requirement) {
var hasAccess = await requirement.CheckAccess(context.User);
if (hasAccess)
context.Succeed(requirement);
else {
var message = "You do not have access to this Foobar function";
AuthorizeHandler.NoPermission(mHttpContextAccessor.HttpContext, context, requirement, message);
}
}
I wrote a static class to handle the redirect, passing in the url expected by the controller and action plus an error message, and the redirect permanent flag set to true:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Foo.BusinessLogic.Security {
public static class AuthorizeHandler {
public static void NoPermission(HttpContext httpContext,
AuthorizationHandlerContext context, IAuthorizationRequirement requirement, string
errorMessage) {
context.Succeed(requirement);
httpContext.Response.Redirect($"/home/nopermission/?m={errorMessage}", true);
}
}
}
Finally, the controller and action that handles the view and message
[AllowAnonymous]
public IActionResult NoPermission(string m) {
return View("NoPermission", m);
}
}
In my case, I am manually selecting the matching endpoint in a DynamicRouteValueTransformer. I have a mostly working solution but have to switch to other priorities. Perhaps someone else can create a more elegant solution using built in Action executors.
RequestDelegate requestDelegate = async (HttpContext x) =>
{//manually handle controller activation, method invocation, and result processing
var actionContext = new ActionContext(x, new RouteData(values), new ControllerActionDescriptor() { ControllerTypeInfo = controllerType.GetTypeInfo() });
var activator = x.RequestServices.GetService(typeof(IControllerActivator)) as ServiceBasedControllerActivator;
var controller = activator.Create(new ControllerContext(actionContext));
var arguments = methodInfo.GetParameters().Select(p =>
{
object r;
if (requestData.TryGetValue(p.Name, out object value)) r = value;
else if (p.ParameterType.IsValueType) r = Activator.CreateInstance(p.ParameterType);
else r = null;
return r;
});
var actionResultTask = methodInfo.Invoke(controller, arguments.ToArray());
var actionTask = actionResultTask as Task<IActionResult>;
if (actionTask != null)
{
var actionResult = await actionTask;
await actionResult.ExecuteResultAsync(actionContext);//errors here. actionContext is incomplete
}
};
var endpoint = new Endpoint(requestDelegate, EndpointMetadataCollection.Empty, methodInfo.Name);
httpContext.SetEndpoint(endpoint);

Using Session with Threads in asp.net

I want to use a Session object through all my pages in my asp.net site, first I Save an object in my session like this, this line of code is in an HttpHandler
HttpContext.Current.Session["DocumnetInfo"] = doc;
after that I created a a thread to manipulate this doc and send the session as parameter to the thread as follows
HttpContext ctx = HttpContext.Current;
Thread t = new Thread(new ThreadStart(() =>
{
// HttpContext.Current = ctx;
SomeMethod(ctx);
}));
t.Start();
and In SomeMethod I read the Session as follows:
private void SomeMethod ( HttpContext ctx)
{
DocResultsBLL doc = (DocResultsBLL)ctx.Session["DocumnetInfo"];
// Here is the logic of the manipulation
// then save the doc in the session back
ctx.Session["DocumnetInfo"]=doc;
Response.Redirect("ResultsPage.aspx");
}
The problem is that I couldn't read the session in the results page.. HttpContext.Current is null.
1-How can I work with session , to send it to a thread, then to get it back outside the thread.
2- Is there any other scenario other than session that is better?
3- How can I stop the Thread if the Client Closed his browser?
The only you can do is like passing http context see the following link and you will understand why its not possible to have session available in multi threading application.
http://blogs.msdn.com/b/webtopics/archive/2009/01/30/why-can-t-i-execute-two-requests-from-the-same-session-simultaneously-for-an-asp-net-application.aspx

Unit testing with WebAPI odata

I am trying to move from a WebAPI based REST service, to one encompassing the new implimentation of OData. I have the service working correctly, but am at a loss on how create unit tests that will test the odata query options.
when unit testing WebAPI methods, I am used to building the httpRequestMessage and injecting it in the constructure:
var request = new HttpRequestMessage();
request.Headers.Add("UserName", "TestUser");
request.Headers.Add("Password", password);
request.Headers.Add("OverRideToken", "false");
request.Headers.Add("AccessSystem", "Mobile");
request.Headers.Add("Seed", "testSeed");
var token = new Token();
var authController = new AuthorizationController(request);
try
{
var returnValue = authController.Get();
how would I go about injecting the odata request? I need to verify that $filter, $inlinecount, and other options are returning the proper records.
You can either test your controller or you can test against a running instance of your Web API (you should probably do both).
Testing your controller won't achieve what you are trying to do, so you will want to test by creating a self hosted in-memory instance of your Web API application. You can then either use HttpClient in your test classes (you will have to manually construct OData requests), or you can use the WCF Data Services Client in your test classes (this will allow you to query via LINQ).
Here's an example using WCF Data Services Client:
public class ODataContainerFactory
{
static HttpSelfHostServer server;
public static MyApplicationServer.Acceptance.ODataService.Container Create(Uri baseAddress)
{
var config = new HttpSelfHostConfiguration(baseAddress);
// Remove self host requirement to run with Adminprivileges
config.HostNameComparisonMode = System.ServiceModel.HostNameComparisonMode.Exact;
// Register Web API and OData Configuration
WebApiConfig.Register(config);
// Configure IoC
ConfigureIoC(dataSource, config);
// Do whatever else, e.g. setup fake data sources etc.
...
// Start server
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
// Create container
var container = new MyApplicationServer.Acceptance.ODataService.Container(new Uri(baseAddress.ToString() + "odata/"));
// Configure container
container.IgnoreResourceNotFoundException = true;
container.IgnoreMissingProperties = true;
return container;
}
private static void ConfigureIoC(MockDatasource dataSource, HttpSelfHostConfiguration config)
{
var container = new UnityContainer();
container.RegisterType<TypeA, TypeB>();
...
...
config.DependencyResolver = new IoCContainer(container);
}
public static void Destroy()
{
server.CloseAsync().Wait();
server.Dispose();
}
}
The key here is the WebApiConfig.Register(HttpConfiguration config) method call, which is calling your Web API project.
Note that prior to the above you will need:
Fire up your Web API project
In your test class add a Service Reference to your OData root path.
This will create a Container object (in the example above MyApplicationServer.Acceptance.ODataService.Container), which you can use to query your OData feed in your tests as follows:
var odataContainer = ODataContainerFactory.Create(new Uri("http://localhost:19194/");
var result = odataContainer.MyEntities
.Expand(s => s.ChildReferenceType)
.Where(s => s.EntityKey == someValue).SingleOrDefault();

Problem with null object reference in Url.Action in MVC3 project

I am trying to set up a mocking scenario for my payment processor on a web site. Normally, my site redirects to the processor site, where the user pays. The processor then redirects back to my site, and I wait for an immediate payment notification (IPN) from the processor. The processor then posts to my NotifyUrl, which routes to the Notify action on my payments controller (PayFastController). To mock, I redirect to a local action, which after a conformation click, spawns a thread to post the IPN, as if posted by the processor, and redirects back to my registration process.
My mock processor controller uses the following two methods to simulate the processor's response:
[HttpGet]
public RedirectResult Pay(string returnUrl, string notifyUrl, int paymentId)
{
var waitThread = new Thread(Notify);
waitThread.Start(new { paymentId, ipnDelay = 1000 });
return new RedirectResult(returnUrl);
}
public void Notify(dynamic data)
{
// Simulate a delay before PayFast
Thread.Sleep(1000);
// Delegate URL determination to the model, vs. directly to the config.
var notifyUrl = new PayFastPaymentModel().NotifyUrl;
if (_payFastConfig.UseMock)
{
// Need an absoluate URL here just for the WebClient.
notifyUrl = Url.Action("Notify", "PayFast", new {data.paymentId}, "http");
}
// Use a canned IPN message.
Dictionary<string, string> dict = _payFastIntegration.GetMockIpn(data.paymentId);
var values = dict.ToNameValueCollection();
using (var wc = new WebClient())
{
// Just a reminder we are posting to Trocrates here, from PayFast.
wc.UploadValues(notifyUrl, "POST", values);
}
}
However, I get an 'Object reference not set to an instance of an object.' exception on the following line:
notifyUrl = Url.Action("Notify", "PayFast", new {data.paymentId}, "http");
data.paymentId has a valid value, e.g. 112, so I'm not passing any null references to the Url.Action method. I suspect I have lost some sort of context somewhere by calling Notify on a new thread. However, if I use just notifyUrl = Url.Action("Notify", "PayFast");, I avoid the exception, but I get a relative action URL, where I need the overload that takes a protocol parameter, as only that overload gives me the absolute URL that WebClient.UploadValues says it needs.
When you are inside the thread you no longer have access to the HttpContext and the Request property which the Url helper relies upon. So you should never use anything that relies on HttpContext inside threads.
You should pass all the information that's needed to the thread when calling it, like this:
waitThread.Start(new {
paymentId,
ipnDelay = 1000,
notifyUrl = Url.Action("Notify", "PayFast", new { paymentId }, "http")
});
and then inside the thread callback:
var notifyUrl = new PayFastPaymentModel().NotifyUrl;
if (_payFastConfig.UseMock)
{
// Need an absoluate URL here just for the WebClient.
notifyUrl = data.notifyUrl;
}

Resources