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

10 KiB

How to add webservice methods

Please refer to the General Structure page to get an overview of the general structure.
Please refer to the On DTOs and Entities 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 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:

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:

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.

[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:

[DataContract]
public class GetThingiesRequest
{
    [DataMember]
    public List<int> ThingyI3Ds { get; set; }
    
    [DataMember]
    public DateTime? Before { get; set; }
    
    [DataMember]
    public DateTime? After { get; set; }
}
[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.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.

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:

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:

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:

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:

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:

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.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:

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).

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.