You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用NLog时Hangfire报错:无参数构造函数未定义求助

解决Hangfire定时任务抛出“No parameterless constructor defined for this object”的问题

这个坑我之前踩过,咱们先拆解问题根源,再给你具体的修复方案:

问题原因

你当前的代码里,把logger.Trace("Test")作为委托传给了RecurringJob.AddOrUpdate,这个委托捕获了方法内的局部变量logger。Hangfire在执行后台任务时,需要序列化这个委托并在独立的执行上下文里反序列化执行,但NLog的Logger对象通常没有无参构造函数,而且包含无法被序列化的内部状态,导致Hangfire无法重建这个Logger实例,从而抛出错误。

另外还有个小问题:你每次执行任务都重新初始化NLog配置,这会导致重复配置、资源浪费,甚至日志写入异常,应该只在应用启动时配置一次。

修复方案

我们把日志写入逻辑抽成独立的、可被Hangfire正确处理的方法,同时把NLog配置移到启动阶段:

重构后的代码示例

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
        // 初始化NLog配置——只做一次!
        InitializeNLog();

        // 配置Hangfire
        GlobalConfiguration.Configuration.UseSqlServerStorage("AppContext"); 
        app.UseHangfireDashboard(); 
        app.UseHangfireServer(); 

        // 启动心跳任务
        HeartbeatSetup.SetupHeartbeat();
    } 

    private void InitializeNLog()
    {
        var config = new NLog.Config.LoggingConfiguration();
        var logFile = new NLog.Targets.FileTarget() 
        { 
            FileName = "heartbeat.txt", 
            Name = "logfile",
            Layout = "${longdate} ${message}" // 可以加上时间格式,日志更清晰
        };
        config.LoggingRules.Add(new NLog.Config.LoggingRule("*", LogLevel.Trace, logFile));
        NLog.LogManager.Configuration = config;
    } 
} 

public class HeartbeatSetup 
{ 
    public static void SetupHeartbeat() 
    { 
        // 注册任务,指向独立的执行方法
        RecurringJob.AddOrUpdate("heartbeat", () => HeartbeatTask.WriteHeartbeat(), Cron.Minutely); 
    } 
}

// 独立的任务执行类,确保有无参构造(默认就有,除非你定义了带参构造)
public class HeartbeatTask
{
    public void WriteHeartbeat()
    {
        var logger = NLog.LogManager.GetCurrentClassLogger();
        logger.Trace("Test");
        // 或者带上时间戳:logger.Trace($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Test");
    }

    // 如果想更简单,也可以用静态方法:
    // public static void WriteHeartbeat()
    // {
    //     var logger = NLog.LogManager.GetCurrentClassLogger();
    //     logger.Trace("Test");
    // }
}

为什么这样能解决问题?

  1. 避免捕获局部变量:现在任务委托指向的是HeartbeatTask.WriteHeartbeat()方法,Hangfire只需要序列化这个方法的引用,而不是捕获的Logger实例。执行时,Hangfire会实例化HeartbeatTask(因为它有无参构造),然后调用方法,方法内部自己获取Logger,完全避开了序列化Logger的问题。
  2. NLog配置只初始化一次:把配置移到Startup里,避免每次任务执行都重复创建配置和文件目标,减少资源消耗。

替代实现方案(依赖注入版)

如果你的项目用了依赖注入(比如Autofac、Microsoft.Extensions.DependencyInjection),可以把Logger注入到任务类里,Hangfire支持和DI容器集成,这样更符合依赖注入的设计:

// 假设用Microsoft DI
public void Configuration(IAppBuilder app) 
{
    // 初始化DI容器
    var services = new ServiceCollection();
    services.AddSingleton<ILogger>(NLog.LogManager.GetCurrentClassLogger());
    services.AddScoped<HeartbeatTask>();

    var container = services.BuildServiceProvider();

    // 配置Hangfire使用DI容器
    GlobalConfiguration.Configuration
        .UseSqlServerStorage("AppContext")
        .UseActivator(new HangfireActivator(container));

    app.UseHangfireDashboard(); 
    app.UseHangfireServer(); 

    HeartbeatSetup.SetupHeartbeat();
    InitializeNLog();
}

// 自定义Hangfire激活器
public class HangfireActivator : JobActivator
{
    private readonly IServiceProvider _serviceProvider;

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

    public override object ActivateJob(Type jobType)
    {
        return _serviceProvider.GetService(jobType);
    }
}

// 任务类接受Logger注入
public class HeartbeatTask
{
    private readonly ILogger _logger;

    // 带参构造,DI容器会自动注入Logger
    public HeartbeatTask(ILogger logger)
    {
        _logger = logger;
    }

    public void WriteHeartbeat()
    {
        _logger.Trace("Test");
    }
}

这个方案更适合复杂项目,能更好地管理依赖。

内容的提问来源于stack exchange,提问作者GeorgeB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:40:56