Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Null Result Issue with SignalRTrigger Functions in .NET8 Upgrade #2651

Closed
Furrman opened this issue Aug 7, 2024 · 8 comments
Closed
Labels
area: migration Items related to migration from the in-process model extensions: signal-r needs-investigation potential-bug Items opened using the bug report template, not yet triaged and confirmed as a bug

Comments

@Furrman
Copy link

Furrman commented Aug 7, 2024

What version of .NET does your existing project use?

.NET 6

What version of .NET are you attempting to target?

.NET 8

Description

Hello,

I wanted to bring up an issue with the SignalRTrigger solution. My team and I are using this approach in .NET6 for our project to trigger some asynchronous operations. In response, we are returning a custom object indicating whether the operation was successful. However, in .NET8, our SignalRTrigger functions consistently return a null result in the server response, regardless of our efforts to fix it. It is worth mentioning that during the .NET8 upgrade, we are migrating our functions to isolated mode.

Expected

Function with SignalR trigger and return type will send custom response in result field to client.

Actual

Function with SignalR trigger is sending null result to client.

Examples

Click me

Here is our server-side code in .NET6:

[SignalRHub(path: "")]
public partial class MainHub : ServerlessHub<IMainHub>
{
    ...
    [FunctionName(nameof(Echo))]
    public async Task<SignalRHubActionRequest> Echo([SignalRTrigger()] InvocationContext invocationContext, string name, string message)
    {
        await Clients.Client(invocationContext.ConnectionId).Echo(name, message);
        return new SignalRHubActionRequest { SignalRHubActionStatus = SignalRHubActionStatus.Accepted };
    }
    ...
}

public class SignalRHubActionRequest
{
    public string Id { get; set; }
    public Status Status { get; set; }
}

public enum Status
{
    Success,
    Failure
}

The only change in .NET8 solution for this code snippet is the Echo function (in the SignalRTrigger attribute):

[Function(nameof(Echo))]
public async Task<SignalRHubActionRequest> Echo([SignalRTrigger(nameof(MainHub), CATEGORY, nameof(Echo), "name", "message")] SignalRInvocationContext invocationContext, string name, string message)
{
	await Clients.Client(invocationContext.ConnectionId).Echo(name, $"async : {message}");
	return new SignalRHubActionRequest { SignalRHubActionStatus = SignalRHubActionStatus.Accepted };
}

This is how we registered SignalR in .NET6:

builder.Services.Configure<SignalROptions>(o => {
    var settings = new JsonSerializerSettings()
    {
        NullValueHandling = NullValueHandling.Ignore,
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    };
    settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());

    o.JsonObjectSerializer = new NewtonsoftJsonObjectSerializer(settings);
});

This is our registration in .NET8:

services.AddSignalR()
        .AddNewtonsoftJsonProtocol(options =>
        {
            options.PayloadSerializerSettings = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };
            options.PayloadSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
        });

And this is our C# SignalR client code:

await using var connection = new HubConnectionBuilder()
	.WithAutomaticReconnect()
	.WithUrl(uriTime, options =>
	{
		options.AccessTokenProvider = () => Task.FromResult(accessToken);
	})
	.AddNewtonsoftJsonProtocol(options =>
	{
		options.PayloadSerializerSettings = new JsonSerializerSettings
		{
			NullValueHandling = NullValueHandling.Ignore,
			ContractResolver = new CamelCasePropertyNamesContractResolver()
		};
		options.PayloadSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
	});
await connection.StartAsync();

var result01 = await connection.InvokeAsync<SignalRHubActionRequest>("Echo", name, message);

We inspected the request and observed that the server side is returning null in the result property, regardless of what we attempt to send (e.g., a string instead of SignalRHubActionRequest or changing serializer to default or json one). We confirmed this by using tools like ngrok and Fiddler.
image
image

We tried several approaches, with the most promising one being the use of the SignalROutput attribute to bind the output:
[SignalROutput(HubName = nameof(MainHub), ConnectionStringSetting = "AzureSignalRConnectionString")]
image
image

This resulted in a server-side error. When we changed the return type to string while using this SignalROutput attribute, we encountered a different error.
image

Ultimately, we downloaded the latest codebase from your repository and used your samples, specifically the Extensions solution, and experienced the same null result issue.

This indicates that the problem lies within your library. We are unsure of the cause of these varying errors, but based on these examples, it appears you can return error objects. This implies you should be able to return an object in the result property as well. We initially thought this functionality was removed, but the errors suggest otherwise. Additionally, your documentation does not indicate that the feature to return objects from a SignalRTrigger function was removed. Therefore, we are requesting your assistance and investigation into this issue.

Thank you.

Project configuration and dependencies

Azure function app

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
	<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
	<GenerateDocumentationFile>True</GenerateDocumentationFile>
	<Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
	<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
	<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.7" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.1.4" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.3.2" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.Analyzers" Version="1.0.2" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Rpc" Version="1.0.0" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.20.0" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.SignalRService" Version="1.14.0" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.3.1" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk.Analyzers" Version="1.2.1" />
	<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk.Generators" Version="1.3.2" />
	<PackageReference Include="SignalRSwaggerGen" Version="4.7.0" />
  </ItemGroup>
  <ItemGroup>
    <Using Include="System.Threading.ExecutionContext" Alias="ExcutionContext" />
  </ItemGroup>
</Project>

Client app:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.7" />
    <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.7" />
  </ItemGroup>
</Project>

Link to a repository that reproduces the issue

https://github.com/Azure/azure-functions-dotnet-worker/tree/main/samples/Extensions/SignalR

@Furrman Furrman added the area: migration Items related to migration from the in-process model label Aug 7, 2024
@waqarzafar
Copy link

I am having same issue as well so looks like SignalR has not caught up with recent .NET8 upgrades

@RedChris
Copy link

RedChris commented Aug 8, 2024

I was just going to post something similar, Im also seeing this issue.

@Furrman
Copy link
Author

Furrman commented Aug 8, 2024

I have did investigation on your source code and looks like you are passing the return object to the Sytem.Threading.Channels library in GrpcWorker:ProcessRequestCoreAsync() to send it over to the client side. I cannot go further but seems something there is ignoring our return object. It can be wrong assigment or anything, but it is outside of my reach.
There is one more thing that got my attention that in your main branch, in DotNetWorker.Grpc project, you do not have setup .net8 dedicated packages - instead you refer to System.Threading.Channels to 6.0.0 version in your .NETStandard references. I checked and the newest package version is 8.0.0 which has high probability it is the version prepared for .NET8. Can it be related to this issue? Maybe updating reference would fix the problem we are facing?
At last I can add, that our codebase with working solution was using Microsoft.Azure.WebJobs.Extensions.SignalRService package instead (for in-process mode in function app).

@Furrman
Copy link
Author

Furrman commented Aug 8, 2024

Can someone from admins add label potential-bug and need-attention?

@Furrman Furrman changed the title Null Result Issue with SignalRTrigger Functions in .NET8 Upgrade Bug: Null Result Issue with SignalRTrigger Functions in .NET8 Upgrade Aug 20, 2024
@nickjd331
Copy link

Hi,

Has anyone seen any updates on this issue being investigated?

This is also impacting my live project and preventing me from upgrading to .NET8, so I'm eager for this to be resolved asap.

Thanks.

@satvu satvu added extensions: signal-r potential-bug Items opened using the bug report template, not yet triaged and confirmed as a bug needs-investigation and removed Needs: Triage (Functions) labels Sep 12, 2024
@satvu
Copy link
Member

satvu commented Sep 12, 2024

@Y-Sindo Is this something you can help look into?

@Y-Sindo
Copy link
Member

Y-Sindo commented Sep 14, 2024

@satvu This is a known issue, see #1496 . Please see #1496 to track the progress.

@satvu
Copy link
Member

satvu commented Oct 1, 2024

Closing this as duplicate - please track using #1496

@satvu satvu closed this as completed Oct 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: migration Items related to migration from the in-process model extensions: signal-r needs-investigation potential-bug Items opened using the bug report template, not yet triaged and confirmed as a bug
Projects
None yet
Development

No branches or pull requests

6 participants