ZayniFramework.Middle.Service

The Middle.Service module provide a self-hosted .NET middleware framework to separate the communication protocol detail with your business logic code. You can use it to build the backend microservices easier. Support TCPSocket, WebSocket and RabbitMQ protocol. You can also implement the IRemoteHost interface and register it into the Middle.Service module as an extension module for this middleware framework. Have a nice day... :) GitLab Repository: https://gitlab.com/ponylin1985/ZayniFramework


Keywords
.NET Core, .NET Framework, .NET Standard, ASP.NET Core, C#, Caching, DataAccess, Extensions, Logging, Micro ORM, Middleware, Serialization, ServiceHost, Validation
Install
Install-Package ZayniFramework.Middle.Service -Version 6.0.138

Documentation

Zayni Framework


  • Create By: Lin, Ping-chen
  • Create Date: 2016-05-17
  • Update Date: 2019-04-04

The Zayni Framework is an application framework developed in C# programming language with .NET Standard 2.0. The Zayni Framework is try to help every .NET developer to build their applications and libraries easier. The concept is make every C# code or .NET libraries reusable, flexible and combinable.

Zayni Framework provide many kinds of common used libraries, extensions and service middleware framework for any .NET application (both .NET Core or .NET Framework application). If you want to know how to use Zayni Framework, you can take a look from the unit test projects or other test projects.

The modules in the Zayni Framework:

  • Common module (Configs, interfaces, extensions, AOP interceptors, dynamic, reflections and converters... etc)
  • Caching module (Memory, Redis, MySQL Memory Table Engine, ZayniFramework.Remote.ShareData.Service.)
  • Caching.RemoteCache module (Out-process remote cache service library.)
  • Caching.RedisClient module (The Redis client components.)
  • Compression module (Provide GZip compression and extensions.)
  • Cryptography module (AES, DES, SHA256, SHA512... etc and KeyMaker SDK tool.)
  • Data Access & ORM module (Micro ORM, dynamic ORM for data access. Support the SQL Server and MySQL, SQL logging and database mirgarion CLI tool.)
  • Data Access with LightORM module (Provide CRUD ORM DataContext template & SQL Engine.)
  • Exception Handling module (Exception handler & exception policies.)
  • Formatting module (Numerical data format, DateTime format, masking... etc)
  • Logging module (Log event delegate, file log, console log, database log, email log & windows event log.)
  • Serialization module (Json.NET, Jil, XML, BinaryFormatter & ZeroFormatter.)
  • Validation module (Provide validator and many kinds of validtion attributes.)
  • Middle.Service (Self-hosted middleware framework. Support multi-hosted remote host services like: TCP Socket, WebSocket, RabbitMQ and also In-Process service mode.)
  • Middle.Service.Client module (The client-side proxy for Middle.Service module. Provide remote client such as the TCPSocket, WebSocket, RabbitMQ and In-Process Proxy to connect and access the Middle.Service remote host service.)
  • Middle.Service.Entity module (The entities and DTOs for service middleware.)
  • Middle.Service.Grpc module (The extension module for Middle.Service module. It provide the gRPC remote service host which is very fast RPC communication. You don't need to know any details about gRPC or ProtoBuf anymore. Just use the Middle.Service module to write your ServiceAction and you will have the benefit in gRPC and Zayni Framework both! :) )
  • Middle.Service.Grpc.Client module (The extension module for Middle.Service.Client module. It provide the gRPC remote client.)
  • Middle.Service.Grpc.Entity module (The extension module for Middle.Service.Entity module.)
  • Middle.Service.HttpCore module (The extension module for Middle.Service module. It provide the ASP.NET Core Kestrel self-hosted http remote service. Using this extension module allow you to have more flexible in your application. You can build the http microservice in you existed console apps or any .NET Core applications, not just only in a ASP.NET Core website application.)
  • Middle.Service.HttpCore.Client module (The extension module for Middle.Service.Client module. It provide the http remote client.)
  • Middle.TelnetService module (Self-hosted telnet service for any .NET application)
  • Remote.ShareData.Service

Here is the overview and main concept about the Zayni Framework.

You are welcome to contact with me if you have any questions or advises. Or create an new issue in this project. My email address is ponylin1985@gmail.com.


The Performance Test Report about the Middle.Service module

The test environment:

  • MacBook Pro 15', Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, 16 GB DDR3 RAM memory, 512 GB SSD.

  • The server side application was build into a docker image and run in a docker container.

  • The server side and client side application runs in the same MacBook Pro machine.

  • Both the server side and client side application is .NET Core 2.2 application.

  • Here is the test application.

  • How to run the performance by yourself:

    • You need to git clone this project.

    • You need to install the .NET Core SDK with the dotnet CLI.

    • The server-side service host application.

      • The configurations is in ./Test/WebAPI.Test/Configs folder. You will need to use this path when you're going to run the docker container.
      • Install the Docker in your machine and register an account in the Docker Hub.
      • Start the docker service in your machine.
      • Run the docker pull ponylin1985/webapi.test to install the server-side application docker image.
      • Run the docker run -d -v /YourConfigurationPathInYourMachineHere:/app/Configs -p 5590:5590 -p 5580:5580 -p 2221:2221 -p 1110:1110 -p 5000:5000 ponylin1985/webapi.test to run the server-side docker container.
      • Or you can use the following docker-compose.yml sample, save it and run docker-compose up -d to run the docker container.(Don't forget to change the config path volume mapping in the docker-compose.yml file. :) )
      version: "3"
      
      services:
          api-service-host:
              image: "webapi.test:${BASE_IMAGE_TAG:-alpine}-x64"
              volumes:
                  # Configuration Path
                  # Change the left-side path if you want to.
                  - ~/GitRepo/MyGitLab/ZayniFramework/Test/WebAPI.Test/Configs:/app/Configs
      
                  # File Log Path
                  # Change the left-side path if you want to.
                  - ~/ApplicationLog/WebAPI.Test:/app/Log
              ports:
                  - 1110:1110   # gRPC Host Service Port
                  - 5590:5590   # TCPSocket Host Port
                  - 5580:5580   # WebSocket Host Port
                  - 5000:5000   # ASP.NET Core Http Port
                  - 5100:5100   # HttpCore Host Port
                  - 2221:2221   # Remote Telnet Command Service Port
              container_name: zayni.webapi.test.${BASE_IMAGE_TAG:-alpine}
      
    • The client-side remote client application.

      • Open your terminal and cd ./Test/ServiceHostTest/ServiceHost.Client.ConsoleApp.
      • Restore the nuget packages. Run dotnet restore.
      • Build the application. Run dotnet build.
      • Run the ServiceHost.Client.ConsoleApp application. Run dotnet run.
      • Enter the init command to connect to the WebAPI.Test application's service host.
      • Test the connection status is good. Enter send magic action command.
      • Run the performance test 1000 command to execute the performance test.
      • Enter exit to exit the ServiceHost.Client.ConsoleApp application.

gRPC extension module.

The fastest protocol in Middle.Service module.

All success. ExecuteCount: 1000. Total Seconds: 0.703946

All success. ExecuteCount: 1000. Total Seconds: 0.708776

All success. ExecuteCount: 1000. Total Seconds: 0.704821

All success. ExecuteCount: 1000. Total Seconds: 0.694161

All success. ExecuteCount: 1000. Total Seconds: 0.707185

All success. ExecuteCount: 1000. Total Seconds: 0.697523

All success. ExecuteCount: 1000. Total Seconds: 0.711107

All success. ExecuteCount: 1000. Total Seconds: 0.695447

All success. ExecuteCount: 1000. Total Seconds: 0.688807

All success. ExecuteCount: 1000. Total Seconds: 0.697563

Average Seconds: 0.7089336 sec. Average: 1000 / 0.7089336 = 1410 req/sec.

====================

WebSocket.

Also a very fast protocol in Middle.Service built-in module.

All success. ExecuteCount: 1000. Total Seconds: 0.859616

All success. ExecuteCount: 1000. Total Seconds: 0.778100

All success. ExecuteCount: 1000. Total Seconds: 0.846912

All success. ExecuteCount: 1000. Total Seconds: 0.767052

All success. ExecuteCount: 1000. Total Seconds: 0.804194

All success. ExecuteCount: 1000. Total Seconds: 0.770630

All success. ExecuteCount: 1000. Total Seconds: 0.812853

All success. ExecuteCount: 1000. Total Seconds: 0.764357

All success. ExecuteCount: 1000. Total Seconds: 0.783368

Average Seconds: 0.718055 sec. Average: 1000 / 0.718055 = 1392 req/sec.

====================

TCPSocket.

All success. ExecuteCount: 1000. Total Seconds: 2.480603

All success. ExecuteCount: 1000. Total Seconds: 2.520240

All success. ExecuteCount: 1000. Total Seconds: 2.321144

All success. ExecuteCount: 1000. Total Seconds: 2.417020

All success. ExecuteCount: 1000. Total Seconds: 2.394493

All success. ExecuteCount: 1000. Total Seconds: 2.305157

All success. ExecuteCount: 1000. Total Seconds: 2.424263

All success. ExecuteCount: 1000. Total Seconds: 2.437105

All success. ExecuteCount: 1000. Total Seconds: 2.374363

All success. ExecuteCount: 1000. Total Seconds: 2.492791

Average Seconds: 2.416717 sec. Average: 1000 / 2.416717 = 413 req/sec.

====================

RabbitMQ.

All success. ExecuteCount: 1000. Total Seconds: 4.324135

All success. ExecuteCount: 1000. Total Seconds: 4.295902

All success. ExecuteCount: 1000. Total Seconds: 4.251610

All success. ExecuteCount: 1000. Total Seconds: 4.252727

All success. ExecuteCount: 1000. Total Seconds: 4.191846

All success. ExecuteCount: 1000. Total Seconds: 4.243388

All success. ExecuteCount: 1000. Total Seconds: 4.189890

All success. ExecuteCount: 1000. Total Seconds: 4.247766

All success. ExecuteCount: 1000. Total Seconds: 4.215274

All success. ExecuteCount: 1000. Total Seconds: 4.210999

Average Seconds: 4.2423537 sec. Average: 1000 / 4.2423537 = 235 req/sec.

====================

HttpCore extension module.

All success. ExecuteCount: 1000. Total Seconds: 5.845160

All success. ExecuteCount: 1000. Total Seconds: 6.624882

All success. ExecuteCount: 1000. Total Seconds: 6.604622

All success. ExecuteCount: 1000. Total Seconds: 6.608820

All success. ExecuteCount: 1000. Total Seconds: 6.149289

All success. ExecuteCount: 1000. Total Seconds: 6.683523

All success. ExecuteCount: 1000. Total Seconds: 6.153207

All success. ExecuteCount: 1000. Total Seconds: 6.525054

All success. ExecuteCount: 1000. Total Seconds: 6.606050

Avage Seconds: 5.7800607 sec. Average: 1000 / 5.7800607 = 173 req/sec.

====================

ASP.NET Core Web API

This is a performance test compare with the ASP.NET Core Web API.

All success. ExecuteCount: 1000. Total Seconds: 6.084474

All success. ExecuteCount: 1000. Total Seconds: 6.084781

All success. ExecuteCount: 1000. Total Seconds: 6.948210

All success. ExecuteCount: 1000. Total Seconds: 6.139683

All success. ExecuteCount: 1000. Total Seconds: 6.880807

All success. ExecuteCount: 1000. Total Seconds: 6.068952

All success. ExecuteCount: 1000. Total Seconds: 6.103602

All success. ExecuteCount: 1000. Total Seconds: 6.103118

All success. ExecuteCount: 1000. Total Seconds: 6.082563

All success. ExecuteCount: 1000. Total Seconds: 6.884348

Avage Seconds: 6.3380538 sec. Average: 1000 / 6.3380538 = 157 req/sec.

====================

InProcess Client. (The Service-side and RemoteClient is in the same runtime process.)

This is always the fastest!

If you are using the v2.2.7 version (or higher version) of Middle.Service and Middle.Service.Client module's nuget packages. You will have the best performance when you using InProcessClient.

And the beauty of using this InProcessClient is: You don't need to change any codes when the in process ServiceHost is seperate out of from your application. You only need to change the config in your serviceClientConfig.json. :)

All success. ExecuteCount: 1000. Total Seconds: 0.0923607

All success. ExecuteCount: 1000. Total Seconds: 0.1034135

All success. ExecuteCount: 1000. Total Seconds: 0.1085959

All success. ExecuteCount: 1000. Total Seconds: 0.1058037

All success. ExecuteCount: 1000. Total Seconds: 0.1064739

All success. ExecuteCount: 1000. Total Seconds: 0.1076791

All success. ExecuteCount: 1000. Total Seconds: 0.0984187

All success. ExecuteCount: 1000. Total Seconds: 0.0943638

All success. ExecuteCount: 1000. Total Seconds: 0.0962036

All success. ExecuteCount: 1000. Total Seconds: 0.0968316

Avage Seconds: 0.10101145 sec. Average: 9899.867 req/sec.


Docker Repository

There are some docker images has been release on the public DockerHub.

Remote.ShareData.Service (Cache Service Image)

  • Run docker pull ponylin1985/zayni.sharedata.service:alpine-x64 command to download the docker image.
  • More detail you can go to the DockerHub Repository.

WebAPI.Test (Middle.Service module test image)

  • Run docker pull ponylin1985/webapi.test:alpine-x64 command to download the docker image.
  • More detail you can go to the DockerHub Repository.

ServiceHost.ServerSide.ConsoleApp (Middle.Service module test image)

This is another test application image for Middle.Service module.

  • Run docker pull ponylin1985/zayni.server.app:alpine-x64 command to download the docker image.
  • More detail you can go to the DockerHub Repository.

ServiceHost.Client.ConsoleApp (Middle.Service.Client module test image)

  • Run docker pull ponylin1985/zayni.client.app:alpine-x64 command to download the docker image.
  • More detail you can go to the DockerHub Repository.

How to install the Zayni Framework.

You need to install .NET Core SDK or nuget first. Then you can use .NET Core CLI command to install each module's nuget package independently.

dotnet add package ZayniFramework.Common
dotnet add package ZayniFramework.Logging
dotnet add package ZayniFramework.DataAccess
dotnet add package ZayniFramework.DataAccess.LightORM
dotnet add package ZayniFramework.Caching
dotnet add package ZayniFramework.Caching.RedisClient
dotnet add package ZayniFramework.Caching.RemoteCache
dotnet add package ZayniFramework.ExceptionHandling
dotnet add package ZayniFramework.Compression
dotnet add package ZayniFramework.Cryptography
dotnet add package ZayniFramework.Formatting
dotnet add package ZayniFramework.Serialization
dotnet add package ZayniFramework.Validation
dotnet add package ZayniFramework.Middle.Service
dotnet add package ZayniFramework.Middle.Service.Client
dotnet add package ZayniFramework.Middle.Service.Entity
dotnet add package ZayniFramework.Middle.Service.Grpc
dotnet add package ZayniFramework.Middle.Service.Grpc.Client
dotnet add package ZayniFramework.Middle.Service.Grpc.Entity
dotnet add package ZayniFramework.Middle.Service.HttpCore
dotnet add package ZayniFramework.Middle.Service.HttpCore.Client
dotnet add package ZayniFramework.Middle.TelnetService

How to use Zayni Framework and Sample Code.

Add namespace using in your code.

using ZayniFramework.Common;

Configuration API example.

Read the app.config sample.

If you app.config's location is the same with your entry assembly. You can also use ConfigurationManager to read the configurations. BUT if your app.config's location is placed in different path with your entry assembly in runtime. You need to set the app.config file path to ConfigManagement.ConfigFullPath property before you using any Zayni Framework's module or libraries. So, the Zayni Framework can load all the configurations correctlly.

// This is the case that you place your app.config in different path with your entry assembly. You need to set the ConfigManagement.ConfigFullPath property before you using any Zayni Framework's module.
// If the app.config's path is the same with your entry assembly. You don't need to set the ConfigManagement.ConfigFullPath property.
// You app.config full path here...
var path = $"{Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location )}/Configs/WebAPI.Test.dll.config";
ConfigManagement.ConfigFullPath = path;

In your app.config.

<connectionStrings>
    <add name="AAA" connectionString="SERVER=XXX.XXX.XXX.XXX;Port=3306;DATABASE=zayni;UID=XXX;PASSWORD=XXX;Allow User Variables=True;Charset=utf8;Convert Zero Datetime=True;"       providerName="MySql.Data.MySqlClient"/>
    <add name="BBB" connectionString="Server=XXX.XXX.XXX.XXX,1433;Database=ZayniFramework;User Id=XXX;Password=XXX;Max Pool Size=20;Min Pool Size=5;Connection Lifetime=30;" providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
    <add key="ABC" value="Hello, Zayni Framework" />
    <add key="DEF" value="Zayni Framework with Docker" />
    <add key="WWW" value="true" />
</appSettings>

Then you can read the configurations like this.

// Read the connection string.
string connectionStringA = ConfigManagement.ConnectionStrings[ "AAA" ].ConnectionString;
string connectionStringB = ConfigManagement.ConnectionStrings[ "BBB" ].ConnectionString;

// Read the appSettings
string a = ConfigManagement.AppSettings[ "ABC" ];
string b = ConfigManagement.AppSettings[ "DEF" ];

// or your can read appSettings like this.
Result r = await AppSettingsLoader.Instance.LoadBooleanConfigSettingAsync( "WWW" );
Assert.IsTrue( r.Success );
Assert.IsTrue( r.Data.Value );  // r.Data.Value is a Boolean type. The value will be true

Read the json config sample.

If you have a JSON file as a configuarion file like xxx.json. You can use ConfigManager.GetConfig( string path ) method to read the json config file.

// The path of your json config file.
string configPath = "./someDir/someConfig.json";

// Load the json configs with dynamic type.
dynamic configs = ConfigManager.GetConfig( configPath );

// Load the json configs with strong C# type. You can define the strong type class if you want.
var cfg = ConfigManager.GetConfig<TheStrongConfigType>( configPath );

The Result type sample.

In the Zayni Framework's common module. It provide the common used IResult interface, BaseResult and Result class for you. You can use those type directlly in your application and also you can extends those types if you want.

Return a IResult type of Result type in your method like this.

// Return a IResult interface type without generic type.
public IResult YourMthod( string someString ) 
{
    IResult result = Result.Create();

    try 
    {
        // do something here...
    }
    catch ( Exception ex ) 
    {
        result.HasException    = true;
        result.ExceptionObject = ex;
        result.Message         = $"Some occur exception: {ex.ToString()}";
        result.Code            = ERROR_CODE.InternalError;
        return result;
    }

    result.Code    = STATUS_CODE.ActionSuccess;
    result.Message = "Success.";
    result.Success = true;
    return result;
}

// Return a IResult<TData> interface type with generic type.
public IResult<List<UserProfile>> GetUserProfiles( string userId ) 
{
    IResult<List<UserProfile>> result = Result.Create<List<UserProfile>>();
    List<UserProfile> models;

    try 
    {
        // do something here...
        var dao = new UserDao();
        IResult<List<UserProfile>> r = dao.GetUserProfile( userId );

        if ( r.Data.IsNullOrEmptyList() ) 
        {
            result.Code    = STATUS_CODE.NoDataFound;
            result.Message = "No user profile data found.";
            result.Success = true;
            return result;        
        }

        models = r.Data;
    }
    catch ( Exception ex ) 
    {
        result.HasException    = true;
        result.ExceptionObject = ex;
        result.Message         = $"Get user profile occur exception: {ex.ToString()}";
        result.Code            = ERROR_CODE.DataAccessError;
        return result;
    }

    result.Data    = models;
    result.Code    = STATUS_CODE.ActionSuccess;
    result.Message = "Userprofile data found.";
    result.Success = true;
    return result;
}

// Return a IResult in a Dynamic Type.
public IResult<UserModel> GetSomeData( string userId ) 
{
    // declare the result in dynamic type.
    dynamic result = Result.CreateDynamicResult<UserProfile>();

    // You can define any kine of Properties here if you want and no need to pre-define those properties in your class.
    result.UserProfile         = null;
    result.UserEmails          = null;
    result.AnythingYouWantHere = 123123;

    // You can still return the strong type in IResult.Data property.
    UserModel userModel = null;

    try 
    {
        // do something here...
        userModel = GetFromSomewhere();
    }
    catch ( Exception ex ) 
    {
        result.ErrorAgent      = "HaHaHa, we got a error here...";  // Only assign the ErrorAgent when occur exception!!!
        result.HasException    = true;
        result.ExceptionObject = ex;
        result.Message         = $"Get user profile occur exception: {ex.ToString()}";
        result.Code            = ERROR_CODE.DataAccessError;
        return result;
    }

    // You can set the dynamic result object here...
    result.Data                = userModel;                                                                 // Strong type return.
    result.UserProfile         = userModel.Profile;                                                         // Dynamic type return.
    result.UserEmails          = new List<Email>( userModel.Emails.ToArray() );                             // Dynamic type return.
    result.AnythingYouWantHere = new { AnythingIWantToPutInHere = "Oh yeah...", SomeMagicNumber = 777 };    // Dynamic type return.

    result.Code    = STATUS_CODE.ActionSuccess;
    result.Message = "Userprofile data found.";
    result.Success = true;
    return result;
}

// =============

IResult r1 = YourMethod( "AAA" );

if ( !r1.Success || r1.HasException ) 
{
    // do something with the error.
    r1.ExceptionObject.IsNotNull( e => { ... } );
}

// =============

IResult<List<UserProfile>> r2 = GetUserProfiles( "BelleClaire" );

if ( !r2.Success || r2.HasException ) 
{
    // do something with the error.
    r2.ExceptionObject.IsNotNull( e => { ... } );
}

if ( r2.Data.IsNullOrEmptyList() && r2.Code == STATUS_CODE.NoDataFound ) 
{
    // do something with no data found result here...
    // Insert a new UserProfile here maybe? :)
}

// You can retrive the strong type data from here.
// You can do anything with the raw data here... :)
List<UserProfile> models = r.Data;

// =============

dynamic r3 = GetSomeData( "BelleClaire" );

if ( !r3.Success && r3.HasException ) 
{
    // You can got the 'ErrorAgent' when occur exception here. :)
    dynamic errorAgent = r3.ErrorAgent;
}

// Retrive the strong type data here.
UserModel userModel = r3.Data;

// Retrive the dynamic type here.
UserProfile userProfile = r3.UserProfile;
List<Email> userEmails  = r3.UserEmails;

// Retrive the dynamic type here. :)
string msg = r3.AnythingYouWantHere.AnythingIWantToPutInHere;
int    num = r3.AnythingYouWantHere.SomeMagicNumber;

// do anything you want to do here...

Extensions API example.

DateTimeExtension

Test a DateTime range. Use the DateTime.IsBetween() extension method.

var dt    = new DateTime( 2013, 12, 24 );
var begin = new DateTime( 2013, 12, 1 );
var end   = new DateTime( 2013, 12, 31 );
Assert.IsTrue( dt.IsBetween( begin, end ) );
Assert.IsFalse( dt.IsBetween( new DateTime( 2013, 12, 28 ), end ) );

Convert DateTime to a UNIX UTC timestamp. Use the DateTime.ToUnixUtcTimestamp() extension method.

long timestamp = DateTime.Now.ToUnixUtcTimestamp( withMilliseconds: true );

ObjectExtension

Convert an object to a Dictionary. Use the Object.ConvertToDictionary() extension method.

public void ConvertToDictionary_Example()
{
    var model = new UserModel
    {
        Name = "Amber",
        Age  = 23,
        Sex  = "female"
    };

    IDictionary dict = model.ConvertToDictionary();

    string name = dict[ "Name" ] + "";
    string sex  = dict[ "Sex" ]  + "";
    int    age  = int.Parse( dict[ "Age" ] + "" );

    Assert.AreEqual( model.Name, name );
    Assert.AreEqual( model.Age,  age );
    Assert.AreEqual( model.Sex,  sex );

    // =====================================================

    var source = new UserModel
    {
        Name = "Sylvia",
        Age  = 26,
        Sex  = "F"
    };

    Dictionary<string, string> d = source.ConvertToStringDictionary();

    string name2 = d[ "Name" ];
    string sex2  = d[ "Sex" ];
    string age2  = d[ "Age" ];

    Assert.AreEqual( source.Name, name2 );
    Assert.AreEqual( source.Age + "",  age2 );
    Assert.AreEqual( source.Sex,  sex2 );

    // =====================================================

    var now = DateTime.Now;

    var obj = new
    {
        Message = "Hello World",
        DoB     = now,
        Money   = 324.52M,
        Cash    = 76.123D,
        Age     = 33,
        User    = model
    };

    dict = obj.ConvertToDictionary();

    Assert.AreEqual( obj.Message, dict[ "Message" ] + "" );
    Assert.AreEqual( obj.Money,   decimal.Parse( dict[ "Money" ] + "" ) );
    Assert.AreEqual( obj.Cash,    double.Parse( dict[ "Cash" ] + "" ) );
    Assert.AreEqual( obj.Age,     int.Parse( dict[ "Age" ] + "" ) );

    DateTime dob = (DateTime)dict[ "DoB" ];
    Assert.AreEqual( obj.DoB.ToString( "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture ), dob.ToString( "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture ) );
    Assert.AreEqual( obj.DoB, dob );

    var userModel = dict[ "User" ] as UserModel;
    Assert.AreEqual( model.Name, userModel.Name );
    Assert.AreEqual( model.Age,  userModel.Age );
    Assert.AreEqual( model.Sex,  userModel.Sex );
}

Convert List to DataTable. Use List.ConvertToDataTable() extension method.

var list = new List<UserModel>() 
{
    new UserModel() 
    {
        Name = "Sylvia",
        Age  = 35,
        Sex  = "Female"
    },
    new UserModel() 
    {
        Name = "Kate",
        Age  = 20,
        Sex  = "Female"
    },
    new UserModel() 
    {
        Name = "John",
        Age  = 23,
        Sex  = "Male"
    }
};

DataTable dt = list.ConvertToDataTable( "SomeTableName" );
Assert.IsNotNull( dt );          

Distinct by conditions. Use IEnumerable.Distinct( Func<TSource, TKey> selector ) extension method.

List<UserModel> models = MakeFakeUsers();
IEnumerable<UserModel> results = models.Distinct( k => new { Name = k.Name, Age = k.Age } );

Deep Clone Object. Use Object.CloneObject() or Object.Clone() extension method.

UserModel clone = obj.CloneObject<UserModel>();
UserModel copy  = obj.Clone<UserModel>();

Invoke the object's method. Use the Object.MethodInvoke() extension method.

public class MemberModel
{
    public string Name { get; set; }

    public string SayHello( string message ) => $"Hello, my name is {Name}. {message}";
}

public void MethodInvoke_Example()
{
    var model  = new MemberModel() { Name = "Sylvia" };
    var result = model.MethodInvoke( "SayHello", "This is a test." );
    Assert.AreEqual( "Hello, my name is Sylvia. This is a test.", result );
}

Get the property value. Use the Object.GetPropertyValue() extension method.

public void GetPropertyValueTest()
{
    var model = new MemberModel()
    {
        Name = "Kate"
    };

    object obj = model.GetPropertyValue( "Name" );
    Assert.IsNotNull( obj );

    string name = obj + "";
    Assert.AreEqual( model.Name, name );

    name = model.GetPropertyValue<string>( "Name" );
    Assert.AreEqual( model.Name, name );
}

StringExtension

Convert a string to byte array. Use the String.ToBytes() extension method. Also demo how to use IEnumerable.IsNotNullAndEmptyArray() entension method.

public void ToBytes_Example()
{
    string test   = "This is a test string.";
    byte[] binary = test.ToBytes();
    Assert.IsTrue( binary.IsNotNullAndEmptyArray() );

    string result = System.Text.Encoding.UTF8.GetString( binary );
    Assert.AreEqual( test, result );
}

Remove the first appeared from a string. Use String.RemoveFirstAppeared() extension method.

string source = "AAABBBCCCAAA";
string result = source.RemoveFirstAppeared( "AAA" );
Assert.AreEqual( "BBBCCCAAA", result );

Remove the last appeared from a string. Use String.RemoveLastAppeared() extension method.

string source = "AAABBBCCCAAACCC";
string result = source.RemoveLastAppeared( "CCC" );
Assert.AreEqual( "AAABBBCCCAAA", result );

Test a string is null or an empty string. Demo the String.IsNullOrEmpty(), String.IsNotNullOrEmpty(), String.FormatTo() extension method.

public void StringIsNullOrEmpty_Example()
{
    string test1 = "";
    Assert.IsTrue( test1.IsNullOrEmpty() );
    Assert.IsFalse( test1.IsNotNullOrEmpty() );

    string test2 = null;
    Assert.IsTrue( test2.IsNullOrEmpty() );
    Assert.IsFalse( test2.IsNotNullOrEmpty() );

    string test3 = "kkk";
    Assert.IsFalse( test3.IsNullOrEmpty() );
    Assert.IsTrue( test3.IsNotNullOrEmpty() );

    string expected1 = "Hello my name is Sylvia. I love Pony.";
    string expected2 = "I'm Pony. Sylvia is my lover.";

    string test4  = "Hello my name is {0}. I love {1}.";
    string result = test4.FormatTo( "Sylvia", "Pony" );
    Assert.AreEqual( expected1, result );

    string result2 = "I'm {0}. {1} is my lover.".FormatTo( "Pony", "Sylvia" );
    Assert.AreEqual( expected2, result2 );
}

Dynamic API example.

Add using.

using ZayniFramework.Common;
using ZayniFramework.Common.Dynamic;

Bind a property to dynamic object in runtime.

Use DynamicHelper.CreateDynamicObject() and DynamicHelper.BindProperty() static method.

public void BindPropertyTest()
{
    dynamic obj = DynamicHelper.CreateDynamicObject();
    Assert.IsNotNull( obj );

    bool isOk = DynamicHelper.BindProperty( obj, "UserName", "Sylvia" ); 
    Assert.IsTrue( isOk );

    string name = obj.UserName;
    Assert.AreEqual( "Sylvia", name );

    bool isOk2 = DynamicHelper.BindProperty( obj, "UserName", "Vivian" );
    Assert.IsTrue( isOk2 );

    string name2 = obj.UserName;
    Assert.AreEqual( "Vivian", name2 );
}

Bind a delegate to dynamic object in runtime.

Use DynamicHelper.BindMethod() static method. Also demo Object.IsNotNull() extension method.

public void BindMethod_Example() 
{
    dynamic obj = DynamicHelper.CreateDynamicObject();
    Assert.IsNotNull( obj );

    bool isOk = DynamicHelper.BindProperty( obj, "UserName", "Sylvia" );
    Assert.IsTrue( isOk );

    string name = obj.UserName;
    Assert.AreEqual( "Sylvia", name );

    SaySomething handler = ( m ) => {
        return string.Format( "Hi, my name is {0}. {1}", obj.UserName, m );
    };

    bool isOk2 = DynamicHelper.BindMethod<SaySomething>( obj, "IntroduceYourself", handler );
    Assert.IsTrue( isOk2 );

    string message = obj.IntroduceYourself( "Nice to meet you." );
    Assert.IsTrue( message.IsNotNullOrEmpty() );
    Assert.AreEqual( "Hi, my name is Sylvia. Nice to meet you.", message );
}

TaskQueue example.

Add using.

using ZayniFramework.Common;
using ZayniFramework.Common.Tasks;

EnQueue an action to TaskQueue.

private static void ExecuteDirectly()
{
    var queue = new TaskQueue( false );

    for ( int i = 0; i < 50; i++ )
    {
        int j = i + 1;

        dynamic queueAction = new QueueAction( new Work().DoSomething );
        queueAction.Index   = j;
        queueAction.Message = new { Test = "123", NumberNo = 55 };
        queue.EnQueue( queueAction );
    }
}

More TaskQueue detail sample here.

DataAccess & ORM example.

Just like using Dapper and plain old ADO.NET style. :)

  • Support Microsoft SQL Server, MySQL and Oracle database with same Dao class.
  • Support SQL log (input SQL statement & output results).
  • Support micro ORM and dynamic ORM.
  • Support DataContext CRUD template.
  • Support database schema migration CLI. (The zdm stands for Zayni framework Database Migration. :) )
  • NO MORE Entity Framework!!! (We don't like Entity Framework......... )

Add config in your app.config or web.config file.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="ZayniFramework" type="ZayniFramework.Common.ZayniConfigSection, ZayniFramework.Common"/>
    </configSections>
    <ZayniFramework>
        <LoggingSettings eventLogEnable="false" logEnable="true">
            <DefaultTextLogger>
                <add name="_DefaultLog" enable="true" maxSize="8">
                    <!-- ZayniFramework's default internal logger. -->
                    <LogFile path="D:\Logs\Zayni\Tracing.log"/>
                    <Filter filterType="deny" category="Information,Warning"/>
                </add>
            </DefaultTextLogger>
        </LoggingSettings>
        <DataAccessSettings>
            <DatabaseProviders>
                <add name="Zayni" dataBaseProvider="MSSQL" sqlLogEnable="true"/>
                <add name="ZayniUnitTest" dataBaseProvider="MSSQL" sqlLogEnable="true"/>
            </DatabaseProviders>
        </DataAccessSettings>
    </ZayniFramework>
    <connectionStrings>
        <!--MSSQL-->
        <add name="Zayni" connectionString="Server=XXX;Database=XXX;User Id=XXX;Password=XXX;Max Pool Size=20;Min Pool Size=5;Connection Lifetime=30;"
             providerName="System.Data.SqlClient" />
        <add name="ZayniUnitTest" connectionString="Server=XXX;Database=XXX;User Id=XXX;Password=XXX;Max Pool Size=20;Min Pool Size=5;Connection Lifetime=30;"
             providerName="System.Data.SqlClient" />

        <!--MySQL-->
        <!--<add name="Zayni" connectionString="SERVER=XXX;DATABASE=XXX;UID=XXX;PASSWORD=XXX;Allow User Variables=True;Charset=utf8;Convert Zero Datetime=True;"
             providerName="MySql.Data.MySqlClient"/>
        <add name="ZayniUnitTest" connectionString="SERVER=XXX;DATABASE=XXX;UID=XXX;PASSWORD=XXX;Allow User Variables=True;Charset=utf8;Convert Zero Datetime=True;"
             providerName="MySql.Data.MySqlClient"/>-->
    </connectionStrings>
</configuration>

Add using.

using ZayniFramework.Common;
using ZayniFramework.DataAccess;

Write your Dao (Data Access Object) class and extends the BaseDataAccess class.

/// <summary>Your Data Access Object (Dao) class
/// </summary>
internal class UserDao : BaseDataAccess

Select data from database using BaseDataAccess.LoadDataToModel() method.

public Result<List<UserModel>> Select_LoadDataToModel_Example( UserModel query )
{
    var result = new Result<List<UserModel>>()
    {
        IsSuccess = false,
        Data      = null
    };

    string sql = @"SELECT AccountId
                        , Name
                        , Age
                        , Sex
                        , Birthday
                        , IsVIP
                        , IsGood
                        , DataFlag
                    FROM fs_unit_test_user
                        WHERE Sex = @Sex ";

    DbCommand cmd = base.GetSqlStringCommand( sql );
    base.AddInParameter( cmd, "@Sex", DbTypeCode.Int32, query.Sex );

    if ( !base.LoadDataToModel( cmd, out List<UserModel> models ) )
    {
        result.Message = base.Message;
        return result;
    }

    result.Data      = models;
    result.IsSuccess = true;
    return result;
}

Multi-select SQL statements from database by using BaseDataAccess.LoadDataToModels() method.

public Result Select_LoadDataToModels( UserModel query )
{
    dynamic result = new Result()
    {
        IsSuccess = false,
        Message   = null
    };

    string sql = @" -- QueryNo1
                    SELECT AccountId AS Account
                        , Name
                        , Age
                        , Sex
                        , Birthday AS DoB
                        , IsVIP
                    FROM fs_unit_test_user
                        WHERE Sex = @Sex;

                    -- QueryNo2
                    SELECT AccountId AS Account
                        , Name
                        , Age
                        , Sex
                        , Birthday AS DoB
                    FROM fs_unit_test_user
                        WHERE Sex = @Sex; ";

    DbCommand cmd = base.GetSqlStringCommand( sql );
    base.AddInParameter( cmd, "@Sex", DbTypeCode.Int32, query.Sex );
    
    Type[] types = { typeof ( UserModel ), typeof ( UserModel ) };

    if ( !base.LoadDataToModels( cmd, types, out List<object[]> datas ) )
    {
        return result;
    }

    List<UserModel> queryResult1 = datas.FirstOrDefault().ToList<UserModel>();
    List<UserModel> queryResult2 = datas.LastOrDefault().ToList<UserModel>();

    result.Rst1      = queryResult1;
    result.Rst2      = queryResult2;
    result.IsSuccess = true;
    return result;
}

Dynamic ORM query using BaseDataAccess.LoadDataToDynamicModel() method. You don't need to define a entity class anymore.

public Result<List<dynamic>> Select_LoadDataToDynamicModel_Example( UserModel query )
{
    var result = new Result<List<dynamic>>()
    {
        IsSuccess = false,
        Data      = null
    };

    string sql = @"SELECT AccountId AS Account
                        , Name
                        , Age
                        , Sex
                        , Birthday AS DoB
                        , IsVIP
                    FROM fs_unit_test_user
                        WHERE Sex = @Sex ";

    DbCommand cmd = base.GetSqlStringCommand( sql );
    base.AddInParameter( cmd, "@Sex", DbTypeCode.Int32, query.Sex );

    List<dynamic> models;

    if ( !base.LoadDataToDynamicModel( cmd, out models ) )
    {
        result.Message = base.Message;
        return result;
    }

    result.Data      = models;
    result.IsSuccess = true;
    return result;
}

For more BaseDataAccess examples see here.

Using DataContext template to execute CRUD methods.

Add using.

using ZayniFramework.Common;
using ZayniFramework.DataAccess.Lightweight;

Write your own DataContext class and extends the DataContext class.

/// <summary>Your DataContext class.
/// </summary>
internal class UserDataContext : DataContext<UserReqArgs, UserModel>

Then you can do some CRUD action like...

DataContext.Insert(); DataContext.Update(); DataContext.Delete(); DataContext.Select();

var model = new UserModel()
{
    Account = "Amber",
    Name    = "Amber Jane",
    Age     = 22,
    DOB     = new DateTime( 1993, 2, 17 ),
    Sex     = 0,
    IsVip   = true
};

var reqArgs = new UserReqArgs()
{
    Account = "Amber"
};

var wheres = new SqlParameter[] 
{ 
    new SqlParameter() { ColumnName = "AccountId", Name = "Account",  DbType = DbTypeCode.String, Value = "Amber001" },
    new SqlParameter() { ColumnName = "Name",      Name = "UserName", DbType = DbTypeCode.String, Value = "Amber" }
};

var r = dataContext.Select( new SelectInfo<UserReqArgs>() { DataContent = reqArgs } );
var c = dataContext.Insert( model );
var u = dataContext.Update( model, wheres: wheres, ignoreColumns: new string[] { "Birthday", "IsVIP" } );
var d = dataContext.Delete( model );

More DataContext examples. You can go to here and here.

Middleware Middle.Service module example.

The Middle.ServiceHost is a self-host application middleware service module. Any kind of .NET application can add package reference of the ZayniFramework.Middle.Service nupkg, then your application will be able to serve any clinet-side application by using this middleware serivce.

First, add a serviceHostConfig.json config file in your app project. You can use TCPSocket, WebSocket or RabbitMQ protocol. Each of them support RPC (Request & Response), Message Publish and Service Event Listening.

If your application use the Middle.Service.Grpc module you can also config the gRPC protocol too. If your application use the Middle.Service.HttpCore module you can also config the HttpCore protocol too.

Something like this...

{
    "enableConsoleTracingLog": true,
    "serviceHosts": [
        {
            "serviceName": "PaymentService",
            "remoteHostType": "TCPSocket",
            "hostName": "GCP-Production",
            "enableActionLogToDB": true,
            "enableActionLogToFile": true,
            "autoStart": true                   // Default Value is true in internal framework
        },
        {
            "serviceName": "OrderService",
            "remoteHostType": "RabbitMQ",
            "hostName": "GCP-Production",
            "enableActionLogToDB": true,
            "enableActionLogToFile": true,
            "autoStart": false                  // Disable auto started
        },
        {
            "serviceName": "WealthManagementService",
            "remoteHostType": "gRPC",
            "hostName": "Azure-Production",
            "enableActionLogToDB": true,
            "enableActionLogToFile": true,
            "autoStart": false
        }
    ],
    "tcpSocketHostSettings": {
        "defaultHost": "GCP-Production",
        "hosts": [
            {
                "hostName": "GCP-Production",
                "tcpPort": 5590,
                "tcpBufferSizeKB": 10
            },
            {
                "hostName": "Azure-Production",
                "tcpPort": 5590,
                "tcpBufferSizeKB": 10
            }
        ]
    },
    "webSocketHostSettings": {
        "defaultHost": "GCP-Production",
        "hosts": [
            {
                "hostName": "GCP-Production",
                "hostBaseUrl": "ws://0.0.0.0:5580",
                "whitelist": []
            },
            {
                "hostName": "Azure-Production",
                "hostBaseUrl": "ws://0.0.0.0:5580",
                "whitelist": []
            }
        ]
    },
    "rabbitMQSettings": {
        "defaultHost": "Azure-Production",
        "hosts": [
            {
                "hostName": "GCP-Production",
                "mbHost": "XXX",
                "mbPort": 5672,
                "mbUserId": "XXX",
                "mbPassword": "XXX",
                "mbVHost": "SomeHost",
                "msgRoutingKey": "Event.Client.",
                "msgQueue": "Middle.Server.ListenClient.Msg",
                "rpcQueue": "Middle.Server.RPC",
                "rpcRoutingKey": "RPC.Server.",
                "publishDefaultExchange": "Middle.Test.Topic",
                "publishExchange": "Middle.Test.Topic",
                "publishRoutingKey": "Event.Server."
            },
            {
                "hostName": "Azure-Production",
                "mbHost": "XXX",
                "mbPort": 5672,
                "mbUserId": "XXX",
                "mbPassword": "XXX",
                "mbVHost": "SomeHost",
                "msgRoutingKey": "Event.Client.",
                "msgQueue": "Middle.Server.ListenClient.Msg",
                "rpcQueue": "Middle.Server.RPC",
                "rpcRoutingKey": "RPC.Server.",
                "publishDefaultExchange": "Middle.Test.Topic",
                "publishExchange": "Middle.Test.Topic",
                "publishRoutingKey": "Event.Server."
            }
        ]
    },
    "gRPCHostSettings": {
        "defaultHost": "GCP-Production",
        "hosts": [
            {
                "hostName": "GCP-Production",
                "gRPCHostServer": "0.0.0.0",
                "port": 1111
            },
            {
                "hostName": "Azure-Production",
                "gRPCHostServer": "0.0.0.0",
                "port": 1155
            }
        ]
    },
    "httpCoreSettings": {
        "hosts": [
            {
                "hostName": "GCP-Production",
                "hostBaseUrl": "http://0.0.0.0:5110"
            },
            {
                "hostName": "Azure-Production",
                "hostBaseUrl": "http://0.0.0.0:5118"
            }
        ]
    }
}

Then, write your DTO (Data Transfer Object) class for communication with outside applications. You can also use ZayniFramework.Validation's validation attribute on your DTO's properties.

using Newtonsoft.Json;
using System;
using ZayniFramework.Validation;


namespace ServiceHost.Test.Entity
{
    /// <summary>Some DTO class
    /// </summary>
    [Serializable()]
    public class SomethingDTO
    {
        /// <summary>Some message
        /// </summary>
        [NotNullOrEmpty( Message = "'someMsg' is invalid." )]
        [JsonProperty( PropertyName = "someMsg" )]
        public string SomeMessage { get; set; }

        /// <summary>Some DateTime
        /// </summary>
        [JsonProperty( PropertyName = "someDate" )]
        public DateTime SomeDate { get; set; }

        /// <summary>Some double number
        /// </summary>
        [JsonProperty( PropertyName = "magicNumber" )]
        public double SomeMagicNumber { get; set; }

        /// <summary>Some Int32 number
        /// </summary>
        [JsonProperty( PropertyName = "luckyNumber" )]
        public int LuckyNumber { get; set; }

        /// <summary>Some boolean value
        /// </summary>
        [JsonProperty( PropertyName = "isSuperXMan" )]
        public bool IsSuperMan { get; set; }
    }
}

Then, you can write your ServiceAction in your own class library project. Add namespace using.

using ZayniFramework.Common;
using ZayniFramework.Logging;
using ZayniFramework.Middle.Service;
using ZayniFramework.Serialization;

Write your ServieAction class and extends ServiceAction abstract class.

/// <summary>Some Servie Action
/// </summary>
public class MagicTestAction : ServiceAction<SomethingDTO, Result<MagicDTO>>

Write your constructor and pass the action name to the base class.

/// <summary>Constructor.
/// </summary>
public MagicTestAction() : base( "MagicTestAction" )
{
    // pass
}

Override the Execute method. You can implement your business logic here. See, your application don't know what kind of protocol or communication framework is using now. The only you need to focus is your business logic!

/// <summary>Execute Action. Your business logic here...
/// </summary>
public override void Execute()
{
    Response = Result.Create<MagicDTO()>();

    Logger.WriteInformationLog( this, $"Receive SomethingDTO is {Environment.NewLine}{JsonConvertUtil.SerializeInCamelCase( Request )}", nameof ( Execute ) );

    SomethingDTO reqDTO = Request;

    var resDTO = new MagicDTO()
    {
        MagicWords = $"You should not wake me up... {Guid.NewGuid().ToString()}",
        MagicTime  = DateTime.Now
    };

    if ( 7.77 == reqDTO.SomeMagicNumber )
    {
        resDTO.TestDouble = 999.999;
    }

    Response.Data    = resDTO;
    Response.Success = true;
}

If you have your own customized implementaion of IRemoteHost as an extension protocol module. You can register you own implemented RemoteHost into the Middle.Service's internal container. Like this,

// Register a 'gRPCRemoteHost' IRemoteHost type into Middle.Service. The name is 'gRPC'.
// You have to register your own IRemoteHost type before MiddlewareService.StartService().
MiddlewareService.RegisterRemoteHostType<gRPCRemoteHost>( "gRPC" );

Then, you need to inject your ServiceAction to the ServiceActionContainer. There are two methods to register you ServiceAction into the internal ServiceActionContainer.

You can register your ServiceAction with the explicit 'serviceName' in serviceHostConfig.json file. Like this:

// Register your servcie action by 'Add' method. You need to add with the 'ServiceName' in json config file explicitly.
MiddlewareService.InitializeServiceActionHandler = () => 
{
    var magicTestAction = new MagicTestAction();

    // Register the MagicTestAction to PaymentService and OrderService service host manually and explicitly.
    ServiceActionContainerManager.Add( "PaymentService", magicTestAction.Name, magicTestAction.GetType() );
    ServiceActionContainerManager.Add( "OrderService", magicTestAction.Name, magicTestAction.GetType() );
    return Result.Create( true );
};

You can also register your ServiceAction without the serviceName, the Zayni Framework will register the ServiceActions for all the service host which the 'autoStart' setting value is true in serviceHostConfig.json file. Like this:

// Register your service action by 'RegisterServiceAction' method. You don't need to pass the 'ServiceName' argument anymore.
MiddlewareService.InitializeServiceActionHandler = () => 
{
    var magicTestAction = new MagicTestAction();

    // Register the MagicTestAction to all autoStarted service host in serviceHostConfig.json file.
    // Arguments are: ActionName, Type of ServiceAction and the serviceHostConfig.json's file path.
    ServiceActionContainerManager.RegisterServiceAction( magicTestAction.Name, magicTestAction.GetType(), configPath: "Your serviceHostConfig.json  path." );

    var getUserModelsAction = new GetUserModelsAction();
    ServiceActionContainerManager.RegisterServiceAction( getUserModelsAction.Name, getUserModelsAction.GetType(), configPath: "Your serviceHostConfig.json  path." );

    return Result.Create( true );
};

If you are using Middle.Service module v2.2.5 higher version. You event don't need to register your ServiceAction by yourself anymore. The Zayni Framework will register all your ServiceAction automatically.

BUT all your ServiceAction must have a default public non-parameter constructor. And your assemblies must be placed in the same path with the ZayniFramework.Middle.Service.dll in runtime.

// If you want to let the Zayni Framework register all your ServiceAction objects. You don't need to assign the InitializeServiceActionHandler delegate anymore. Like this:
MiddlewareService.InitializeServiceActionHandler = null;

Finally, you just need to start the Middle.ServiceHost service in your application.

var configPath = "./Configs/serviceHostConfig.json";

// Start the service host with the explicitly 'serviceName'.
var r = new MiddlewareService().StartService( "PaymentService", path: configPath );
var j = new MiddlewareService().StartService( "OrderService", path: configPath );

// Start all the autoStarted service hosts.
var g = new MiddlewareService().StartServiceHosts( configPath );

More sample, you can go to /Test/WebAPI.Test directory to see the complete sample code.

Middleware Middle.Service.Client module example.

You have define your own business logic service by using Middle.Service module. Now you can use Middle.Service.Client module to send a RPC request, trigger a service event or publish a message to your service action.

First, add a serviceClientConfig.json in your client-side app project.

{
    "serviceClients": [
        {
            "serviceClientName": "PaymentServiceProxy",
            "remoteServiceName": "PaymentService",
            "remoteHostType": "TCPSocket",
            "remoteClient": "GCP-Production",
            "enableActionLogToDB": true
        },
        {
            "serviceClientName": "OrderServiceProxy",
            "remoteServiceName": "OrderService",
            "remoteHostType": "RabbitMQ",
            "remoteClient": "GCP-Production",
            "enableActionLogToDB": true
        },
        {
            "serviceClientName": "SomeService",
            "remoteServiceName": "SomeInProcessService",
            
            // This is a in-process proxy. That means the 'SomeInProcessService' is a in-process service in your client side process.
            // You will need to config the serviceHostConfig.json your client side application too!
            "remoteHostType": "InProcess",
            "enableActionLogToDB": true
        }
    ],
    "rabbitMQProtocol": {
        "defaultRemoteClient": "GCP-Production",
        "remoteClients": [
            {
                "name": "GCP-Production",
                "mbHost": "XXX",
                "mbPort": 5672,
                "mbUserId": "XXX",
                "mbPassword": "XXX",
                "mbVHost": "SomeHost",
                "defaultExchange": "Middle.Test.Topic",
                "rpc": {
                    "exchange": "Middle.Test.Topic",
                    "routingKey": "RPC.Server.",
                    "rpcReplyQueue": "Middle.Server.RPC.Res",
                    "rcpResponseTimeout": 5
                },
                "publish": {
                    "exchange": "Middle.Test.Topic",
                    "routingKey": "Event.Client."
                },
                "listen": {
                    "msgQueue": "Middle.Client.ListenServer.Msg",
                    "msgRoutingKey": "Event.Server."
                }
            },
            {
                "name": "Azure-Production",
                "mbHost": "XXX",
                "mbPort": 5672,
                "mbUserId": "XXX",
                "mbPassword": "XXX",
                "mbVHost": "SomeHost",
                "defaultExchange": "Middle.Test.Topic",
                "rpc": {
                    "exchange": "Middle.Test.Topic",
                    "routingKey": "RPC.Server.",
                    "rpcReplyQueue": "Middle.Server.RPC.Res",
                    "rcpResponseTimeout": 5
                },
                "publish": {
                    "exchange": "Middle.Test.Topic",
                    "routingKey": "Event.Client."
                },
                "listen": {
                    "msgQueue": "Middle.Client.ListenServer.Msg",
                    "msgRoutingKey": "Event.Server."
                }
            }
        ]
    },
    "tcpSocketProtocol": {
        "defaultRemoteClient": "GCP-Production",
        "remoteClients": [
            {
                "name": "GCP-Production",
                "tcpServerHost": "XXX",
                "tcpServerPort": 5590,
                "tcpSendBufferSize": 5,
                "tcpReceiveBufferSize": 10,
                "connectionTimeout": 1,
                "tcpResponseTimeout": 5
            },
            {
                "name": "Azure-Production",
                "tcpServerHost": "XXX",
                "tcpServerPort": 5590,
                "tcpSendBufferSize": 5,
                "tcpReceiveBufferSize": 5,
                "connectionTimeout": 2,
                "tcpResponseTimeout": 5
            }
        ]
    },
    "webSocketProtocol": {
        "defaultRemoteClient": "GCP-Production",
        "remoteClients": [
            {
                "name": "GCP-Production",
                "webSocketHostBaseUrl": "ws://XXX.XXX.XXX.XXX:5580",
                "wsResponseTimeout": 5
            },
            {
                "name": "Azure-Production",
                "webSocketHostBaseUrl": "ws://XXX.XXX.XXX.XXX:5580",
                "wsResponseTimeout": 5
            }
        ]
    },
    "gRPCProtocol": {
        "remoteClients": [
            {
                "name": "Azure-Procution",
                "serverHost": "XXX.XXX.XXX.XXX",
                "serverPort": 1155
            }
        ]
    },
    "httpCoreProtocol": {
        "remoteClients": [
            {
                "name": "GCP-Production",
                "httpHostUrl": "http://XXX.XXX.XXX.XXX:5110/action",
                "httpResponseTimeout": 5
            },
            {
                "name": "Azure-Production",
                "httpHostUrl": "http://XXX.XXX.XXX.XXX:5118/action",
                "httpResponseTimeout": 5
            }
        ]
    }
}

Write your ClientProxy class and extends RemoteProxy class. Add namespace using.

using ZayniFramework.Common;
using ZayniFramework.Middle.Service.Client;

/// <summary>Some client proxy.
/// </summary>
public class PaymentProxy : RemoteProxy

Write your constructor and pass the service client name to base class.

/// <summary>預設建構子
/// </summary>
public PaymentProxy() : base( "PaymentServiceProxy" )
{
    // pass
}

/// <summary>多載建構子
/// </summary>
/// <param name="path">服務客戶端 serviceClientConfig.json 設定檔的路徑</param>
public PaymentProxy( string path = "./serviceClientConfig.json" ) : base( serviceClientName: "PaymentServiceProxy", configPath: "./serviceClientConfig.json" )
{
    // pass
}

Then, you can use the RemoteProxy base methods, you can send a RPC request or publish message to your Middle.Service service action.

/// <summary>執行遠端服務動作的測試
/// </summary>
public new Result<TResDTO> Execute<TReqDTO, TResDTO>( string actionName, TReqDTO request ) => base.Execute<TReqDTO, TResDTO>( actionName, request );

/// <summary>發佈 ServiceEvent 服務事件訊息的測試
/// </summary>
public new Result Publish<TMsgDTO>( string actionName, TMsgDTO dto ) => base.Publish<TMsgDTO>( actionName, dto );

You might need to register the extension IRemoteClient type into RemoteClientFactory before you create your RemoteProxy. If you don't have other extension IRemoteClient, then you won't need to do this.

// Register the 'gRPCRemoteClient' into the RemoteClientFactory. The name is 'gRPC'.
RemoteClientFactory.RegisterRemoteClientType<gRPCRemoteClient>( "gRPC" );

Finally, you can use your own ClientProxy object to send RPC request or publish message.

var paymentProxy = new PaymentProxy( "./serviceClientConfig.json" );

var reqDTO = new SomethingDTO() 
{
    SomeMessage     = "This is a message from masOS or linux.",
    SomeDate        = DateTime.Now,
    SomeMagicNumber = 24.52D,
    LuckyNumber     = 333,
    IsSuperMan      = true
};

// Send a RPC request to the ServiceHost server-side.
var r = paymentProxy.Execute<SomethingDTO, MagicDTO>( "MagicTestAction", reqDTO );

More detail sample code. You can go to /Test/ServieHostTest/ServiceHost.Client.ConsoleApp directory to see the complete sample code.

Extension modules for Middle.Service & Middle.Service.Client.

The Zayni Framework allow you to write your own customized RemoteHost and RemoteClient implementaion as a extension module of Middle.Service and Middle.Service.Client.

  • Create an .NET Standard C# project for the extension modules.

  • Write your RemoteHost or RemoteClient class.

    • Your RemoteHost class need to extends the IRemoteHost interface.
    • Your RemoteClient class need to extends the IRemoteClient interface.
  • Implement you the IRemoteHost or IRemoteClient interface in your class.

  • You can follow the json configuration pattern like Middle.Service and Middle.Service.Client to create your json config section.

    • Design the json config section.
    • Write your C# configuration entity class.
  • Register you IRemoteHost or IRemoteClient type into Zayni Framework's internal container.

  • Then, use it.

More detail sample code. You can to to Source/ServiceHost/Middle.Service.Grpc and Source/ServiceHost/Middle.Service.Grpc.Client directory to see how to write a extension module for the Middleware Service module.

The gRPC is not a built-in support protocol in Middle.Service and Middle.Service.Client. The Middle.Service.Grpc and Middle.Service.Grpc.Client are extension modules.

Middle.TelnetService example.

The Middle.TelnetService and Common module provide the API of ICommand, ConsoleCommand and RemoteCommand for your application. You can use it to build a telnet service to receive the remote telnet command from any telent client very easily!

Add namespace using.

using ZayniFramework.Common;
using ZayniFramework.Middle.TelnetService;

Write your ConsoleCommand class and extends the ConsoleCommand base class.

public sealed class YourConsoleCommand : ConsoleCommand
{
    /// <summary>Execute the command.
    /// </summary>
    public override void ExecuteCommand() 
    {
        // Implement your code here...
        Result.Success = true;
    } 
}

Write your telnet RemoteCommand class and extends the RemoteCommand base class.

public class YourRemoteCommand : RemoteCommand
{
    /// <summary>Execute the command.
    /// </summary>
    public override void ExecuteCommand()
    {
        // Implement your code here...
        Result.Success = r.Success;
    }
}

Then, you have to register your ICommand object to the CommandContainer. Like this,

// Plain-old fashion way...
private static CommandContainer RegisterCommands()
{
    var commandContainer = new CommandContainer();
    commandContainer.RegisterCommand<YourConsoleCommand>( "the command name 1", commandText => 0 == string.Compare( commandText, "some command line", true ) );
    commandContainer.RegisterCommand<YourRemoteCommand>( "the command name 12", commandText => 0 == string.Compare( commandText, "some command line", true ) );
    commandContainer.RegisterCommand<ClearConsoleCommand>( "cls", commandText => 0 == string.Compare( commandText, "cls", true ) );
    commandContainer.RegisterUnknowCommand<UnknowRemoteCommand>();
    return commandContainer;
}

// Plain-old chain method invoke may
private static CommandContainer RegisterCommands() => 
    CommandContainer
        .Build()
        .Register<PushCommand>( "push", commandText => 0 == string.Compare( commandText, "push", true ) )
        .Register<ExitConsoleCommand>( "exit", commandText => 0 == string.Compare( commandText, "exit", true ) )
        .Register<ClearConsoleCommand>( "cls", commandText => 0 == string.Compare( commandText, "cls", true ) )
        .RegisterUnknow<UnknowRemoteCommand>();

Finally, you can start the console command and telnet remote command service to receive the commands. Like this.

var commandContainer = RegisterCommands();
TelnetCommandService.StartDefaultTelnetService( commandContainer );
ConsoleCommandService.StartDefaultConsoleService( commandContainer );

More detail and sample. You can go to the /Test/ServiceHostTest/ServiceHost.Client.ConsoleApp directory.

CacheManager example.

Add namespace using.

using ZayniFramework.Caching;
using ZayniFramework.Common;

Add CachingSettings config section in your app.config or web.config file.

<!--managerMode support: Memory, Redis, MySQLMemory, RemoteCache-->
<CachingSettings managerMode="Redis">
    <CachePool resetEnable="true" cleanerIntervalSecond="3" maxCapacity="10000" refreshIntervalMinute="1" />
    <CacheData timeoutInterval="0" />
    <RedisCache>
        <add name="XXXRedisService" isDefault="true" dbActionLog="true" host="XXX.XXX.XXX.XXX" port="6379" account="" password="" dbNumber="10" connectTimeout="1000" responseTimeout="3500" />
        <!--<add name="BBBRedisService" isDefault="true" dbActionLog="true" host="XXX.XXX.XXX.XXX" port="6379" account="" password="" dbNumber="10" connectTimeout="1000" responseTimeout="3500" />-->
    </RedisCache>
    <RemoteCache defaultCacheClientName="RemoteCacheTest1"></RemoteCache>
    <!--<MySQLMemoryCache defaultConnectionName="ZayniCache"></MySQLMemoryCache>-->
</CachingSettings>

Put data into CacheManager. Use CacheManager.Put() static method.

public void PutDataTest()
{
    string key = "somekey";

    var model = new
    {
        Name   = "Amber",
        Age    = 23,
        Sex    = "F",
        Emails = new List<string>()
        {
            "amber@gmail.com",
            "amber@hotmail.com"
        },
        Profile = new
        {
            Token   = Guid.NewGuid().ToString(),
            DoB     = new DateTime( 1998, 2, 14 ),
            Comment = "Hello, this is Amber."
        }
    };

    var cacheProp = new CacheProperty()
    {
        CacheId         = key,
        Data            = model,
        RefreshCallback = () => new
        {
            Name   = "Belle Claire",
            Age    = 23,
            Sex    = "F",
            Emails = new List<string>()
            {
                "belle.claire@gmail.com",
                "belle.claire@hotmail.com"
            },
            Profile = new
            {
                Token   = Guid.NewGuid().ToString(),
                DoB     = new DateTime( 1998, 2, 14 ),
                Comment = "Hi"
            }
        }
    };

    bool success = CacheManager.Put( cacheProp );
    Assert.IsTrue( success );
}

Put data into CacheManager in a HashProperty data structure. Get HashProperty data from CacheManager.

var hashs = new HashProperty[] 
{
    new HashProperty( "a1", "Hello Fiona!" ),
    new HashProperty( "a2", true ),
    new HashProperty( "a3", new DateTime( 2018, 2, 7 ) ),
    new HashProperty( "a4", 45.26 ),
    new HashProperty( "a5", 500 ),
    new HashProperty( "a6", 3333.77M ),
};

bool success = CacheManager.PutHashProperty( "aaa", hashs );

var a1 = CacheManager.GetHashProperty( "aaa", "a1" );
Assert.AreEqual( "Hello Fiona", a1.Data + "" );

var a2 = CacheManager.GetHashProperty( "aaa", "a2" );
Assert.AreEqual( true, Convert.ToBoolean( a2.Data + "" ) );

var a3 = CacheManager.GetHashProperty( "aaa", "a3" );
Assert.AreEqual( new DateTime( 2018, 2, 7 ), Convert.ToDateTime( a3.Data ) );

// Get all hash properties with the main cacheId.
var gp = CacheManager.GetHashProperties( "aaa" );

Put and Get data with CacheManager.

var model = new UserTestModel()
{
    Name = "Amber",
    Age  = 23,
    Sex  = "F"
};

bool success = CacheManager.Put( new CacheProperty() { CacheId = "test02", Data = model } );
var r = CacheManager.Get<UserTestModel>( "test02" );

Clear all the data in CacheManager. User CacheManager.Clear() static method.

CacheManager.Clear();

More detail sample code. You can go to /Test/UnitTests/Zayni.Caching.Test directory.

Caching.RedisClient module example.

You can use the ZayniFramework.Caching.RedisClient module to help you to access the Redis Server. It helps you control the connection between and provide all the request & response action log between your application and Redis Server.

Add namespace using. You might need to add the StackExchange.Redis namespace too.

using StackExchange.Redis;
using ZayniFramework.Caching.RedisClientComponent;
using ZayniFramework.Common;
using ZayniFramework.Serialization;

Then, config your Redis Server connection in your app.config file. Like this,

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="ZayniFramework" type="ZayniFramework.Common.ZayniConfigSection, ZayniFramework.Common" />
    </configSections>
    <ZayniFramework>
        <LoggingSettings eventLogEnable="false" logEnable="true">
            <DefaultTextLogger>
                <add name="_DefaultLog" enable="true" maxSize="8">
                    <LogFile path="D:\Logs\Zayni\Tracing.log" />
                    <Filter filterType="deny" category="Information,Warning" />
                </add>

                <!-- The action log between you application and redis server -->
                <add name="RedisClientTrace:MyLab" enable="true" maxSize="8" dbLoggerName="Zayni" dbLoggerType="MySQL">
                    <LogFile path="D:\Logs\Zayni\RedisClient-Tracing-Taipei-Dev.log" />
                    <!--<Filter filterType="deny" category="Information,Warning" />-->
                </add>
            </DefaultTextLogger>
        </LoggingSettings>
        <RedisClientSettings>
            <RedisClients>
                <add name="RedisServer1" isDefault="true" traceLoggerName="RedisClientTrace:MyLab" host="XXX.XXX.XXX.XXX " port="6379" password="" dbNumber="1" connectTimeout="5000" responseTimeout="1000" />
            </RedisClients>
        </RedisClientSettings>
    </ZayniFramework>
    <connectionStrings>
        <add name="Zayni" connectionString="SERVER=XXX.XXX.XXX.XXX;DATABASE=zayni;UID=XXX;PASSWORD=XXX;Allow User Variables=True;Charset=utf8;Convert Zero Datetime=True"
             providerName="MySql.Data.MySqlClient" />
        <!--<add name="Zayni" connectionString="Server=localhost;Database=ZayniFramework;User Id=XXX;Password=XXX;Max Pool Size=20;Min Pool Size=5;Connection Lifetime=30;"
             providerName="System.Data.SqlClient" />-->
    </connectionStrings>
</configuration>

Get a RedisClient object from the RedisClientContainer.

// Get the 'isDefault=ture'.
RedisClient redisClient = RedisClientContainer.Get();

// Get a RedisClient from the RedisClientContainer. By name.
RedisClient redisClient = RedisClientContainer.Get( "RedisServer1" );

Using StackExchange.Redis method to access redis server.

  • With better performance.
  • But no request and response action log between redis server.
// You can use the StackExchange.Redis' method like this. BUT you won't have a action log if you choose this way to access redis server. (Better performance!)
// Put string data into redis server. Using StringSet method.
bool isSuccess = redisClient.Db.StringSet( "MyKey", "AAA", when: When.Always );

// Get string data from redis sever. Using StringGet method.
string result = redisClient.Db.StringGet( "MyKey" );

Using RedisClient's wrapper method to access redis server.

  • You will have the request and response log between redis server if you config the app.config correctly.
  • The performance will be slower...
// Declare a delegate to wrap the StachExchange.Redis' method like this.
Delegate stringSet = (Func<string, string, When, bool>)( ( key, value, when ) => redisClient.Db.StringSet( key, value, when: when ) );

// Use RedisClient's ExecResult wrapper method to invoke the delegate like this.
// Put string data into redis server.
var r1 = redisClient.ExecResult<bool>( stringSet, "ZayniTest2", "BBB", When.Always );
Assert.IsTrue( r1.Success );
Assert.IsTrue( r1.Data );

Delegate stringGet = (Func<string, string>)( key => redisClient.Db.StringGet( key ) );
var r2 = redisClient.ExecResult<string>( stringGet, "ZayniTest2" );
Assert.IsTrue( r2.Success );
Assert.AreEqual( "BBB", r2.Data );

// =================

var hashName = new HashEntry( "name", "Pony Lin" );
var hashAge  = new HashEntry( "age", 32 );
var hashSex  = new HashEntry( "sex", 1 );
var hashDoB  = new HashEntry( "dob", "1985-05-25" );

var hashSet = (Action<string, HashEntry[]>)( ( key, hashFields ) => redisClient.Db.HashSet( key, hashFields ) );
var r = redisClient.Exec( hashSet, "ZayniTest3", new HashEntry[] { hashName, hashAge, hashSex, hashDoB } );
Assert.IsTrue( r.Success );

var hashGet = (Func<string, string, string>)( ( key, hashField ) => redisClient.Db.HashGet( key, hashField ) );
var name    = redisClient.ExecResult<string>( hashGet, "ZayniTest3", "name" ).Data;
var age     = redisClient.ExecResult<string>( hashGet, "ZayniTest3", "age" ).Data;
var sex     = redisClient.ExecResult<string>( hashGet, "ZayniTest3", "sex" ).Data;
var dob     = redisClient.ExecResult<string>( hashGet, "ZayniTest3", "dob" ).Data;

Assert.AreEqual( "Pony Lin", name );
Assert.AreEqual( "32", age );
Assert.AreEqual( "1", sex );
Assert.AreEqual( "1985-05-25", dob );

Use the RedisClient's PutHashObject and GetHashObject methods.

var source = new UserModel()
{
    Name        = "Nancy",
    Age         = 26,
    Birthday    = new DateTime( 1993, 2, 24 ),
    CashBalance = 3000,
    TotalAssets = 4100
};

string key = $"ZayniTest123:{source.Name}";

// Put a data into redis in a hash type.
var p = redisClient.PutHashObject( key, source );
Assert.IsTrue( p.Success );

// Get a data from redis in a hash type.
var g = redisClient.GetHashObject<UserModel>( key );
Assert.IsTrue( g.Success );
Assert.AreEqual( source.Name, g.Data.Name );
Assert.AreEqual( source.Age, g.Data.Age );
Assert.AreEqual( source.Birthday, g.Data.Birthday );
Assert.AreEqual( source.CashBalance, g.Data.CashBalance );
Assert.AreEqual( source.TotalAssets, g.Data.TotalAssets );

More detail sample code. You can go to /Test/UnitTests/Zayni.Caching.RedisClient.Test directory.

Serialization module example.

Add namespace using.

using ZayniFramework.Common;
using ZayniFramework.Serialization;

Using ISerializer and SerializerFactory to serialize and deserialize.

var model = new UserModel()
{
    Name     = "Kate",
    Birthday = new DateTime( 1998, 2, 14, 15, 24, 36 ).AddMilliseconds( 257 ),
    Sex      = 0
};

// Using JsonNetSerializer.
var serializer = SerializerFactory.Create( "Json.NET" );
var 5 = serializer.Serialize( model ) + "";
var d1 = serializer.Deserialize<UserModel>( 5 );

// Using JilSerializer.
var serializer2 = SerializerFactory.Create( "Jil" );
var s2 = serializer2.Serialize( model ) + "";
var d2 = serializer2.Deserialize<UserModel>( s2 );

// Using XmlSerializer.
var serializer3 = SerializerFactory.Create( "XML" );
var s3 = serializer3.Serialize( model ) + "";
var d3 = serializer3.Deserialize<UserModel>( s3 );

// Using BinaryFormatSerializer.
var serializer4 = SerializerFactory.Create( "BinaryFormatter" );
var s4 = serializer4.Serialize( model ) + "";
var d4 = serializer4.Deserialize<UserModel>( s4 );

// Using ZeroFormatSerializer.
// ZeroFormatter serialize and deserialize is SUPER FAST... But you have to pay the price for it like this...
// 如果要使用 ZeroFormatter 進行無敵快的序列化和反序列化,使用起來就會失去一些彈性... 沒辦法,要快,要付出某些架構上的代價...
var serializer5 = (ZeroFormatSerializer)SerializerFactory.Create( "ZeroFormatter" );
var s5 = serializer5.SerializeObject<UserModel>( model );
var d5 = serializer5.Deserialize<UserModel>( s5 );

You can even define your own ISerializer and register it into SerializerFactory.

/// <summary>You customer Serializer implementation class
/// </summary>
public class YourSerializer : ISerializer
{
    /// <summary>How to deserialize here...
    /// </summary>
    /// <typeparam name="TResult"></typeparam>
    /// <param name="obj"></param>
    /// <returns></returns>
    public TResult Deserialize<TResult>( object obj ) => default ( TResult );

    /// <summary>How to serialize here...
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public object Serialize( object obj ) => "AAA";
}

And then, you can register your YourSerializer into SerializerFactory like this:

var name = "YourSerializer";
var r = SerializerFactory.Register( name, new YourSerializer() );
Assert.IsTrue( r.IsSuccess );

var serializer = SerializerFactory.Create( name );
Assert.IsNotNull( serializer );

var s = serializer.Serialize( new object() );
Assert.AreEqual( "AAA", s );

The JsonConvertUtil Serialize sample.

var model = new UserModel()
{
    Name     = "Test",
    Birthday = new DateTime( 1998, 2, 14, 15, 24, 36 ),
    Sex      = 0
};

string json = JsonConvertUtil.SerializeInCamelCase( model, false );

The JsonConvertUtil Deserialize sample.

UserModel obj = JsonConvertUtil.DeserializeFromCamelCase<UserModel>( json, false );

Logging module example.

Add LoggingSettings section in your app.config or web.config file.

<LoggingSettings eventLogEnable="false" logEnable="true">
    <DefaultTextLogger>
        <add name="_DefaultLog" enable="true" maxSize="8" consoleOutput="true">
            <LogFile path="D:\Logs\Zayni\Logging.TestLog.log"/>
            <!--<Filter filterType="deny" category="Information,Warning" />-->
        </add>
        <add name="TextLogWithoutEmail" enable="true" maxSize="5">
            <LogFile path="D:\Logs\Zayni\Zayni.UnitTest.log"/>
            <!--<Filter filterType="deny" category="Information,Warning" />-->
        </add>
        <add name="TextLogWithEmail" enable="true" maxSize="5" consoleOutput="true" emailNotifyLoggerName="EMailSmtpNotify1" dbLoggerName="ZayniLogging" dbLoggerType="MySQL">
            <LogFile path="D:\Logs\Zayni\Zayni.UnitTest.log"/>
            <!--<Filter filterType="deny" category="Information,Warning" />-->
        </add>
    </DefaultTextLogger>
    <EmailNotifyLogger>
        <add name="EMailSmtpNotify1" smtpPort="587" smtpHost="smtp.gmail.com" enableSsl="true" smtpDomain="" smtpAccount="test@gmail.com" smtpPassword="XXX" fromEmailAddress="system@gmail.com" fromDisplayName="Some Title" toEmailAddress="test@gmail.com"/>
        <add name="EMailSmtpNotify2" smtpPort="25" smtpHost="XXX.XXX.XXX.XXX" enableSsl="false" smtpDomain="" smtpAccount="tester" smtpPassword="Admin123" fromEmailAddress="system@gmail.com" fromDisplayName="Email Logger Test" toEmailAddress="test@gmail.com"/>
    </EmailNotifyLogger>
</LoggingSettings>

Then, you can use the Logging module.

using ZayniFramework.Logging;

static void Main( string[] args )
{
    // Register the error log event handler.
    // When an error log event is fired, your error log event handler will be trigger.
    Logger.Instance.WriteErrorLogEvent += ( sender, message, title ) => 
    {
        Console.WriteLine( $"Ho Ho Ho, this is a error event fired..." );
        Console.WriteLine( $"Error Title: {title}" );
        Console.WriteLine( $"Error Message: {message}" );
    };

    // ===========

    // Use Logger to write a log.
    Logger.WriteInformationLog( nameof ( Program ), "Start console.", Logger.GetTraceLogTitle( nameof ( Program ), nameof ( Main ) ), "TextLogWithEmail" );
    Logger.WriteErrorLog( nameof ( Program ), "Something error!!", "MyTest" );
    Logger.WriteInformationLog( nameof ( Program ), "End console.", Logger.GetTraceLogTitle( nameof ( Program ), nameof ( Main ) ), "TextLogWithEmail" );
    
    // Use ConsoleLogger to output a log to console.
    ConsoleLogger.Log( "This is a console log message", ConsoleColor.DarkGreen );
    ConsoleLogger.LogError( "This is a console log error message" );

    // Write a log to windows event log.
    EventLogger.WriteLog( "ZayniFramework", "Testing", EventLogEntryType.Information, true );
    Console.ReadLine();
}

ExceptionHandling module example.

Add LoggingSettings and ExceptionHandling section in your app.config or web.config file.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="ZayniFramework" type="ZayniFramework.Common.ZayniConfigSection, ZayniFramework.Common"/>
    </configSections>
    <ZayniFramework>
        <LoggingSettings eventLogEnable="false" logEnable="true">
            <DefaultTextLogger>
                <add name="_DefaultLog" enable="true" maxSize="8" consoleOutput="true">
                    <LogFile path="D:\Logs\Zayni\Logging.TestLog.log"/>
                    <!--<Filter filterType="deny" category="Information,Warning" />-->
                </add>
            </DefaultTextLogger>
        </LoggingSettings>
        <ExceptionHandling>
            <DefaultTextLoggingPolicy enableDefaultPolicy="true" enableDefaultPolicyEventLog="true" needRethrow="false" defaultPolicyLogFilePath="D:\Logs\Zayni\ExceptionHandling.UnitTest.Text.log"/>
            <EMailNotifyPolicy textLogEnable="true" textLogPath="D:\Logs\Zayni\ExceptionHandling.UnitTest.Email.log" eventLogEnable="false" needRethrow="false" emailNotifyEnable="true" smtpPort="587" smtpHost="smtp.gmail.com" enableSsl="true" smtpDomain="" smtpAccount="test@gmail.com" smtpPassword="XXX" fromEmailAddress="system@gmail.com" fromDisplayName="Some Error Happend" toEmailAddress="test@gmail.com" mailSubject="ZayniFramework Exception Handling Unit Test"/>
        </ExceptionHandling>
    </ZayniFramework>
</configuration>

Add namespace using

using ZayniFramework.ExceptionHandling;

Then, you can handle the exception like this:

public static void DoSomethingWrong() => throw new ApplicationException( "Opps... Something goes wrong..." );

try
{
    DoSomethingWrong();
}
catch ( Exception ex )
{
    ExceptionAgent.ExecuteDefaultExceptionPolicy( ex, eventTitle: "AAA" );
}

try
{
    DoSomethingWrong();
}
catch ( Exception ex )
{
    ExceptionAgent.ExecuteEMailNotifyExceptionPolicy( ex, eventTitle: "FFF" );
}

Cryptography module example.

Add CryptographySettings section in your app.config or web.config file.

<CryptographySettings symmetricAlgorithmKeyPath="~\ZayniCryptoTest.key">
    <AesEncryptor keySize="128"/>
    <RijndaelEncryptor blockSize="256" keySize="256"/>
    <HashEncryptor needSalt="true"/>
</CryptographySettings>

You will also need to use KeyMaker to generate a symmetric algorithm key. See Source/SDKTools/Zayni.Cryptography.KeyMaker. Or you can use the DefaultKey.key in ZayniFramework.

The DeaultKey.key

o6iPM3HirEntMdZqd0t5i7yYOTmJrJon8SHfE87SV6ivxKCxrQu2mkJh7A0rGwqYO0lsLscVsRYbO3a4GoRROm1M9ZX37ECYFAWw
BnUGGlKbmhOIlDLr9MPX3q8IPpRKKCVkbA5K9SsAV0SbttOIZnXODw1KfCpQK0HZCdga80ifSF22OWOVQ8jWd6We9kevcSoUirOF
YFTlBGUJTwaXiXibYn6Nyq2j4cgaE6IQ6XeHyCWQbUgvyUYFWLufCPUhCjhYYt4Me9RArQcLHflT96jY3R3sBbDg2w4EUwqhQJGp
IjNE6RVHPs9vEWUrMJ0mglLg92jyqlGgmSfYPhEkX4T5dAAWTmgBmsoWOf53hVQdcR8lpuRe4FEcHF3xrCV345stc9AjcA21dpRB
tOCJXn1m1f2pu8rONYc2UtvU67ZnUNlAnOgQqNjrv2Urk7JJgt8YTieySgDPEDIcsq2VRRWyoj5SP3jxmF6aEDf5jgw9AJdijqys

Then you can encrypt or decrypt something like:

ISymmetricEncryptor encryptor = SymmetricEncryptorFactory.Create( _encryptMode );
string source = "ABCDEFG_HIJKLMN_OPQRST_UVW_XYZ";
string result = encryptor.Encrypt( source );
string plain  = encryptor.Decrypt( result );

You can also use the HASH algorithm like:

string userId   = "SomeUserIdHere";
string password = "SomePasswordHere";

IHashEncryptor encryptor = HashEncryptorFactory.Create( "sha256" );
string result = encryptor.HashEncrypt( password, saltKey: userId );

Formatting module example.

Add Formatting section in your app.config or web.config file if you are going to do some DateTime formatting.

<FormattingSettings>
    <DateTimeFormatStyle>
        <add language="zh-TW" format="yyyy/MM/dd" isDefault="true"/>
        <add language="zh-CN" format="yyyy/MM/dd"/>
        <add language="en-US" format="MM-dd-yyyy"/>
    </DateTimeFormatStyle>
</FormattingSettings>

Then you can add the Formatting Attribute in your Properties.

using ZayniFramework.Formatting;

public class MasterModel : BasicModel<MasterVModel>
{
    // For deep formatting
    [CollectionFormat()]
    public List<DetailModel> Details { get; set; }
    
    public string Name { get; set; }

    [DateFormat()]
    public DateTime Birthday { get; set; }

    [NumberFormat( Length = 2 )]
    public decimal Age { get; set; }
    
    [CollectionFormat()]
    public DetailInfoModel DetailInfo { get; set; }
}

public class MasterVModel : BasicViewModel
{
    public string Name { get; set; }

    public string Birthday { get; set; }

    public string Age { get; set; }

    // In the ViewModel only suppory 'String' property.
    //public DetailInfoModel DetailInfo { get; set; }
}

Finally, you can format the Model class like this:

// Test data here...
var model = new MasterModel();

model.Name       = "John";
model.Birthday   = new DateTime( 1988, 1, 1 );
model.Age        = 16;
model.DetailInfo = new DetailInfoModel() 
{ 
    InfoName = "JJJ", 
    NAVDate  = new DateTime( 2013, 5, 7 ) 
};
model.Details = new List<DetailModel>() 
{
    new DetailModel 
    {
        DetailName       = "IUIU",
        Length          = 3M,
        Pay             = 45673.52434M,
        NavDate         = new DateTime( 2013, 3, 5 ),
        Name            = "Joe",
        MaskStartIndex  = 1,
        MaskLength      = 1,
        Name2           = "Jimmy"
    },
    new DetailModel 
    {
        DetailName      = "Jack",
        Length          = 2M,
        Pay             = 785.5497M,
        NavDate         = new DateTime( 2013, 7, 5 ),
        Name            = "Kindding",
        MaskStartIndex  = 2,
        MaskLength      = 3,
        Name2           = "Ruby"
    }
};

// do the format here...
var viewModelType = typeof( MasterVModel );
var formatter     = new DataFormatter();
var result        = formatter.DoModelDeepFormat( model, viewModelType );

// get the format result here...
MasterModel  resultModel  = (MasterModel)result;
MasterVModel resultVModel = resultModel.ViewModel;

Or you can also use the Dynamic Formatting.

Then, you don't have to define a ViewModel class anymore. But the dynamic formatting is a little slower than using ViewModel formatting.

// Your class will extend the BasicDynamicModel instead of BasicModel
public class MasterModel2 : BasicDynamicModel
// Use dynamic formatting here...
var formatter = new DataFormatter();
object result = formatter.FormatDynamicModel( model );

// Get the dynamic formatting here...
MasterModel2 resultModel  = (MasterModel2)result;
dynamic      resultVModel = resultModel.ViewModel;  // dynamic type

Development Require Softwares & SDKs:

  1. Visual Studio 2017 Version 15.8.6 or higher version.

  2. Visual Studio 2017 SQL Server Express LocalDB.

  3. Visual Studio Code with C# extensions.

    3.1. C# for Visual Studio Code.

    3.2. C# Extensions.

    3.3. C# XML Documentation Comments.

    3.4. Nuget Package Management.

    3.5. .NET Core Extension Pack. (by Will保哥)

  4. Microsoft .NET Core SDK v2.1.402 or higher version.

  5. SQL Server Express 2017.

  6. MySQL Server v5.6 or higher version like v5.7 or 8.0.

  7. Nuget Package Managment.

  8. Sandcastle Help File Builder.

  9. Git.

  10. Docker.

Nuget Package Management Settings:

(You don't need to do the Nuget package settings anymore... This is just for record... :) )

  1. Git Clone to your local repository.
  2. Open your Visual Studio 2017 and Go to Tools/Nuget Package Manager/Nuget Package Settings.
  3. Select 'Package Sources'.
  4. Add a new local package source.

Windows Environment

  1. Add a new local package source and rename it.
  2. The new local package source location should be solution directory 'CommonBin\RabbitMQ.Msg.Client'.

macOS or Linux Ubuntu Environment

  1. If you are using macOS or Linux Ubuntu to develop.
  2. Open your terminal cosole and cd to the ZayniFramework.sln root directory.
  3. Execute dotnet restore command.
  4. In the terminal, change directory to NuGet directory. Like cd ~/.nuget/NuGet/.
  5. Use a text editor (Visual Studio Code or Notepad++) open ~/.nuget/NuGet/NuGet.Config file.
  6. Add add local package source repository for RabbitMQ.Msg.Client.nupkg. Example: <add key="RabbitMQ.Msg.Client.Reop" value="The path of RabbitMQ.Msg.Client.nupkg." />

Sample NuGet.Config like:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
        <add key="RabbitMQ.Msg.Client.Reop" value="The full path your RabbitMQ.Msg.Client.nupkg in your local machine." />
    </packageSources>
</configuration>

API Help Document

Here is the API Help Document. You are welcome to check it out and find out the functionalities from the Zayni Framework.

See more sample code

You can git clone the project and see all the unit tests and test application in /Test directory. There are many sample code and sample app.config and json config in the /Test directory.