I have an ASP.NET Boilerplate v3.6.2 project (.NET Core / Angular) in which I need to call a client function from a backend method, so I'm using the ASP.NET Core SignalR implementation.
I followed the official documentation, so:
In the backend
In my module, I added the dependency to AbpAspNetCoreSignalRModule:
[DependsOn(typeof(AbpAspNetCoreSignalRModule))]
public class MyModule : AbpModule
And added this NuGet package to the module's project.
Then I extended the AbpCommonHub class to take advantage of the built-in implementation of the SignalR Hub, adding a SendMessage() method used to send the message:
public interface IMySignalRHub : ITransientDependency
{
Task SendMessage(string message);
}
public class MySignalRHub: AbpCommonHub, IMySignalRHub {
protected IHubContext<MySignalRHub> _context;
protected IOnlineClientManager onlineClientManager;
protected IClientInfoProvider clientInfoProvider;
public MySignalRHub(
IHubContext<MySignalRHub> context,
IOnlineClientManager onlineClientManager,
IClientInfoProvider clientInfoProvider)
: base(onlineClientManager, clientInfoProvider) {
AbpSession = NullAbpSession.Instance;
_context = context;
}
public async Task SendMessage(string message) {
await _context.Clients.All.SendAsync("getMessage", string.Format("User {0}: {1}", AbpSession.UserId, message));
}
}
I changed the mapping of the '/signalr' url to MySignalRHub:
public class Startup {
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
[...]
# if FEATURE_SIGNALR
app.UseAppBuilder(ConfigureOwinServices);
# elif FEATURE_SIGNALR_ASPNETCORE
app.UseSignalR(routes => {
routes.MapHub<MySignalRHub>("/signalr");
});
# endif
[...]
}
}
Then I use the hub to send a message in a service implementation:
public class MyAppService: AsyncCrudAppService<MyEntity, MyDto, int, PagedAndSortedResultRequestDto, MyCreateDto, MyDto>, IMyAppService {
private readonly IMySignalRHub _hub;
public MyAppService(IRepository<MyEntity> repository, IMySignalRHub hub) : base(repository) {
_hub = hub;
}
public override Task<MyDto> Create(MyCreateDto input) {
_hub.SendMessage("test string").Wait();
[...]
}
}
In the client
All the configurations and inclusions are already in place from the original template. When I open the Angular app I can see this console logs:
DEBUG: Connected to SignalR server!
DEBUG: Registered to the SignalR server!
When I try to call the backend service which sends the message to the client, I get this warning in console:
Warning: No client method with the name 'getMessage' found.
I tried many solutions found in the official documentation and on the Internet but none of them worked. I'm not able to define the 'getMessage' handler in the client code.
Some non-working examples I tried:
Implementation 1:
// This point is reached
abp.event.on('getMessage', userNotification => {
debugger; // Never reaches this point
});
Implementation 2:
// This point is reached
abp.event.on('abp.notifications.received', userNotification => {
debugger; // Never reaches this point
});
Implementation 3:
// This is taken from the official documentation and triggers the error:
// ERROR TypeError: abp.signalr.startConnection is not a function
abp.signalr.startConnection('/signalr', function (connection) {
connection.on('getMessage', function (message) {
console.log('received message: ' + message);
});
});
Have you ever found yourself in this situation? Do you have a simple working example of the handler definition in the Angular client?
UPDATE
I tried this alternative solution, changing the implementation of the SignalRAspNetCoreHelper class (a shared class which is shipped with the base template):
export class SignalRAspNetCoreHelper {
static initSignalR(): void {
var encryptedAuthToken = new UtilsService().getCookieValue(AppConsts.authorization.encrptedAuthTokenName);
abp.signalr = {
autoConnect: true,
connect: undefined,
hubs: undefined,
qs: AppConsts.authorization.encrptedAuthTokenName + "=" + encodeURIComponent(encryptedAuthToken),
remoteServiceBaseUrl: AppConsts.remoteServiceBaseUrl,
startConnection: undefined,
url: '/signalr'
};
jQuery.getScript(AppConsts.appBaseUrl + '/assets/abp/abp.signalr-client.js', () => {
// ADDED THIS
abp.signalr.startConnection(abp.signalr.url, function (connection) {
connection.on('getMessage', function (message) { // Register for incoming messages
console.log('received message: ' + message);
});
});
});
}
}
Now in the console I can see both the messages:
Warning: No client method with the name 'getMessage' found.
SignalRAspNetCoreHelper.ts:22 received message: User 2: asd
So it is working, but not fully. Somewhere the 'getMessage' handler is not visible.
What is the proper way to implement the messages handler in Angular with ASP.NET Boilerplate?
You should use Clients.Others.SendAsync or Client.AllExcept.SendAsync instead of Clients.All.SendAsync.
Clients.All.SendAsync is designed for scenarios where the client wants to send AND receive messages (like a chat room). Hence all connected clients are supposed to implement connection.on('getMessage', in order to receive the notifications. If they don't, they raise the warning No client method with the name 'x' found when receiving back the notification they just pushed.
In your scenario, I understand you have one kind of client pushing notifications and another kind receiving them (like a GPS device sending positions to a tracking application). In that scenario, using Clients.Others.SendAsync or Client.AllExcept.SendAsync will ensure pushing clients will not be broadcasted back the notification they (or their kind) just pushed.
Set autoConnect: false to start your own connection:
abp.signalr = {
autoConnect: false,
// ...
};
Better yet...
Don't extend AbpCommonHub. You'll find that real-time notification stops working and you need to replace IRealTimeNotifier because SignalRRealTimeNotifier uses AbpCommonHub.
Do you have a simple working example of the handler definition in the Angular client?
What is the proper way to implement the messages handler in Angular with ASPNet Boilerplate?
Follow the documentation and create a separate hub.
I was facing the same error in my Angular application where I use the package "#aspnet/signalr": "1.1.4".
The cause of this issue was I wasn't calling the subscribe method after join the channel.
public async getWorkSheetById(worksheetId: string): Promise < Worksheet > {
const worksheet: Worksheet = await this._worksheetService.getWorksheet(worksheetId);
this.displayWorksheet(worksheet);
await this._worksheetEventsService.joinWorksheetChannel(this._loadedWorksheet.id);
return worksheet;
}
So, to fix this I called the subscribe method after join await this.subscribeToSendMethod(this._loadedWorksheet))
public subscribeToSendMethod(loadedWorksheet: Worksheet): Worksheet {
let newWorksheet: Worksheet;
this._hubConnection.on('Send', (groupId: string, payloadType: string, payload: string, senderUserId: string)=> {
newWorksheet=this._worksheetHandler.handlePayload(payloadType, payload, loadedWorksheet);
this.displayWorksheet(newWorksheet);
}
);
return newWorksheet;
}
Related
I have set up default gRPC server via Visual Studio .net 6.0.
The proto file is as foolowing:
syntax = "proto3";
option csharp_namespace = "GrpcService";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
And the Greeter service as following:
using Grpc.Core;
namespace GrpcService.Services
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
So the default one, nothing fancy. I know how to call this service via C#. And I get back the result as per the function above.
I though don't understand what packages and how should I call it from a web app client via javascript, in a next.js component.
I want to achieve that on a click [for example] I will be calling this.
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";
const Home: NextPage = () => {
const grpcCall = () => {
try {
const grpc = require("grpc");
const protoLoader = require("#grpc/proto-loader");
const packageDef = protoLoader.loadSync("greet.proto", {});
const grpcObject = grpc.loadPackageDefinition(packageDef);
const greetPackage = grpcObject.greet;
const client = new greetPackage.Greeter(
"https://localhost:49153/",
grpc.credentials.createInsecure()
);
client.SayHello({ name: "Say Hello" });
} catch (error) {
console.log(error);
}
};
return (
<div className={styles.container}>
<div onClick={grpcCall} className={styles.title}>
Press to call gRPC HERE
</div>
</div>
);
};
export default Home;
The above code though complains about fs not being resolved, though I don't really think it is the issue.
Error:
Module not found: Can't resolve 'fs'
Import trace for requested module:
./node_modules/#grpc/proto-loader/build/src/index.js
./pages/index.tsx
https://nextjs.org/docs/messages/module-not-found
Could not find files for / in .next/build-manifest.json
Could not find files for / in .next/build-manifest.json
And the whole project is a default next.js app with typescript setting.
So the question is How do I do the call from a javascript client to C# server via gRPC? What code should I add here?
I would apprieciete a simple solution here and if possible detailed explanation.
Regards,
I have a client using HttpClient.GetAsync to call into a Azure Function Http Trigger in .Net 5.
When I call the function using PostMan, I get my custom header data.
However, when I try to access my response object (HttpResponseMessage) that is returned from HttpClient.GetAsync, my header data empty.
I have my Content data and my Status Code. But my custom header data are missing.
Any insight would be appreciated since I have looking at this for hours.
Thanks for you help.
Edit: Here is the code where I am making the http call:
public async Task<HttpResponseMessage> GetQuotesAsync(int? pageNo, int? pageSize, string searchText)
{
var requestUri = $"{RequestUri.Quotes}?pageNo={pageNo}&pageSize={pageSize}&searchText={searchText}";
return await _httpClient.GetAsync(requestUri);
}
Edit 8/8/2021: See my comment below. The issue has something to do with using Blazor Wasm Client.
For anyone having problems after following the tips on this page, go back and check the configuration in the host.json file. you need the Access-Control-Expose-Headers set to * or they won't be send even if you add them. Note: I added the "extensions" node below and removed my logging settings for clarity.
host.json (sample file):
{
"version": "2.0",
"extensions": {
"http": {
"customHeaders": {
"Access-Control-Expose-Headers": "*"
}
}
}
}
This is because HttpResponseMessage's Headers property data type is HttpResponseHeaders but HttpResponseData's Headers property data type is HttpHeadersCollection. Since, they are different, HttpResponseHeaders could not bind to HttpHeadersCollection while calling HttpClient.GetAsync(as it returns HttpResponseMessage).
I could not find a way to read HttpHeadersCollection through HttpClient.
As long as your Azure function code is emitting the header value, you should be able to read that in your client code from the Headers collection of HttpResponseMessage. Nothing in your azure function (which is your remote endpoint you are calling) makes it any different. Remember, your client code has no idea how your remote endpoint is implemented. Today it is azure functions, tomorrow it may be a full blown aspnet core web api or a REST endpoint written in Node.js. Your client code does not care. All it cares is whether the Http response it received has your expected header.
Asumming you have an azure function like this where you are adding a header called total-count to the response.
[Function("quotes")]
public static async Task<HttpResponseData> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("Quotes");
logger.LogInformation("C# HTTP trigger function processed a request for Quotes.");
var quotes = new List<Quote>
{
new Quote { Text = "Hello", ViewCount = 100},
new Quote { Text = "Azure Functions", ViewCount = 200}
};
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("total-count", quotes.Count.ToString());
await response.WriteAsJsonAsync(quotes);
return response;
}
Your existing client code should work as long as you read the Headers property.
public static async Task<HttpResponseMessage> GetQuotesAsync()
{
var requestUri = "https://shkr-playground.azurewebsites.net/api/quotes";
return await _httpClient.GetAsync(requestUri);
}
Now your GetQuotesAsync method can be called somewhere else where you will use the return value of it (HttpResponseMessage instance) and read the headers. In the below example, I am reading that value and adding to a string variable. HttpResponseMessage implements IDisposable. So I am using a using construct to implicitly call the Dispose method.
var msg = "Total count from response headers:";
using (var httpResponseMsg = await GetQuotesAsync())
{
if (httpResponseMsg.Headers.TryGetValues("total-count", out var values))
{
msg += values.FirstOrDefault();
}
}
// TODO: use "msg" variable as needed.
The types which Azure function uses for dealing with response headers is more of an implementation concern of azure functions. It has no impact on your client code where you are using HttpClient and HttpResponseMessage. Your client code is simply dealing with standard http call response (response headers and body)
The issue is not with Blazor WASM, rather if that header has been exposed on your API Side. In your azure function, add the following -
Note: Postman will still show the headers even if you don't expose the headers like below. That's because, Postman doesn't care about CORS headers. CORS is just a browser concept and not a strong security mechanism. It allows you to restrict which other web apps may use your backend resources and that's all.
First create a Startup File to inject the HttpContextAccessor
Package Required: Microsoft.Azure.Functions.Extensions
[assembly: FunctionsStartup(typeof(FuncAppName.Startup))]
namespace FuncAppName
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<HttpContextAccessor>();
}
}
}
Next, inject it into your main Function -
using Microsoft.AspNetCore.Http;
namespace FuncAppName
{
public class SomeFunction
{
private readonly HttpContext _httpContext;
public SomeFunction(HttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
[FunctionName("SomeFunc")]
public override Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, new[] { "post" }, Route = "run")] HttpRequest req)
{
var response = "Some Response"
_httpContext.Response.Headers.Add("my-custom-header", "some-custom-value");
_httpContext.Response.Headers.Add("my-other-header", "some-other-value");
_httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "my-custom-header, my-other-header");
return new OkObjectResult(response)
}
If you want to allow all headers you can use wildcard (I think, not tested) -
_httpContext.Response.Headers.Add("Access-Control-Expose-Headers", "*");
You still need to add your web-app url to the azure platform CORS. You can add * wildcard, more info here - https://iotespresso.com/allowing-all-cross-origin-requests-azure-functions/
to enable CORS for Local Apps during development - https://stackoverflow.com/a/60109518/9276081
Now to access those headers in your Blazor WASM, as an e.g. you can -
protected override async Task OnInitializedAsync()
{
var content = JsonContent.Create(new { query = "" });
using (var client = new HttpClient())
{
var result = await client.PostAsync("https://func-app-name.azurewebsites.net/api/run", content);
var headers = result.Headers.ToList();
}
}
When I add a new user to my IdentiyServerService the following MassTransit code is called:
var newUserCreated = new UserCreated
{
UserId = userId.ToString(),
Name = user.Name
};
await _bus.Publish(newUserCreated);
My destination is that my ProfileService receive this event by RabbitMq.
My RabbitMq configuration in my Startup.cs (IdentiyServerService)
private static void ConfigureBus(ContainerBuilder builder)
{
builder.Register(context =>
{
return Bus.Factory.CreateUsingRabbitMq(config =>
{
var host = config.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
});
}).As<IBus, IBusControl, IPublishEndpoint>().SingleInstance();
}
I start the bus like this
//Startup.cs IdentityServerService
var container = containerBuilder.Build();
var busControl = container.Resolve<IBusControl>();
busControl.Start();
The configuration in my ProfileService look quite almost the same. The difference is, that I add an consumer in my Startup.cs (ProfileService)
config.ReceiveEndpoint(host, "user_queue", ep =>
{
ep.Consumer<UserCreatedConsumer>(); // The consumer is registered explicitly this time.
});
I add an IConsumer as well
public class UserCreatedConsumer : IConsumer<UserCreated>
{
public Task Consume(ConsumeContext<UserCreated> context)
{
var user = context.Message;
Debug.WriteLine("My debug string here");
return TaskUtil.Completed;
}
}
When I create a new user the messages receive the RabbitMq (publish rates increase). But then nothing goes on. The total number of messages in the Queued messages does not change.
I have two connections (I would expect IdentityServerService and ProfileService) and I have different queues (I was only expecting one: user_queue)
When I implement the IConsumer inside my IdentityServerService I receive the message.
I have no error log, warnings or something else.
Anyway... 1) Why does ProfileService not receive the message? 2) And why do I have so much queues?
If you need some more information... please tell
Edit
When I send a message in the rabbitMq-management my ProfileService receive a message, but now I get the following error
MassTransit.Messages Error: 0 : R-FAULT
rabbitmq://localhost/user_queue_new Value cannot be null.
Parameter name: source,
System.Runtime.Serialization.SerializationException: An exception occurred while deserializing the message envelope ---> System.ArgumentNullException: Value cannot be null.
Parameter name: source
I have a Angular 7 application in which I want to implement push notification using SignlaR (2.4.4 version) with API in C#. Can anyone suggest how to solve my problem?
I have tried everything that I can, but I am not able to establish a connection between Angular SignalR client and SignalR Hub created in C# API.
1) This is my Startup.cs
HubConfiguration cfg = new HubConfiguration();
app.MapSignalR<PersistentConnection>("/ClientHub");
app.MapSignalR(cfg);
2) This is my Hub
public class ClientHub : Hub
{
public Task SendNotificationCount(int chatUserCnt)
{
var context =
GlobalHost.ConnectionManager.GetHubContext<ClientHub>();
return context.Clients.All.SendAsync("Send", chatUserCnt);
}
public Task SendHelloToAll(string message)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ClientHub>();
return context.Clients.All.SendMsgAsync("SendToAll", message);
}
}
3) This is Angular Code where I am trying to Connect to the Hub.
hubUrl = http://localhost:60067/ClientHub
connectToHub(hubUrl:string) {
this.connection = new HubConnectionBuilder().withUrl(hubUrl).build();
this.connection.start().then(() => {
console.log("connected to hub");
this.connection.on('SendToAll', (message) => {
alert(message);
});
}).catch((error) => {
console.log(error);
});
}
I am expecting, whenever there is a change in the database for notification to get alerts through the Hub in realtime. Also I want to send database changes using signalR hub to Angular. But I am not able to connect with hub from Angular.
This is where I would start looking:
https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr-typescript-webpack?view=aspnetcore-2.2&tabs=visual-studio
I recently upgraded a project from SignalR 2.0.0-beta1 to 2.0.0-rc1. I understand that in RC1, configuration of support for cross domain requests changed. I've updated my project to use the new syntax however I'm now getting the following error when attempting to communicate with my hub:
XMLHttpRequest cannot load
=1377623738064">http://localhost:8080/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3&=1377623738064.
Origin http://localhost:7176 is not allowed by
Access-Control-Allow-Origin.
The client site is running at http://localhost:7176 and the hub is listening via a console application at http://localhost:8080. Am I missing something here? Cross domain requests were working before I upgraded to RC1.
CONSOLE APP ENTRY POINT
static void Main(string[] args)
{
var chatServer = new ChatServer();
string endpoint = "http://localhost:8080";
chatServer.Start(endpoint);
Console.WriteLine("Chat server listening at {0}...", endpoint);
Console.ReadLine();
}
CHATSERVER CLASS
public class ChatServer
{
public IDisposable Start(string url)
{
return WebApp.Start<Startup>(url);
}
}
STARTUP CONFIGURATION
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(new HubConfiguration { EnableJSONP = true });
});
}
}
Something is wrong with your client configuration.
XMLHttpRequest cannot load =1377623738064">http://localhost:8080/negotiate?connectionData=%5B%7B%22name%22%3A%22chathub%22%7D%5D&clientProtocol=1.3&=1377623738064. Origin http://localhost:7176 is not allowed by Access-Control-Allow-Origin.
The negotiate request should be made to http://localhost:8080/signalr/negotiate?... not http://localhost:8080/negotiate?.... To fix this you can try the following before you call $.connection.hub.start:
$.connection.hub.url = http://localhost:8080/signalr;
Not sure if this question has been adequately answered, but I made the following changes to the sample provided by Microsoft:
public void Configuration(IAppBuilder app)
{
var config = new HubConfiguration();
config.EnableJSONP = true;
app.MapSignalR(config);
}
And I added the following to the JS sample:
$.connection.hub.start({ jsonp: true }).done(function () {
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.send($('#displayname').val(), $('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
And now the Cross domain scripting is enabled. Hope this helps someone else, I was really puzzling with it for a while.
For Microsoft.Owin 2.x and above:
Add Microsoft.Owin.Cors package via NuGet by this command in Package Manager console:
PM> Install-Package Microsoft.Owin.Cors
and then using this package in Startup class file:
using Microsoft.Owin;
using Microsoft.Owin.Cors;
then change your source code like this:
// app.MapHubs(new HubConfiguration { EnableCrossDomain = true });
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
I think the best way to handle Cross Domain is documented here
CrossDomain Signal R