I want to know if there is a way to read data changes on MySQL with signal R. I saw that you can do this on SQL with SQL dependency. My objective is to do this to display notifications on ASP .Net Core. This is what I have:
Hub
public class NotificationsHub : Hub
{
private readonly INotificationsUserService _notificationsUserService;
private readonly IMapper _mapper;
public NotificationsHub(INotificationsUserService notificationsUserService, IMapper mapper)
{
_notificationsUserService= notificationsUserService;
_mapper = mapper;
}
public async Task GetNotifications(int userId)
{
var notifications = await _notificationsUserService.GetAllNotificationUsers();
notifications = notifications .Where(n => n.IdUser == userId);
var userNotifications = new List<Notifications>();
foreach (var item in notifications )
{
userNotifications.Add(item.IdNotificationsNavigation);
}
var notificationResource = _mapper.Map<IEnumerable<Notifications>, IEnumerable<NotificationsResource>>(userNotifications);
await Clients.All.SendAsync("usersNotifications", notificationResource);
}
}
Js
$(() => {
let connection = new signalR.HubConnectionBuilder().withUrl("/notificationsHub").build();
let userId = $('#userId').html();
setInterval(function () {
connection.start().then(() => {
connection.invoke("GetNotificationes", parseInt(userId)).catch(function (err) {
return console.error(err.toString());
});
});
}, 5000);
var notificaciones =
connection.on("usersNotifications", function (notifications) {
$.ajax({
type: "POST",
url: "/Home/RefreshNavComponent",
data: {
"notifications": notifications
},
success: function (data) {
$('#header').html(data);
connection.stop();
},
error: function (req, status, error) {
alert(req + " " + status + " " + error);
console.log(req);
console.log(status);
}
});
});
Home Controller
[HttpPost]
public IActionResult RefreshNavComponent(IEnumerable<NotificationsResource> notifications)
{
if (notifications != null)
{
return ViewComponent("NavMenu", notifications);
}
return Ok();
}
As you can see, currently im updating every 5 seconds my Nav where the notifications icon is. But I dont want it to update the full Nav. And I think calling a timer defeats the purpose of Signal R. Let me know if you have any suggestions.
Not seeing it in your code, so assuming that you have a method that updates the notifications table/record in the database. You can update your clients by using a Client.All or Clients.User(user) or Clients.Group(groupName) in the same method but after the update.
If you do this from a controller and not the Hub, you will want to check out how to
send from outside the hub.
Related
I'm working on an ASP.NET Core MVC application and I have a view who do a post request as:
$.ajax({
url:'/Advertisers/ActiveAdvertiser?id='+id+'&isActive='+!isActive,
method: 'POST',
success: function(r){
Swal.fire("Inactivated!", "Advertiser inactivated successfully", "success");
},
error: function (request) {
console.log(request.responseText)
Swal.fire("Error!", "Something went wrong, please try again`", "warning");
}
});
Controller:
[HttpPost]
public async Task<JsonResult> ActiveAdvertiser(int id, bool isActive)
{
var advertiser = await _advertisersService.GetAdvertiserByAdvertiserIdAsync(id);
if (advertiser != null)
{
var model = AssingAdvertiserViewModel(advertiser, id);
model.IsActive = isActive;
var result = await _advertisersService.UpdateAdvertiserAsync(model, GetCurrentUserAsync().Id);
if (result != null)
{
return Json(new { result = "OK" });
}
}
return Json(new { result = "BadRequest" });
}
Post method services:
public Task<Advertiser?> GetAdvertiserByAdvertiserIdAsync(int advertiserId)
{
return _db.Advertisers
.Include(a => a.Address)
.Include(pc => pc.PrimaryContact)
.Include(ac => ac.AlternateContact)
.FirstOrDefaultAsync(x => x.AdvertiserId == advertiserId);
}
private AdvertiserViewModel AssingAdvertiserViewModel(Advertiser advertiser, int id)
{
var model = new AdvertiserViewModel()
{
//Fill model here
};
return model;
}
public async Task<Advertiser?> UpdateAdvertiserAsync(AdvertiserViewModel model, int updatedById)
{
var advertiser = await GetAdvertiserByAdvertiserIdAsync(model.AdvertiserId);
if (advertiser is null)
return null;
advertiser.Name = model.Name;
// fill model here
await _db.SaveChangesAsync();
return advertiser;
}
The problem is I do the first request, and it returns Success with any issues, but if I try to do a second one, it throws an exception:
System.InvalidOperationException: A second operation was started on
this context instance before a previous operation completed. This is
usually caused by different threads concurrently using the same
instance of DbContext.
If I stop the project and run it again it works one time again and in the second time get the error again
I read about this issue in other questions, and apparently is because you don't use the await services, I check my code and almost everything uses await. Can someone see something that I don't see? Regards
You could check this document for how to handle this error:
Therefore, always await async calls immediately, or use separate DbContext instances for operations that execute in parallel.
So you could check if misssing the await keyword on async operation
and use separate Dbcontext instances with DbcontextFactory as below:
regist the factory in service collection:
builder.Services.AddDbContextFactory<SomeContext>();
inject it into controller/Service/Somewhereelse:
public class SomeEntitiesController : Controller
{
private readonly IDbContextFactory<SomeContext> _factory;
public SomeEntitiesController(IDbContextFactory<SomeContext> factory)
{
_factory = factory;
}
}
create a new dbcontext:
_factory.CreateDbContext()
I solve this by adding ServiceLifetime.Transient into my services as:
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbConnection")),
ServiceLifetime.Transient
);
I am new to working with dialogflow and fairly new to .NET. I have been struggling for a while now to create my fulfillment webhook. I have got it to work with the node.js inline-editor but want to create my own WebhookController in .NET so I can make external API calls/db calls more easily. Here is what I have so far:
I have a really basic whats-app-like UI where a user can input some text which is appended to a chat window and then the javascript is pinged for the chatbot's response:
function userSubmit() {
var userInput = document.getElementById('user-input').value;
$.ajax({
type: "GET",
url: "/Home/CheckIntentAsync",
data: {
userInput: userInput
},
async: true,
contentType: "application/json",
success: function (data) {
var reply = data;
var botNode = document.createElement("div");
botNode.classList.add('chat');
botNode.classList.add('bot-chat');
botNode.innerHTML = reply; // <--- appends chat window with the reply from Dialogflow
chatWindow.appendChild(botNode);
chatWindow.scrollTop = chatWindow.scrollHeight;
console.log(data);
},
error: function () {
var reply = "I didn't quite catch that, can you rephrase? :/";
var botNode = document.createElement("div");
botNode.classList.add('chat');
botNode.classList.add('bot-chat');
botNode.innerHTML = reply;
chatWindow.appendChild(botNode);
}
});
The ajax call pings my HomeController class which connects to Dialogflow:
public class HomeController : Controller
{
private string sessionID = "XXX"; // my session ID
private string projectID = "XXX"; // my projectID
public ActionResult Index()
{
SetEnvironmentVariable();
return View();
}
private void SetEnvironmentVariable()
{
try
{
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "MY PATH TO SERVICE ACCOUNT PRIVATE KEY IS HERE");
}
catch (ArgumentNullException)
{
throw;
}
catch (ArgumentException)
{
throw;
}
}
[HttpGet]
public async Task<JsonResult> CheckIntentAsync(string userInput)
{
var sessionClient = await SessionsClient.CreateAsync();
var sessionName = new SessionName(projectID, sessionID);
QueryInput queryInput = new QueryInput();
var queryText = new TextInput();
queryText.Text = userInput;
queryText.LanguageCode = "en";
queryInput.Text = queryText;
// Make the request
DetectIntentResponse response = await sessionClient.DetectIntentAsync(sessionName, queryInput);
var reply = response.QueryResult;
return Json(reply, JsonRequestBehavior.AllowGet);
}
}
So far all of the above works a charm with the inline-editor in Dialogflow. I now am creating my webhook fulfilment in .NET and cannot get it to work. My API class looks like this:
public class WebhookController : ApiController
{
private static readonly JsonParser jsonParser =
new JsonParser(JsonParser.Settings.Default.WithIgnoreUnknownFields(true));
[HttpPost]
public async Task<HttpResponseMessage> Post()
{
WebhookRequest request;
using (var stream = await Request.Content.ReadAsStreamAsync())
{
using (var reader = new StreamReader(stream))
{
request = jsonParser.Parse<WebhookRequest>(reader);
}
}
// Simply sets the fulfillment text to equal the name of the intent detected by Dialogflow
WebhookResponse webhookResponse = new WebhookResponse
{
FulfillmentText = request.QueryResult.Intent.DisplayName
};
HttpResponseMessage httpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(webhookResponse.ToString())
{
Headers = { ContentType = new MediaTypeHeaderValue("text/json") }
}
};
return httpResponse;
}
}
When I run this, I get in the dialogflow console's diagnostic info a 'DEADLINE_EXCEEDED' message however the webhook is doing so little I don't understand why this is?
"webhookStatus": {
"code": 4,
"message": "Webhook call failed. Error: DEADLINE_EXCEEDED."
}
I don't know if I'm supposed to perform some sort of authentication in the webhook as well as in my HomeController?
Some help would be greatly greatly appreciated!!!
Many thanks!
I was getting this same error when i enabled the webhook call from a follow up intent that wasn't mapped (to handler) in fulfillment inline editor.
I want to allow my users connect to a specific group in my SignalR hub, to do this i generate a unique id that the users in that group can share with others. Once a user connects to the hub, the id is generated. On the "connected" event their URL updates with the unique id. When I then use the URL to join the newly created room it seems like two negotiation requests are sent, both containing the the group id as well as the users connection id, yet sometimes(not always) I get a response from the Hub containing a newly generated Group.
Is the ?pod parameter I pass into the url not always assigned before the request is made?
To me it seems completely random, but it's most likely some error I've made in my connection code since I'm relatively new to Angular.
This request happened correctly and I joined the room I wanted.
Correct behavior
This one happened incorrectly and a new room was generated even though, seemingly(?), the request looks the same, save for the web socket connection containing the "pid".
Incorrect behavior
Any help is greatly appreciated!
The code for the Home component where the connection is initiated
export class HomeComponent implements OnInit, OnDestroy{
welcomeMessage:string;
podId:string;
users:User[];
constructor(private signalrService: SignalRService, private http: HttpClient, private activeRoute: ActivatedRoute,
private router: Router, private location: Location) {}
ngOnInit() {
this.activeRoute.queryParams.subscribe(params => {
this.podId = params['pod'];
this.connectToPod();
});
}
ngOnDestroy(): void {
}
connectToPod(){
this.signalrService.startConnection(this.podId);
this.signalrService.addPodConnectedLisener((data: Pod) => {
this.podId = data.id;
this.users = data.users;
this.location.replaceState('/?pod=' + this.podId)
this.welcomeMessage = window.location.origin + '/?pod=' + this.podId;
});
}
}
The code for the SignalR service
export class SignalRService {
private hubConnection: signalR.HubConnection;
private baseUrl = environment.apiUrl;
constructor() { }
public startConnection (podId?: string) {
let idToSend = podId == undefined ? '' : '?pid=' + podId;
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.baseUrl + '/pod' + idToSend)
.build();
this.hubConnection
.start()
.then(() => console.log('Connection started'))
.catch(err => console.log('Error while starting connection: ' + err));
}
public addPodConnectedLisener (connectedCallback: Function) {
return this.hubConnection.on('connected', data => {
connectedCallback(data);
});
}
}
The code for the SignalR Hub
public class PodHub : Hub
{
private readonly IPodConnectionManger _podConnectionManager;
public PodHub(IPodConnectionManger podConnectionManager)
{
_podConnectionManager = podConnectionManager;
}
public override async Task OnConnectedAsync()
{
var podId = Context.GetHttpContext().Request.Query["pid"].ToString();
if (string.IsNullOrEmpty(podId))
{
await CreatePod();
}
else
{
await JoinPod(podId);
}
}
private async Task CreatePod()
{
var newPodId = await _podConnectionManager.AddPod();
var podToSend = await _podConnectionManager.GetPod(newPodId);
await podToSend.AddUser(Context.ConnectionId);
await Groups.AddToGroupAsync(Context.ConnectionId, podToSend.Id);
await Clients.Group(podToSend.Id).SendAsync("connected", podToSend);
}
private async Task JoinPod(string id)
{
var podToJoin = await _podConnectionManager.GetPod(id);
await podToJoin.AddUser(Context.ConnectionId);
await Groups.AddToGroupAsync(Context.ConnectionId, podToJoin.Id);
await Clients.Group(podToJoin.Id).SendAsync("connected", podToJoin);
}
}
I am connecting to the Quickbooks api, download the employees inforamtion and saving it to my local database. I am using angularjs, webapi to accomplish this. I am getting the following error when I am saving the info to database. I do see all the functions have async and await. Can some body please help me why I am getting this error.
Error :
Server Error in '/' Application.An asynchronous module or handler completed while an asynchronous operation was still pending.
Problem is happening in the below pasted piece of code:
var candidate = await CandidateLoginBL.AddCandidateByEmployeeAsync(new CandidateLoginBO()
{
FirstName = e.GivenName,
MiddleName = e.MiddleName,
LastName = e.FamilyName
});
}
});
The full flow is as follows :
js :
QuickbookModule.factory('QuickbookService', ['$http', function ($http) {
return {
getQuickbooksSync: function () {
return $http({
url: '/api/QuickbookService/syncQuickbooks',
method: 'GET',
params: { IdCompany: sessionStorage.CID }
});
}
API Controller :
[HttpGet]
[Route("syncQuickbooks")]
public async Task<IHttpActionResult> syncQuickbooks(int IdCompany)
{
var result = await QuickbooksBL.FullQuickbooksSync(IdCompany);
return Ok(result);
}
QuickbooksBL :
public static async Task<List<IncompleteEmp>> FullQuickbooksSync(int IdCompany)
{return await SyncronizeEmps(IdCompany); }
public static async Task<List<IncompleteEmp>> SyncronizeEmps(int companyId)
{
......
List<EmployeeBO> empList = new List<EmployeeBO>();
await AddToHumanEfits(companyId, inQBEmpsInfo); ....
}
return IncompleteEmps;
}
public static async Task AddToHumanEfits(int companyId, List<EmployeeQbOnlineBO> qbEmpsList)
{
....
qbEmpsList.ForEach(async e =>
{
// Add a record into Candidate Login.
var candidate = await CandidateLoginBL.AddCandidateByEmployeeAsync(new CandidateLoginBO()
{
FirstName = e.GivenName,
MiddleName = e.MiddleName,
LastName = e.FamilyName });
}
});
}
CandidateContactBL :
public static async Task<CandidateLoginBO> AddCandidateByEmployeeAsync(CandidateLoginBO model)
{
return await CandidateLoginDAL.AddCandidateByEmployeeAsync(model);
}
CandidateContactDAL :
public static async Task<CandidateLoginBO> AddCandidateByEmployeeAsync(CandidateLoginBO model)
{
CandidateLoginBO candidate = new CandidateLoginBO();
candidate = await GetByUserNameAsync(new CandidateLoginBO() { Email = model.Email }); candidate = await AddEmployeeAsync(model);
return candidate;
}
This kind of error is commonly caused by async void. And I see one right here:
qbEmpsList.ForEach(async e =>
{
...
});
You'd probably want to make this into a regular foreach:
foreach (var e in qbEmpsList)
{
...
}
My connection does not start.
This code worked in 1.x but in version 2 is not working.
SignalR seems to be trying to connect but without success.
The hub method is never called.
Attached sending an image with SignalR debug.
Javascript:
<script type="text/javascript">
$.connection.hub.logging = true;
var options = { transport: ['webSockets', 'longPolling'] };
$(function() {
var userHub = $.connection.userHub;
//Iniciar connecção
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
</script>
Hub:
[HubName("userHub")]
public class UserHub : Hub
{
public void Ini()
{
Clients.Client(Context.ConnectionId).iniDone(string.Format("Conectado com o id: {0}", Context.ConnectionId));
}
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Add(connectionId);
return base.OnConnected();
}
public override Task OnDisconnected()
{
var connectionId = Context.ConnectionId;
var email = string.IsNullOrWhiteSpace(Context.User.Identity.Name) ? Context.Headers["email"] : Context.User.Identity.Name;
if (email != null && connectionId != null)
UserData.GetInstance(email).ConnectionsIds.Remove(connectionId);
return base.OnDisconnected();
}
}
Debug:
SignalR Debug Image
EDIT:
I found the problem! The GetInstance method of my Singleton has problems.
public static UserData GetInstance(string username)
{
if (_sharedUsers == null)
lock (_lockCreate)
_sharedUsers = new Dictionary<string, UserData>();
if (!_sharedUsers.ContainsKey(username))
lock (_lockAdd)
_sharedUsers.Add(username, new UserData(username));
return _sharedUsers[username];
}
the method stops always here: lock (_lockAdd)
I want to save all user connectionsIds Any ideas?
Thanks
Try moving the client method subscription to be before you connect. If it's not registered by the time the connection is started, then it will not be callable from the server.
So change it to the following:
$(function() {
var userHub = $.connection.userHub;
//Register Client handlers first
userHub.client.iniDone = function (connectionId) {
console.log(connectionId);
};
//Now you can connect.
window.hubReady = $.connection.hub.start(options);
window.hubReady.done(function () {
userHub.server.ini();
});
$.connection.hub.connectionSlow(function() {
console.log('slow connection...');
});
window.hubReady.fail(function(error) {
console.log(error);
});
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 2000);
});
});
Edit
Based on your comment around a server error in the OnConnected method, it seems like you may have a two problems then. Isolate the connection tracking part out (just comment it out) to get the full round-trip going between client and server. Then add back the connection tracking which is possibly a DB connection error - check the server logs.
Edit
In terms of storing the user connections, you've a few options.
Use ConcurrentDictionary:
One of the simplest is storing in a static ConcurrentDictionary, similar to what you have. Try to avoid the use of so many locks - using a ConcurrentDictionary means you'll actually end up with none.
e.g.
public class UserData
{
public UserData(string username)
{
UserName = username;
ConnectionIds = new HashSet<string>();
}
public string UserName { get; private set; }
public HashSet<string> ConnectionIds { get; private set; }
}
public static class ConnectionStore
{
private static readonly ConcurrentDictionary<string, UserData> _userData = new ConcurrentDictionary<string, UserData>();
public static void Join(string username, string connectionId)
{
_userData.AddOrUpdate(username,
u => new UserData(u), /* Lambda to call when it's an Add */
(u, ud) => { /* Lambda to call when it's an Update */
ud.ConnectionIds.Add(connectionId);
return ud;
});
}
}
See MSDN for more info: http://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx
Use a database:
The other option is to store in a database (using Entity Framework) which has the added benefit of tracking user data across server recycles.
Have a look at http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections which shows all these options a couple of others.
Had the same problem for so long, so gave up the whole signalR at some point, but had to pick it up again for our project:
I have written an answer which might lead you and others on the right track (step by step)...In the answer I am using PersistentConnection rather than Hub, but the principle should be the same:
https://stackoverflow.com/a/25304790/3940626