向调度程序添加作业时:值不能为空,作业 class 不能为空?

When adding a Job to scheduler: Value cannot be null, Job class cannot be null?

我的问题非常类似于: Quartz.net - "Job's key cannot be null" 但是它的设置与我使用 Rest API 不同。 通过 Startup.cs 添加时,我能够 运行 一份工作,但是当我调用 API 使用 javascript 添加工作时,它失败并出现以下错误:

错误:

System.ArgumentNullException: Value cannot be null. (Parameter 'typeName')

   at System.RuntimeType.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark)

   at System.Type.GetType(String typeName)

   at Quartz.Web.Api.JobsController.AddJob(String schedulerName, String jobGroup, String jobName, String jobType, Boolean durable, Boolean requestsRecovery, Boolean replace) in E:\Amit\DotNet\QuartzApi\QuartzApi\Controllers\JobsController.cs:line 108

   at lambda_method14(Closure , Object )

   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()

--- End of stack trace from previous location ---

   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)

   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)

   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)

   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

 

HEADERS

=======

Accept: application/json

Accept-Encoding: gzip, deflate, br

Accept-Language: en-US,en;q=0.9

Connection: close

Content-Length: 83

Content-Type: application/json

Host: localhost:44379

Referer: https://localhost:44379/jobs.html

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36

sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"

sec-ch-ua-mobile: ?0

origin: https://localhost:44379

sec-fetch-site: same-origin

sec-fetch-mode: cors

sec-fetch-dest: empty

设置:

在 VS 中,我在单个项目中创建了 Quartz REST API 和前端。 运行 该项目在后台加载带有作业的网页和 API 运行ning。

除 AddJob 外,所有控制器端点均正常工作。 (即获取作业、查看作业详细信息、暂停、恢复、触发、删除)

依赖关系: Quartz.Extensions.Hosting3.3.3

JobsController.cs quartznet/JobsController.cs at main · quartznet/quartznet · GitHub

    [HttpPut]

    [Route("{jobGroup}/{jobName}")]

    public async Task AddJob(string schedulerName, string jobGroup, string jobName, string jobType, bool durable, bool requestsRecovery, bool replace = false)

    {

        var scheduler = await GetScheduler(schedulerName).ConfigureAwait(false);

        var jobDetail = new JobDetailImpl(jobName, jobGroup, Type.GetType(jobType), durable, requestsRecovery);

        await scheduler.AddJob(jobDetail, replace).ConfigureAwait(false);

    }

HelloWorldJob.cs: https://andrewlock.net/using-quartz-net-with-asp-net-core-and-worker-services/

Startup.cs:(添加一个没有 API 的作业,并且 运行 在开始时使用触发器)

    void ConfigureHostQuartz(IServiceCollection services)

    {

        services.AddQuartz(q =>

        {

            q.UseMicrosoftDependencyInjectionScopedJobFactory();



            var jobKey = new JobKey("HelloWorldJob");

            q.AddJob<HelloWorldJob>(opts => opts.WithIdentity(jobKey));

            q.AddTrigger(opts => opts

                .ForJob(jobKey)

                .WithIdentity("HelloWorldJob-trigger")

                .WithCronSchedule("0/5 * * * * ?"));

        });



        services.AddQuartzHostedService(

            q => q.WaitForJobsToComplete = true);

    }

Html/Javascript前端: 按照这个例子: Tutorial: Call an ASP.NET Core web API with JavaScript | Microsoft Docs

<form action="javascript:void(0);" method="POST" onsubmit="addJob()">

    <input type="text" id="add-name" placeholder="New job">

    <input type="submit" value="Add">

</form>

<script>

function addJob() {

    const addNameTextbox = document.getElementById('add-name').value.trim();

 

    const item = {

        jobType: "HelloWorldJob",

        durable: true,

        requestsRecovery: false,

        replace: false

    };

 

    fetch(`${uri}/DEFAULT/${addNameTextbox}`, {

        method: 'PUT',

        headers: {

            'Accept': 'application/json',

            'Content-Type': 'application/json'

        },

        body: JSON.stringify(item)

    })

        .then(response => console.log(response))

        .then(() => {

            getJobs();

            addNameTextbox.value = '';

        })

        .catch(error => console.error('Unable to add job.', error));

}

</script>

我尝试更新 API 以在 url 中包含 jobType,然后它给出了不同的错误:

Job class cannot be null
at Qurtz.Impl.JobDetilImpl.set_JobType(Type value)

您需要提供程序集限定名称作为作业类型。问题在这里:

jobType: "HelloWorldJob",

jobType 应该类似于 "MyNameSpace.JobType, MyAssembly" - 你可能会用 Console.WriteLine(typeof(HelloWorldJob).AssemblyQualifiedName) 把它写到控制台 - 你可以忽略版本等,只键入带有命名空间和程序集名称的名称需要。

另请注意,您的设置具有安全隐患,因为您允许从 UI.

传递 CLR 类型

API 控制器变化:

正如 Marko 上面提到的,jobType 需要完全限定的名称,但在我的情况下不需要程序集引用,因为我在同一个程序集中有作业。

    [HttpPut]
    [Route("{jobGroup}/{jobName}/{jobType}/{replace}/new")]
    public async Task NewJob(string schedulerName, string jobGroup,
        string jobName, string jobType, bool replace = false)
    {
        //Note: Job added without a trigger must be durable.
        var scheduler = await GetScheduler(schedulerName).ConfigureAwait(false);
        var jobDetail = new JobDetailImpl(jobName, jobGroup, 
            Type.GetType("QuartzApi.Jobs." + jobType), true, false);
        await scheduler.AddJob(jobDetail, replace).ConfigureAwait(false);
    }

JavaScript 获取查询更改:

删除了 JSON 正文标签并向 url 添加了额外的参数。请注意,这是一项没有触发器的工作。在稍后阶段,jobType 可以是一个变量,目前它包含在获取字符串中。

function addJob() {
    const addNameTextbox = document.getElementById('add-name').value.trim();

    fetch(`${uri}/DEFAULT/${addNameTextbox}/HelloWorldJob/false/new`, {
        method: 'PUT',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
    })
        .then(response => console.log(response))
        .then(() => {
            getJobs();
            addNameTextbox.value = '';
        })
        .catch(error => console.error('Unable to add job.', error));
}

运行 UI 添加作业的请求,现在无需触发器即可添加(将在单独的部分中处理)。为了确认,我然后 运行 API 在浏览器中请求使用以下方法获取 运行 调度程序的所有作业: [https://localhost:44379/api/schedulers/QuartzScheduler/jobs] 结果:

[{"name":"HelloWorldJob","group":"DEFAULT"},{"name":"TestJob","group":"DEFAULT"}]

这意味着一些事情:

  1. 在正文中传递 JSON 对象不会将其与 API 函数参数相关联。我需要在 url 字符串中添加所有参数才能使用它们。可能有一种方法可以使用正文参数。
  2. 现在 class 已在 API 中正确引用,我可以继续仅通过 UI 传递 class 名称,无需命名空间和程序集以确保其安全因为 class 是在构建时在项目中定义的。
  3. 在 API 函数中添加 Console.Writeline 在运行时没有 return 任何输出。