As the title says, I'm trying to handle Exceptions in my Azure functions middleware. It should be possible according to some articles, but I've not managed to make their code work for me. I've also checked the docs but that didn't handle Exception handling.
My Code (also on Github):
Program.cs:
using Microsoft.Extensions.Hosting;
namespace MiddleWareTest
{
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(
builder =>
{
builder.UseMiddleware<ExceptionLoggingMiddleware>();
}
)
.Build();
host.Run();
}
}
}
ExceptionLoggingMiddleware.cs:
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace MiddleWareTest
{
public class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
var logger = context.GetLogger(context.FunctionDefinition.Name);
logger.LogError("Unexpected Error in {0}: {1}", context.FunctionDefinition.Name, ex.Message);
}
}
}
}
Function1.cs
using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace MiddleWareTest
{
public class Function1
{
[Function("Function1")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req,
FunctionContext context)
{
throw new Exception("yo");
}
}
}
To my understanding this code should suffice to handle Exceptions, but whenever I execute Function1, the exception does not get handled by my Middleware, it's uncaught.
My question:
What am I missing/doing wrong in order to implement Exception handling in my Azure Functions Middleware?
Thanks in advance.
Thank you Andy for your suggestions, We believe the issue has been fixed as a result of the discussions in the comments, thus we are turning the above comments to an Answer for this thread to assist other community members.
As you may have seen, the issue is that you are using an outdated version of Microsoft.Azure.Functions.Worker updating to the lastest version from your Nuget package manager fixed your issue.
Related
I have an Azure Function HTTP triggered function which writes to Azure Table that may end in duplicated entries. I noticed that even if I try/catch'd the whole function, there will still be an Exception "leaked" to the function runner thus returning HTTP 500. Is there any way to catch this kind of exception?
Here's a minified version of the code:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
namespace FunctionTest
{
public class Entry
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
}
public static class Debug
{
[FunctionName("Debug")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequest req,
[Table("Debug")]
IAsyncCollector<Entry> tableBinding,
ILogger log)
{
try
{
await tableBinding.AddAsync(new Entry()
{
PartitionKey = "1111",
RowKey = "1111",
});
await tableBinding.FlushAsync();
}
catch (StorageException)
{
// we expect an Exception "The specified entity already exists"
return new OkObjectResult("This passes test");
}
return new OkObjectResult("This passes test too");
}
}
}
The code is written under Azure Function runtime 2.0 (the .NET Core one).
Trigger /api/debug twice or more and you will see:
HTTP 500
The catch{} code is entered, and still returns an HTTP 500(!)
In Application Insights, two table dependency call per request (shouldn't happen, the documentation says table do not have auto retry)
I guess, that using IAsyncCollector<> breaks things here. If you want to avoid such problems, try to exchange the following binding:
[Table("Debug")] IAsyncCollector<Entry> tableBinding
to:
[Table("Debug")] CloudTable tableBinding
Then, instead of using tableBinding.AddAsync() use the following snippet:
var op = TableOperation.Insert(new Entry());
await tableBinding.ExecuteAsync(op);
With that approach, you should be able to catch the exception, without leaking it to the Functions runtime.
Your try/catch block should look like following to catch all errors
try
{
}
catch (StorageException)
{
return new OkObjectResult("This passes test");
}
catch (Exception ex)
{
// return different error code
}
I've followed the getting started tutorial and currently have a TODO CRUD app.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api-mac?view=aspnetcore-2.1
I want to add a background worker that updates the todo database every 5 minutes and sets Item 1 to a random value for its Name, and isCompleted properties.
This is pretty easy in Java SpringBoot or Elixir's Phoenix...
Is there a quick and painless way in c# to do this?
The doc I found on Microsoft website was from 2012... so I assume there is a more elegant way to do this by now.
Edit: I went with DNTScheduler.Core and it was relatively painless to set up. Followed the exampleApp setup that was on github repo and here is the task i ended up using:
using System;
using System.Threading.Tasks;
using DNTScheduler.Core.Contracts;
using Microsoft.Extensions.Logging;
using myapp.Models;
namespace MyApp.Tasks
{
public class TaskOne : IScheduledTask
{
private readonly ILogger<DoBackupTask> _logger;
private readonly TodoContext _context; // autoinjects since it was added in startup.cs in configure()
public TaskOne(ILogger<DoBackupTask> logger, TodoContext context)
{
_logger = logger;
_context = context;
}
public Task RunAsync()
{
var todo = _context.TodoItems.Find(1);
if (todo == null)
{
return Task.CompletedTask;
}
string[] names = new string[] { "val1", "val2"};
Random r = new Random();
string random_name = names[r.Next(0, names.Length)];
todo.Name = random_name;
_context.TodoItems.Update(todo);
_context.SaveChanges();
_logger.LogInformation("Ran My Task\n\n ");
return Task.CompletedTask;
}
}
}
I would suggest you use Scheduler, there are some packages that can do that, but the best package that I have seen so far is DNTScheduler.Core
DNTScheduler.Core is a lightweight ASP.NET Core's background tasks runner and scheduler.
for more information, you can visit this link
I would argue that the correct answer is wrong if you want to follow the native practice.
Make use of IHostedService to perform repetitive actions.
Here’s an answer I’ve written that addresses that.
There is a saying i've read somewhere: "If its worth doing, its worth doing right. If its not worth doing right - find something that is."
So, as other colleagues already said, web application is not meant to be a container for background processes for reasons already mentioned.
Depending on your environment, you'll be much better off using:
windows services or even windows scheduler (on classic hosting) or some of the libraries for scheduling but outside of the web app. then, those scheduler, whichever it is, could just trigger API endpoint within your web app so you have business logic at one place
azure scheduler if you work with azure
pretty much anything else is looking for trouble when you don't need one.
In this sample I Insert a record to my database ( Word-Suggestion-Table in here) every hour using UnitOfWork and Repository Pattern. No error on Cannot consume scoped service *IUnitOfWork* from singleton
1- Add work class and IWork interface as below:
using System.Threading;
using System.Threading.Tasks;
namespace Map118.App.BackgroundTasks
{
public interface IWorker
{
Task DoWork(CancellationToken cancellationToken);
}
}
Work.cs :
using Map118.DataLayer.IRepositories;
using Map118.Models;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Map118.App.BackgroundTasks
{
public class Worker : IWorker
{
private readonly IServiceProvider serviceProvider;
public Worker( IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = serviceProvider.CreateScope())
{
var _unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
while (!stoppingToken.IsCancellationRequested)
{
WordSuggestion wordSuggestion = new WordSuggestion()
{
word = Guid.NewGuid().ToString(),
};
await _unitOfWork.wordSuggestionRepository.Add(wordSuggestion);
await _unitOfWork.Save();
await Task.Delay(1000 * 3600);
}
}
}
}
}
}
2- Add another class as below:
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
namespace Map118.App.BackgroundTasks
{
public class UserActivitiesCleaner : BackgroundService
{
private readonly IWorker worker;
public UserActivitiesCleaner(IWorker worker)
{
this.worker = worker;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await worker.DoWork(stoppingToken);
}
}
}
3- Add services to Startup.cs :
services.AddHostedService<UserActivitiesCleaner>();
services.AddSingleton<IWorker, Worker>();
I'm trying to use a Windows Runtime Component to provide interoperability between my Javascript UWP app and C# logic that I've written. If I set the minimum version to Fall Creator's Update (build 16299, needed to use .NET Standard 2.0 libraries), I get the following error when trying to call a simple method:
Unhandled exception at line 3, column 1 in ms-appx://ed2ecf36-be42-4c35-af69-93ec1f21c283/js/main.js
0x80131040 - JavaScript runtime error: Unknown runtime error
If I run this code using Creator's Update (15063) as the minimum, then the code runs fine.
I've created a Github repo containing a sample solution that generates the error for me when running locally.
Here's what main.js looks like. The error occurs when trying to run the getExample function:
// Your code here!
var test = new RuntimeComponent1.Class1;
test.getExample().then(result => {
console.log(result);
});
This is what Class1.cs looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
namespace RuntimeComponent1
{
public sealed class Class1
{
public IAsyncOperation<string> GetExample()
{
return AsyncInfo.Run(token => Task.Run(getExample));
}
private async Task<string> getExample()
{
return "It's working";
}
}
}
I can't think of a much simpler test case than that - I have no NuGet packages installed or anything like that. I have no idea what might be causing this. Anyone else have ideas?
There is nothing actually async about this function, even as a simplified example
private async Task<string> getExample()
{
return "It's working";
}
Also if the said function is already return a Task then there is no need to wrap it in Task.Run here
return AsyncInfo.Run(token => Task.Run(getExample));
Refactor the code to follow advised syntax
public sealed class Class1 {
public IAsyncOperation<string> GetExampleAsync() {
return AsyncInfo.Run(token => getExampleCore());
}
private Task<string> getExampleCore() {
return Task.FromResult("It's working");
}
}
Since there is nothing to be awaited, use Task.FromResult to return the Task<string> from the private getExampleCore() function.
Do note also that because the original functions were returning unstarted tasks, that this causes an InvalidOperationException to be thrown by AsyncInfo.Run<TResult>(Func<CancellationToken, Task<TResult>>) Method
You can also consider taking advantage of the AsAsyncOperation<TResult> extension method, given the simple definition of the called function.
public IAsyncOperation<string> GetExampleAsync() {
return getExampleCore().AsAsyncOperation();
}
And invoked in JavaScript
var test = new RuntimeComponent1.Class1;
var result = test.getExampleAsync().then(
function(stringResult) {
console.log(stringResult);
});
This is not proper async method:
private async Task<string> getExample()
{
return "It's working";
}
The reason for this is that it was supposed to return Task<string>, not just string.
So, you should change it to:
private async Task<string> getExample()
{
return Task.FromResult("It's working");
}
Code to demonstrate the problem:
Assume Test Thing is an real implementation, such as DB invocation.
The documentation for Web API states that unhanded exceptions will be caught in a global handler allowing you to process them.
If I replace MyErrorHandler with an ExceptionFilter this does indeed work, except the code base I'm working with uses handlers because the error logic is a cross cutting concern and will be the same regardless of where the error came from.
If the type of exception thrown is not a TaskCancelledException this invokes the handler as expected.
I've tried the latest version of Web API too (5.2.3).
The only work around is to add a try/catch block around everywhere that can throw this type of exception, needless to say this is painful and something I wish to avoid hence the use of the handler.
I hate to call this a bug given this is not my code, but after hours of attempts it's starting to feel that way.
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
namespace WebApplication3.Controllers
{
public class TestController : ApiController
{
public async Task<string> Get()
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
return await new TestThing().ExecuteAsync(cancellationTokenSource.Token);
}
}
public class MyErrorHandler : ExceptionHandler
{
public override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
public class TestThing
{
public async Task<string> ExecuteAsync(CancellationToken cancellationToken)
{
// Remove this to see the problem, I don't want to add these
// try/catch statements all over the codebase.
try
{
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
catch (Exception ex)
{
throw new Exception("Failure...");
}
return await Task.FromResult("Testing...");
}
}
}
Given the lack of suggestions or answers I went with a custom message handler.
public class AsyncFixHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await base.SendAsync(request, cancellationToken);
}
catch (TaskCanceledException)
{
// TODO: Log the issue here
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
}
This isn't ideal, but the try/catch is in one place. I'm using this successfully as the solution until something better comes along.
It seems like it's and old bug, and there are some people reporting issues still. I suggest you to create an issue in ASP.NET Github repo and use your workaround or another one for the moment.
In response of another SO questions, I came across a problem when running async Task with Xunit and visual studio 2015 ctp6.
here is the code:
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Xunit;
using Microsoft.AspNet.Builder;
using System.Net.Http;
namespace Multi.Web.Api
{
public class TestServerHelper : IDisposable
{
public TestServerHelper()
{
ClientProvider = new TestClientProvider();
ApiServer = TestServer.Create((app) =>
{
app.UseServices(services =>
{
services.AddTransient<IClientProvider>(s => ClientProvider);
});
app.UseMulti();
});
}
public TestClientProvider ClientProvider { get; private set; }
public TestServer ApiServer { get; private set; }
public void Dispose()
{
ApiServer.Dispose();
ClientProvider.Dispose();
}
}
public class MultiMiddlewareTest : IClassFixture<TestServerHelper>
{
TestServerHelper _testServerHelper;
public MultiMiddlewareTest(TestServerHelper testServerHelper)
{
_testServerHelper = testServerHelper;
}
[Fact]
public async Task ShouldReturnToday()
{
using (HttpClient client = _testServerHelper.ApiServer.CreateClient())
{
var response = await client.GetAsync("http://localhost/today");
String content = await response.Content.ReadAsStringAsync();
Assert.Equal(content, "2015-04-15 count is 1");
}
}
[Fact]
public async Task ShouldReturnYesterday()
{
using (HttpClient client = _testServerHelper.ApiServer.CreateClient())
{
var response = await client.GetAsync("http://localhost/yesterday");
String content = await response.Content.ReadAsStringAsync();
Assert.Equal(content, "2015-04-14 count is 1");
}
}
}
}
in visual studio TestExplorer, when running the test one by one (right click and select debug selected test) it's ok, but when running all, none of the passes and I have the following error
Message : Response status code does not indicate success : 404 (Not Fount)
all the code is available on the other so question, in that question, I answered on how to use multiple instance of TestServer to mock external Api. And I think it has to do with some Synchronization context.
I think I wrote my Helper not in a good way because I see it disposes objects before the call is actually done (sometimes not...). does someone had the same issue and had a solution on this ?
UPDATE : link to full code on github
Xunit is running your tests in parallel by default when you run all tests; I'm guessing this is probably causing your test servers to collide and have side effects. (I'm not sure what all packages you're using in your project.json, so I could be mistaken.) Take a look at Running tests in parallel in the Xunit documentation and find the solution right for you.
Options:
Use the CollectionAttribute such as [Collection("Localhost http server")].
Specify -parallel none as the option on the command line.
Change the default behavior using an assembly attribute such as [assembly: CollectionBehavior(DisableTestParallelization = true)]
UPDATE 2:
Update 1 was a red herring, so I removed it entirely. Removing await next(context); from the FakeExternalApi middleware seems to have removed the intermittent issues. A comment from #Tratcher indicates that "calling Next ... is discouraged," but I'm not sure if that's specifically related to OwinMiddleware or if it is good general advice, but it seems to apply here.