Read the following page and take notice of the section on IQueryables. abp repositories
But right now it's time for bed.
Yes, implement an IQueryable for your query and then execute with the ToList() or await ToListAsync(). If you would like to post your code I can advise further.
@moetarhini - there is no foreign key because it is a multi-database solution. Your entity may not be in the same database as your tenant.
Hi @moetarhini - your entities may be saved in a different database to your tenants. It would not be a good idea with the Zero framework which explicitly allows a multi-database solution. As a DBA I would also question the use of composite keys, amongst other things they can cause havoc with replication but maybe I'm just old-school.
@timmackey, implement an sql repository in the entity framework project and call the methods directly from your app service. All of my grid information is read in such a way, sometimes it is simpler and more performant to work directly with the database without the Zero overhead.
As promised: The use case here is to send data to the host dashboard from a service in the application layer. The following dashboard widget is interactively updated using SignalR.
In Web.Core project create a new hub, e.g
using Abp.Dependency;
using Abp.Runtime.Session;
using Castle.Core.Logging;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;
namespace Nuagecare.Web.App.SignalR.FormSubmissionHub
{
public class FormSubmissionHub : Hub, ITransientDependency
{
public IAbpSession AbpSession { get; set; }
public ILogger Logger { get; set; }
public FormSubmissionHub()
{
AbpSession = NullAbpSession.Instance;
Logger = NullLogger.Instance;
}
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("getFormSubmission", string.Format("User {0}: {1}", AbpSession.UserId, message));
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
Logger.Debug("A client connected to FormSubmissionHub: " + Context.ConnectionId);
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
Logger.Debug("A client disconnected from FormSubmissionHub: " + Context.ConnectionId);
}
}
}
Create a hub communicator in the same location:
public class SignalRFormSubmissionCommunicator : ISignalRFormSubmissionCommunicator, ITransientDependency
{
private readonly IHubContext<FormSubmissionHub> _formSubmissionHub;
public IAbpSession AbpSession { get; set; }
public ILogger Logger { get; set; }
public SignalRFormSubmissionCommunicator(IHubContext<FormSubmissionHub> formSubmissionHub)
{
_formSubmissionHub = formSubmissionHub;
AbpSession = NullAbpSession.Instance;
Logger = NullLogger.Instance;
}
public async Task SendFormSubmission(int tenantId)
{
await _formSubmissionHub.Clients.All.SendAsync("getFormSubmission", tenantId);
}
}
The interface for the communicator goes in the Core project. Now comes the bit of magic from the Zero guys to give you access to the communicator from the Application project. It’s all a bit wierd and took some fiddling with. In the same location as your communicator interface, in the Core project create a null communicator class:
public class NullFormSubmissionCommunicator : ISignalRFormSubmissionCommunicator
{
public async Task SendFormSubmission(int tenantId)
{
await Task.CompletedTask;
}
}
To complete the plumbing we register our null communicator class and somehow, by magic, your own communicator is used. Go figure. In the PostInitialize() function of your CoreModule add the following line:
IocManager.RegisterIfNot<ISignalRFormSubmissionCommunicator, NullFormSubmissionCommunicator>();
All of this work is so we can call the hub from the application project with a line similar to that below.
await _signalRFormSubmissionCommunicator.SendFormSubmission((int)AbpSession.TenantId);
This message is sent to all connected clients so the next job is to hook it up in angular.
In the angular project create a new component using angular-cli below the admin dashboard and display this on your dashboard. In the same location create a new service using angular-cli called HostDashboardSignalR, something like below. I have created an ngx-tabsheet on the Zero dashboard where I add my own tabbed content, my new tab is called ApiTab. Your service file looks like this:
import { Injectable, Injector, NgZone } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { HubConnection } from '@aspnet/signalr';
@Injectable()
export class FormSubmissionSignalrService extends AppComponentBase {
constructor(
injector: Injector,
public _zone: NgZone
) {
super(injector);
}
formSubmissionHub: HubConnection;
isFormSubmissionHubConnected = false;
init(): void {
this._zone.runOutsideAngular(() => {
abp.signalr.connect();
abp.signalr.startConnection(abp.appPath + 'signalr-formSubmission', connection => {
abp.event.trigger('app.formSubmission.connected');
this.isFormSubmissionHubConnected = true;
this.configureConnection(connection);
});
});
}
configureConnection(connection): void {
// Set the common hub
this.formSubmissionHub = connection;
// Reconnect if hub disconnects
connection.onclose(e => {
this.isFormSubmissionHubConnected = false;
if (e) {
abp.event.trigger('app.formSubmission.disconnected', e);
} else {
abp.event.trigger('app.formSubmission.disconnected');
}
if (!abp.signalr.autoConnect) {
return;
}
setTimeout(() => {
connection.start().then(result => {
console.log(result);
this.isFormSubmissionHubConnected = true;
});
}, 5000);
});
// Register to get notifications
this.registerGetFormSubmission(connection);
}
registerGetFormSubmission(connection): void {
connection.on('getFormSubmission', message => {
abp.event.trigger('app.formSubmission.newSubmission', message);
});
}
}
Make sure you add it as a provider to your admin.module.ts file:
providers: [
ImpersonationService,
TreeDragDropService,
FormSubmissionSignalrService,
Inject this service into your new component and you’re good to go. Here’s my component:
import { Component, OnInit, Injector, NgZone } from '@angular/core';
import { FormSubmissionSignalrService } from '../host-dashboard-signalr/formSubmission-signalr.service';
import { HostDashboardServiceProxy, GetHostDashboardDataOutput, GetTenantSummaryDataForHostDashboard_Output } from '@shared/service-proxies/service-proxies';
import { AppComponentBase } from '@shared/common/app-component-base';
import { SignalRHelper } from '@shared/helpers/SignalRHelper';
import * as moment from 'moment';
import * as _ from 'lodash';
@Component({
selector: 'app-host-dashboard-apitab',
templateUrl: './host-dashboard-apitab.component.html',
styleUrls: ['./host-dashboard-apitab.component.css']
})
export class HostDashboardAPITabComponent extends AppComponentBase implements OnInit {
public connected: string;
public formCount: number;
loading = false;
hostDashboardData = new GetHostDashboardDataOutput();
tenantSummary = new Array<GetTenantSummaryDataForHostDashboard_Output>();
public isFormSubmissionHubConnected: boolean;
constructor(
injector: Injector,
private _formSubmissionSignalrService: FormSubmissionSignalrService,
private _hostDashboardService: HostDashboardServiceProxy,
public _zone: NgZone
) {
super(injector);
}
init(): void {
this.getTenantDataForHostDashboard();
this.registerEvents();
}
ngOnInit(): void {
if (this.appSession.application) {
SignalRHelper.initSignalR(() => { this._formSubmissionSignalrService.init(); });
}
this.init();
}
getTenantDataForHostDashboard() {
let midnight = moment().startOf('day');
let now = moment();
this.loading = true;
this._hostDashboardService.getTenantDataForHostDashboard(
midnight,
now,
0
).subscribe(result => {
this.hostDashboardData = result;
this.tenantSummary = _.orderBy(result.tenantSummary, 'formSubmissionSum', 'desc');
this.loading = false;
});
}
registerEvents(): void {
abp.event.on('app.formSubmission.connected', (message) => {
this._zone.run(() => {
this.isFormSubmissionHubConnected = true;
this.connected = 'hub connected';
});
});
abp.event.on('app.formSubmission.disconnected', (message) => {
this._zone.run(() => {
this.isFormSubmissionHubConnected = false;
this.connected = 'hub disconnected: ' + message;
});
});
abp.event.on('app.formSubmission.newSubmission', (message) => {
this._zone.run(() => {
this.updateTenant(message);
this.formCount = this.formCount + 1;
});
});
}
formatDate(tenant: GetTenantSummaryDataForHostDashboard_Output): string {
if (tenant.formSubmissionSum === 0) {
return '';
} else {
return moment(moment(tenant.lastUpdated).utc().valueOf()).format('DD/MM/YYYY H:mm:ss');
}
}
For good measure here is my html;
<div class="col-lg-4 col-md-6 col-sm-12">
<div class="card border-primary rounded-0 mb-2">
<div class="card-header bg-primary text-white">
<h5><i class="flaticon-profile mr-3"></i>API Stats</h5>
</div>
<div class="card-body">
<div *ngIf="isFormSubmissionHubConnected" class="small text-muted m--font-success"><small>{{connected}}</small></div>
<div *ngIf="!isFormSubmissionHubConnected" class="small text-muted m--font-danger"><small>{{connected}}</small></div>
<div class="row">
<div class="col-4">
<h6>Tenant</h6>
</div>
<!-- <div class="col-4">
<h6>Users</h6>
</div> -->
<div class="col-4">
<h6>Care actions</h6>
</div>
<div class="col-4">
<h6>Last updated</h6>
</div>
</div>
<div class="row" *ngFor="let tenant of tenantSummary; index as i">
<div class="col-4">{{tenant.tenancyName}}</div>
<!-- <div class="col-4">{{tenant.signedInUsersSum}}</div> -->
<div class="col-4">
<strong>{{tenant.formSubmissionSum}}</strong>
</div>
<div class="col-4">
<strong>{{formatDate(tenant)}}</strong>
</div>
</div>
</div>
</div>
</div>
The result is: The numbers are updated from the hub and the last updated is modified on the client. I have a lot of apps entering data into my Zero implementation so this simple table allows me to see that all of my tenants are working at a glance. I thought Musa could use this for the new dashboard, number of active users by tenant would be great.
I seem to remember a discussion about how to get the count of online users for each tenant using SignalR. Can anyone point me to the correct discussion? I have searched this forum and the github pages to no avail.
@ismcagdas, I finally got this working. Give the weekend and i will write up how I did it then you can use it in your documentation should you so desire. Then I'll close the issue.
Taking this a bit further I noticed the line:
IocManager.RegisterIfNot<IChatCommunicator, NullChatCommunicator>();
in [ProjectName]CoreModule.cs in the Core project.
I create a new class, NullFormSubmissionCommunicator in the same folder as my interface in the Core project:
using System.Threading.Tasks;
namespace Nuagecare.App.SignalR
{
public class NullFormSubmissionCommunicator : ISignalRFormSubmissionCommunicator
{
public async Task SendFormSubmission(int tenantId)
{
await Task.CompletedTask;
}
}
}
and add the following to my module:
IocManager.RegisterIfNot<ISignalRFormSubmissionCommunicator, NullFormSubmissionCommunicator>();
But the message is still not received in the angular project. I must be close but can't yet light up my cigar. What am I missing?