SignalR - ส่งข้อความถึงผู้ใช้เฉพาะโดยใช้ (IUserIdProvider) * NEW 2.0.0 *


102

ใน Asp.Net SignalR เวอร์ชันล่าสุดได้เพิ่มวิธีใหม่ในการส่งข้อความไปยังผู้ใช้เฉพาะโดยใช้อินเทอร์เฟซ "IUserIdProvider"

public interface IUserIdProvider
{
   string GetUserId(IRequest request);
}

public class MyHub : Hub
{
   public void Send(string userId, string message)
   {
      Clients.User(userId).send(message);
   }
}

คำถามของฉันคือฉันจะรู้ได้อย่างไรว่าฉันกำลังส่งข้อความถึงใคร? คำอธิบายของวิธีการใหม่นี้ดูผิวเผินมาก และร่างคำชี้แจง SignalR 2.0.0 ที่มีข้อบกพร่องนี้และไม่ได้รวบรวม มีใครใช้ฟีเจอร์นี้บ้าง?

ข้อมูลเพิ่มเติม: http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider

กอด.


1
คุณต้องตรวจสอบการพิสูจน์ตัวตนและการอนุญาตด้วย SignalR UserId จะเป็นส่วนหนึ่งของผู้ให้บริการ IPrincipal
Gjohn

คำตอบ:


154

SignalR จัดเตรียม ConnectionId สำหรับการเชื่อมต่อแต่ละครั้ง ในการค้นหาว่าการเชื่อมต่อใดเป็นของใคร (ผู้ใช้) เราจำเป็นต้องสร้างการจับคู่ระหว่างการเชื่อมต่อกับผู้ใช้ ขึ้นอยู่กับว่าคุณระบุผู้ใช้ในแอปพลิเคชันของคุณอย่างไร

ใน SignalR 2.0 สิ่งนี้ทำได้โดยใช้ inbuilt IPrincipal.Identity.Nameซึ่งเป็นตัวระบุผู้ใช้ที่ล็อกอินตามที่ตั้งไว้ระหว่างการพิสูจน์ตัวตน ASP.NET

อย่างไรก็ตามคุณอาจต้องแมปการเชื่อมต่อกับผู้ใช้โดยใช้ตัวระบุอื่นแทนการใช้ Identity.Name เพื่อจุดประสงค์นี้ผู้ให้บริการใหม่นี้สามารถใช้กับการใช้งานแบบกำหนดเองของคุณสำหรับการจับคู่ผู้ใช้ด้วยการเชื่อมต่อ

ตัวอย่างการแม็พผู้ใช้ SignalR กับ Connections โดยใช้ IUserIdProvider

สมมติว่าแอปพลิเคชันของเราใช้userIdเพื่อระบุผู้ใช้แต่ละคน ตอนนี้เราจำเป็นต้องส่งข้อความไปยังผู้ใช้เฉพาะ เรามีuserIdและmessageแต่ SignalR ต้องทราบการแมประหว่าง userId ของเราและการเชื่อมต่อด้วย

เพื่อให้บรรลุสิ่งนี้อันดับแรกเราต้องสร้างคลาสใหม่ซึ่งใช้IUserIdProvider:

public class CustomUserIdProvider : IUserIdProvider
{
     public string GetUserId(IRequest request)
    {
        // your logic to fetch a user identifier goes here.

        // for example:

        var userId = MyCustomUserClass.FindUserId(request.User.Identity.Name);
        return userId.ToString();
    }
}

ขั้นตอนที่สองคือบอก SignalR ให้ใช้ของเราCustomUserIdProviderแทนการใช้งานเริ่มต้น สามารถทำได้ใน Startup.cs ขณะเริ่มต้นการกำหนดค่าฮับ:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var idProvider = new CustomUserIdProvider();

        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);          

        // Any connection or hub wire up and configuration should go here
        app.MapSignalR();
    }
}

ตอนนี้คุณสามารถส่งข้อความไปยังผู้ใช้ที่ระบุโดยใช้userIdตามที่ระบุไว้ในเอกสารเช่น:

public class MyHub : Hub
{
   public void Send(string userId, string message)
   {
      Clients.User(userId).send(message);
   }
}

หวังว่านี่จะช่วยได้


สวัสดีเพื่อนขอโทษสำหรับข้อเสนอแนะที่ล่าช้า! แต่ฉันพยายามเข้าถึง Id ที่สร้าง CustomUserIdProvider แต่วิธี "OnConnected" มันไม่เหมือนกัน ฉันจะเชื่อมโยงไปยังผู้ใช้ในฐานข้อมูลได้อย่างไร? ขอบคุณ!
Igor

7
MyCustomUserClass คืออะไร?
Danny Bullis

2
"MyCustomUserClass" อาจเป็นคลาสผู้ใช้ที่กำหนดเองของคุณซึ่งมีเมธอด FindUserId นี่เป็นเพียงตัวอย่างเท่านั้น คุณสามารถมีเมธอดใดก็ได้ในคลาสใดก็ได้ที่ส่งคืน UserId และใช้มันที่นี่
Sumant

5
ขอบคุณ @Sumant ปัญหาของฉันคือ b / c ที่ฉันอยู่ในโครงการ Web API ที่ฉันติดตั้ง OAuth 2 กับโทเค็นผู้ถือฉันต้องใช้ตรรกะเพื่อส่งโทเค็นผู้ถือในสตริงการสืบค้นเนื่องจากไม่สามารถดึงจาก ส่วนหัวของคำขอเชื่อมต่อสัญญาณเริ่มต้นนั้น ไม่สามารถใช้ request.User.Identity.Name
xinunix

1
@Sumant ฉันได้รับการแก้ไขแล้ว ปัญหาคือฉันใส่app.MapSignalR();global.asax ก่อนการตรวจสอบสิทธิ์
Mr. Robot

38

นี่คือจุดเริ่มต้น .. เปิดรับข้อเสนอแนะ / การปรับปรุง

เซิร์ฟเวอร์

public class ChatHub : Hub
{
    public void SendChatMessage(string who, string message)
    {
        string name = Context.User.Identity.Name;
        Clients.Group(name).addChatMessage(name, message);
        Clients.Group("2@2.com").addChatMessage(name, message);
    }

    public override Task OnConnected()
    {
        string name = Context.User.Identity.Name;
        Groups.Add(Context.ConnectionId, name);

        return base.OnConnected();
    }
}

JavaScript

(สังเกตวิธีการaddChatMessageและsendChatMessageวิธีการในรหัสเซิร์ฟเวอร์ด้านบน)

    $(function () {
    // Declare a proxy to reference the hub.
    var chat = $.connection.chatHub;
    // Create a function that the hub can call to broadcast messages.
    chat.client.addChatMessage = function (who, message) {
        // Html encode display name and message.
        var encodedName = $('<div />').text(who).html();
        var encodedMsg = $('<div />').text(message).html();
        // Add the message to the page.
        $('#chat').append('<li><strong>' + encodedName
            + '</strong>:&nbsp;&nbsp;' + encodedMsg + '</li>');
    };

    // Start the connection.
    $.connection.hub.start().done(function () {
        $('#sendmessage').click(function () {
            // Call the Send method on the hub.
            chat.server.sendChatMessage($('#displayname').val(), $('#message').val());
            // Clear text box and reset focus for next comment.
            $('#message').val('').focus();
        });
    });
});

การทดสอบ ป้อนคำอธิบายภาพที่นี่


สวัสดีเพื่อนขอโทษสำหรับข้อเสนอแนะที่ล่าช้า! แต่ฉันจะป้องกันการสูญหายของ CONNECTIONID ได้อย่างไร? ขอบคุณ.
Igor

5
@lgao ฉันไม่รู้
The Muffin Man

ทำไมต้องใช้บรรทัดนี้ --- Clients.Group ("2@2.com") addChatMessage (ชื่อข้อความ); ??
Thomas

@ โทมัสฉันอาจรวมไว้เพื่อประโยชน์ในการสาธิต ต้องมีวิธีอื่นในการออกอากาศไปยังกลุ่มเฉพาะเนื่องจากเป็นแบบฮาร์ดโค้ด
The Muffin Man

วิธีง่ายๆนี้ช่วยแก้ปัญหาของฉันในการส่งข้อความไปยังผู้ใช้ที่บันทึกไว้ ง่ายรวดเร็วและเข้าใจง่าย ฉันจะโหวตคำตอบนี้หลาย ๆ ครั้งถ้าทำได้
Rafael AMS

5

นี่คือวิธีใช้ SignarR เพื่อกำหนดเป้าหมายผู้ใช้เฉพาะ (โดยไม่ต้องใช้ผู้ให้บริการใด ๆ ):

 private static ConcurrentDictionary<string, string> clients = new ConcurrentDictionary<string, string>();

 public string Login(string username)
 {
     clients.TryAdd(Context.ConnectionId, username);            
     return username;
 }

// The variable 'contextIdClient' is equal to Context.ConnectionId of the user, 
// once logged in. You have to store that 'id' inside a dictionaty for example.  
Clients.Client(contextIdClient).send("Hello!");

2
วิธีที่คุณใช้สิ่งนี้contextIdClientไม่ได้รับ :(
นีโอ

2

ดูที่SignalR Testsสำหรับคุณลักษณะนี้

การทดสอบ "SendToUser" จะใช้ข้อมูลประจำตัวผู้ใช้ที่ส่งผ่านโดยอัตโนมัติโดยใช้ไลบรารีการตรวจสอบสิทธิ์ของ Owin ปกติ

สถานการณ์คือคุณมีผู้ใช้ที่เชื่อมต่อจากอุปกรณ์ / เบราว์เซอร์หลายเครื่องและคุณต้องการส่งข้อความไปยังการเชื่อมต่อที่ใช้งานอยู่ทั้งหมดของเขา


ขอบคุณ! แต่โครงการเวอร์ชัน 2.0 ไม่ได้รวบรวม SignalR ที่นี่ :( น่าเสียดายที่ไม่สามารถเข้าถึงได้
อิกอร์

0

กระทู้เก่า แต่เพิ่งเจอในตัวอย่าง:

services.AddSignalR()
            .AddAzureSignalR(options =>
        {
            options.ClaimsProvider = context => new[]
            {
                new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"])
            };
        });

0

สำหรับใครที่พยายามทำในแกน asp.net คุณสามารถใช้การอ้างสิทธิ์

public class CustomEmailProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
    }
}

สามารถใช้ตัวระบุใด ๆ ได้ แต่ต้องไม่ซ้ำกัน ตัวอย่างเช่นหากคุณใช้ตัวระบุชื่อหมายความว่าหากมีผู้ใช้หลายคนที่มีชื่อเดียวกันกับผู้รับข้อความก็จะถูกส่งถึงพวกเขาเช่นกัน ฉันเลือกอีเมลเพราะเป็นอีเมลเฉพาะสำหรับผู้ใช้ทุกคน

จากนั้นลงทะเบียนบริการในคลาสเริ่มต้น

services.AddSingleton<IUserIdProvider, CustomEmailProvider>();

ต่อไป. เพิ่มการอ้างสิทธิ์ระหว่างการลงทะเบียนผู้ใช้

var result = await _userManager.CreateAsync(user, Model.Password);
if (result.Succeeded)
{
    await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Model.Email));
}

เพื่อส่งข้อความไปยังผู้ใช้เฉพาะ

public class ChatHub : Hub
{
    public async Task SendMessage(string receiver, string message)
    {
        await Clients.User(receiver).SendAsync("ReceiveMessage", message);
    }
}

หมายเหตุ: ผู้ส่งข้อความจะไม่ได้รับแจ้งว่ามีการส่งข้อความ หากคุณต้องการการแจ้งเตือนในส่วนท้ายของผู้ส่ง เปลี่ยนSendMessageวิธีการเป็นนี้

public async Task SendMessage(string sender, string receiver, string message)
{
    await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", message);
}

ขั้นตอนเหล่านี้จำเป็นต่อเมื่อคุณต้องการเปลี่ยนตัวระบุเริ่มต้น มิฉะนั้นให้ข้ามไปยังขั้นตอนสุดท้ายที่คุณสามารถส่งข้อความโดยส่ง userIds หรือ connectionIds ไปSendMessageได้ สำหรับข้อมูลเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.