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
241 lines
10 KiB
Markdown
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. |