After the migration: What to do once your Function App is up and running again
The Function App that has been migrated to the isolated worker compiles, restarts, and the functions are indexed. At the same time, configuration, dependency injection, serialization, and logging behave differently compared to the in-process model. Some of these differences show up as unnoticed misbehavior without any error message. The isolated worker accepts code in many places that still follows the old model, and this code is the source of the problems that only become apparent during runtime. The isolated worker guide only mentions this crucial point as a comment and an HTTP 200 is not proof of correct serialization. For the most part, it doesn't matter which .NET version is used, whether it's .NET 8 as an intermediate step, .NET 10, or .NET 11, which is scheduled to be released on November 10, 2026. With one exception, the following points depend on the model and the package versions, not the .NET version.
If you haven't yet migrated from in-process to the isolated worker, you can find the steps on how to do so in my previous article on migrating to .NET 10.
appsettings.json is not taken into account
In the in-process model, the FunctionsStartup was a guest within the host of the Functions runtime. You overrode Configure, and the foreign runtime environment was what called it. In contrast, the Program.cs of the isolated model is a fully-fledged entry point, similar to a console application. This file explicitly defines which configurations are loaded, which services are registered, and which middleware is used during function calls.
For the Program.cs, there are two common patterns. The older approach creates the host using new HostBuilder(), while the current approach uses FunctionsApplication.CreateBuilder(args):
var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();
builder.Build().Run();
The isolated worker guide shows both variants. FunctionsApplication.CreateBuilder(args) is the primary approach, which requires the 2.x versions of the worker NuGet packages.
The HostBuilder variant remains a supported alternative. A key difference is in the configuration: FunctionsApplication.CreateBuilder() automatically loads appsettings.json, while new HostBuilder(), used in the older examples, does not. Host.CreateDefaultBuilder(args) loads it, however. If you use an example with new HostBuilder(), you need to explicitly include the file as a configuration source, otherwise the settings will never be applied.
Two HTTP modes, and the choice has already been made
When you first created the Program.cs, you had already chosen one of two HTTP modes. Calling ConfigureFunctionsWebApplication() enables the ASP.NET Core integration, while omitting this line runs the app in the built-in default mode. For migrated in-process code, the integration is the preferred approach because it supports HttpRequest and IActionResult. Three differences are the focus of the rest of this article:
| ASP.NET Core Integration | Standard Mode | |
|---|---|---|
| Request and Response Types | HttpRequest and IActionResult, IResult, HttpResponse; HttpRequestData and HttpResponseData remain possible |
HttpRequestData, HttpResponseData |
| HTTP Response Serialization | For IActionResult and IResult, the ASP.NET Core layer is used; for HttpResponseData, the worker serializer is still used |
Worker serializer from the WorkerOptions |
AllowSynchronousIO |
Required whenever something accesses the request or response stream synchronously | Never required |
The two type families are not mutually exclusive. In an integrated application, a function can serialize using MVC, while the next function can use the worker. Therefore, both layers are listed in the middle row.
HttpRequestMessage and HttpResponseMessage no longer exist in the isolated model. Functions with this class model must be refactored in any case, regardless of the mode. The integration is also not a complete ASP.NET Core. The middleware pipeline of ASP.NET Core is not available, and routing remains the responsibility of the [HttpTrigger] attributes. Cross-cutting logic in the isolated model should be placed in the worker's middleware, which has its own model.
The logger is null, and the startup doesn't reveal it
In the in-process model, the host automatically recognized certain parameter types, especially ILogger, even in static classes without any DI registration. The isolated worker no longer knows this mechanism for ILogger. Instead, an instance class with constructor injection is used:
public class OrderFunctions
{
private readonly ILogger<OrderFunctions> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public OrderFunctions(ILogger<OrderFunctions> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
}
Dependency registration is done in the Program.cs via the Services property of the builder, with the familiar lifetimes of Singleton, Scoped, and Transient. IHttpClientFactory is provided via builder.Services.AddHttpClient(). You can bind typed settings using the Options pattern with builder.Services.Configure<T>().
In helper methods without their own constructor, the logger can be obtained via FunctionContext.GetLogger(). This requires that the FunctionContext actually reaches that point, i.e., is passed from the function. This is not the correct approach for your own services. They receive their ILogger<T> via constructor injection.
The error pattern when a copied ILogger parameter is used is twofold. The host still indexes the function, starts cleanly, and lists it completely. The worker runtime does not reject the unbound parameter when called; it passes null, silently and without warning. This behavior is not guaranteed anywhere. The error only becomes apparent during the first call. The stack trace shows ArgumentNullException.ThrowIfNull within LoggerExtensions.Log, called from, for example, LogInformation.
For a function that does not use the logger at all, nothing indicates the unbound parameter. The call still returns HTTP 200. A clean start does not guarantee a successful migration.
A second DI scenario highlights the difference between local execution and Azure. With versions 2.x of the worker, the host enables ValidateOnBuild and ValidateScopes by default in the Development environment. The Core Tools automatically set this environment locally. In Azure, the default is Production. There, these checks are not automatically enabled. Therefore, a misconfiguration, such as a singleton with a scoped dependency, will break the host startup locally with an AggregateException, but it may go unnoticed in Azure. The scoped service will then be resolved from the root scope and effectively live as long as the singleton. If you explicitly set AZURE_FUNCTIONS_ENVIRONMENT to Development in Azure, the difference disappears. Another approach involves the DI container itself and is independent of the environment:
using Microsoft.Extensions.DependencyInjection;
var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureContainer(new DefaultServiceProviderFactory(new ServiceProviderOptions
{
ValidateOnBuild = true,
ValidateScopes = true
}));
Log level changed, nothing happens
A common misunderstanding after the migration concerns the location of a setting. In the isolated model, two processes are configured separately:
| Setting | Location |
|---|---|
| Trigger and binding behavior (e.g., batch sizes) | host.json |
| Host log level | host.json |
| Connection settings for triggers and bindings | Environment variables, locally local.settings.json |
| Services, options, custom configuration sources, log level of your code | Worker (Program.cs, appsettings.json) |
General serializer (WorkerOptions) |
Worker (Program.cs) |
Custom configuration sources for the worker only reach your own code. Trigger and binding configurations must be provided through the Functions platform itself, i.e., via App Settings, Key Vault references, or App Configuration. Log levels that should apply to both processes must be set in both places. They do not have to be identical. The migration guide explicitly states that host.json does not affect the worker's logging, and vice versa.
The exception mentioned earlier applies to this section. Since .NET 10, the configuration binder also maps a null value to non-nullable value types and sets it to default(T). A "MaxRetries": null in the appsettings.json therefore results in 0. Previously, the same line would fail when binding with an InvalidOperationException and the message Failed to convert configuration value at 'MaxRetries' to type 'System.Int32', because the JSON provider would convert null into an empty string. Thus, an explicit error becomes an unnoticed numerical value. If this value controls a retry logic, it will only be noticed in a place far from the configuration. Those who take .NET 8 as an intermediate step still have this case ahead of them.
out and IBinder are gone
IBinder, out parameters, and IAsyncCollector<T> no longer exist. For IAsyncCollector<T>, the migration guide provides a direct replacement: binding to an array of the target type, i.e., T[].
For imperative bindings at runtime, the migration guide recommends the direct approach using the Service SDKs, i.e., injected clients instead of binding infrastructure. These are registered via the Microsoft.Extensions.Azure package and AddAzureClients() in the Program.cs. The same approach covers cases that cannot be mapped with T[]: incremental writing or a number of outputs that is only determined at runtime.
Multiple outputs from a function are placed into a dedicated return class, whose properties carry the output attributes. With ASP.NET Core integration, the [HttpResult] attribute marks the property that provides the HTTP response:
public class MyOutputType
{
[HttpResult]
public required IActionResult Result { get; set; }
[QueueOutput("myQueue")]
public required string MessageText { get; set; }
}
The availability of [HttpResult] depends on the package versions. The guide specifies the minimum versions as Azure.Functions.Sdk 1.0.0, Microsoft.Azure.Functions.Worker.Extensions.Http 3.2.0, and Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore 1.3.0. If the attribute is missing in an older project, these three package references are the first place to look for errors.
Contrary to what might be expected, [HttpResult] comes from the base package Microsoft.Azure.Functions.Worker.Extensions.Http, not from the AspNetCore variant. The ASP.NET Core integration handles IActionResult, IResult, and HttpResponseData at this point. However, the type is not checked during the build. If a DTO is used, the project compiles, and the source generator still writes a http binding to the metadata. This results in an HTTP 200 response with Content-Length: 0. The only message generated is No HTTP response returned, which is written at the Trace level. Therefore, with a typical logging configuration, an empty body remains behind a green status code.
Two more cases from the same area: Placing [QueueOutput] directly on the method instead of a property binds $return to the queue, and no HTTP binding is created. However, the generator reports two binding attributes on a method as AZFW0005. This is the only binding case that will prevent deployment.
Durable Functions are a separate topic with their own scope, ranging from the namespace change of the orchestration context to modified default values. The Durable Functions migration guide covers these changes separately.
HTTP status code 200 and a field that remains null
The unnoticed misbehavior that remains undetected for the longest time after the migration has no specific trigger. It occurs while the previous, explicit misbehavior is being corrected.
Synchronous operations are disallowed: the explicit error
The triggering line is unremarkable and was literally copied from the in-process code. An out parameter used as an output binding excludes async, and the compiler reports this combination with CS1988. If you had such an output binding in the in-process model, you had to read the request body synchronously:
var body = new StreamReader(req.Body).ReadToEnd();
This very line causes an explicit failure after the model change. With ConfigureFunctionsWebApplication(), Kestrel is in the call path, and Kestrel has prohibited synchronous reading since ASP.NET Core 3.0: "Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead." The same Kestrel restriction applies to the response with HttpResponseData.WriteString() and is documented in Issue #2184.
At the time of my tests, the isolated worker guide suggested a workaround as a commented-out line in a code example, but limited it to HttpRequestData and HttpResponseData, as well as serializers without asynchronous input/output:
// Only needed if using HttpRequestData/HttpResponseData and a serializer that doesn't support asynchronous IO
// builder.Services.Configure<KestrelServerOptions>(options => options.AllowSynchronousIO = true);
This restriction was too narrow. In the demo repository, the error occurs with HttpRequest and IActionResult, without any serializer being configured. Without ASP.NET Core integration, the same synchronous access results in HTTP 200. Therefore, the key factor is whether synchronous access is performed on the request or response stream when the integration is active.
The live guide now states the general condition first and the concrete examples after it (documentation pull request #128726). Asynchronous access to the HTTP streams is the recommended path, AllowSynchronousIO only a compatibility option.
The unnoticed error occurs when fixing the explicit one
The direct way to resolve the HTTP 500 error is to use the asynchronous version of the same read operation. In this case, Newtonsoft remains responsible for deserialization:
var body = await new StreamReader(req.Body).ReadToEndAsync();
var order = JsonConvert.DeserializeObject<Order>(body);
This change eliminates the synchronous access without changing the serialization behavior. The function now returns an HTTP 200 status code, and customer_name retains its value.
The silent misbehavior only occurs in the next, seemingly straightforward refactoring. The explicit read and deserialization code can apparently be replaced with a single line:
var order = await req.ReadFromJsonAsync<Order>();
However, this change not only alters the API but also the serializer. ReadFromJsonAsync<T>() uses System.Text.Json, while the DTO still uses [JsonProperty("customer_name")] from the Newtonsoft world. System.Text.Json ignores the foreign attribute, finds no matching property, and leaves it at its default value. There is no exception, no log entry, an HTTP status code of 200, but customer_name is null.
The return type determines the serialization path
The serialization layer that an HTTP response passes through is determined by the return type for JSON. IActionResult and IResult can also return files, streams, redirects, or plain status codes, and an HttpResponseData can be written manually. In these cases, no serialization occurs.
| Return Type | Layer | Default and Configuration |
|---|---|---|
HttpResponseData |
Worker pipeline | System.Text.Json (default) with Configure<JsonSerializerOptions>() for configuration or WorkerOptions.Serializer to switch serializers |
IActionResult |
MVC | System.Text.Json (default) with AddJsonOptions() for configuration or AddNewtonsoftJson() to switch to Newtonsoft |
IResult |
Minimal API | For JSON results, System.Text.Json (default) with ConfigureHttpJsonOptions() for global configuration |
A detail on the row HttpResponseData: Starting with Microsoft.Azure.Functions.Worker 2.0.0, WriteAsJsonAsync() no longer sets the status code to 200 OK. This preserves any previously set error code.
With ASP.NET Core integration enabled and IActionResult active, the response does not pass through the general worker serialization. Therefore, a configuration via WorkerOptions.Serializer is ineffective. The ASP.NET Core layer must be configured separately using the IMvcBuilder returned by AddMvc(). AddControllers() also provides an IMvcBuilder. If you adopt the example for general worker serialization, you will not receive any errors or warnings. The behavior remains the same.
On the input side, when ASP.NET Core integration is enabled, there are also three possible paths, and AddNewtonsoftJson() hits exactly one of them. The same DTO with [JsonProperty("customer_name")] and the same call. The table shows whether CustomerName is bound:
| Input path | Default | WorkerOptions.Serializer |
AddMvc().AddNewtonsoftJson() |
|---|---|---|---|
await req.ReadFromJsonAsync<T>() |
null |
null |
null |
[FromBody] from Microsoft.Azure.Functions.Worker.Http |
null |
null |
"Ada" |
[FromBody] from Microsoft.AspNetCore.Mvc |
null |
null |
null |
The middle row contradicts the expectation. The attribute carries the worker namespace, which is why you would expect the worker serializer to have an influence. However, this is not the case when ASP.NET Core integration is enabled. There are two implementations of IFromBodyConversionFeature, and ConfigureFunctionsWebApplication() determines which one is used. Without integration, DefaultFromBodyConversionFeature deserializes via WorkerOptions.Serializer. With integration, the FunctionsHttpProxyingMiddleware sets the FromBodyConversionFeature of the AspNetCore extension in the FunctionContext, and this binds via IModelBinderFactory with BindingSource.Body, i.e., via the MVC input formatters. There, AddNewtonsoftJson() takes effect, and WorkerOptions.Serializer does not. The Issue #2131 explicitly describes this behavior: "The FromBodyConversionFeature does not use the workerOptions.Serializer."
The unnoticed misbehavior remains the same in both cases, only the setting changes. If your fix does not work, it is worth first looking at the using directives.
[JsonIgnore] flips in both directions for the same reason, because each serializer only respects its own attributes. A field that was previously consciously excluded from the response becomes one that suddenly appears. If there is an internal value there, this is not just a cosmetic problem. On the input side, the same effect is simply over-posting. A field that the client should not be able to set is bound again.
Three solution paths, the same response body
In the demo repository, three solution paths are presented side by side. All of them produce the same compact response body as the in-process version:
{"orderId":"ORD-5","customer_name":"Ada","quantity":1}
This body is the benchmark. The in-process host serializes IActionResult with Newtonsoft and a camelCase policy. Only the field named [JsonProperty] retains its original casing.
Restructure attributes:
[JsonProperty]becomes[JsonPropertyName], Newtonsoft is removed from the project. This is the clean target solution because it only uses one attribute world afterwards.AddMvc().AddNewtonsoftJson()plus explicit deserialization withJsonConvert:
This path makes both serialization layers visible, but it is unnecessarily complex as a permanent solution.AddNewtonsoftJson()plus[Microsoft.Azure.Functions.Worker.Http.FromBody]:
This is the simplest transitional solution while maintaining the existing JSON structure. The fully qualified name prevents the accidental use of a similarly named MVC attribute. If both namespaces are included, the compiler reports an ambiguity withCS0104. If onlyMicrosoft.AspNetCore.Mvcis included,[FromBody]compiles, but the Functions worker does not recognize it as a body binding.
The three paths have different reach, so search the entire project for ReadFromJsonAsync, JsonConvert, and accesses to req.Body.
If you later replace the explicit JsonConvert deserialization with ReadFromJsonAsync, the previously described null field reappears. AddNewtonsoftJson() does not reach this API.
A ContractResolver does not need to be configured for any of the three approaches. AddNewtonsoftJson() already uses camelCase by default. If you still set the DefaultContractResolver, Newtonsoft serializes the body with PascalCase: {"OrderId":"ORD-5","customer_name":"Ada","Quantity":1}. This means that two of the three field names deviate from the in-process baseline. Also, AllowSynchronousIO is not needed for any of the three approaches, because all accesses to the request and response streams remain asynchronous.
The isolated worker guide does not mention either ReadFromJsonAsync or [FromBody] in relation to this limitation. This is precisely why the error is difficult to classify: both APIs process the same request body, but react to different serializer configurations. Issue #2131 confirms this behavior for [FromBody].
Both guides cited System.Text.Json as the worker default, but neither mentioned as a migration step that unmodified Newtonsoft attributes are silently ignored. That warning is now in the migration guide (documentation pull request #128734).
Enums, spelling, and DateTime
The rest of the serialization change is a matter of craftsmanship, but not without pitfalls. The worker serializer ignores the case of property names by default when reading. If you need stricter binding, you can override PropertyNameCaseInsensitive. For enums, the JsonStringEnumConverter from System.Text.Json replaces the StringEnumConverter from Newtonsoft. For DateTime and DateTimeOffset, System.Text.Json by default only accepts the extended ISO-8601-1:2019 profile. Deviating formats that the previous Newtonsoft code handled via DateFormatString or custom converters require a JsonConverter after the change.
Constructor selection, required members, numbers in quotes, reference cycles, polymorphism, public fields and unknown JSON members differ as well. The official page Migrate from Newtonsoft.Json to System.Text.Json covers each case. Which of them reach you depends on the layer from the table above. On the ASP.NET Core path, the web defaults already cover camelCase, case-insensitive matching and numbers in quotes. On the worker pipeline the same body is rejected without NumberHandling.
Two log sources and one filter rule
The table above indicates that hosts and workers are configured separately. A specific characteristic applies to logging. For an ILogger<T> injected via the constructor, the log category corresponds to the fully qualified class name. Because this name contains periods, its log level cannot be overridden using environment variables in Linux. Instead, it can be configured in the code or in appsettings.json. With the new HostBuilder() pattern, the restriction on appsettings.json described above still applies.
The isolated worker guide recommends using OpenTelemetry throughout for Application Insights: builder.Services.AddOpenTelemetry().UseFunctionsWorkerDefaults().UseAzureMonitorExporter() in the worker, with the packages Microsoft.Azure.Functions.Worker.OpenTelemetry and Azure.Monitor.OpenTelemetry.Exporter, and "telemetryMode": "OpenTelemetry" in the host.json. The name may be misleading. The UseFunctionsWorkerDefaults() in this chain is an OpenTelemetry extension method and has nothing to do with the ConfigureFunctionsWorkerDefaults() of the old HostBuilder pattern. This page no longer uses the traditional approach via AddApplicationInsightsTelemetryWorkerService and ConfigureFunctionsApplicationInsights. However, the migration guide shows this approach unchanged in both Program.cs examples. Both approaches are supported, but OpenTelemetry is recommended.
The most significant example of the separation of log sources concerns legacy applications using the traditional approach. The Application Insights SDK registers a default filter rule in the worker process that only allows warnings and higher-level messages to pass through. LogInformation calls seemingly do not reach Application Insights for no apparent reason. As with the serialization case, data is lost without any error occurring. The circulating fix removes this rule from the LoggerFilterOptions:
builder.Logging.Services.Configure<LoggerFilterOptions>(options =>
{
var defaultRule = options.Rules.FirstOrDefault(r =>
r.ProviderName == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider"
&& r.CategoryName is null
&& r.LogLevel == LogLevel.Warning);
if (defaultRule is not null) options.Rules.Remove(defaultRule);
});
These three conditions together exactly match the default rule of the SDK. If you only check for the provider name, you may inadvertently remove your own rule for the same provider.
Legacy applications are often still using the HostBuilder pattern. In that pattern, the same call is present in ConfigureServices because builder.Logging.Services and builder.Services are the same IServiceCollection.
The Functions guide no longer describes the filter rule in its current version, but it still exists in the package. This behavior is only documented on the archived page for the Classic API. The recommended approach is to switch to OpenTelemetry. This fix allows a legacy application to continue running until the migration, but the .NET Application Insights Classic API SDK 2.x itself retires on March 31, 2027.
The OpenTelemetry path does not set this rule. In the traditional approach, ApplicationInsightsLoggerProvider with Warning is the first filter rule, while in the OpenTelemetry approach, it is missing, and the remaining rules all come from FunctionsApplication.CreateBuilder(args).
Nine steps to check off
The sections build on each other, and there is a reason for this order. If you refactor the serialization before dependency injection, you will be testing functions whose logger may still be null. If you tackle the log sources before separating the configuration, you will be looking for the log level in the wrong place.
- Check
Program.csagainst the current builder pattern. With adoptedHostBuilderexamples in particular, settle theappsettings.jsonquestion. - Explicitly confirm the HTTP mode and standardize signatures.
- Refactor static function classes into instance classes with constructor injection, and remove
ILoggerparameters. - Sort the configuration according to the table above and set the log level at the appropriate location.
- Move cross-cutting logic into worker middleware, where the worker provides its own pipeline:
UseMiddleware<T>()for the entire invocation path,UseWhen<T>(predicate)for individual trigger types. - Implement successors for
IBinderandIAsyncCollector<T>using injected SDK clients and return classes. - Identify and convert any synchronous access to the request body to asynchronous operations before considering
AllowSynchronousIO. - Decide on a serialization strategy and implement it completely. Start at the layer that your invocation actually traverses, and test for
nullfields after an HTTP status code of 200. - Review log sources and filter rules, and determine the connection method.
A specific step is intentionally omitted from this list. Since September 1, 2026, Azure.Functions.Sdk has replaced the previous Microsoft.Azure.Functions.Worker.Sdk, integrated as an SDK attribute at the project level rather than as a PackageReference:
<Project Sdk="Azure.Functions.Sdk/1.0.1">
Four entries have to go: the PackageReference to the old SDK, OutputType, AzureFunctionsVersion, and FunctionsEnableWorkerIndexing. One reference remains. When cleaning up, Microsoft.Azure.Functions.Worker looks like the same package, but it is the worker itself. If you also remove it, you will get AZFW0111. Do not perform this change at the same time as the migration, otherwise you will attribute every issue to the wrong topic.
What this migration also enables: Azure Functions integration in .NET Aspire only supports the isolated model. It could not orchestrate the initial state, but it can orchestrate the target state. An AppHost runs against the migrated app without any changes to the application code. First, the necessary changes, then the possibilities that open up afterward. The prerequisites around the Functions code and the restore order during the first build are documented in the demo repository at docs/aspire.md. Introduce Aspire after the migration, as you would with an SDK change.
Wrapping up
A Function App that runs according to the new model has completed the smaller part of the journey. What remains will not cause the build to fail, and therefore falls outside the scope of a build-driven migration, remaining unnoticed. A null field after an HTTP status code of 200, an ArgumentNullException that only appears on the first log call, an empty body after Content-Length: 0, a 0 originating from a null in the appsettings.json, missing telemetry behind an invisible filter rule.
The serialization process took the most time when building this example. Not a single error, but the order in which the errors occur. The HTTP 500 when reading synchronously was found in a minute. Its clean repair creates the unnoticed null field, and the obvious fix for that affects a layer that your invocation doesn't even traverse. Three steps, each of which looks correct on its own.
With the nine steps in the checklist, an in-process application becomes one that truly uses the isolated model. You can reproduce all the described cases in the example repository, with each state as its own Git tag. The path to the isolated worker itself is described in my previous article on migrating to .NET 10.
If you perform this migration in your own codebase, I would like to know which of the unnoticed issues you encountered and how long it took you to discover them.
References
- Guide for running C# Azure Functions in the isolated worker model
- Differences between in-process and isolated worker process .NET Azure Functions
- Migrate .NET apps from the in-process model to the isolated worker model
- Migrate Durable Functions from in-process to isolated worker (.NET)
- Application Insights .NET Classic API, archived: Worker Service and ILogger
- azure-docs #128726: Correct the AllowSynchronousIO condition in the isolated worker guide
- azure-docs #128734: Warn that Newtonsoft attributes are ignored after the switch to System.Text.Json
- azure-functions-dotnet-worker #2184: Synchronous operations are disallowed
- azure-functions-dotnet-worker #2131: FromBody and FromBodyConversionFeature
- Breaking Change in .NET 10: Null values preserved in configuration
- Migrate from Newtonsoft.Json to System.Text.Json
- Azure.Functions.Sdk on NuGet