Files
Masterarbeit/QuellCode/CentronERP/docs/guides/services/add-webservice-methods.md
T
Christoph Schwörer f045b99a25 Codebasis als Dateien ins Arbeitsrepo statt als Gitlink
QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
2026-08-26 07:43:51 +02:00

241 lines
10 KiB
Markdown

# How to add webservice methods
Please refer to the [General Structure](../../getting-started/general-structure.md) page to get an overview of the general structure.
Please refer to the [On DTOs and Entities](../../reference/architecture/dtos-and-entities.md) to get a better understanding on what types to use where.
For a reference with a correct implementation check out `MyDayBL.cs` and associated classes.
## Businesslogic
### BL
For this we will assume that `ThingyBL.cs` contains the method `public Result<Thingy> SaveOrUpdateThingy(Thingy item, LoggedInUser currentUser)`. This method checks the input, checks userrights and saves the `Thingy` to the database.
BL methods should always return a result with actual useable results (don't just `return Result.AsSuccess()`). Save methods must always return the entity itself so we get an entity with a valid I3D.
### WebServiceBL
The webserviceBL must be named the same as the BL (`ThingyWebServiceBL.cs` in this case) and the method itself should be named the same as the BL one (`public Result<ThingyDTO> SaveOrUpdateThingy(ThingyDTO item, LoggedInUser currentUser)` in our example).
It's sole purpose is to convert and incoming `ThingyDTO` to a `Thingy` entity and back. Please consult [On DTOs and Entities](../../reference/architecture/dtos-and-entities.md) on how to convert correctly.
This must also return a `Result<Thingy>`. The best way to ensure the result from the BL gets carried over is using `Result<Thingy>.FromResult(<your ThingyDTO>, <yourBLResult>);`.
Our method should look something like this:
``` csharp
public Result<ThingyDTO> SaveOrUpdateThingy(ThingyDTO item, LoggedInUser currentUser)
{
var entity = this.ConvertThingyDTOToEntity(item);
var result = this._thingyBL.SaveOrUpdate(entity, currentUser);
var mappedResult = this.ConvertThingyToDTO(result.Data);
return Result<ThingyDTO>.FromResult(mappedResult, result);
}
```
## RestService
Next we'll create the method in the actual webservice. Please note that both `CentronRestService` and `ICentronRestService` are partial classes and have parts for some categories (`CentronRestService.Helpdesk`, `CentronRestService.Docuboard` etc.). Also note that the methods here are wildly inconistently placed and are pretty much all over the place.
If you find a partial part for the method you want to create use that part, if you know you'll create more than 2-3 methods consider making your own partial part and if neither of those just throw it into `CentronRestService` (or `CentronRestService.DTOPart`).
As our API has a flat surface methods here need to be named more specific than they are in the BL-layers. They **must** contain the name of your entity or something else that make them identifiable on what category they belong in. For example we work with `Thingy` here, so all webservice methods doing anything with them (load/saveorupdate/delete etc.) have to include `Thingy` somewhere (LoadThingies, SaveOrUpdateThingy etc.).
### CentronRestService
It **must** always accept a `Request<T>`/`Request` and return a `Response<T>`/`Response` and nothing else.
The method itself can only contain 2 rows:
1. call the webserviceBL
2. convert the `Result<T>` to a `Response<T>` and return
This is how our method should look like:
``` csharp
public Response<ThingyDTO> SaveOrUpdateThingy(Request<ThingyDTO> request)
{
var result = this.Session.GetBL<ThingyWebServiceBL>().SaveOrUpdateThingy(request.Data, this.GetLoggedInUserByTicket(request.Ticket));
return Response<ThingyDTO>.FromBLResult(result);
}
```
Note the `GetLoggedInUserByTicket(request.Ticket)`-Method to get a `LoggedInUser`.
### ICentronRestService
Creating the method in the interface is very simple, just copy the method signature from the CentronRestService-method over, then add the needed attributes. We always need the following 3 attributes to register the method for the webservice. Please note that the [WebInvoke..] method **must** be 'POST' and the UriTemplate **must** be the same as the method name.
``` csharp
[WebInvoke(Method = "POST", UriTemplate = "SaveOrUpdateThingy")]
[Authenticate]
Response<ThingyDTO> SaveOrUpdateThingy(Request<ThingyDTO> request)
```
With this we have a working webservice method that can be used by external apps via the `Centron.WebServices.Core` / `Centron.Interfaces` reference or a WSDL-Service-Reference or similar.
*Remember to connect to your self-hosted webservice when creating methods or you'll be very confused when it doesnt work (speaking from experience ;) )*
### Request classes
Quite often you need to send several properties in your request for example in filters. For this simply create a `<yourWebServiceMethodName>Request` that aggragates all the needed properties and then use `Request<YourRequest>` as our parameter in the restservice. So if we wanted to filter our `Thingies` we would write our request like this:
``` csharp
[DataContract]
public class GetThingiesRequest
{
[DataMember]
public List<int> ThingyI3Ds { get; set; }
[DataMember]
public DateTime? Before { get; set; }
[DataMember]
public DateTime? After { get; set; }
}
```
``` csharp
[WebInvoke(Method = "POST", UriTemplate = "GetThingies")]
[Authenticate]
Response<ThingyDTO> GetThingies(Request<GetThingiesRequest> request)
```
Note, that these requests are basically DTOs and all restrications/problems of DTOs apply.
Request go into the `Centron.WebServices.Core` project and then the `RestRequests` directory.
## Logics
To use the webservice method in c-entron<span>.NET we must also implement the ILogic interfaces.
Again search for an existing fitting `ILogic` for your method or create your own (`IThingyLogic` for us).
### ILogic
Methods in your ILogic **must** return a `Task<Result<T>>` and should be named the same as the webservice method. But here we can be unspecific again as the context comes from the Logic itself, so instead of `SaveOrUpdateThingy` we can just use `SaveOrUpdate` if we want.
``` csharp
public interface IThingyLogic
{
Task<Result<ThingyDTO>> SaveOrUpdateThingy(ThingyDTO item);
}
```
### BLLogic
This is necessary for the direct database connection and must be named `BL..Logic` (`BLThingyLogic` for us).
It must have 1 constructur that accepts a `ConnectionInfo`:
``` csharp
private readonly ConnectionInfo _connectionInfo;
public BLThingyLogic(ConnectionInfo connectionInfo)
{
Guard.NotNull(connectionInfo, nameof(connectionInfo))
this._connectionInfo = connectionInfo;
}
```
The method itself should only start a task via `Task.Run(..)`, in this task create the webserviceBL and call the correct method. It should look something like this:
``` csharp
public Task<Result<ThingyDTO>> SaveOrUpdateThingy(ThingyDTO item)
{
return Task.Run(() =>
{
using (var session = new BLSession())
{
return session.GetBL<MyThingyWebServiceBL>().SaveOrUpdate(item);
}
});
}
```
### WSLogic
This is necessary for the webservice connection and must be named `WS..Logic` (`WSThingyLogic` for us).
It must have 1 constructur that accepts a `CentronWebServiceConnection`:
``` csharp
private readonly CentronWebServiceConnection _webServiceConnection;
public WSThingyLogic(CentronWebServiceConnection webServiceConnection)
{
Guard.NotNull(webServiceConnection, nameof(webServiceConnection))
this._webServiceConnection = webServiceConnection;
}
```
The method itself simply uses an expression to call the ICentronRestService method you just created.
It is important to never create a request yourself, instead always create it via _webServiceConnection, as this ensures the ticket is set properly.
If you don't need to await the call:
``` csharp
public Task<Result<ThingyDTO>> SaveOrUpdateThingy(ThingyDTO item)
{
return this._webServiceConnection.CallWebServiceMethodWithSingleResultAsync(f => f.SaveOrUpdateThingy(this._webServiceConnection.GetRequest(item)));
}
```
or, if you need to actually await it:
``` csharp
public async Task<Result<ThingyDTO>> SaveOrUpdateThingy(ThingyDTO item)
{
var result = await this._webServiceConnection.CallWebServiceMethodWithSingleResultAsync(f => f.SaveOrUpdateThingy(this._webServiceConnection.GetRequest(item))).ConfigureAwait(false);
// your logic
return result;
}
```
If you await the call, you also need to add the `.ConfigureAwait(false)`. Always try to return the task instead of awaiting it.
The webservice can be called with several methods depending the signature of your webservice method:
returntype | method to use
---|---
Result|CallWebServiceMethodWithResultAsync\<TRequest>
Result\<T>|CallWebServiceMethodWithSingleResultAsync<TRequest, TResult>
Result\<IList\<T>>|CallWebServiceMethodWithListResultAsync<TRequest, TResult>
`TRequest` must be equal to `T` in your `Request<T>` in `ICentronRestService`. As such there can never be a `TRequest` of type `Request<T>`/`Request`.
These methods can also be used without request objects (when your `ICentronRestService` methods parameter is a simple `Request`):
returntype | method to use
---|---
Result|CallWebServiceMethodWithResultAsync
Result\<T>|CallWebServiceMethodWithSingleResultAsync\<TResult>
Result\<IList\<T>>|CallWebServiceMethodWithListResultAsync\<TResult>
## How to use in c-entron<span>.NET
There are two ways to call a webservice method from a viewmodel in Centron.WPF.UI.
If you need the Logic just once simply use `ClassContainer.Instance.WithInstance(..)` like this:
``` csharp
var thingy = await ClassContainer.Instance.WithInstance((IThingyLogic logic) => logic.GetThingyByI3D(thingyI3D)).ThrowIfError();
```
If you need the logic several times, you should create it once via `ClassContainer.Instance.GetInstance<T>()` and use that in your class. This means you also need to implement the `IDisposable` interface and release the instance upon disposing by calling `ClassContainer.Instance.ReleasInstance(yourInstance)`.
``` csharp
public class ThingiesViewModel : IDisposable
{
..
public readonly IThingyLogic ThingyLogic;
public ThingiesViewModel()
{
this.ThingyLogic = ClassContainer.Instance.GetInstance<IThingyLogic>();
}
public void Dispose()
{
ClassContainer.Instance.ReleaseInstance(this.ThingyLogic);
}
..
public void Get()
{
var thingy = await this.ThingyLogic.GetThingyByI3D(thingyI3D).ThrowIfError();
..
}
}
```
Please note the use of `await` and `ThrowIfError()` in both cases.