HttpControllerContext.Configuration is sometimes null during Content negotiation - c#

We are setting up a ASP.NET 4.6.2 Web Api host and noticing that some requests fail because of the following error:
HttpControllerContext.Configuration must not be null.
I've been unable to reproduce the issue and wanted to ask if anybody could steer me in the right direction?
This is the stack trace:
[0] System.InvalidOperationException "HttpControllerContext.Configuration must not be null."
at System.Web.Http.Results.NegotiatedContentResult`1.ApiControllerDependencyProvider.EnsureResolved()
at System.Web.Http.Results.NegotiatedContentResult`1.ApiControllerDependencyProvider.get_ContentNegotiator()
at System.Web.Http.Results.BadRequestErrorMessageResult.Execute()
at System.Web.Http.Results.BadRequestErrorMessageResult.ExecuteAsync(CancellationToken cancellationToken)
at System.Web.Http.Controllers.ApiControllerActionInvoker.d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__15.MoveNext()

A similar error occurred in my tests when adding headers to a BadRequestErrorMessageResult. The fix only required to initialize the controller's Configuration.
...and subsequently initialize the Request.
So my test's setup ended up looking something like
public class FooControllerTest
{
private FooController _sut;
[Setup]
public void Setup()
{
_sut = new FooController();
_sut.Configuration = new System.Web.Http.HttpConfiguration();
_sut.Request = new System.Net.Http.HttpRequestMessage();
}
}
Hope that helps.

Related

How to retrieve cancellation token in web-api action that consumes multipart/* request (.Net 5)

I have a web.api action that accepts multipart/form-data via streaming, so it doesn't have any arguments:
[HttpPost("api/longrunning")]
[Consumes("multipart/form-data")]
public async Task<IActionResult> LongRunningAsync(){
string? boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
DefaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
MultipartSection? section = await reader.ReadNextSectionAsync(); // <---- exception thrown here if below argument is specified
// imagine the rest of below link is implemented here
// https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-5.0#upload-large-files-with-streaming
// tldr: it streams incoming files directly to disk, and some form-data in memory, each part is processed
// as a stream, so I can't use attributes like [FormData], because that would put everything in memory
// If I used [FormData] with a poco, and cancellation token as two arguments, it would probably work, but that doesn't support streaming
}
However this way I don't have any cancellation tokens available.
So I try to use the default .Net supported cancellation:
public async Task<IActionResult> LongRunningAsync(CancellationToken cancellationToken){
In a normal controller this works fine, however in this type of controller I get an exception because of the Multipartreader:
Unexpected end of Stream, the content may have already been read by another component.
at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.<ReadAsync>d__36.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Microsoft.AspNetCore.WebUtilities.StreamHelperExtensions.<DrainAsync>d__3.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.WebUtilities.MultipartReader.<ReadNextSectionAsync>d__20.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at MyNamespace.MyController.<LongRunningAsync>d__8.MoveNext() in C:\Source\Project\MyController.cs:line 94
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.<Execute>d__0.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Threading.Tasks.ValueTask`1.get_Result()
at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeActionMethodAsync>g__Logged|12_1>d.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeNextActionFilterAsync>g__Awaited|10_0>d.MoveNext()
So how can I solve this? Usually when the framework detects the request is cancelled or abandoned, the built-in cancellation token fires. But in this case I can't use that, but I would really like to have the api cancelled requests, so it can trickle down and cancel all applicable operations.
Since in .Net web api the controllers derive from ControllerBase, they all have a HttpContext instance property, you can use its RequestAborted token.
corresponding docs
So something like this:
[HttpPost("api/longrunning")]
[Consumes("multipart/form-data")]
public async Task<IActionResult> LongRunningAsync(){
var cancellationToken = this.HttpContext.RequestAborted;
string? boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
DefaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
MultipartSection? section = await reader.ReadNextSectionAsync(cancellationToken); // you can already start passing it to methods that support it
// ....
}

ReflectionTypeLoadException exception in Asp.Net Core in-memory TestServer

I create a IWebHostBuilder like so:
public IWebHostBuilder GetWebHostBuilder()
{
return new WebHostBuilder().UseContentRoot(_contentRoot)
.ConfigureServices(InitializeServiceCollection)
.UseEnvironment(_environment)
.UseConfiguration(GetConfiguration())
.UseStartup(typeof(TStartup));
}
Here, the InitializeServiceCollection is implemented like so:
private void InitializeServiceCollection(IServiceCollection services)
{
var manager = new ApplicationPartManager();
manager.ApplicationParts.Add(new AssemblyPart(_assembly));
manager.FeatureProviders.Add(new ControllerFeatureProvider());
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
services.AddSingleton(manager);
}
Then I create the TestServer like so:
var myTestServer = new TestServer(GetWebHostBuilder());
Here I get the exception (full exception below). It gets thrown at services.AddAutoMapper(); method call in the system under test. However, when I run the system under test on it's own and test it manually with Postman, it's working fine and the object mapping with Automapper is working well too. It just raises the exception in the integration test.
Full exception:
System.AggregateException : One or more errors occurred. (Unable to load one or more of the requested types.
Could not load type 'Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalFullAnnotationNames' from assembly 'Microsoft.EntityFrameworkCore.Relational, Version=2.2.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.) (The following constructor parameters did not have matching fixture data: TestFixture fixture)
---- System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.
Could not load type 'Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalFullAnnotationNames' from assembly 'Microsoft.EntityFrameworkCore.Relational, Version=2.2.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
---- The following constructor parameters did not have matching fixture data: TestFixture fixture
----- Inner Stack Trace #1 (System.Reflection.ReflectionTypeLoadException) -----
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.RuntimeAssembly.get_DefinedTypes()
at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.ToArray()
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at AutoMapper.ServiceCollectionExtensions.AddAutoMapperClasses(IServiceCollection services, Action`2 configAction, IEnumerable`1 assembliesToScan) in xxx\automapper-extensions-microsoft-dependencyinjectio\src\AutoMapper.Extensions.Microsoft.DependencyInjection\ServiceCollectionExtensions.cs:line 72
at xxx.Startup.ConfigureServices(IServiceCollection services) in xxx\Startup.cs:line 35
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Microsoft.AspNetCore.TestHost.TestServer..ctor(IWebHostBuilder builder, IFeatureCollection featureCollection)
at xxx.Tests.TestFixture.InitializeServer() in xxx.Tests\TestFixture.cs:line 67
at xxx.Tests.TestFixture..ctor() in xxx.Tests\TestFixture.cs:line 31
----- Inner Stack Trace #2 (Xunit.Sdk.TestClassException) -----
So, I managed to resolve the issue by replacing services.AddAutoMapper() with services.AddAutoMapper(Type[] types) overload. Not sure what is wrong with the original method, but in order to avoid that reflective call on the assemblies, I used this overload and it's working now.

How to log exception stack trace using stackdriver in .net-core?

I have setup stackdriver logging on a .net core 2.0 project and it seems to work fine. There is one issue while logging exceptions - stack trace or any exception related info is not getting logged.
The below piece of code throws a null exception and .LogError method is used to log the exception - it takes an exception object as a parameter.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory) {
StackdriverLogger = loggerFactory.CreateLogger("ImageCompressInfo");
try {
throw new NullReferenceException("null");
} catch (Exception ex) {
StackdriverLogger.LogError(ex, "Exception in configure 2", null);
}
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseGoogleExceptionLogging();
app.UseMvc();
}
The following log is generated. As you can see there is no exception related info - not even the string argument passed while throwing the exception.
Am I using the library correctly? Is there a way to capture the entire stack trace using .net-core logging and stackdriver?
Edit - Unhandled Errors are caught and reported to Error Reporting. The same errors are also present in Logs with complete stack trace. It seems when using .LogError() function, errors do not forward to Error Reporting.

The service on Local Computer started then stopped

I've created a Service that I've installed on several machines. They all work fine except for one. When I try to start the service via Windows Services I get the following error:
The My.Service service on Local Computer started and then stopped. Some services stop automatically if they are not in use by other services or programs.
I've seen other posts suggesting you change the service's log on properties to log on as a local system account - I'm already doing this.
Event viewer gives me this information:
Application: myProject.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AggregateException
Stack:
at Microsoft.AspNetCore.Server.Kestrel.KestrelServer.Start[[Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context, Microsoft.AspNetCore.Hosting, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]](Microsoft.AspNetCore.Hosting.Server.IHttpApplication`1<Context>)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.Start()
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(Microsoft.AspNetCore.Hosting.IWebHost, System.Threading.CancellationToken, System.String)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(Microsoft.AspNetCore.Hosting.IWebHost)
at Nexus.Startup.Main(System.String[])
I've tried running the service manually from a command prompt and passing in --debug and received this error message:
09:05:47.289 [1] ERROR -
System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Listener.<DisposeAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.ListenerPrimary.<DisposeAsync>d__20.MoveNext()
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout)
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, TimeSpan timeout)
at Microsoft.AspNetCore.Server.Kestrel.Internal.KestrelEngine.DisposeListeners(List`1 listeners)
at Microsoft.AspNetCore.Server.Kestrel.Internal.KestrelEngine.CreateServer(ServerAddress address)
at Microsoft.AspNetCore.Server.Kestrel.KestrelServer.Start[TContext](IHttpApplication`1 application)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.Start()
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host, CancellationToken token, String shutdownMessage)
at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
at MyProject.Startup.Main(String[] args) in D:\bamboo-home\xml-data\build-dir\163841\CUT-CUP-CR\repos\myProject\Startup.cs:line 42
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Listener.<DisposeAsync>d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.ListenerPrimary.<DisposeAsync>d__20.MoveNext()<---
I'm fairly confused since this service starts up perfectly fine on other machines.
Here is my Startup.cs main method:
public static void Main(string[] args)
{
var exePath = Process.GetCurrentProcess().MainModule.FileName;
var directoryPath = Path.GetDirectoryName(exePath);
if (Debugger.IsAttached || args.Contains("--debug"))
{
var host = new WebHostBuilder()
.CaptureStartupErrors(true)
.UseKestrel()
.UseUrls("http://localhost:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
else
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://localhost:5000")
.UseContentRoot(directoryPath)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.RunAsService();
}
}
#ScottChamberlain Yes, I passed in --debug – Roka545
You can't do that. Run() is a blocking call that does not allow the windows service to fully start. You need to always need to do RunAsSevice() from inside a service even if you are debugging. The pattern I normally do is
if (args.Contains("--console"))
{
var host = new WebHostBuilder()
.CaptureStartupErrors(true)
.UseKestrel()
.UseUrls("http://localhost:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
else
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://localhost:5000")
.UseContentRoot(directoryPath)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.RunAsService();
}
So debugger attached or not, the service runs as a service when you start it from within a service. If you want to run it standalone (like in F5 from visual studio) you pass --console to the arguments.

Performing a server-side call using async/await pattern (error: TaskCanceledException)

I'm performing an AJAX call to my Web API, using the technology WebAPI.
The method that handles that request is the following:
[Route("RenderNotificationsPopup")]
[HttpGet]
public async Task<string> RenderNotificationsPopup(bool responsive = false)
{
return await GetContentAsync(string.Format("RenderNotificationsPopup?responsive={0}", responsive ? 1 : 0));
}
This method calls GetContentAsync which peforms a server-side call using the class HttpClient.
private async Task<string> GetContentAsync(string urlSuffix)
{
using (var client = new HttpClient())
{
return await client.GetStringAsync(string.Format(
"{0}/Header/{1}",
ConfigurationManager.AppSettings["GatewaysHttpRoot"],
urlSuffix));
}
}
Everything is working fine, except when users close the page while the AJAX query is pending a response from WebApi. In that case, I get this error:
Message: System.Threading.Tasks.TaskCanceledException: A task was
canceled. at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at
System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
— End of stack trace from previous location where exception was thrown
— at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at
System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()
— End of stack trace from previous location where exception was thrown
— at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task
task) at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at
System.Web.Http.Controllers.ExceptionFilterResult.d__0.MoveNext()
I can reproduce this issue myself by loading the page and closing it before I get the response of the AJAX query, like if leaving the page before while WebApi is processing the query is struggling itself.
I searched on the Web and saw that there is a bug related to that (ASP.NET Web API OperationCanceledException when browser cancels the request). Despite that, it apparently concerns an old version of WebApi package and I'm using the latest version.
Did I do a mistake in my code about the async/await pattern or is it always that nasty bug ?
Many thanks !
EDIT: I tried the workaround given in the SO post I linked (using the class CancelledTaskBugWorkaroundMessageHandler) and I still get the TaskCanceledException.
EDIT 2: Seems that this issue cannot be avoided. Is it possible to handle such exceptions in WebApi applications ?
I use my own derived class of ExceptionFilterAttribute in order to log the exception related to the WebApi application, overriding the method OnException.
The proper way I found to handle the exception TaskCanceledException is to override OnExceptionAsync instead of OnException and rely on the property IsCancellationRequested to detect when the task is canceled.
Here is the method I use as error handling:
public override async Task OnExceptionAsync(HttpActionExecutedContext context, CancellationToken cancellationToken)
{
if (!cancellationToken.IsCancellationRequested)
{
//Handle domain specific exceptions here if any.
//Handle all other Web Api (not all, but Web API specific only ) exceptions
Logger.Write(context.Exception, "Email");
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "An unhandled exception was thrown.");
}
}
Where the class Logger comes from the Enterprise Library Application Blocks.
It is normal for the task to be canceled, if the underlying client connection is dead. You need to handle the exception.

Categories