|
|
***NOTE: This document is under construction and a work in progress. Please stay tuned for updates!***
|
|
|
|
|
|
## About This Tutorial
|
|
|
In this document, we will walk through the development of a simple SiLA server to show the necessary steps for implementing your own SiLA 2 Server in C#.
|
|
|
|
|
|
## Prerequisites
|
|
|
|
|
|
### Clone the repo and create a working branch
|
|
|
1. If you haven't already cloned and built the repo, do these steps here: [Quick Start](Quick-Start)
|
|
|
|
|
|
2. In your terminal create a new branch with any name
|
|
|
```bash
|
|
|
cd sila_csharp
|
|
|
git checkout -b my_first_sila_server
|
|
|
```
|
|
|
![1_create_branch](uploads/7e31cd5199458799916ba0629a9b3314/1_create_branch.png)
|
|
|
|
|
|
## Project setup
|
|
|
|
|
|
### Open the solution and create a new project
|
|
|
1. Open the solution file `sila_csharp/sila_csharp.sln`with VisualStudio 2017 (or higher)
|
|
|
|
|
|
2. Create a new subfolder e.g. 'MyFirstServer' within the 'examples' project folder
|
|
|
|
|
|
![2_create_folder](uploads/b6e33a3c175045422a31a19036ecc000/2_create_folder.png)
|
|
|
![3_folder_created](uploads/73c9cf9e349538fb03ce9ecd19ec1138/3_folder_created.png)
|
|
|
|
|
|
3. Create a new .NET core console application e.g. 'MyFirstServer.App' within the created subfolder (`MyFirstServer`)
|
|
|
|
|
|
![4_create_project](uploads/6394ebc331e9c515bae084f8ccf060bb/4_create_project.png)
|
|
|
|
|
|
***NOTE: Pay attention to add the `examples\MyFirstServer` folder to the `Location:` entry
|
|
|
![5_create_project_options](uploads/6c12ea2c9a68cc2463c8af91ce338832/5_create_project_options.png)
|
|
|
|
|
|
4. Add the SiLA project dependencies `Sila2`and `Sila2.Utils`
|
|
|
|
|
|
*Option 1: Adding package source references:*
|
|
|
|
|
|
![6_select_project_dependencies](uploads/2110c4313a291fd346c136d3f2b1c465/6_select_project_dependencies.png)
|
|
|
![7_project_dependencies](uploads/13c0ae3517f3701aa19f73e6dbbf5e63/7_project_dependencies.png)
|
|
|
|
|
|
*Option 2: Add the dependencies as NuGet packages*
|
|
|
|
|
|
Instead of adding the project to the existing C# repo, you can start with a fresh solution, creating a new .NET console application and install the Sila2 NuGet package `Sila2.0.1.0-master.nupkg` that can be found in the repo folder
|
|
|
```bash
|
|
|
sila_csharp/src/Sila2/bin/Debug
|
|
|
```
|
|
|
|
|
|
In Visual Studio, you have to install the package manually from a local source that has to be created in the NuGet Manager options.
|
|
|
![24_add_nuget_package](uploads/d111b851f2af8adf6e64252557862616/24_add_nuget_package.png)
|
|
|
![25_sila2_nuget_package](uploads/74f98bc75e59365c5199c3fe550c2eed/25_sila2_nuget_package.png)
|
|
|
|
|
|
You may also follow the steps [here](https://gitlab.com/SiLA2/sila_csharp/blob/master/docs/deployment.md)
|
|
|
|
|
|
The following steps are the same for both ways.
|
|
|
|
|
|
|
|
|
## Start Development
|
|
|
|
|
|
### It all starts with SiLA 2 Features (create the first Feature)
|
|
|
|
|
|
1. Create a `features` sub folder within the `MyFirstServer.App`project
|
|
|
|
|
|
![8_create_features_subfolder](uploads/f9448df3ebf2e1cf4dd226ed8154eb63/8_create_features_subfolder.png)
|
|
|
![8a_features_subfolder](uploads/bc95b02c8de5cbf06c2826c652577230/8a_features_subfolder.png)
|
|
|
|
|
|
2. Create a feature definiton XML file e.g. `MyFirstFeature.sila.xml`
|
|
|
|
|
|
![9_create_feature_file](uploads/579e777ee2caa1c3c331497a9ad53aee/9_create_feature_file.png)
|
|
|
![10_create_feature_file_options](uploads/757de2728e946e4c32bba87f43f7604b/10_create_feature_file_options.png)
|
|
|
|
|
|
3. Open the created file and add the following XML tag
|
|
|
```xml
|
|
|
<Feature SiLA2Version="0.1" FeatureVersion="1.0" Originator="org.silastandard" Category="examples"
|
|
|
xmlns="http://www.sila-standard.org"
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
xsi:schemaLocation="http://www.sila-standard.org https://gitlab.com/SiLA2/sila_features/raw/master/schema/FeatureDefinition.xsd">
|
|
|
</Feature>
|
|
|
```
|
|
|
This immediately enables intellisense according to the referenced SiLA 2 Feature Definition schema
|
|
|
![11_feature_definition_intellisense](uploads/6a76e9e5e0f1e3cf6b1078b7e2ea1e0b/11_feature_definition_intellisense.png)
|
|
|
|
|
|
**Note**: You may have to enable downloading XML schema in the Visual Studio options.
|
|
|
![11a_incorrect_options](uploads/0507420ea655f234313b415d40676ee9/11a_incorrect_options.png)
|
|
|
|
|
|
To do that, open the `Options` dialog under `Tools|Options` and go to `Text Editor | XML | Miscellanious` and activate the `Automatically download XDSs and schemas`.
|
|
|
![11b_VS_options](uploads/565dc4311a08f3fe3d638d09943e1d4f/11b_VS_options.png)
|
|
|
![11c_option_for_intellisense](uploads/8fcd88acbf09d8ec26913358113793d4/11c_option_for_intellisense.png)
|
|
|
|
|
|
4. Now we can develop a simple feature with one command which counts characters of a string and one property which provides the day of the week as a string:
|
|
|
```xml
|
|
|
<?xml version="1.0" encoding="utf-8" ?>
|
|
|
<Feature SiLA2Version="0.1" FeatureVersion="1.0" Originator="org.silastandard" Category="examples"
|
|
|
xmlns="http://www.sila-standard.org"
|
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
|
xsi:schemaLocation="http://www.sila-standard.org https://gitlab.com/SiLA2/sila_features/raw/master/schema/FeatureDefinition.xsd">
|
|
|
<Identifier>MyFirstFeature</Identifier>
|
|
|
<DisplayName>My First Feature</DisplayName>
|
|
|
<Description>This is my first feature, I don't know yet what it will be doing someday</Description>
|
|
|
<Command>
|
|
|
<Identifier>CountCharacters</Identifier>
|
|
|
<DisplayName>Count Characters</DisplayName>
|
|
|
<Description>Returns the number of characters of the passed string</Description>
|
|
|
<Observable>No</Observable>
|
|
|
<Parameter>
|
|
|
<Identifier>InputString</Identifier>
|
|
|
<DisplayName>InputString</DisplayName>
|
|
|
<Description>The string that characters have to be counted</Description>
|
|
|
<DataType>
|
|
|
<Basic>String</Basic>
|
|
|
</DataType>
|
|
|
</Parameter>
|
|
|
<Response>
|
|
|
<Identifier>NumberOfCharacters</Identifier>
|
|
|
<DisplayName>Number Of Characters</DisplayName>
|
|
|
<Description>The number of characters of the passed input string</Description>
|
|
|
<DataType>
|
|
|
<Basic>Integer</Basic>
|
|
|
</DataType>
|
|
|
</Response>
|
|
|
</Command>
|
|
|
<Property>
|
|
|
<Identifier>DayOfWeek</Identifier>
|
|
|
<DisplayName>Day Of Week</DisplayName>
|
|
|
<Description>Returns the current day of week</Description>
|
|
|
<DataType>
|
|
|
<Basic>String</Basic>
|
|
|
</DataType>
|
|
|
<Observable>No</Observable>
|
|
|
</Property>
|
|
|
</Feature>
|
|
|
```
|
|
|
|
|
|
***NOTE:*** As the implementations are work in progress, the referenced feature definiton schema slightly differs from the currently implemented tooling, so the VisualStudio XML editor marks the `<Property>` definition as invalid as the order of `<DataType>` and `<Observable>` tag has recently changed. Just ignore that for now.
|
|
|
|
|
|
![11a_schema_inconsistency](uploads/f83cc5feb96d88ad5ab998c822803118/11a_schema_inconsistency.png)
|
|
|
|
|
|
### Generate Protos and Stubs from Features
|
|
|
|
|
|
1. Create a new sub folder `generated` within the `MyFirstServer.App`project
|
|
|
|
|
|
![12_create_generated_folder](uploads/8fc25b102174314b618f1fa308fb75be/12_create_generated_folder.png)
|
|
|
|
|
|
2. Generate the protobuf file as well as the required C# stubs from your feature by calling a shell script (e.g. in the Git bash)
|
|
|
```bash
|
|
|
cd sila_csharp/examples/MyFirstServer/MyFirstServer.App
|
|
|
../../../tools/generate_proto_and_stub.sh features/MyFirstFeature.sila.xml generated/ generated/
|
|
|
```
|
|
|
![13_generate_proto_and_stub_files](uploads/675dee3856eb280afc52850ac966cd25/13_generate_proto_and_stub_files.png)
|
|
|
|
|
|
***NOTE:*** The script can not be started from `cmd.exe` (recommendation is to use the Git bash).
|
|
|
|
|
|
The script should generate the follwing files:
|
|
|
* `MyFirstFeature.proto` - the protobuf file
|
|
|
* `MyFirstFeature.cs`and `MyFirstFeatureGrpc.cs` - the C# stubs
|
|
|
|
|
|
![14_generated_files](uploads/719cf3ae9bce8585fb55bd88067fa921/14_generated_files.png)
|
|
|
|
|
|
### Implement gRPC Server Call Handler
|
|
|
|
|
|
1. Create a new implementation class `MyFirstFeatureImpl.cs` of the MyFirstFeature gRPC calls
|
|
|
|
|
|
![15_create_impl_class](uploads/fd525764fa54dd25637ef8a912ba71e1/15_create_impl_class.png)
|
|
|
![16_create_impl_class_options](uploads/55d152dd74790f3f0d71b218bf2c8fe6/16_create_impl_class_options.png)
|
|
|
|
|
|
2. Add the required usings
|
|
|
|
|
|
```chsarp
|
|
|
using System.Threading.Tasks;
|
|
|
using Grpc.Core;
|
|
|
using Sila2.Org.Silastandard.Examples.Myfirstfeature.V1;
|
|
|
using SilaFramework = Sila2.Org.Silastandard;
|
|
|
```
|
|
|
|
|
|
3. First lets inspect the important contents of the `MyFirstFeatureGrpc.cs` file, containing the interfaces
|
|
|
which you will override in your `MyFirstFeatureImpl`
|
|
|
|
|
|
```csharp
|
|
|
...
|
|
|
|
|
|
/// <summary>Base class for server-side implementations of MyFirstFeature</summary>
|
|
|
public abstract partial class MyFirstFeatureBase
|
|
|
{
|
|
|
public virtual global::System.Threading.Tasks.Task<global::Sila2.Org.Silastandard.Examples.Myfirstfeature.V1.CountCharacters_Responses> CountCharacters(global::Sila2.Org.Silastandard.Examples.Myfirstfeature.V1.CountCharacters_Parameters request, grpc::ServerCallContext context)
|
|
|
{
|
|
|
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
|
|
}
|
|
|
|
|
|
public virtual global::System.Threading.Tasks.Task<global::Sila2.Org.Silastandard.Examples.Myfirstfeature.V1.Get_DayOfWeek_Responses> Get_DayOfWeek(global::Sila2.Org.Silastandard.Examples.Myfirstfeature.V1.Get_DayOfWeek_Parameters request, grpc::ServerCallContext context)
|
|
|
{
|
|
|
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>Client for MyFirstFeature</summary>
|
|
|
public partial class MyFirstFeatureClient : grpc::ClientBase<MyFirstFeatureClient>
|
|
|
{
|
|
|
|
|
|
...
|
|
|
```
|
|
|
|
|
|
The relevant part of all the generated code is the `virtual` methods of the `MyFirstFeatureBase` class in the file. As you can see these methods reflect the commands and properties defined in the `MyFirstFeature.sila.xml` feature.
|
|
|
|
|
|
4. Derive the class from the generated C# gRPC stub class `MyFirstFeatureBase`
|
|
|
|
|
|
![17_derive_impl_from_stub](uploads/c7417855c0c6849bb298510047d41eb1/17_derive_impl_from_stub.png)
|
|
|
|
|
|
5. Now that you know the virtual methods of `MyFirstFeatureBase`, you can overrride them.
|
|
|
|
|
|
If you are on VisualStudio or any other IDE then you can let it do the rest for you :)
|
|
|
|
|
|
![18_override_command_call](uploads/125747f9f995b340408a2fb7efd6bb1e/18_override_command_call.png)
|
|
|
|
|
|
When you've overridden the `CountCharacters` and `DayOfWeek` methods should just call the base class methods,
|
|
|
and should look like this:
|
|
|
|
|
|
![19_overriden_calls](uploads/dddfa5fde1d96baa477edba491f9d240/19_overriden_calls.png)
|
|
|
|
|
|
6. Now lets implement the `CountCharacters` command
|
|
|
|
|
|
First, get the input parameters from the `request` parameter:
|
|
|
```csharp
|
|
|
...
|
|
|
|
|
|
public override Task<CountCharacters_Responses> CountCharacters(CountCharacters_Parameters request, ServerCallContext context)
|
|
|
{
|
|
|
var inputStr = request.InputString.Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
...
|
|
|
```
|
|
|
|
|
|
Now, calculate the number of characters in the string which is the result of the command:
|
|
|
```csharp
|
|
|
...
|
|
|
public override Task<CountCharacters_Responses> CountCharacters(CountCharacters_Parameters request, ServerCallContext context)
|
|
|
{
|
|
|
var inputStr = request.InputString.Value;
|
|
|
var numberOfCharacters = inputStr.Length;
|
|
|
}
|
|
|
...
|
|
|
```
|
|
|
|
|
|
For sending back the result we have to assemble a `CountCharacters_Responses`object and pass it to the return result object:
|
|
|
```csharp
|
|
|
...
|
|
|
public override Task<CountCharacters_Responses> CountCharacters(CountCharacters_Parameters request, ServerCallContext context)
|
|
|
{
|
|
|
var inputStr = request.InputString.Value;
|
|
|
var numberOfCharacters = inputStr.Length;
|
|
|
return Task.FromResult(new CountCharacters_Responses { NumberOfCharacters = new SilaFramework.Integer { Value = numberOfCharacters } });
|
|
|
}
|
|
|
...
|
|
|
```
|
|
|
|
|
|
|
|
|
7. Lets implement the `DayOfWeek` property:
|
|
|
|
|
|
Here we simply replace the return statement by:
|
|
|
|
|
|
```csharp
|
|
|
...
|
|
|
public override Task<Get_DayOfWeek_Responses> Get_DayOfWeek(Get_DayOfWeek_Parameters request, ServerCallContext context)
|
|
|
{
|
|
|
var response = new Get_DayOfWeek_Responses {DayOfWeek = new SilaFramework.String {Value = DateTime.Now.DayOfWeek.ToString()}};
|
|
|
return Task.FromResult(response);
|
|
|
}
|
|
|
...
|
|
|
```
|
|
|
|
|
|
And we have finished the Feature implementation :)
|
|
|
|
|
|
### Implement Server application (bind it all together)
|
|
|
|
|
|
1. Add a new class `Server.cs` to the project `MyFirstServer.App`
|
|
|
|
|
|
![20_add_server_class](uploads/aa1e976777a3b1f4945399bb33584658/20_add_server_class.png)
|
|
|
|
|
|
2. Add the required usings
|
|
|
```csharp
|
|
|
using System.Net.NetworkInformation;
|
|
|
using Sila2;
|
|
|
using Sila2.Server;
|
|
|
|
|
|
...
|
|
|
```
|
|
|
|
|
|
3. Derive class from `Sila2Server` and auto-create the required constructor that should result in this code:
|
|
|
```csharp
|
|
|
...
|
|
|
|
|
|
internal class Server : SiLA2Server
|
|
|
{
|
|
|
public Server(ServerInformation serverInformation, int portNumber, NetworkInterface networkInterface, string configFile = null) : base(serverInformation, portNumber, networkInterface, configFile)
|
|
|
{
|
|
|
}
|
|
|
}
|
|
|
|
|
|
...
|
|
|
```
|
|
|
|
|
|
4. Modify the constructor to implicitely set the Server information:
|
|
|
```csharp
|
|
|
...
|
|
|
|
|
|
internal class Server : SiLA2Server
|
|
|
{
|
|
|
public Server(int portNumber, NetworkInterface networkInterface, string configFile = null) :
|
|
|
base(new ServerInformation("My First SiLA 2 Server", "This is my first SiLA 2 server", "www.equicon.de", "1.0"),
|
|
|
portNumber, networkInterface, configFile)
|
|
|
{
|
|
|
}
|
|
|
}
|
|
|
|
|
|
...
|
|
|
```
|
|
|
|
|
|
5. Add the feature as a Service to the gRPC server:
|
|
|
|
|
|
Add these lines to the constructor:
|
|
|
```csharp
|
|
|
...
|
|
|
|
|
|
{
|
|
|
public Server(int portNumber, NetworkInterface networkInterface, string configFile = null) :
|
|
|
base(new ServerInformation("My First SiLA 2 Server", "This is my first SiLA 2 server", "www.equicon.de", "1.0"),
|
|
|
portNumber, networkInterface, configFile)
|
|
|
{
|
|
|
var myFeature = ReadFeature("features/MyFirstFeature.sila.xml");
|
|
|
this.GrpcServer.Services.Add(Sila2.Org.Silastandard.Examples.Myfirstfeature.V1.MyFirstFeature.BindService(new MyFirstFeatureImpl()));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
...
|
|
|
```
|
|
|
|
|
|
`ReadFeature` has to be called even if we don't need the result here. Usually the read feature object will be passed to the respective implementation file, as it contains information that is not covered by the proto / stub files, like parameter restrictions or standard execution errors.
|
|
|
|
|
|
6. Copy the feature definition file `MyFirstFeature.sila.xml` to the output
|
|
|
In order to be able to read the feature file, it has to be copied to the project output. Change the according property of `MyFirstFeature.sila.xml`:
|
|
|
|
|
|
![21_add_feature_definition_file_to_output](uploads/fa0be9c57c85e053a2c38bcddd9d542d/21_add_feature_definition_file_to_output.png)
|
|
|
![22_feature_definition_file_properties](uploads/d3e23482805f1bc3923bfd5dea4e97f4/22_feature_definition_file_properties.png)
|
|
|
|
|
|
7. To create, setup and start the sever, modify the application entry point in `Program.cs` as follows:
|
|
|
|
|
|
```csharp
|
|
|
static void Main(string[] args)
|
|
|
{
|
|
|
int port = 50052;
|
|
|
MyFirstServer.App.Server server = new Server(port, null);
|
|
|
server.StartServer();
|
|
|
|
|
|
Console.WriteLine($"Server running and listening to port {port}\n\nPress Enter to stop the server ...");
|
|
|
Console.ReadLine();
|
|
|
|
|
|
server.ShutdownServer().Wait();
|
|
|
Console.WriteLine("Server has been shut down");
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Test it!
|
|
|
|
|
|
You can now run the server either directly from Visual Studio or via
|
|
|
```bash
|
|
|
cd examples/MyFirstServer/MyFirstServer.App/bin/Debug/netcoreapp2.0
|
|
|
dotnet MyFirstServer.App.dll
|
|
|
```
|
|
|
|
|
|
![23_running_server](uploads/cd22ffd64fb476261529888f06c5c9c8/23_running_server.png)
|
|
|
|
|
|
You can now use the SiLA Browser to check if it can communicate with your SiLA 2 Server, like described [here](https://gitlab.com/SiLA2/sila_base/wikis/SiLA-Browser-Quickstart#install-and-run-the-sila-2-browser)
|
|
|
|
|
|
|
|
|
## Congratulations, you have just developed your own SiLA 2 Server! |
|
|
\ No newline at end of file |