【问题标题】:Is there some way to pass a string to a IHostedService timed background task?有没有办法将字符串传递给 IHostedService 定时后台任务?
【发布时间】:2019-08-17 08:30:51
【问题描述】:

我正在运行定时后台任务来发送电子邮件,并且我想在电子邮件中包含生成的链接。

当我通过控制器中的用户交互发送其他电子邮件时,我正在使用这个小方法来生成链接:

public string BuildUrl(string controller, string action, int id)
{
    Uri domain = new Uri(Request.GetDisplayUrl());
    return domain.Host + (domain.IsDefaultPort ? "" : ":" + domain.Port) +
        $@"/{controller}/{action}/{id}";
}

当然,后台任务对 Http 上下文一无所知,所以我需要替换链接的域部分,如下所示:

public string BuildUrl(string controller, string action, int id)
{
    return aStringPassedInFromSomewhere + $@"/{controller}/{action}/{id}";
}

我在 startup.cs ConfigureServices 中像这样启动后台任务:

services.AddHostedService<ProjectTaskNotifications>();

我在想也许可以从资源文件中获取域名,但我不妨将它硬编码到任务方法中。

有没有办法将这些信息动态传递给后台任务?

更多信息

这是整个后台任务:

internal class ProjectTaskNotifications : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;
    private readonly IServiceScopeFactory scopeFactory;
    private readonly IMapper auto;

    public ProjectTaskNotifications(
        ILogger<ProjectTaskNotifications> logger, 
        IServiceScopeFactory scopeFactory, 
        IMapper mapper)
    {
        _logger = logger;
        this.scopeFactory = scopeFactory;
        auto = mapper;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero,
            TimeSpan.FromSeconds(30));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");

        // Connect to the database and cycle through all unsent
        // notifications, checking if some of them are due to be sent:
        using (var scope = scopeFactory.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();

            List<ProjectTaskNotification> notifications = db.ProjectTaskNotifications
                .Include(t => t.Task)
                    .ThenInclude(o => o.TaskOwner)
                .Include(t => t.Task)
                    .ThenInclude(p => p.Project)
                        .ThenInclude(o => o.ProjectOwner)
                .Where(s => !s.IsSent).ToList();

            foreach (var notification in notifications)
            {
                if (DateTime.UtcNow > notification.Task.DueDate
                   .AddMinutes(-notification.TimeBefore.TotalMinutes))
                {
                    SendEmail(notification);
                    notification.Sent = DateTime.UtcNow;
                    notification.IsSent = true;
                }
            }
            db.UpdateRange(notifications);
            db.SaveChanges();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }

    public void SendEmail(ProjectTaskNotification notification)
    {   // Trimmed down for brevity

        // Key parts
        string toAddr = notification.Task.TaskOwner.Email1;
        BodyBuilder bodyBuilder = new BodyBuilder
        {
            HtmlBody = TaskInfo(auto.Map<ProjectTaskViewModel>(notification.Task))
        };

        return;
    }

    public string TaskInfo(ProjectTaskViewModel task)
    {   // Trimmed down for brevity
        return $@"<p>{BuildUrl("ProjectTasks", "Edit", task.Id)}</p>";
    }

    public string BuildUrl(string controller, string action, int id)
    {   
        // This is where I need the domain name sent in from somewhere:
        return "domain:port" + $@"/{controller}/{action}/{id}";
    }
}

【问题讨论】:

  • 这个答案可以帮助你:stackoverflow.com/questions/49813628/…
  • 您的后台任务是如何工作的,它从哪里获取应该发送的电子邮件?
  • @Fabio 查看更新的问题。我添加了后台任务类以显示我如何确定要发送哪些电子邮件。
  • 为什么不能在数据库的通知中保存域 URL。那么您将能够使用任何域 url 发送电子邮件。
  • 将“通知”记录视为包含要发送的电子邮件信息的事件。后台任务(或事件处理程序)不应该关心如何构建所需的数据,所有数据都准备好并保存在事件中。事件处理程序(后台任务)只会处理它。

标签: c# asp.net-core asp.net-core-mvc


【解决方案1】:

您可以通过构造函数将任何对象传递给IHostedService 提供程序。

public ProjectTaskNotifications(IUrlPrefixProvider provider)
{
    _urlPrefixProvider = urlPrefixProvider
}

private string BuildUrl(<Your args>)
{
    var prefix = _urlPrefixProvider.GetPrefix(<args>);
    ....
}

在 startup.cs 你可以有

services.AddSingleton<IUrlPrefixProvider, MyUrlPrefixProvider>()
services.AddHostedService<ProjectTaskNotifications>();

让依赖注入来处理剩下的事情。

【讨论】:

  • 什么是 IUrlPrefixProvider? Visual Studio 询问我是否可能缺少 using 指令或程序集引用,但是当我按下 CTRL+ 时。没有任何使用指令的建议。
  • 应该已经澄清了。我不知道你从哪里得到aStringPassedInFromSomewhere,所以我假设你会有一些自定义对象(我命名为MyUrlPrefixProvider),你的后台任务可以查询这些对象来获取前缀。我可能没有正确理解您的用例。
  • 这行不通,因为正如 OP 所提到的,可以根据请求上下文生成 url。 Provider 只会被实例化一次,并且无法访问当前上下文。
猜你喜欢
  • 2022-01-07
  • 1970-01-01
  • 2014-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-01
  • 1970-01-01
相关资源
最近更新 更多