# 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 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 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`. The best way to ensure the result from the BL gets carried over is using `Result.FromResult(, );`. Our method should look something like this: ``` csharp public Result 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.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`/`Request` and return a `Response`/`Response` and nothing else. The method itself can only contain 2 rows: 1. call the webserviceBL 2. convert the `Result` to a `Response` and return This is how our method should look like: ``` csharp public Response SaveOrUpdateThingy(Request request) { var result = this.Session.GetBL().SaveOrUpdateThingy(request.Data, this.GetLoggedInUserByTicket(request.Ticket)); return Response.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 SaveOrUpdateThingy(Request 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 `Request` that aggragates all the needed properties and then use `Request` 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 ThingyI3Ds { get; set; } [DataMember] public DateTime? Before { get; set; } [DataMember] public DateTime? After { get; set; } } ``` ``` csharp [WebInvoke(Method = "POST", UriTemplate = "GetThingies")] [Authenticate] Response GetThingies(Request 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>` 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> 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> SaveOrUpdateThingy(ThingyDTO item) { return Task.Run(() => { using (var session = new BLSession()) { return session.GetBL().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> 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> 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\ Result\|CallWebServiceMethodWithSingleResultAsync Result\>|CallWebServiceMethodWithListResultAsync `TRequest` must be equal to `T` in your `Request` in `ICentronRestService`. As such there can never be a `TRequest` of type `Request`/`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\|CallWebServiceMethodWithSingleResultAsync\ Result\>|CallWebServiceMethodWithListResultAsync\ ## 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: ``` 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()` 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(); } 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.