简体   繁体   English

ASP.NET Core 3.1 SignalR:方法未被调用

[英]ASP.NET Core 3.1 SignalR: method not being called

I have an app that is React on the front end and nodejs on the backend.我有一个应用程序,前端是 React,后端是 nodejs。 I have decided to rewrite the back end in ASP.NET Core 3.1 using SignalR.我决定使用 SignalR 在 ASP.NET Core 3.1 中重写后端。 I am new to both ASP.NET Core 3.1 and SignalR, and so I am having a little difficulty identifying the cause of the problem I am having.我是 ASP.NET Core 3.1 和 SignalR 的新手,所以我在确定我遇到的问题的原因时有点困难。

The problem is that the hub method that I am calling from the front end is not being hit.问题是我从前端调用的集线器方法没有被命中。 I followed the example a few days ago and managed to get the hub method to be called, but since introducing a few features, such as JWT authentication as described here , and MongoDB, the method is now not being called.几天前我按照这个例子成功地调用了集线器方法,但是由于引入了一些特性,例如这里描述的JWT 身份验证和 MongoDB,现在没有调用该方法。 I cannot work out why!我不知道为什么!

The connection appears to be successful considering the logs output in the browser.考虑到浏览器中的日志输出,连接似乎是成功的。

My startup.cs looks like this:我的startup.cs看起来像这样:

namespace MpApp.API
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddCors(options =>
      {
        options.AddPolicy("AllowSpecificOrigin",
          builder =>
          {
            builder
              .WithOrigins("http://localhost:3000", "http://localhost:3010")
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials();
          });
      });

      var domain = $"https://{Configuration["Auth0:Domain"]}/";
      services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
          options.Authority = domain;
          options.Audience = Configuration["Auth0:Audience"];

          options.Events = new JwtBearerEvents
          {
            OnMessageReceived = context =>
            {
              var accessToken = context.Request.Query["access_token"];

              // If the request is for our hub...
              var path = context.HttpContext.Request.Path;
              if (!string.IsNullOrEmpty(accessToken) &&
                  (path.StartsWithSegments("/chathub")))
              {
                // Read the token out of the query string
                context.Token = accessToken;
              }

              return Task.CompletedTask;
            }
          };
        });

      services.AddAuthorization(options =>
      {
        options.AddPolicy("read:messages",
          policy => policy.Requirements.Add(new HasScopeRequirement("read:messages", domain)));
      });

      services.AddControllers();

      services.AddSignalR();

      // Register the scope authorization handler
      services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();

      Debug.WriteLine("===== about to init config =====");
      services.Configure<DatabaseSettings>(
        Configuration.GetSection(nameof(DatabaseSettings)));

      services.AddSingleton<IDatabaseSettings>(sp =>
        sp.GetRequiredService<IOptions<DatabaseSettings>>().Value);

      services.AddSingleton<IBaseService, BaseService>();

      services.AddSingleton<CollectionService<Profile>>();
      services.AddSingleton<CollectionService<User>>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }
      else
      {
        app.UseHsts();
      }

      app.UseHttpsRedirection();

      app.UseRouting();

      app.UseCors("AllowSpecificOrigin");

      app.UseAuthentication();
      app.UseAuthorization();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllers();
        endpoints.MapHub<ChatHub>("/chathub");
      });
    }
  }
}

and my ChatHub.cs looks like this:我的ChatHub.cs看起来像这样:

namespace MyApp.API.Hubs
{
  public class ChatHub : Hub
  {
    protected CollectionService<Profile> _profileService;
    protected CollectionService<User> _userService;

    public ChatHub(CollectionService<Profile> profileService, CollectionService<User> userService)
    {
     // This constructor is being called
      _profileService = profileService;
      _userService = userService;
    }

    [Authorize]
    public async Task UpdateProfile()
    {
      // I have put a breakpoint here but it is not being hit
      await Clients.All.SendAsync("Test");
    }
  }
}

The React front end appears to be waiting for the connection correctly, as well as invoking the method correctly, but it's not working! React 前端似乎在正确等待连接,以及正确调用方法,但它不起作用!

The pertinent code from my React provider looks like this:我的 React 提供者的相关代码如下所示:

useEffect(() => {
  (async () => {
    if (isAuthenticated) {
      const accessToken = await getAccessTokenSilently();

      const connection = new signalR.HubConnectionBuilder()
        .configureLogging(signalR.LogLevel.Debug)
        .withUrl('http://localhost:3010/chathub', {accessTokenFactory: () => accessToken})
        .build();

      await connection.start();
      setConnected(true);
    }
    // eslint-disable-next-line
  })()
}, [isAuthenticated]);

useEffect(() => {
  (async () => {
    if(connected && user) {
      // this code is being hit, but the method on the back end is not
      await connection?.send('UpdateProfile');
    }
  })()
}, [connected, user])

Does anyone know why this might be?有谁知道为什么会这样? I have tried a lowercase method name in the call, but this doesn't help.我在调用中尝试了小写的方法名称,但这没有帮助。

put [Authorize] attribute on top of your ChatHub class.[Authorize]属性放在ChatHub类的顶部。 If you didn't configure any default authorization scheme on Startup, you need to include an authorization scheme like this如果在启动时没有配置任何默认授权方案,则需要包含这样的授权方案

    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class ChatHub: Hub 
    {
          //...
    }

*Note: When I am working with SignalR I convert my DTOs to JSON String and pass them to the server, maybe you need to do that for the function that have an argument. *注意:当我使用 SignalR 时,我将我的 DTO 转换为 JSON 字符串并将它们传递给服务器,也许您需要为具有参数的函数执行此操作。 like this :像这样 :

     public void FunctionName(string  dtoString)
     {
        
        var dto = JsonConvert.DeserializeObject<MyObjectDto>(dtoString);
        //Do something with my DTO
        
      }

and also pass back objects as JSON String to the client like this并像这样将对象作为 JSON 字符串传回给客户端

    var resultString = JsonConvert.SerializeObject(ResultObject, new 
    JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            });
    Clients.Caller.SendAsync("ClientFunction", resultString  );

token in SignalR is sent in query, so you need to read them from query and put them on the header. SignalR 中的令牌在查询中发送,因此您需要从查询中读取它们并将它们放在标头上。

  services.AddAuthentication()
        .AddJwtBearer(options =>
        {
            options.RequireHttpsMetadata = false;
            options.SaveToken = true;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false,
                ValidIssuer = [Issuer Site],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes([YOUR SECRET KEY STRING]))
            };
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var path = context.Request.Path;
                    var accessToken = context.Request.Query["access_token"];
                    if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chathub"))
                    {
                        
                        context.Request.Headers.Add("Authorization", new[] { $"Bearer {accessToken}" });
                    }
                    return Task.CompletedTask;
                }
            };
        });

Let me know if you have any trouble.如果您有任何问题,请告诉我。 Hope it works!希望它有效!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM