Skip to content

Commit

Permalink
Initial dotnet Reaction SDK (#112)
Browse files Browse the repository at this point in the history
* initial commit

* advanced example

* inline docs

* js examples

* rename

* external images

* wip

* dotnet reaction sdk

* wip

* examples

* wip

* wip

* packaging

* error handling

* wip

* license headers

* Update README.md

typo

---------

Co-authored-by: Ruokun (Tommy) Niu <ruokunniu@microsoft.com>
  • Loading branch information
danielgerlag and ruokun-niu authored Nov 14, 2024
1 parent 8fe094e commit 44a2329
Show file tree
Hide file tree
Showing 57 changed files with 2,779 additions and 9 deletions.
30 changes: 30 additions & 0 deletions reactions/sdk/dotnet/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
1 change: 1 addition & 0 deletions reactions/sdk/dotnet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Drasi.Reaction.SDK\Drasi.Reaction.SDK.csproj" />
</ItemGroup>

</Project>
87 changes: 87 additions & 0 deletions reactions/sdk/dotnet/Drasi.Reaction.SDK.Tests/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2024 The Drasi 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 Drasi.Reaction.SDK.Models.QueryOutput;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Channels;

namespace Drasi.Reaction.SDK.Tests
{
public class Fixture<TQueryConfig> : IDisposable
where TQueryConfig : class
{
public string QueryDirectory { get; private set; }
public Channel<ChangeEvent> ChangeEventChannel { get; private set; }

public Reaction<TQueryConfig> Reaction { get; private set; }

public HttpClient Client { get; private set; }

public Fixture()
{
QueryDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(QueryDirectory);
ChangeEventChannel = Channel.CreateUnbounded<ChangeEvent>();
var port = GetAvailablePort();

Reaction = new ReactionBuilder<TQueryConfig>()
.UseChangeEventHandler(async (evt, config) =>
{
await ChangeEventChannel.Writer.WriteAsync(evt);
})
.Configure(cfg =>
{
cfg["QueryConfigPath"] = QueryDirectory;
cfg["APP_PORT"] = port.ToString();
})
.Build();

_ = Reaction.StartAsync();

Client = new HttpClient();
Client.BaseAddress = new Uri("http://localhost:" + port);
}

public void ClearChannels()
{
while (ChangeEventChannel.Reader.Count > 0)
{
ChangeEventChannel.Reader.TryRead(out _);
}
}

public void Dispose()
{
Directory.Delete(QueryDirectory, true);
_ = Reaction.StopAsync();
}

public static int GetAvailablePort()
{
// Create a TcpListener on port 0 (let OS select an available port)
TcpListener listener = new(IPAddress.Loopback, 0);
listener.Start();

// Retrieve the assigned port number
int port = ((IPEndPoint)listener.LocalEndpoint).Port;

// Stop the listener immediately to free up the port
listener.Stop();

return port;
}
}
}
53 changes: 53 additions & 0 deletions reactions/sdk/dotnet/Drasi.Reaction.SDK.Tests/ReactionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2024 The Drasi 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 Drasi.Reaction.SDK.Tests;

using Drasi.Reaction.SDK;
using Drasi.Reaction.SDK.Models.QueryOutput;
using Microsoft.AspNetCore.TestHost;
using System.Net.Http.Json;
using System.Threading.Channels;

public class ReactionTests : IClassFixture<Fixture<object>>
{

private readonly Fixture<object> _fixture;

public ReactionTests(Fixture<object> fixture)
{
_fixture = fixture;
_fixture.ClearChannels();
}

[Fact]
public async void ChangeEventsAreHandled()
{
var myEvt = new ChangeEvent()
{
Sequence = 1,
QueryId = "query1",
AddedResults = [],
UpdatedResults = [],
DeletedResults = []
};

await _fixture.Client.PostAsJsonAsync("event", myEvt, ModelOptions.JsonOptions);

Assert.True(await _fixture.ChangeEventChannel.Reader.WaitToReadAsync(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token));
var result = await _fixture.ChangeEventChannel.Reader.ReadAsync();
Assert.Equal(myEvt.QueryId, result.QueryId);
Assert.Equal(myEvt.Sequence, result.Sequence);
}
}
6 changes: 6 additions & 0 deletions reactions/sdk/dotnet/Drasi.Reaction.SDK/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bin
obj
CopilotIndices
DesignTimeBuild
FileContentIndex
v17
31 changes: 31 additions & 0 deletions reactions/sdk/dotnet/Drasi.Reaction.SDK/Drasi.Reaction.SDK.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Drasi.Reaction.SDK</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<PackageId>Drasi.Reaction.SDK</PackageId>
<Authors>The Drasi authors</Authors>
<Description>Reaction SDK for Drasi</Description>
<PackageProjectUrl>https://drasi.io</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/drasi-project/drasi-platform.git</RepositoryUrl>
<Version>0.1.2</Version>
<PackageIcon>drasi.png</PackageIcon>
<PackageVersion>0.1.2-alpha</PackageVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="Drasi.Reaction.SDK.Tests" />
<None Include="README.md" Pack="true" PackagePath="\"/>
<None Include="drasi.png" Pack="true" PackagePath="\"/>
<PackageReference Include="Dapr.AspNetCore" Version="1.14.0" />
<PackageReference Include="Dapr.Client" Version="1.14.0" />
<PackageReference Include="YamlDotNet" Version="16.1.3" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions reactions/sdk/dotnet/Drasi.Reaction.SDK/IChangeEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 The Drasi 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 Drasi.Reaction.SDK.Models.QueryOutput;
using System;

namespace Drasi.Reaction.SDK
{
public interface IChangeEventHandler : IChangeEventHandler<object>
{
}

public interface IChangeEventHandler<TQueryConfig> where TQueryConfig : class
{
Task HandleChange(ChangeEvent evt, TQueryConfig? queryConfig);
}
}
28 changes: 28 additions & 0 deletions reactions/sdk/dotnet/Drasi.Reaction.SDK/IControlEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 The Drasi 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 Drasi.Reaction.SDK.Models.QueryOutput;
using System;

namespace Drasi.Reaction.SDK
{
public interface IControlEventHandler : IControlEventHandler<object>
{
}

public interface IControlEventHandler<TQueryConfig> where TQueryConfig : class
{
Task HandleControlSignal(ControlEvent evt, TQueryConfig? queryConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2024 The Drasi 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;

namespace Drasi.Reaction.SDK.Models.QueryOutput
{
public static class ModelOptions
{
public static JsonSerializerOptions JsonOptions => Converter.Settings;
}
}



Loading

0 comments on commit 44a2329

Please sign in to comment.