Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added .NET client for new Dapr Jobs API support #1320

Closed
wants to merge 65 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
3301b03
Added .NET client for new Dapr Jobs API support
WhitWaldo Jul 6, 2024
5732ca5
Removed explicit .NET 7 targeting
WhitWaldo Jul 8, 2024
debf7ad
Using `AddDaprJobsClient` for consistency with other SDKs
WhitWaldo Jul 8, 2024
c3b2a8c
Refactored the DaprClientBuilder out to a common project. Updated dep…
WhitWaldo Jul 9, 2024
12fb796
Renamed to reflect that it's a Dapr exception
WhitWaldo Jul 9, 2024
cfd74f7
Updated solution to propertly find Dapr.Common in /src directory
WhitWaldo Jul 9, 2024
24ae98c
Added scheduler.proto and updated project file to autogenerate approp…
WhitWaldo Jul 9, 2024
4e5d856
Removed unused type
WhitWaldo Jul 9, 2024
d0e577b
Fixed an XML comment that didn't make sense
WhitWaldo Jul 9, 2024
5faa914
Implemented service-specific equivalent of DaprClientBuilder for Dapr…
WhitWaldo Jul 9, 2024
4ca40eb
Updated to support DaprJobClientOptions which specify the App ID and …
WhitWaldo Jul 9, 2024
b0291bf
Added DI registration extensions that support options
WhitWaldo Jul 9, 2024
797be63
Refactored out DaprException to common project - retained same namesp…
WhitWaldo Jul 9, 2024
c4b028b
Fixed method not returning properly
WhitWaldo Jul 9, 2024
c15e090
Added extension method for parsing a TimeSpan from a Golang interval
WhitWaldo Jul 9, 2024
f3a7c41
Implemented all but the Watch method on the client
WhitWaldo Jul 9, 2024
6157cd0
Added missing copyright headers
WhitWaldo Jul 9, 2024
0f0954d
Finished out most of the WatchJobs method.
WhitWaldo Jul 9, 2024
2cdd554
Adding attribute to mark endpoints for Job trigger invocations
WhitWaldo Jul 10, 2024
e713661
Partially through bidirectional watcher implementation, but stopping …
WhitWaldo Jul 10, 2024
49c78eb
Updates to use the correct protos (dapr runtime, not the scheduler se…
WhitWaldo Jul 10, 2024
e76b323
Added default null values
WhitWaldo Jul 10, 2024
79e56d6
Added schedule job overloads for when the developer doesn't want to s…
WhitWaldo Jul 10, 2024
9723ff5
Rather than 6 overloads of the same method, changed the names of each…
WhitWaldo Jul 10, 2024
6bd99c5
Solution update
WhitWaldo Jul 10, 2024
63df29d
Removed serialization from SDK - swapping to only accepting and retur…
WhitWaldo Jul 11, 2024
d6cbf51
Update to use ReadOnlyMemory<byte> instead of byte[]
WhitWaldo Jul 12, 2024
bcdf08b
Refactored the generic client builder so that less identical code nee…
WhitWaldo Jul 12, 2024
597ac0d
Updating constructor to use protected instead of public
WhitWaldo Jul 12, 2024
c08376a
Neglected to update DaprJobsGrpcClient to reflect signature changes. …
WhitWaldo Jul 12, 2024
ff3cf9c
Removed DaprJobClientOptions as no longer needed since we're using th…
WhitWaldo Jul 15, 2024
3e1b0f3
Consolidated all the extensions
WhitWaldo Jul 15, 2024
e44dca0
Renamed to ScheduledJobAttribute to match the API name
WhitWaldo Jul 15, 2024
5b7600d
Adding ASP.NET Core to package to add route builder extension support
WhitWaldo Jul 15, 2024
c15fa66
Added another registration overload so the developer isn't forced to …
WhitWaldo Jul 15, 2024
a29a4dd
Shifted schedule deserialization into the JobDetails record itself an…
WhitWaldo Jul 15, 2024
1252ef6
Adding JobsSample project to prove out API
WhitWaldo Jul 15, 2024
4057ca5
Removed tentatively unnecessary attribute since we're relying on ASP.…
WhitWaldo Jul 15, 2024
449609e
Simplified example
WhitWaldo Jul 15, 2024
0101327
Fixing unit tests - resolving errors due to lack of InternalsVisibleT…
WhitWaldo Jul 15, 2024
c860260
Fixed last locally-broken test client due to lack of InternalsVisible…
WhitWaldo Jul 15, 2024
fb983a9
Updated name of invocation endpoint registration method to include "D…
WhitWaldo Jul 15, 2024
b3db843
Building out unit tests
WhitWaldo Jul 16, 2024
f6333e9
Removed unused using
WhitWaldo Jul 16, 2024
2bf4cfd
Added some null validation
WhitWaldo Jul 16, 2024
e5d5646
Refactored to use the same naming convention as the other types in th…
WhitWaldo Jul 16, 2024
61b1978
Building on last commit, updated extension names
WhitWaldo Jul 16, 2024
90a5fe1
Adding more unit tests
WhitWaldo Jul 16, 2024
e674fa8
Removed unused type
WhitWaldo Jul 16, 2024
4215d87
Updated to use int? instead of uint? for repeats parameter. Added che…
WhitWaldo Jul 16, 2024
8e72f5a
Added support for IDisposable
WhitWaldo Jul 16, 2024
20be1d5
Updated to support provisioning an HttpClient from IHttpClientFactory…
WhitWaldo Jul 16, 2024
1780337
Fixed an issue with the API token not being properly passed into clie…
WhitWaldo Jul 16, 2024
22d3a17
Another tweak to finalize fix for Dapr API token - updated tests as well
WhitWaldo Jul 16, 2024
60b1c88
Adding helper deserialization extension methods + tests
WhitWaldo Jul 16, 2024
db4f93f
Added more helper extension methods for serializing strings and JSON-…
WhitWaldo Jul 16, 2024
62052e6
Naming correction to ensure parameter names are similar from one sche…
WhitWaldo Jul 16, 2024
9fef177
Removed JsonSerializerOptions support from DaprJobsClient as it's not…
WhitWaldo Jul 16, 2024
39dbcf8
First pass at updating documentation
WhitWaldo Jul 16, 2024
8d59928
Updated packages to fix build errors because of everything's dependen…
WhitWaldo Jul 16, 2024
4a101a6
Forced to update these packages to 8.0.0 because of other transitive …
WhitWaldo Jul 16, 2024
ffdb47b
Fixed build errors introduced by the transient reference to Microsoft…
WhitWaldo Jul 16, 2024
8eb57f5
Fixed issues in the unit tests caused by over-eager fix on the Dapr A…
WhitWaldo Jul 16, 2024
81f9ef5
Merge branch 'master' into job-api-sdk
WhitWaldo Jul 24, 2024
2e26e13
Fixed incorrect unit test
WhitWaldo Jul 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ This repo builds the following packages:
- Dapr.Actors.AspNetCore
- Dapr.Extensions.Configuration
- Dapr.Workflow
- Dapr.Jobs

### Prerequisites

Expand Down
14 changes: 14 additions & 0 deletions all.sln
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.Actors.Genera
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cryptography", "examples\Client\Cryptography\Cryptography.csproj", "{C74FBA78-13E8-407F-A173-4555AEE41FF3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Jobs", "src\Dapr.Jobs\Dapr.Jobs.csproj", "{D34F9326-8D8C-43C4-975B-7201A9C97E6E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs.Test", "test\Dapr.Jobs.Test\Dapr.Jobs.Test.csproj", "{EDEE625E-6815-40E1-935F-35129771A0F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -290,6 +294,14 @@ Global
{C74FBA78-13E8-407F-A173-4555AEE41FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C74FBA78-13E8-407F-A173-4555AEE41FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C74FBA78-13E8-407F-A173-4555AEE41FF3}.Release|Any CPU.Build.0 = Release|Any CPU
{D34F9326-8D8C-43C4-975B-7201A9C97E6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D34F9326-8D8C-43C4-975B-7201A9C97E6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D34F9326-8D8C-43C4-975B-7201A9C97E6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D34F9326-8D8C-43C4-975B-7201A9C97E6E}.Release|Any CPU.Build.0 = Release|Any CPU
{EDEE625E-6815-40E1-935F-35129771A0F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDEE625E-6815-40E1-935F-35129771A0F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDEE625E-6815-40E1-935F-35129771A0F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDEE625E-6815-40E1-935F-35129771A0F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -343,6 +355,8 @@ Global
{AF89083D-4715-42E6-93E9-38497D12A8A6} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{B5CDB0DC-B26D-48F1-B934-FE5C1C991940} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{C74FBA78-13E8-407F-A173-4555AEE41FF3} = {A7F41094-8648-446B-AECD-DCC2CC871F73}
{D34F9326-8D8C-43C4-975B-7201A9C97E6E} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{EDEE625E-6815-40E1-935F-35129771A0F8} = {DD020B34-460F-455F-8D17-CF4A949F100B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
Expand Down
16 changes: 16 additions & 0 deletions src/Dapr.Jobs/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Dapr.Jobs.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2" )]
23 changes: 23 additions & 0 deletions src/Dapr.Jobs/Dapr.Jobs.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6;net7;net8</TargetFrameworks>
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Dapr.Jobs</PackageId>
<Title>Dapr Jobs Authoring SDK</Title>
<Description>Dapr Jobs SDK for scheduling jobs and tasks with Dapr</Description>
<VersionPrefix>0.1.0</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*"/>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.*"/>
</ItemGroup>

<ItemGroup>
<Compile Include="..\Shared\DaprDefaults.cs" Link="DaprDefaults.cs" />
</ItemGroup>

</Project>
106 changes: 106 additions & 0 deletions src/Dapr.Jobs/DaprJobsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System.Net;
using System.Net.Http.Json;

namespace Dapr.Jobs;

/// <summary>
/// Defines client operations for managing Dapr jobs.
/// </summary>
public class DaprJobsClient
{
private readonly HttpClient httpClient;

/// <summary>
/// Initializes a new instance of the <see cref="DaprJobsClient"/> class.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/> used to communicate with the Dapr sidecar.</param>
public DaprJobsClient(HttpClient httpClient)
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
{
this.httpClient = httpClient;
}

/// <summary>
/// Schedules a job with a name.
/// </summary>
/// <param name="name">The name of the job being scheduled.</param>
/// <param name="jsonSerializableData">A string value providing any related content. Content is returned when the reminder expires.</param>
/// <param name="dueTime">Specifies the time after which this job is invoked.</param>
public async Task ScheduleJobAsync(string name, object jsonSerializableData, TimeSpan dueTime)
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
{
ArgumentNullException.ThrowIfNull(name, nameof(name));
ArgumentNullException.ThrowIfNull(jsonSerializableData, nameof(jsonSerializableData));
ArgumentNullException.ThrowIfNull(dueTime, nameof(dueTime));

var options =
new ScheduleJobOptions(new ScheduleJobInnerOptions(jsonSerializableData, dueTime.ToDurationString()));

var response = await httpClient.PostAsJsonAsync($"/{name}", options);

switch (response.StatusCode)
{
case HttpStatusCode.BadRequest:
throw new MalformedJobException(response);
case HttpStatusCode.InternalServerError:
throw new DaprJobsServiceException(response);
default:
return;
}
}

/// <summary>
/// Gets a job from its name.
/// </summary>
/// <param name="name">The name of the scheduled job being retrieved.</param>
/// <returns>The job data.</returns>
public async Task<string> GetJobDataAsync(string name)
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
{
ArgumentNullException.ThrowIfNull(name, nameof(name));

var response = await httpClient.GetAsync($"/{name}");

switch (response.StatusCode)
{
case HttpStatusCode.BadRequest:
throw new MalformedJobException(response);
case HttpStatusCode.InternalServerError:
throw new DaprJobsServiceException(response);
default:
return await response.Content.ReadAsStringAsync();
}
}

/// <summary>
/// Deletes a named job.
/// </summary>
/// <param name="name">The name of the job being deleted.</param>
/// <returns></returns>
public async Task DeleteJobAsync(string name)
{
ArgumentNullException.ThrowIfNull(name, nameof(name));

var response = await httpClient.DeleteAsync($"/{name}");

switch (response.StatusCode)
{
case HttpStatusCode.BadRequest:
throw new MalformedJobException(response);
case HttpStatusCode.InternalServerError:
throw new DaprJobsServiceException(response);
default:
return;
}
}
}
38 changes: 38 additions & 0 deletions src/Dapr.Jobs/DaprJobsServiceException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Jobs;

/// <summary>
/// The exception type thrown when an exception is encountered using the Dapr Jobs service.
/// </summary>
[Serializable]
public class DaprJobsServiceException : Exception
{
/// <summary>
/// Initializes a new <see cref="DaprJobsServiceException"/> for a non-successful HTTP request.
/// </summary>
/// <param name="response"></param>
public DaprJobsServiceException(HttpResponseMessage? response) : base(FormatExceptionForFailedRequest())
{
Response = response;
}

/// <summary>
/// Gets the <see cref="HttpResponseMessage"/> of the request that failed. Will be <c>null</c> if the
/// failure was not related to an HTTP request or preventing the response from being received.
/// </summary>
public HttpResponseMessage? Response { get; }

private static string FormatExceptionForFailedRequest() => "An exception occurred while interacting with the Jobs API";
}
50 changes: 50 additions & 0 deletions src/Dapr.Jobs/JobsServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using Microsoft.Extensions.DependencyInjection;

namespace Dapr.Jobs;

/// <summary>
/// Contains extension methods for using Dapr Jobs with dependency injection.
/// </summary>
public static class JobsServiceCollectionExtensions
{
/// <summary>
/// Adds Dapr Jobs support to the service collection.
/// </summary>
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
public static IServiceCollection AddDaprJobs(this IServiceCollection serviceCollection)
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
{
ArgumentNullException.ThrowIfNull(serviceCollection, nameof(serviceCollection));

serviceCollection.AddHttpClient<DaprJobsClient>(httpClient =>
{
var jobsHttpEndpoint = new Uri($"{DaprDefaults.GetDefaultHttpEndpoint()}/v1.0-alpha1/jobs/");
if (jobsHttpEndpoint.Scheme != "http" && jobsHttpEndpoint.Scheme != "https")
throw new InvalidOperationException("The HTTP endpoint must use http or https");

httpClient.BaseAddress = jobsHttpEndpoint;
httpClient.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

var daprApiToken = DaprDefaults.GetDefaultDaprApiToken();
if (!string.IsNullOrEmpty(daprApiToken))
{
httpClient.DefaultRequestHeaders.Add("dapr-api-token", daprApiToken);
}
});

return serviceCollection;
}
}
38 changes: 38 additions & 0 deletions src/Dapr.Jobs/MalformedJobException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.Jobs;

/// <summary>
/// The exception type thrown when an malformed request is made to the Dapr Jobs service.
/// </summary>
[Serializable]
public class MalformedJobException : Exception
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Initializes a new <see cref="DaprJobsServiceException"/> for a non-successful HTTP request.
/// </summary>
/// <param name="response"></param>
public MalformedJobException(HttpResponseMessage? response) : base(FormatExceptionForFailedRequest())
{
Response = response;
}

/// <summary>
/// Gets the <see cref="HttpResponseMessage"/> of the request that failed. Will be <c>null</c> if the
/// failure was not related to an HTTP request or preventing the response from being received.
/// </summary>
public HttpResponseMessage? Response { get; }

private static string FormatExceptionForFailedRequest() => "The request made to the Jobs API was malformed";
}
31 changes: 31 additions & 0 deletions src/Dapr.Jobs/ScheduleJobOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System.Text.Json.Serialization;

namespace Dapr.Jobs;

/// <summary>
/// The options used to schedule a new job.
/// </summary>
/// <param name="JobData">The internal job options.</param>
internal record ScheduleJobOptions([property: JsonPropertyName("job")] ScheduleJobInnerOptions JobData);

/// <summary>
/// The payload used to schedule a new job.
/// </summary>
/// <param name="JsonSerializableData">The JSON serializable data payload.</param>
/// <param name="DueTime">The </param>
internal record ScheduleJobInnerOptions(
[property: JsonPropertyName("data")] object JsonSerializableData,
[property: JsonPropertyName("dueTime")] string DueTime);
WhitWaldo marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading