Hello @Sampath, @Prabhavathi Manchala
I guess the above flow is bypassing the webpubsub and client is directly hitting server via http post. I am looking for proper way of clients sending messages via pubsub.
So yes it didnot solved the problem.
here is my complete code:
PubSubController.cs
using Microsoft.AspNetCore.Mvc;
using Azure.Messaging.WebPubSub;
[ApiController]
[Route("api/v1/[controller]")]
// This endpoint provides the clients the URL to WebPubSub to hit.
public class PubSubController : ControllerBase
{
private readonly WebPubSubServiceClient _webPubSubServiceClient;
private readonly string hubConnectionString; // WebPubSub resource connection string in Azure
public PubSubController(IConfiguration configuration)
{
hubConnectionString = configuration["WebPubSubConnectionString"];
_webPubSubServiceClient = new WebPubSubServiceClient(hubConnectionString, "socketio"); // Specify the hub name as "chat"
}
[HttpGet]
[Route("negotiate")]
public IActionResult Get()
{
var uri = _webPubSubServiceClient.GetClientAccessUri(
userId: "Dev");
var url = uri.AbsoluteUri.Replace("https://", "").Replace("/client/hubs/socketio", "/clients/socketio/hubs/socketio");
return Ok(new { url });
}
}
Hub.cs
using Microsoft.Azure.WebPubSub.AspNetCore;
using Microsoft.Azure.WebPubSub.Common;
using System.Text.Json;
using Azure.Core;
// Corrected server side code for chatting via hub
sealed class Hub : WebPubSubHub
{
private readonly WebPubSubServiceClient<Hub> _serviceClient;
public Hub(WebPubSubServiceClient<Hub> serviceClient)
{
_serviceClient = serviceClient;
}
public override Task OnConnectedAsync(ConnectedEventRequest request)
{
Console.WriteLine($"[SYSTEM] {request.ConnectionContext.UserId} joined.");
return Task.CompletedTask;
}
public override async ValueTask<UserEventResponse> OnMessageReceivedAsync(UserEventRequest request, CancellationToken cancellationToken)
{
// Create message data
var messageData = new
{
from = request.ConnectionContext.UserId,
message = request.Data.ToString()
};
// Convert to JSON
var json = JsonSerializer.Serialize(messageData);
// Use the simplest overload of SendToAllAsync
await _serviceClient.SendToAllAsync(json);
return new UserEventResponse();
}
}
Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.WebPubSub.AspNetCore;
using Microsoft.Azure.WebPubSub.Common;
using Azure.Core; // Add this using directive for RequestContent and ContentType
using Microsoft.Extensions.Primitives; // Add this using directive for StringValues
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Load configuration from appsettings.Development.json
builder.Configuration.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
// Retrieve connection string
string connectionString = builder.Configuration["WebPubSubConnectionString"]
?? throw new InvalidOperationException("WebPubSubConnectionString is not configured.");
// Read connection string from environment
if (connectionString == null)
{
throw new ArgumentNullException(nameof(connectionString));
}
builder.Services.AddWebPubSub(o =>
{
o.ServiceEndpoint = new WebPubSubServiceEndpoint(connectionString);
})
.AddWebPubSubServiceClient<Hub>(); // Explicitly set the hub name
var app = builder.Build();
// Map Web PubSub endpoint
app.UseRouting();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
//app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseRouting();
app.UseCors("AllowAll");
// Map a Web PubSub hub to an API endpoint in server to communicate
app.MapWebPubSubHub<Hub>("/eventhandler");
app.MapControllers();
app.Run();
My Hub class name is 'Hub' and pubsub's hub name is 'socketio' and per documentation these 2 need not be same, it needs mapping socketio hub <----> Hub.cs which is done.
Tried keeping same name also but no difference.
JS client code:
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Azure Web PubSub Minimal Client</title>
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.5.4/dist/socket.io.min.js"></script>
</head>
<body>
<script>
// Function to connect to Azure Web PubSub
async function connectToWebPubSub() {
try {
const response = await fetch(
"http://localhost:5000/api/v1/pubsub/negotiate"
);
if (!response.ok) {
console.error(
"Failed to negotiate, status code =",
response.status
);
return;
}
const data = await response.json();
console.log("Negotiated WebSocket URL:", data.url);
// Extract path from negotiated URL
const url = new URL(data.url);
const socket = io(url.origin, {
path: url.pathname, // Use path from negotiated URL
transports: ["websocket"],
pingInterval: 55000,
extraHeaders: {
"Sec-WebSocket-Protocol": "json.webpubsub.azure.v1",
},
});
socket.on("connect", () => {
console.log("Connected successfully!");
});
socket.on("message", (msg) => {
console.log("Message from server:", msg);
});
socket.on("connect_error", (err) => {
console.error("Connection error:", err);
});
// Send a message to the server
socket.emit("howdy", "stranger");
} catch (error) {
console.error("Error connecting to Web PubSub:", error);
}
}
window.onload = connectToWebPubSub;
</script>
</body>
</html>
So if you place these files in an empty API project, it should run for you.
Issue: I will be having multiple hubs for multiple purpose like chatHub, mediaHub so I guess it is important to establish a socket connection between client and pubsub via sockets for real time conversation and map pubsub hubs to related .cs classes and override methods in c#.net.