3
Pilz.Net
Pilzinsel64 edited this page 2024-10-17 09:41:21 +00:00

WebApi

Pilz.Net.Api introduces a very simple way to create a WebApi server and client. It's an alternative to the implemenation in ASP.NET which uses Newtonsoft.Json instead of Json.NET and is even more easier to implement and more extendable via plugins. As of now Pilz.Net.Api not fully REST compliant, this might be fixed in the future (PRs welcome).

How it works

The process is simple:

  1. The client sends a request with a given URL and an optional message to the server.
  2. The server evaluates the request and response containing a status code and an optional a new message.
  3. The client gets the response and can evaluate it, but doesn't need to.

Client

Create new ApiClient

You can either use the pre-difined ApiClient or create your own by implementing IApiClient. Pass throw the api base url.

var client = new ApiClient("http://localhost");

Make a request

Creating a request can be done with the asyncron IApiClient.MakeRequest() and IApiClient.MakeRequest<T>() methods. You need to pass throw the api url (relative to the api base url). Optionally you can also pass an ApiMessage instance or your own IApiMessageSerializer.

// Run asyncron
var result = await client.SendRequest<SDxGetResponse>("/sdx/get");

// Run syncron
var result = client.SendRequest<SDxGetResponse>("/sdx/get").Result;

Check result

The result is always an instance of ApiResponse and contains the status code or the optional response message.

You have several ways to check the status code:

  • Check the status code manually via StatusCode for more detailed check.
  • Check the status code via the method IsOk() which returns an boolean if the StatusCode is Ok and the Message is not null.
  • Check the status code via the method EnsureOk() which throws an exception if the StatusCode is not Ok and Message is not null.
// Check manually
If (result.StatusCode == HttpStatusCode.OK)
    // ...
else
    // ...

// Check via IsOk()
If (result.IsOk())
    // ...
else
    // ...

// Check via EnsureOk()
result.EnsureOk();

Check message

If the status code is fine and the message is not null, you can use it. It's typed as T while T is an type that inherits from ApiMessage which you can define at SendRequest<T>() method.

Example messages
public class SDxGetRequest(int id) : ApiMessage
{
    public int Id { get; } = id;
}

public class SDxGetResponse(List<SDx> sdx) : ApiMessage
{
    public List<SDx> SDx { get; } = sdx;
}

Server

Create new ApiServer

You can either use the pre-difined ApiServer or create your own by implementing IApiServer. Pass throw the api base url.

var server = new Apiserver("http://localhost");

Register your own handlers or let your plugins register their handlers anonymously.

// Register object instance
// This registers all private and public methods with the `ApiMessageHandler` attribute.
server.RegisterHandler(new SDxApiServer());

// Register single method
server.Register(MyMethod);

Create message handlers

When a request reaches the server then the server checks each registered handler method for its ApiMessageHandler attribute.

  • The return value of the method always needs to be an instance of ApiResult.
  • The method itself can be private or public.
  • The ApiMessageHandler defines the api url (relative to the api base url) and optionally if authentication is requied for this request and also an serializer type for deseralizing the ApiMessage.
[ApiMessageHandler("/sdx/get")]
private ApiResult GetSDx()
{
    return ApiResult.Ok(new SDxResponse([.. Database.SDxList.Select(s => s.ToClient())]));
}

If your request can have an (optional) message, you can define a parameter with the target message type.

[ApiMessageHandler("/sdx/get")]
private ApiResult GetSDx(SDxGetRequest message)
{
    if (message != null)
    {
        if (Database.SDxList.FirstOrDefault(s => s.Id == message.Id) is not SDx sdx)
            return ApiResult.NotFound();
        return ApiResult.Ok(new SDxGetResponse([.. sdx]);
    }
    return ApiResult.Ok(new SDxGetResponse([.. Database.SDxList.Select(s => s.ToClient())]));
}

If you need the whole request, you can define a parameter with the ApiRequest instance which also contains the ApiMessage, if available. However, you can even define both parameters at the same time, if you want. The ApiRequest can be useful if you want to check authentication conditionally depenting on the request message instead of defined RequiesAuth = true at the parameter.

[ApiMessageHandler("/sdx/get")]
private ApiResult GetSDx(ApiRequest request, SDxGetRequest message)
{
    // ...
}

Authentication

For authentication you can either use the pre-defined events or create your own Server and Client class. Either inherit from ApiServer/ApiClient or create your complete own implementation by implement IApiServer/IApiClient. However, the ApiServer and ApiClient classes has some pre-defined methods you can overwrite to easily check the authentication.

Use events

You can use the event for one-way and two-way encryption.

Client

Register this method to the ApiClient.OnEcryptAuthKey event. Encode the authKey (unencrypted) and return the decrypted value.

protected virtual string? EncodeAuthKey()
{
    return AuthKey;
}

Server

Register this method to the ApiServer.OnCheckAuthentication event. Decode the authKey (encrypted) and return the decrypted value.

protected virtual string? DecodeAuthKey(string authKey)
{
    return authKey;
}

Inherit & Overwrite

You can use this way for one-way and two-way encryption. For one-way encryption you should overwrite CheckAuthentication instead of DecodeAuthKey and may not need EncodeAuthKey (depending on your security implementation.

Client

protected virtual string? EncodeAuthKey()
{
    return AuthKey;
}

Server

protected virtual string? DecodeAuthKey(string authKey)
{
    return authKey;
}