无法访问已处置的对象。对象名称:使用后台队列时的“ IServiceProvider”

时间:2020-05-13 09:36:10

标签: c# .net-core

我正在使用https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio中的后台任务队列

在我的startup.cs类中,添加了以下内容:

 services.AddHostedService<QueuedHostedService>();
 services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();

添加了API的所有其他服务,例如services.AddScoped<IUserService, UserService>();

然后在我的控制器中上载文件,将其发送到blob存储并返回OK()状态,同时开始处理文件的队列。正常工作,直到在处理期间我想更新数据库中的值:

private void ProcessFile(string tempFilePath, FileFormatType fileFormat, IFormFile file, Type type, UploadData uploadData) {
  var delimiter = ",";
  Queue.QueueBackgroundWorkItem(async token => {
    // do the processing
    switch (type) {
      case Type.NewVersion:
        var headersMap = new NewersionHeaders();
        await ImportFile(tempFilePath, file.Length, uploadData.UserId, fileFormat, headersMap, delimiter ? ? ",", "yyyy-MM-dd");
        break;
    }
  });
}

private async Task ImportFile( string filePath, long fileSize, int userId, Type dataHeadersMap, string delimiter, string dateFormat) {
  using(var stream = File.OpenRead(filePath)) {
    var stopwatch = Stopwatch.StartNew();
    var user = await _userRepository.Get(userId);
    uploadData.FileName = Path.GetFileName(stream.Name);

    // Log Unzipping time elapsed and unzipped file size
    stopwatch.Stop();
    uploadData.TimeElapsedUnzipping = stopwatch.ElapsedMilliseconds;
    uploadData.FileSizeUnzipped = stream.Length;
    stopwatch.Restart();

    await _uploadDataRepository.Add(uploadData);

    stopwatch.Stop();
    uploadData.TimeElapsedInserting = stopwatch.ElapsedMilliseconds;
    uploadData.UploadStatusType = UploadStatusType.Finished;
    await _uploadDataRepository.Update(uploadData);
  }
}

调用存储库var user = await _userRepository.Get(userId) / await _uploadDataRepository.Update(uploadData)失败

具有例外Error occurred executing workItem. System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'.

UserRepository.cs:

public class UserRepository: BaseRepository < User > , IUserRepository {
  public UserRepository(IServiceProvider services): base(services) {}

  public async Task < User > GetByEmail(string email) {
    return await Store()
      .Filtered(nameof(User.Email), email)
      .FirstOrNull < User > ();
  }
}


// BaseRepository
namespace API.Repositories.Base {
  public abstract class BaseRepository < T > where T: class, IEntity, new() {
    protected readonly IServiceProvider _services;

    public BaseRepository(IServiceProvider services) => _services = services;

    public virtual IDataStore Store() => _services.GetService < IDataStore > ().As < T > ();

    public async virtual Task < T > Add(T entity) => await Store().Add(entity);
  }
}

// injecting into service like:

public class ImportService: BaseService, IImportService {
  private readonly IUploadDataRepository _uploadDataRepository;
  private readonly IUserRepository _userRepository;
  public IBackgroundTaskQueue Queue {
    get;
  }
  private
  const string AZURE_BLOB_CONTAINER = "blobcontainer";

  public ImportService(IServiceProvider services, IBackgroundTaskQueue queue): base(services) {
    _uploadDataRepository = services.GetUploadDataRepository();
    _userRepository = services.GetUserRepository();
    Queue = queue;
  }
}



// Startup.cs

public void ConfigureServices(IServiceCollection services) {
  //Add config file as singleton
  services.AddScoped(v => new ConfigurationBuilder()
    .SetBasePath(_env.ContentRootPath)
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($ "appsettings.{_env.EnvironmentName}.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables()
    .Build());
  services.AddSingleton < IHttpContextAccessor, HttpContextAccessor > ();
  var config = services.BuildServiceProvider().CreateScope().ServiceProvider.GetService < IConfigurationRoot > ();

  services.Configure < ConfigurationSettings > (config);
  // Form file configuration to except large files
  services.Configure < FormOptions > (x => {
    x.ValueLengthLimit = int.MaxValue;
    x.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart
  });

  services.AddTransient(s => s.GetService < IOptions < ConfigurationSettings >> ().Value);

  // Repos and services
  ServiceExtensions.ConfigureServices(services, config);

  services.AddApplicationInsightsTelemetry(config);
  services.AddCors();

  var mvc = services.AddMvc(v => {
    // Order is important here!!
    v.Filters.Add < SessionTokenAuthenticateFilter > ();
    v.Filters.Add < SessionTokenAuthorizeFilter > ();
  });

  mvc.AddJsonOptions(
    opt => {
      opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
      opt.SerializerSettings.Converters.Add(new StringEnumConverter {
        CamelCaseText = true
      });
      opt.SerializerSettings.Converters.Add(new UnixDateTimeConverter());
      opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
      opt.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
    });

  // SQL
  services.AddScoped(v => {
    return new SqlConnection(config.GetDefaultConnection());
  });

  services.AddScoped < TransactionStore > ();

  services.AddScoped < SqlTransaction > (v => {
    var c = v.GetService < SqlConnection > ();

    if (c.State == ConnectionState.Broken || c.State == ConnectionState.Closed)
      c.Open();

    var transaction = c.BeginTransaction(IsolationLevel.ReadUncommitted);
    var transactionStore = v.GetRequiredService < TransactionStore > ();
    transactionStore.Transaction = transaction;
    return transaction;
  });
  services.AddSingleton < IHttpContextAccessor, HttpContextAccessor > ();
  // Fluent migrator
  services
    .AddFluentMigratorCore()
    .ConfigureRunner(rb => rb
      .AddSqlServer2014()
      .WithGlobalConnectionString(config.GetDefaultConnection())
      // Define the assembly containing the migrations
      .ScanIn(typeof(M201807201046_FirstMigration).Assembly).For.Migrations())
    // Enable logging to console in the FluentMigrator way
    .AddLogging(lb => lb.AddFluentMigratorConsole())
    .BuildServiceProvider(false);

  // Migrate
  var servicesMigrationScope = services.BuildServiceProvider().CreateScope().ServiceProvider;
  UpdateDatabase(servicesMigrationScope);

  services.AddHostedService < QueuedHostedService > ();
  services.AddSingleton < IBackgroundTaskQueue, BackgroundTaskQueue > ();

  // Hangfire
  GlobalConfiguration.Configuration.UseActivator(new HangfireJobActivator(services));
  GlobalConfiguration.Configuration.UseSqlServerStorage(config.GetDefaultConnection());
  services.AddHangfire(x => x.UseStorage(JobStorage.Current));

  // Hangfire Jobs
  //RecurringJob.AddOrUpdate<DKVStrategy>(d => d.ImportBlacklist(), Cron.Daily(1, 30));

  // Register the Swagger services
  services.AddSwaggerDocument();
}

/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
/// <param name="loggerFactory"></param>
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IHttpContextAccessor accessor) {
  var config = app.ApplicationServices.CreateScope().ServiceProvider.GetConfig();
  loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Information);

  if (env.IsDevelopment()) {
    app.UseDeveloperExceptionPage();
  }

  app.UseStaticFiles();
  app.UseHttpsRedirection();

  // Register the Swagger generator and the Swagger UI middlewares
  app.UseOpenApi();
  app.UseSwaggerUi3();

  app.UseMiddleware < ApiExceptionMiddleware > ();

  app.UseHsts();

  //if (env.IsDevelopment())
  app.UseCors(options => options.WithOrigins(config.Cors.Origins).AllowAnyMethod().AllowAnyHeader());

  app.UseMvc();

  // The rest of the hangfire config
  app.UseHangfireServer();
  app.UseHangfireDashboard(
    "/hangfire",
    new DashboardOptions {
      Authorization = new [] {
        new HangfireDashboardFilter()
      }
    });
}

控制器:

namespace Api.Controllers {
    /// <summary>
    /// Controller for handling imports
    /// </summary>
    [SessionTokenAuthorize(SessionTokenType.Web)]
    [Route("api/[controller]")]
    public class ImportController: BaseController {
      private readonly IImportService _importService;

      public ImportController(IServiceProvider services): base(services) {
        _importService = services.GetImportService();
      }

      [HttpPost("importFile")]
      [ProducesResponseType(StatusCodes.Status200OK)]
      public async Task < IActionResult > ImportFile(IFormFile file, Type type) {
        var userId = GetCurrentUserId();

        if (!userId.HasValue) {
          throw new CVTException(CVTExceptionCode.User.NotFound);
        }

        try {
          await _importService.UploadToBlobStorage(file, userId.Value, type);
        } catch (Exception e) {}
        return Ok();
      }


    }

为什么在使用队列时不能调用其他存储库以及如何解决该问题?

1 个答案:

答案 0 :(得分:1)

QueuedHostedService构造函数中导入IServiceProvider

public QueuedHostedService(IServiceProvider serviceProvider){
 _serviceProvider = serviceProvider;
}

然后在您的ImportFile方法中从IServiceProvider创建一个范围,然后使用该范围获取所需的服务。

private async Task ImportFile( string filePath, long fileSize, int userId, Type dataHeadersMap, string delimiter, string dateFormat) {
   using (var scope = _serviceProvider.CreateScope())
   {
     var userRepository = scope.ServiceProvider.GetService<IUserRepository>();
     // import other services
     // use them
   }
}