仅当使用Azure SignalR在Angular中更改数据时才刷新表吗?

问题描述

我有一个Angular应用程序,它可以调用我的webapi(带有Azure sql的.Net Core)。假设我有一个不断变化的员工sql表(id,fname,lname,email)。我希望能够以某种方式侦听数据库更改并更新前端以反映数据库中的新更改,而不必每秒发起一次对我的webapi的调用。从理论上讲,就像sql向我的前端发送某种标志一样,以便Angular知道何时发送另一个api请求。

我阅读了有关Azure SignalR的信息,并试图实现针对我的问题的解决方案,所以这是我的工作顺序:

首先:我创建了一个Azure SignalR服务并获得了连接字符串

第二次::我添加了CORS并将SignalR服务注入到我的startup.cs文件中,如下所示:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DataContext>(x => x.UsesqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddSignalR().AddAzureSignalR(Configuration["Azure:SignalRConnectionString"]);
            services.AddControllers().AddNewtonsoftJson(opt => {
                opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
            });

            services.AddControllers();

            string corsDomains = "http://localhost:4200";  
            string[] domains = corsDomains.Split(",".tochararray(),StringSplitOptions.RemoveEmptyEntries);  
  
            services.AddCors(o => o.AddPolicy("AppCORSPolicy",builder =>  
            {  
                builder.AllowAnyOrigin()  
                       .AllowAnyMethod()  
                       .AllowAnyHeader()  
                       .AllowCredentials()  
                       .WithOrigins(domains);  
            }));

            services.AddAutoMapper(typeof(IRepo).Assembly);
            services.AddScoped<IRepo,Repo>();
        }

public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler(builder => {
                    builder.Run(async context => {
                        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

                        var error = context.Features.Get<IExceptionHandlerFeature>();
                        if (error != null)
                        {
                            context.Response.AddApplicationError(error.Error.Message);
                            await context.Response.WriteAsync(error.Error.Message);
                        }
                    });
                });

                // Enable in Production
                app.UseHsts();
            }

            // app.UseHttpsRedirection();

            app.UseRouting();

            // app.UseAuthentication();  
            app.UseAuthorization(); 

            app.UseCors("AppCORSPolicy");

            app.UseDefaultFiles();
            app.UseStaticFiles();
            
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHub<EmployeeHub>("");
            });

        }

您应该知道我正在使用本地主机环境运行此应用程序。

第三:在我的角度应用程序中,我使用以下命令安装了Signalr:

npm install @aspnet/signalr –-save

第四步:我创建了一个Timer类,它将在控制器中调用GetAllEmployees方法,如下所示:

public class TimerManager
{
    private Timer _timer;
    private AutoResetEvent _autoResetEvent;
    private Action _action;
    public DateTime TimerStarted { get; }
    public TimerManager(Action action)
    {
        _action = action;
        _autoResetEvent = new AutoResetEvent(false);
        _timer = new Timer(Execute,_autoResetEvent,1000,2000);
        TimerStarted = DateTime.Now;
    }
    public void Execute(object stateInfo)
    {
        _action();
    }
}
 

我的控制器如下所示:

[ApiController]
    [Route("api/[controller]")]
    public class EmpsController : ControllerBase
    {
        private readonly IRepo _repo;
        private readonly IConfiguration _config;
        private IHubContext<EmployeeHub> _hub;

        public EmpsController(IRepo repo,IConfiguration config,IHubContext<EmployeeHub> hub)
        {
            _config = config;
            _repo = repo;
            _hub = hub;
        }

        [HttpGet]
        public async Task<IActionResult> GetAllEmployees()
        {
            var emps = await _repo.GetAllEmployees();
            var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferempdata",emps));

            return Ok(new { Message = "Calling the api one time" });
        } 
    }

在Angular应用程序中,我创建了一个类似于以下内容的员工服务

import { Injectable } from '@angular/core';
import { HttpClient,HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Emp } from '../_models/emp';
import { GlobalConstants } from '.././_common/global-constants';
import * as signalR from "@aspnet/signalr";

@Injectable({
  providedIn: 'root'
})

export class EmpService {

    baseUrl = GlobalConstants.apiUrl;
    httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json'
        })
    };
    public employees: Emp[];

    private hubConnection: signalR.HubConnection;
    public startConnection = () => {
      this.hubConnection = new signalR.HubConnectionBuilder()
                              .withUrl('http://localhost:5000')
                              .build();
      this.hubConnection
        .start()
        .then(() => console.log('Connection started'))
        .catch(err => console.log('Error while starting connection: ' + err))
    }

    public addTransferEmployeeDataListener = () => {
      this.hubConnection.on('transferempdata',(data) => {
        this.employees = data;
        console.log(this.employees); // returns the employees array every 2 seconds
      });
    }

    constructor(private http: HttpClient
    ) { }

    getEmployees(): Observable<Emp[]> {
      this.httpOptions = {
          headers: new HttpHeaders({
              'Content-Type': 'application/json'
          })

      };

      return this.http.get(this.baseUrl + 'emps/',this.httpOptions)
          .pipe((response: any) => {
              return response;
          });
    }

}

,然后在我的应用程序组件中利用此EmpService,如下所示:

export class AppComponent {
  title = 'angular8app';
  employees: Emp[];
  errorMessage: any;
  constructor(private empService: EmpService,private http: HttpClient) { }

  ngOnInit(): void {
    this.empService.startConnection();
    this.empService.addTransferEmployeeDataListener();

    this.empService.getEmployees()
    .subscribe((res: Emp[]) => {
      this.employees = this.empService.employees;
      console.log(res); // returns the message: Calling the api one time
    },error => {
      console.log('error');
    });
  }
}

最后,当我运行该应用程序时,一切运行良好,除了在将其控制台到浏览器中时没有得到更新的数据,这是因为我将结果emps传递给了控制器,而不是{{1 }},

await _repo.GetAllEmployees()

如果我尝试用var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferempdata",emps)); 更改/替换emps,那么我的命令将变成这样:

await _repo.GetAllEmployees()

然后我得到以下异常

var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferempdata",await _repo.GetAllEmployees()));

这基本上意味着我的_repo第一次调用此api时就被销毁了。

我该如何解决这个问题,每次调用计时器时,它都会从我的存储库中获取新数据,我真的需要一个计时器来实现此目的吗?

我的GetAllEmployees()回购方法如下:

Unhandled exception. System.ObjectdisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling dispose() on the context,or wrapping the context in a using statement. If you are using dependency injection,you should let the dependency injection container take care of disposing context instances.
Object name: 'DataContext'.
   at Microsoft.EntityFrameworkCore.DbContext.Checkdisposed()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_Model()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Collections.Generic.IAsyncEnumerable<TEntity>.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source,CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable`1 source,CancellationToken cancellationToken)
   at Test.API.Data.Repository.Repo.GetAllEmployees() in C:\Users\rjar\Documents\VS Code Project\AzureSignalR\Test.API\Data\Repository\Repo.cs:line 37
   at CRM.API.Controllers.EmpsController.<GetAllEmployees>b__4_0() in C:\Users\rjar\Documents\VS Code Project\AzureSignalR\Test.API\Controllers\EmpsController.cs:line 31
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_1(Object state)
   at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi)
   at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext,Action`1 callback,TState& state)
   at System.Threading.QueueUserWorkItemCallback.Execute()
   at System.Threading.ThreadPoolWorkQueue.dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)