Unverified Commit 0d73b915 authored by Almouhannad Hafez's avatar Almouhannad Hafez Committed by GitHub

Merge pull request #13 from Almouhannad/B_Add-SignalR

B add signal r
parents 33ad80b5 0f2dd34b
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" /> <ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Domain\Domain.csproj" /> <ProjectReference Include="..\Domain\Domain.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
<ProjectReference Include="..\Persistence\Persistence.csproj" /> <ProjectReference Include="..\Persistence\Persistence.csproj" />
<ProjectReference Include="..\Presentation\Presentation.csproj" /> <ProjectReference Include="..\Presentation\Presentation.csproj" />
</ItemGroup> </ItemGroup>
......
...@@ -3,6 +3,8 @@ using API.Options.JWT; ...@@ -3,6 +3,8 @@ using API.Options.JWT;
using API.SeedDatabaseHelper; using API.SeedDatabaseHelper;
using Application.Behaviors; using Application.Behaviors;
using FluentValidation; using FluentValidation;
using Infrastructure.BackgroundServices.Notifications;
using Infrastructure.NotificationsService;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
...@@ -12,6 +14,10 @@ using Persistence.Context; ...@@ -12,6 +14,10 @@ using Persistence.Context;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
#region Add Database context #region Add Database context
// First, get database options // First, get database options
builder.Services.ConfigureOptions<DatabaseOptionsSetup>(); builder.Services.ConfigureOptions<DatabaseOptionsSetup>();
...@@ -35,7 +41,16 @@ builder.Services.AddDbContext<ClinicsDbContext>( ...@@ -35,7 +41,16 @@ builder.Services.AddDbContext<ClinicsDbContext>(
}); });
#endregion #endregion
// Add services to the container. #region Add SignalR
builder.Services.AddSignalR();
// Background services:
builder.Services.AddHostedService<ServerTimeNotifier>();
#endregion
#region Add CORS
builder.Services.AddCors();
#endregion
#region Link interfaces implemented in persistence #region Link interfaces implemented in persistence
// Using Scrutor library // Using Scrutor library
...@@ -126,9 +141,17 @@ if (app.Environment.IsDevelopment()) ...@@ -126,9 +141,17 @@ if (app.Environment.IsDevelopment())
app.UseHttpsRedirection(); app.UseHttpsRedirection();
#region Map notification HUB
app.MapHub<NotificationHub>("api/Notifications");
#endregion
#region CORS #region CORS
// TODO: Configure allows // TODO: Configure allows
app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin()); app.UseCors(policy =>
policy
.AllowAnyHeader().AllowAnyMethod().AllowAnyHeader()
.AllowCredentials().SetIsOriginAllowed(origin => true));
#endregion #endregion
......
...@@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Presentation", "Presentatio ...@@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Presentation", "Presentatio
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{2B5111ED-7AB8-46E2-90F8-AF0C70265618}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{2B5111ED-7AB8-46E2-90F8-AF0C70265618}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{15B2AA13-EBBD-408B-A4B3-4BAB43D74267}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API", "API\API.csproj", "{15B2AA13-EBBD-408B-A4B3-4BAB43D74267}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{4EB41743-695F-4822-87F6-E6F9B48A8E6B}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
...@@ -47,6 +49,10 @@ Global ...@@ -47,6 +49,10 @@ Global
{15B2AA13-EBBD-408B-A4B3-4BAB43D74267}.Debug|Any CPU.Build.0 = Debug|Any CPU {15B2AA13-EBBD-408B-A4B3-4BAB43D74267}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15B2AA13-EBBD-408B-A4B3-4BAB43D74267}.Release|Any CPU.ActiveCfg = Release|Any CPU {15B2AA13-EBBD-408B-A4B3-4BAB43D74267}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15B2AA13-EBBD-408B-A4B3-4BAB43D74267}.Release|Any CPU.Build.0 = Release|Any CPU {15B2AA13-EBBD-408B-A4B3-4BAB43D74267}.Release|Any CPU.Build.0 = Release|Any CPU
{4EB41743-695F-4822-87F6-E6F9B48A8E6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EB41743-695F-4822-87F6-E6F9B48A8E6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EB41743-695F-4822-87F6-E6F9B48A8E6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EB41743-695F-4822-87F6-E6F9B48A8E6B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
...@@ -59,6 +65,7 @@ Global ...@@ -59,6 +65,7 @@ Global
{CE41FA28-76D7-4DC0-A980-B953F87253F5} = {6FC36DEB-2BC3-4980-B46B-7E518DAF09BF} {CE41FA28-76D7-4DC0-A980-B953F87253F5} = {6FC36DEB-2BC3-4980-B46B-7E518DAF09BF}
{D04C0D6C-3FC9-4359-A017-DFCFA1475639} = {878AE2C9-3E34-4332-ACC0-E6B601085710} {D04C0D6C-3FC9-4359-A017-DFCFA1475639} = {878AE2C9-3E34-4332-ACC0-E6B601085710}
{2B5111ED-7AB8-46E2-90F8-AF0C70265618} = {FDA56BCD-A53D-4BA1-A59D-3F44FA32DDD7} {2B5111ED-7AB8-46E2-90F8-AF0C70265618} = {FDA56BCD-A53D-4BA1-A59D-3F44FA32DDD7}
{4EB41743-695F-4822-87F6-E6F9B48A8E6B} = {FDA56BCD-A53D-4BA1-A59D-3F44FA32DDD7}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E9FC9252-1283-485F-8F84-3574CFA12633} SolutionGuid = {E9FC9252-1283-485F-8F84-3574CFA12633}
......
using Infrastructure.NotificationsService;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Infrastructure.BackgroundServices.Notifications;
public class ServerTimeNotifier : BackgroundService
{
private static readonly TimeSpan Period = TimeSpan.FromSeconds(3);
#region CTOR DI
private readonly ILogger<ServerTimeNotifier> _logger;
private readonly IHubContext<NotificationHub, INotificationClient> _context;
public ServerTimeNotifier(ILogger<ServerTimeNotifier> logger, IHubContext<NotificationHub, INotificationClient> context)
{
_logger = logger;
_context = context;
}
#endregion
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var timer = new PeriodicTimer(Period);
while (!stoppingToken.IsCancellationRequested &&
await timer.WaitForNextTickAsync(stoppingToken))
{
var dateTime = DateTime.Now;
_logger.LogInformation("Executing {Service} {Time}", nameof(ServerTimeNotifier), dateTime);
await _context.Clients.All.ReceiveNotification($"Server time = {dateTime}");
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="MediatR" Version="12.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Scrutor" Version="4.2.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
</Project>
using Microsoft.AspNetCore.SignalR;
namespace Infrastructure.NotificationsService;
public class NotificationHub : Hub<INotificationClient>
{
public override async Task OnConnectedAsync()
{
await Clients.Client(Context.ConnectionId).ReceiveNotification(
$"Connected successfully { Context.User?.Identity?.Name}");
await base.OnConnectedAsync();
}
}
public interface INotificationClient
{
Task ReceiveNotification(string message);
}
\ No newline at end of file
This diff is collapsed.
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"@angular/platform-browser-dynamic": "^18.1.0", "@angular/platform-browser-dynamic": "^18.1.0",
"@angular/router": "^18.1.0", "@angular/router": "^18.1.0",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@microsoft/signalr": "^8.0.7",
"@ng-bootstrap/ng-bootstrap": "^17.0.0", "@ng-bootstrap/ng-bootstrap": "^17.0.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
......
...@@ -5,6 +5,7 @@ import { RoleGuard } from './services/authentication/guards/role-guard'; ...@@ -5,6 +5,7 @@ import { RoleGuard } from './services/authentication/guards/role-guard';
import { Roles } from './classes/Authentication/roles'; import { Roles } from './classes/Authentication/roles';
import { ForbiddenComponent } from './components/errors/forbidden/forbidden.component'; import { ForbiddenComponent } from './components/errors/forbidden/forbidden.component';
import { NotFoundComponent } from './components/errors/not-found/not-found.component'; import { NotFoundComponent } from './components/errors/not-found/not-found.component';
import { TestSignalRComponent } from './test-signal-r/test-signal-r.component';
const routes: Routes = [ const routes: Routes = [
{ {
...@@ -25,6 +26,16 @@ const routes: Routes = [ ...@@ -25,6 +26,16 @@ const routes: Routes = [
canActivate: [RoleGuard], canActivate: [RoleGuard],
data: { role: Roles.NotRegistered } data: { role: Roles.NotRegistered }
}, },
// #region Testing SignalR
{
path: 'testing',
component: TestSignalRComponent,
canActivate: [RoleGuard],
data: { role: Roles.NotRegistered }
},
// #endregion
// Everything else // Everything else
{ {
path: '**', path: '**',
......
...@@ -18,6 +18,8 @@ import { AuthenticationService } from './services/authentication/authentication. ...@@ -18,6 +18,8 @@ import { AuthenticationService } from './services/authentication/authentication.
import { AuthenticationInterceptor } from './services/authentication/interceptor/authentication.interceptor'; import { AuthenticationInterceptor } from './services/authentication/interceptor/authentication.interceptor';
import { ForbiddenComponent } from './components/errors/forbidden/forbidden.component'; import { ForbiddenComponent } from './components/errors/forbidden/forbidden.component';
import { NotFoundComponent } from './components/errors/not-found/not-found.component'; import { NotFoundComponent } from './components/errors/not-found/not-found.component';
import { TestSignalRComponent } from './test-signal-r/test-signal-r.component';
import { SignalRService } from './notifications/signal-r.service';
@NgModule({ @NgModule({
...@@ -39,6 +41,7 @@ import { NotFoundComponent } from './components/errors/not-found/not-found.compo ...@@ -39,6 +41,7 @@ import { NotFoundComponent } from './components/errors/not-found/not-found.compo
providers: [ providers: [
AuthenticationService, AuthenticationService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true}, { provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true},
SignalRService
], ],
// components and directives that belong to this module // components and directives that belong to this module
...@@ -52,7 +55,8 @@ import { NotFoundComponent } from './components/errors/not-found/not-found.compo ...@@ -52,7 +55,8 @@ import { NotFoundComponent } from './components/errors/not-found/not-found.compo
HomeComponent, HomeComponent,
LoginFormComponent, LoginFormComponent,
ForbiddenComponent, ForbiddenComponent,
NotFoundComponent NotFoundComponent,
TestSignalRComponent
], ],
// identifies the root component that Angular should // identifies the root component that Angular should
......
import { Injectable } from '@angular/core';
import { HubConnectionBuilder } from '@microsoft/signalr';
import * as config from '../../../config'
@Injectable({
providedIn: 'root'
})
export class SignalRService {
constructor() { }
private readonly NOTIFICATIONS_ENDPOINT: string = `${config.apiUrl}/Notifications`
hubConnection: signalR.HubConnection;
startConnection(): void {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.NOTIFICATIONS_ENDPOINT)
.build();
this.hubConnection
.start()
.then(() => {
console.log('Connected to signalR!')
})
.catch(err => console.error('Error while starting connection: ' + err))
}
endConnection(): void {
if (this.hubConnection) {
this.hubConnection
.stop()
.then(() => {
console.log('disonnected from signalR!');
})
.catch(err => console.error('Error while stopping connection: ' + err));
} else {
console.log('No active connection to stop.');
}
}
}
\ No newline at end of file
<div>
<div class="text-center">
<h2>Notifications:</h2>
<h3>{{notification}}</h3>
<button (click)="onClick()" class="btn btn-outline-danger">Stop</button>
</div>
</div>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { SignalRService } from '../notifications/signal-r.service';
@Component({
selector: 'app-test-signal-r',
templateUrl: './test-signal-r.component.html',
styleUrls: ['./test-signal-r.component.css']
})
export class TestSignalRComponent implements OnInit {
notification: string = '';
constructor(private signalR: SignalRService){}
ngOnInit(): void {
this.signalR.startConnection();
this.signalR.hubConnection.on('ReceiveNotification', (message) => {
this.notification = message;
})
}
onClick(): void {
this.signalR.endConnection();
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment