This is a lightweight CRUD ORM template for .NET data access application. You can use the DataContext to execute Insert, Update, Delete and Select action to database. Currently only support the MSSQL and MySQL database. GitLab Repository: https://gitlab.com/ponylin1985/zayniframework
Install-Package ZayniFramework.DataAccess.LightORM -Version 2.3.12
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:
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 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.
./Test/WebAPI.Test/Configs
folder. You will need to use this path when you're going to run the docker container.docker pull ponylin1985/webapi.test
to install the server-side application docker image.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.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.
cd ./Test/ServiceHostTest/ServiceHost.Client.ConsoleApp
.dotnet restore
.dotnet build
.dotnet run
.init
command to connect to the WebAPI.Test application's service host.send magic action
command.performance test 1000
command to execute the performance test.exit
to exit the ServiceHost.Client.ConsoleApp application.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.
====================
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.
====================
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.
====================
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.
====================
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.
====================
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.
====================
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.
There are some docker images has been release on the public DockerHub.
docker pull ponylin1985/zayni.sharedata.service:alpine-x64
command to download the docker image.docker pull ponylin1985/webapi.test:alpine-x64
command to download the docker image.This is another test application image for Middle.Service module.
docker pull ponylin1985/zayni.server.app:alpine-x64
command to download the docker image.docker pull ponylin1985/zayni.client.app:alpine-x64
command to download the docker image.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
using ZayniFramework.Common;
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
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 );
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...
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 );
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 );
}
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 );
}
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 );
}
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.
Just like using Dapper and plain old ADO.NET style. :)
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
// 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.
// 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.
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 );
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();
}
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" );
}
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 );
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
Visual Studio 2017 Version 15.8.6 or higher version.
Visual Studio 2017 SQL Server Express LocalDB.
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保哥)
Microsoft .NET Core SDK v2.1.402 or higher version.
SQL Server Express 2017.
MySQL Server v5.6 or higher version like v5.7 or 8.0.
Nuget Package Managment.
Sandcastle Help File Builder.
Git.
Docker.
(You don't need to do the Nuget package settings anymore... This is just for record... :) )
dotnet restore
command.cd ~/.nuget/NuGet/
.<add key="RabbitMQ.Msg.Client.Reop" value="The path of RabbitMQ.Msg.Client.nupkg." />
<?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>
Here is the API Help Document. You are welcome to check it out and find out the functionalities from the Zayni Framework.
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.