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
This commit is contained in:
Christoph Schwörer
2026-08-26 07:43:51 +02:00
parent 18edae75b6
commit f045b99a25
24664 changed files with 5846716 additions and 1 deletions
@@ -0,0 +1,154 @@
# On DTOs and Entities
In our c-entron<span>.NET solution we use three different types of objects that hold actual data:
type | layer | usage
---|---|---
Entity | BL/WebServiceBL | interaction with the database via NHibernate
DTO | WebService/Logics |transfer from entity to viewmodel via webservice
ViewModels | ViewModel/UI|display data to the user and allow user to edit it
*Because ViewModels are pretty straight forward and quite boring we're just going to ignore them here.*
We're going to use the `Thingy` class for examples here again. For a real implementation check out MyDayWorkItem and associated classes.
## Entities
On the most basic level entities are just a row in a database table managed by our ORM [NHibernate](https://nhibernate.info/). If we have a `Thingy` there is a corresponding row in the `Thingies` table, where the **primary key equals** the **I3D**. Entities can never leave the BL-Layer as they cannot be used in the webservice and need a connection to NHibernate and the DB itself. CRUD-Operations are done via `Session.GetGenericDAO\<T>().SaveOrUpdate/Get/Delete` and can only be executed with a valid entity.`
As an entity is simply for holding data it should never include any logic, no overrides and not even a ctor. To avoid confusion all properties present in an entity should also be mapped to a column in the database.
An entitiy needs 2 classes:
The actual entity that holds all data and is used for all operations. It must inherit the `BaseEntity` abstract base class that adds the unique identifier (**I3D**) for us. The correct project for entities is `Centron.Entities` and then the `Entities` directory.
All properties must be declared **virtual** and have to have both a **setter** and a **getter**. Both things are required for the interaction with NHibernate.
``` csharp
public class Thingy : BaseEntity
{
public virtual string SomeProperty { get; set; }
public virtual int SomeOtherProperty { get; set; }
}
```
Aside from the actual entity we also need a mapping class from [Fluent NHibernate](https://github.com/FluentNHibernate/fluent-nhibernate). This class maps the properties to the database column and must inherit `ClassMap<T>`. Even tough NHibernate can figure a lot of the properties out itself, you should always set the table, the id and ALL properties. Mapped properties need to be described with `.Not`, `.Nullable()`, `.Lenght()` etc. as closely as possible. Check out ['Fluent NHibernate in a Nutshell'](https://github.com/FluentNHibernate/fluent-nhibernate/wiki/Getting-started) for more infos. The correct project for entities is `Centron.DAO` and then the `Mappings` directory.
``` csharp
public class ThingyMaps : ClassMap<Thingy>
{
public ThingyMaps
{
this.Table("Thingies");
this.Id(f => f.I3D);
this.Map(f => f.SomeProperty).Nullable();
this.Map(f => f.SomeOtherProperty).Not.Nullable();
}
}
```
## DTOs
>In the field of programming a data transfer object (DTO) is an object that carries data between processes. (https://en.wikipedia.org/wiki/Data_transfer_object)
For us DTOs do exactly that. They transfer the data from the entity on the bl-layer to the viewmodel in the UI over WebService or direct database connection. Our webservice exclusivly uses DTOs, so all other applications and the c-entron<span>.NET ILogics use them. The project for DTOs is `Centron.WebServices.Core` and then the wrongly named `Entities` directory.
Like `entities DTOs` should never hold any kind of logic, no overrides and no ctors. It can include properties not present in the entity but this should be only be done in very specific circumstances.
Constructing DTOs is very easy: just create a class `<yourclass>DTO` (`ThingyDTO` for us), inherit `BaseDTO` and copy all properties from the entity. Then add the `[DataContract]` attribute to your class and the `[DataMember]` attribute to each property.
There are certain things that need to be avoided.
Properties of type DateTime can be dangerous. The default value of DateTime in .NET is 01.01.0001 which cannot be parsed to json and *will* throw an exception. Especially if the DateTime property gets filled by the database, is used in multiple locations or by other applications. Using a nullable `DateTime?` is the easiest way around that.
List properties **must** use `List<T>`. Using `IList<T>` or other types of list (`IEnumerable<T>`, `ICollection<T>`) can lead to problems with serializing it into json. As this is not widely done in our DTOs please consider updating existing DTOs.
### KnownTypes
If your DTO inherits from a different class (NOT `BaseDTO`) you must add the type to `GetKnownTypes()` in `KnownTypes.cs`. Additionally each webservice method that uses that DTO also needs the `[ServiceKnownType(nameof(KnownTypes.GetKnownTypes), typeof(KnownTypes))]` attribute in `ICentronRestService`. Otherwise the webservice *will* throw an exception while parsing your DTO to json.
## Converting DTOs to Entities and vice versa
### DTO -> Entity
Converting from a DTO to an entity can be somewhat annoying and complicated.
**NEVER use the `ObjectMapper` for this.** *(Yes, I know, there are a lot of places that do)*
What we need to do is as follows:
1. check if the I3D of the `ThingyDTO` is 0.
\> yes: `new Thingy()`;
\> no: load the entity from the database (via the appopriate BL method)
2. take over all properties from the DTO to the entity by hand.
This is done to ensure that all entities that are already saved to the DB (I3D != 0) are unique and properly managed by NHibernate.
``` csharp
public Thingy ConvertThingyDTOToEntity(ThingyDTO dto)
{
Guard.NotNull(dto, nameof(dto));
Thingy entity;
if(dto.I3D != 0)
entity = new ThingyBL(this.Session).LoadThingy(dto.I3D);
else
entity = new Thingy();
entity.I3D = dto.I3D;
entity.SomeProperty = dto.SomeProperty;
[..]
return entity;
}
```
### Entity -> DTO
Converting an entity to a DTO is very simple as we can just use the `ObjectMapper.Map<TEntity, TDTO>()`.
To use the Objectmapper you should create a configuration file.
These files go under `Centron.Bl` -> `Webservice` -> `ObjectMapperConfiguration`.
If there is a fitting class already just add `this.CreateMap<Thingy, ThingyDTO>();`, else we need to create a new file.
``` csharp
public class ThingyConfiguration : Profile
{
protected ovveride void Configure()
{
this.CreateMap<Thingy, ThingyDTO>();
}
}
```
### Generating mapping and entities
The following SQL statements generate your mappings and entities for you.
**There could be errors inside, you need to manually verify them.**
``` SQL
SELECT 'Map(m => m.' + c.name + ').Column("' + c.name + '")' +
CASE WHEN ty.name = 'text' THEN '.Length(int.MaxValue)' ELSE '' END +
CASE WHEN ty.name = 'varchar' THEN '.Length(' + CONVERT(varchar(100), c.max_length) + ')' ELSE '' END +
CASE WHEN ty.name = 'nvarchar' THEN '.Length(' + CONVERT(varchar(100),c.max_length / 2) + ')' ELSE '' END +
CASE WHEN c.is_nullable = 1 THEN '.Nullable()' ELSE '' END + ';', c.max_length
FROM sys.all_columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = 'TableName' AND ty.name <> 'sysname' AND c.Name <> 'I3D'
SELECT 'public virutal ' +
CASE WHEN ty.name IN ('text', 'nvarchar', 'varchar') THEN 'string' ELSE '' END +
CASE WHEN ty.name = 'bit' THEN 'bool' ELSE '' END +
CASE WHEN ty.name = 'datetime' THEN 'DateTime' ELSE '' END +
CASE WHEN c.is_nullable = 1 AND ty.name = 'datetime' THEN 'DateTime' ELSE '' END +
CASE WHEN ty.name = 'float' THEN 'double' ELSE '' END +
CASE WHEN ty.name = 'int' THEN 'int' ELSE '' END +
CASE WHEN ty.name = 'uniqueidentifier' THEN 'Guid' ELSE '' END +
CASE WHEN ty.name = 'char' THEN 'char' ELSE '' END +
CASE WHEN c.is_nullable = 1 AND ty.name NOT IN ('text', 'nvarchar', 'varchar', 'datetime') THEN '?' ELSE '' END +
' ' + c.name + ' { get; set; }'
FROM sys.all_columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = 'TableName' AND ty.name <> 'sysname' AND c.Name <> 'I3D'
```
**In the mapping Length(0) should be .Length(int.MaxValue)**
**For the entities ? should be DateTime?**
@@ -0,0 +1,3 @@
# MVVM in centron
I don't know, but I would like to - please tell me.
@@ -0,0 +1,224 @@
# Results and Responses in c-entron.NET
This document explains the `Result` and `Response` classes in c-entron.NET, their purpose, and how they interact across the system layers. These classes form a critical part of the error handling and communication pattern throughout the application.
## Overview
In c-entron.NET, we use a standardized approach for operation results and API responses:
1. **`Result`** - Internal class used within the business logic layer to indicate operation success/failure
2. **`Response`** - Web service API class that translates `Result` objects to client-friendly responses
This pattern provides:
- Consistent error handling across all application layers
- Type-safe return values with status information
- Clean separation between internal logic and API responses
- Standardized way to include error messages and codes
## Result Class
### Location
`src/backend/Centron.Interfaces/Results/Result.cs`
### Purpose
The `Result` class represents the outcome of an operation in the business logic layer. It includes not just the success/failure status, but also contextual information like error messages and exception details.
### Structure
```csharp
public class Result
{
public string Message { get; protected set; }
public int? MessageCode { get; protected set; }
public Exception Error { get; protected set; }
public ResultStatus Status { get; protected set; }
// Factory methods and constructors...
}
```
### Status Values
The `Result` object can have one of the following statuses:
- **Success** - The operation completed successfully
- **Error** - The operation failed due to an error
- **Warning** - The operation completed but with warnings
### Factory Methods
The `Result` class uses factory methods (instead of constructors) for creating result objects:
```csharp
// Success results
Result.AsSuccess();
Result.AsSuccess("Operation completed successfully");
// Error results
Result.AsError("The operation failed", messageCode: 100);
Result.AsError("Invalid input", error: exception);
// Warning results
Result.AsWarning("Some fields could not be processed");
// From exceptions
Result.FromException("Failed to process request", exception);
Result.FromException(exception);
```
### Generic Version
There is also a generic version `Result<T>` that carries data along with the status:
```csharp
// Success with data
Result<Customer> customerResult = Result<Customer>.AsSuccess(customer);
// Error with no data
Result<Customer> errorResult = Result<Customer>.AsError("Customer not found");
```
## Response Class
### Location
`src/webservice/Centron.WebServices.Core/Messages/Response.cs`
### Purpose
The `Response` class serves as the API response format returned by web services to clients. It translates the internal `Result` objects into standardized API responses.
### Structure
```csharp
[DataContract]
public class Response
{
[DataMember]
public StatusCode Status { get; set; }
[DataMember]
public string Message { get; set; }
[DataMember]
public int MessageCode { get; set; }
// Factory methods...
}
[DataContract]
public class Response<T> : Response
{
[DataMember]
public List<T> Result { get; set; }
// Additional factory methods...
}
```
### Status Values
The `Response` object can have one of the following statuses:
- **Success** - The operation completed successfully
- **Failed** - The operation failed due to an error
Note that unlike `Result`, `Response` maps both `ResultStatus.Success` and `ResultStatus.Warning` to `StatusCode.Success`.
## Interaction Between Result and Response
The key interaction occurs through the `FromBLResult` methods in the `Response` class:
```csharp
// Converting a Result to Response
Response response = Response.FromResult(result);
// Converting a Result<T> to Response<T>
Response<Customer> response = Response<Customer>.FromBLResult(customerResult);
```
### Mapping Logic
1. `Result.Status` is mapped to `Response.Status`:
- `ResultStatus.Success` → `StatusCode.Success`
- `ResultStatus.Warning` → `StatusCode.Success` (warnings are treated as success at API level)
- `ResultStatus.Error` → `StatusCode.Failed`
2. `Result.Message` is directly copied to `Response.Message`
3. `Result.MessageCode` is copied to `Response.MessageCode` (with a default if not provided)
4. For `Result<T>`, the data is added to the `Response<T>.Result` collection
## Usage Examples
### Business Logic Layer (BL)
```csharp
public Result<CustomerDTO> GetCustomerById(int customerId)
{
try
{
var customer = this._repository.GetCustomerById(customerId);
if (customer == null)
return Result<CustomerDTO>.AsError("Customer not found");
var dto = this.ConvertToDTO(customer);
return Result<CustomerDTO>.AsSuccess(dto);
}
catch (Exception ex)
{
return Result<CustomerDTO>.FromException(ex);
}
}
```
### Web Service Layer
```csharp
public Response<CustomerDTO> GetCustomerById(int customerId)
{
var result = this._customerBL.GetCustomerById(customerId);
return Response<CustomerDTO>.FromBLResult(result);
}
```
## Best Practices
1. **Always use factory methods** instead of constructors for creating `Result` objects
```csharp
// Good
return Result.AsSuccess("Operation succeeded");
// Avoid
return new Result(ResultStatus.Success, "Operation succeeded");
```
2. **Include meaningful error messages** to help diagnose issues
```csharp
// Good
return Result.AsError($"Customer with ID {id} not found");
// Avoid
return Result.AsError("Not found");
```
3. **Propagate exceptions appropriately** using `FromException`
```csharp
try
{
// Operation code
}
catch (Exception ex)
{
return Result.FromException("Failed to complete operation", ex);
}
```
4. **Use `ThrowIfError` extension method** when chaining operations
```csharp
var result = await someOperation().ThrowIfError();
```
5. **Convert between `Result` and `Response` only at service boundaries** to maintain clean separation of concerns
## Conclusion
The `Result` and `Response` classes provide a robust pattern for error handling and operation results throughout the c-entron.NET application. Understanding how they work together is essential for maintaining consistency in API responses and proper error handling across all application layers.
@@ -0,0 +1,10 @@
# Where can i find Stani's secret API-Documentation
<details>
<summary>Here</summary>
<summary>
P:\Entwicklung C#\c-entron 2.0 Web-Services\Dokumentationen\c-entron Web-Service Dokumentation.pdf
</summary>
</details>
@@ -0,0 +1,36 @@
#We use TraySoft AddTAPI.NET component on all our products with TAPI intigration
https://www.traysoft.com/add-tapi-telephony-library
Currently c-entron.NET, c-entron Outlook Addin and ServiceBoard have TAPI-Modules.
The dlls can be found on our network drives under `P:\Entwicklung C#\Components\AddTapi.NET Professional 18.11.2021`
**OUR Traysoft.AddTAPI.dll has been modified to work with .NET 5/6**
If you update this .dll you need to check if this needs to be modified again.
##How to update
First you need a decompiled version of TraySoft.AddTapi.dll. The easiest way to do this is to use dotPeek, decompile the dll and then export as csproj.
Then navigate to the following location:
TapiLine.cs > ProcessIncomingCall
In this method the BeginInvoke call is no longer supported in our new .NET runtimes.
We can simply replace this call with the following code that *should* result in the same behavior.
```
Task.Factory.StartNew(() =>
{
tapiCallHandler.Invoke(call);
});
```
##How to debug
There are a few ways to debug tapi related things:
1. a simple TAPI-Test app under `P:\Entwicklung C#\Components\tapi_test_app` which allows a very low level testing.
2. the product itself of course, there should be vast amounts of logging everywhere (check PhoneManager.cs & TapiPhoneConnectionManager.cs in c-entron.NET)
3. if your product uses the UI in centron.Controls there's a testtool in Centron.Controls.Preview
If you need very low level logging of the AddTapi.dll itself, you can use VS and check the output tab when debugging as the dll writes its log there.