(Original)
In the past six months, writing something regularly or irregularly has become a habit for me. However, I have been busy with my work for the past two months and have been leaving enough time for myself. Although I have made a long string of writing plans for myself, I am not enough. This work section mainly helps the company develop a distributed development framework, and has a new understanding of some technologies and design methods. The two days of work mainly focused on how to introduce the PIAB (Policy Injection Application Block) of Enterprise Library V3.1 into our own framework, and conducted some research on PIAB, I would like to share this opportunity with you.
- Part I: PIAB Overview
- Part II: PIAB Design and Implementation Principles
- Part III: PIAB extension-how to create Custom CallHandler
I. Business LogicAnd Infrastructure LogicSeparation
For any enterprise-level application developers, the code they write is not only to process pure business logic, but also to process a lot of non-business logic, such: caching, Transaction Enlist, Authorization, Auditing, Exception Handling, Logging, Validation, and even Performance Counter. I am used to turning these non-business logicInfrastructure Logic. Because these Infrastructure logics are usually injected into the execution of specific business Logic based on specific needs, they are moreCrosscutting Concern.
According to the traditional OO programming method, we usually combine these Business Concern and Crosscutting Concern, which leads to a tight coupling of some meaning. Such tight coupling in a large enterprise application may have a fatal impact on application scalability. RequiredSome way to achieve the separation of Business Concern and Crosscutting Concern is usually the goal of AOP.
For a simple example, we now have a simple order processing operation, and the specific business logic may be very simple. However, this operation may face a lot of non-business logic. For example, perform Authorization before processing to determine whether the current user has the permission for this operation.
· Auditing: records the users and processing time.
· Transaction Enlist: All Data Access operations are included in the same Transaction to ensure Data consistency.
· Perform Exception Handling.
· If an exception occurs, record the log.
The above Code can be reflected in the following Code:
Public void ProcessOrder (Order order)
{
Authorize ();
Audit ();
Using (TransactionScope scope = new TransactionScope ())
{
Try
{
OrderDataAccess ();
}
Catch (Exception ex)
{
Handleappstoin (ex );
Log ();
}
}
}
We can see that there is only one line related to the business in the above lines of code. Although the implementation of these non-business logic is usually completed by calling an encapsulated method or component, you only need to Copy Paste, however, the combination of so many Infrastructure logics and Business Logic in this way poses a lot of risks.
- First, large-scale replication of some public Source code cannot ensure its identity and correctness. I personally think that for a program, if you frequently use Ctrl + C and Ctrl + V, you should think of your code to be restructured.
- Second, the coupling of Infrastructure Logic and Business Logic leads to changes to Infrastructure Logic, which leads to changes to the Source Code.
- In addition, this programming method increases the pressure on programmers. It may not be known to a Junior programmer about the specific role of this Infrastructure Logic, in fact, for the end programmers, these should be transparent to them.
So we need to find a wayInject these Infrastructure Logic into a specific operation, without implementing all the Logic in the specific method definition.. For example, you can configure it in the configuration file, or add Attribute declaration. All of this can be achieved through PIAB of the Enterprise Library.
Ii. PIAB(Policy Injection Application Block) Overview
PIAB is introduced in Enterprise Library 3.0 (Note: Do not confuse PIAB and DIAB. The DIAB-Dependence Injection Application Block will be introduced in Enterprise Library 4.0, I personally think this will be a significant AB. Its use will greatly reduce the dependency between modules or components and has been widely used in the Software Factory ). PIAB provides a simple and effective way to inject the Crosscutting concern you need into the Invocation stack of a method. For example, you can inject Auditing execution before a method call and execute the Transaction commit call after the call is successful. In case of an Exception, Exception Handling can be performed.
For PIAB, Policy means: "inject corresponding processing operations into corresponding method calls ". This actually includes two aspects: how to inject the processing operation and how to match the specific method. The former is encapsulated in an object called CallHandler, and the matching relationship is expressed by another object called MatchingRule. So it can be said that Policy = CallHandler + MatchingRule.
PIAB provides us with many custom CallHandler systems, which can meet most of our needs in general, such:
- Enterprise Library Security Application Block Authorization.
- CachingCallHandler: saves the method return Value as Value and parameter list as Key to the Cache. If the same parameter list is called next time, the Value in the Cache is directly returned, this eliminates the impact of the re-execution method on performance. Like normal Cache processing, you can set the Cache expiration time.
- ExceptionCallHandler: supports Exception handling by calling the Enterprise Library Exception Handing Application Block.
- LogCallHandler: Call the Enterprise Library Logging Application Block to process logs.
- ValidationCallHandler: verifies parameters and other functions. This CallHandler depends on the Enterprise Library Validation Application Block.
- PerformanceCounterCallHandler.
MatchingRule actually defines a rule for matching CallHander and the corresponding Method. Similarly, a series of MatchingRule are defined. For example, the Assembly-based MatchingRule matches CallHandler to all objects of an Assembly; the Custom Attribute-based MatchingRule matches an Attribute declared on an element. In addition, there are MatchingRule Based on NameSpace, Method Signature, and so on.
The existing CallHandler and MatchingRule cannot meet your specific requirements. You can also define Custom CallHandler or Custom MatchingRule. In the subsequent sections, we will introduce an example of a complete definition of Custom CallHandler.
3. Get Started
After the introduction of PIAB, I would like to give you a simple understanding of the purpose of using PIAB. In order to deepen your image, we will make a simple Walkthrough in this section, here is a simple example of using PIAB. In this example, PIAB is used to implement two main functions: Caching and Logging. For the sake of simplicity, we use a Console Application to implement such an example. In a hypothetical scenario, an order is processed and the processed order is returned.
Step I: Create a Console Application and add the following Dll Reference.
- Microsoft. Practices. EnterpriseLibrary. PolicyInjection
- Microsoft. Practices. EnterpriseLibrary. PolicyInjection. CallHandlers
Step II:Write Order ProcessingCode:
Public class OrderProcessor: MarshalByRefObject
{
Public Order Process (Order order)
{
Console. WriteLine ("OrderProcessor. Process () is invocated! ");
Return order;
}
}
Public class Order
{}
The reason for inheriting MarshalByRefObject will be introduced in the implementation principle of PIAB later. Write the call to OrderProcessor in Main ().
Static void Main (string [] args)
{
OrderProcessor processor = PolicyInjection. Create <OrderProcessor> ();
Order order = new Order ();
Processor. Process (order );
Processor. Process (order );
}
Step III: Create app. config and use the Enterprise Library Configuration ConsoleConfigure:
1. Add a Logging Application Block
2. Add a Policy Injection Application Block in the same way, and then add a Policy to the PIAB node. Create a Method Name Matching Rule under the Match Rules node under the Policy (the MatchingRule matches the CallHandler according to the Method name ). Configure the MatchingRule as follows:
Then the CallHandler of this Policy will be automatically applied to the Method whose Method name is Process.
3. Add two CallHandler: Caching Handler and Logging Handler to the Handlers node under the Policy. Keep the default configuration of Caching Handler and configure Loging Handler as follows:
Through the above Configuration, we will get the following Configuration:
Configuration
Run the following program:
The output results show that although the Process method is called twice in Main (), the actual execution is only once. This is caused by Caching. The first time you call PIAB to save the returned value to the Cache, the Key of the Cache Entry is the Order object parameter. The second call directly returns the last result because the input parameters are the same.
The above shows the effect of Caching. Now we are looking at Logging. By default, the Logging entry is written to the Event Log. Is the Log we write.
· [Original] One of the Enterprise Library Policy Injection Application blocks: PIAB Overview
· [Original] Enterprise Library Policy Injection Application Block 2: PIAB Design and Implementation Principles
· [Original] Enterprise Library Policy Injection Application Block 3: PIAB extension-create custom CallHandler (download Source Code)
· [Original] Enterprise Library Policy Injection Application Block 4: how to control the execution sequence of CallHandler
In the previous article, I briefly introduced the PIAB (Policy Injection Application Block) in the Enterprise Library. In this article, I will talk about my personal understanding of PIAB design and implementation principles. In the process of introduction, I try to use a simple and in-depth approach, combined with examples, Source Code. I hope that this article will give you a comprehensive and profound understanding of PIAB.
I. MBR, ObjRef, RealProxy, TransparentProxy
Before entering PIAB, we will talk about some background knowledge related to it. MBR, ObjRef, RealProxy, and TransparentProxy. For these terms, I 'd like to be familiar with or be familiar with. NET Remoting. Since the implementation mechanism of PIAB depends on the Remoting sort aling, it is difficult for readers who do not know about it to understand it later. Therefore, it is necessary to make the following brief introduction.
We know that CLR uses AppDomain to isolate different applications. In general, different AppDomains have different shared memory. Objects Created in one AppDomain cannot be directly used by programs running in another AppDomain. Cross-AppDomain object sharing relies on an important process: discovery aling. There are two different ways of using aling: Using aling by Value and using aling by Reference. The former adopts the Serialization/Deserialization method, while the latter uses the method of passing the Reference. Its implementation is as follows:
Remoting Infrastructure: the object's ObjRef Instance, ObjRef (System. runtime. remoting. objRef) represents a Reference of a remote object. It stores all the information required for remote calling across AppDomain, such as URI, type hierarchies, and Implemented interfaces. ObjRef can be serialized, that is, it can be partitioned by Value. So it can be said that the stored aling by Reference depends on the distributed aling by Value of ObjRef.
When the ObjRef origin is implemented in another AppDomain, two Proxy objects: RealProxy and TransparentProxy will be generated based on the ObjRef data. RealProxy is created based on ObjRef and TransparentProxy is created through RealProxy. When a remote call is made, the Client directly deals with TransparentProxy. The call to TransparentProxy will be forwarded to RealProxy, realProxy uses the Communicate and Activation mechanisms of Remoting Infrastructure to pass Invocate to the activated Remote Object.
MBR generally implements remote calls in a cross-AppDomain environment. However, this mechanism is still effective with an AppDomian and can avoid performance loss across AppDomains. PIAB makes full use of this MBR under the same AppDomain.
2. Method Interception & Custom RealProxy
In the first part, we know that PIAB is implemented by applying the Policy to the corresponding Method. before actually executing the specific Method of the target object, PIAB intercepts the call of the entire Method, then Call all CallHandler that should be included in the Policy on the Method One by one (in the previous chapter we mentioned Policy = Matching Rule + Call Handler), and finally Call the Method of the real target object. We turn this mechanism into Method Injection.
How can we implement this Method Injection? This requires the MBR we introduced in the previous section. Through the above introduction, we know that the process of calling an MBR Object is as follows:
Client Code => Transparent Proxy => Real Proxy => Target Object
In the above Invocate Chain, because the method of the real target object is called only in the last part, we can "hijack" the call in the middle so that it can call our CallHandler first. The breakthrough of such Inject lies in RealProxy. In FCL (Framework Class Library), RealProxy (System. runtime. remoting. proxies. realProxy) is a special Abstract Class. You can inherit from RealProxy to define your own Custom RealProxy and write the operations you need to inject into the Invoke method. PIAB adopts such a solution.
We will not be very busy introducing the specific implementation principles of PIAB, because it is relatively complicated. To make it easier for readers to understand the implementation of PIAB, I wrote a simple example. We can see the implementation mechanism of PIAB in general.
This is a simple Console Application. I first defined a Custom RealProxy:
Public class MyRealProxy <T>: RealProxy
{
Private T _ target;
Public MyRealProxy (T target)
: Base (typeof (T ))
{
This. _ target = target;
}
Public override IMessage Invoke (IMessage msg)
{
// Invoke injected pre-operation.
Console. WriteLine ("The injected pre-operation is invoked ");
// Invoke the real target instance.
IMethodCallMessage callMessage = (IMethodCallMessage) msg;
Object returnValue = callMessage. MethodBase. Invoke (this. _ target, callMessage. Args );
// Invoke the injected post-operation.
Console. WriteLine ("The injected post-peration is executed ");
// Return
Return new ReturnMessage (returnValue, new object [0], 0, null, callMessage );
}
}
This is a Generic RealProxy. In the Invoke method, there are two consoles. write () indicates the call of CallHandler injected by PIAB (the CallHandler operation can be called before or after the Target Object is called, we may call these two types of operations as Pre-operation and Post-op.
Eration ). The call to the Target object is actually called through Reflection (callMessage. MethodBase. Invoke ). MyRealProxy is created by passing in the Target Instance.
We are creating a Factory Class to create TransparentProxy. In PIAB, such a Class is acted by Microsoft. Practices. EnterpriseLibrary. PolicyInjection. PolicyInjection.
Public static class PolicyInjectionFactory
{
Public static T Create <T> ()
{
T instance = Activator. CreateInstance <T> ();
MyRealProxy <T> realProxy = new MyRealProxy <T> (instance );
T transparentProxy = (T) realProxy. GetTransparentProxy ();
Return transparentProxy;
}
}
Create a Target Instance in Reflection mode. Use this Instance to create the Custom RealProxy defined above. Finally, a TransparentProxy is returned through RealProxy.
With the above two classes, we write the following Code to verify our Method Injection:
Public class Foo: MarshalByRefObject
{
Public void DoSomeThing ()
{
Console. WriteLine ("The method of target object is invoked! ");
}
}
Public class Program
{
Public static void Main ()
{
Foo foo = PolicyInjectionFactory. Create <Foo> ();
Foo. DoSomeThing ();
}
}
Let's take a look at the results after the program runs:
We can see the results we need. In this example, we can see that our Client code only contains Business Logic-related code, other business-independent codes are injected to the Invocation Stack in the form of Custom RealProxy. This fully demonstrates the separation of Business Concern and Crosscutting Concern.
3. Call Handler Pipeline
I have thought of the above theories and examples as the basis. It is not hard for you to understand the true implementation of PIAB. Let's first introduce an important concept of PI: CallHandler Pipeline. We know that a Policy has multiple CallHandler in terms of Method used. All methods are often associated with a series of CallHandler. This is very common now. Just like the example given in the first part, a simple ProcessOrder needs to execute many additional business-independent logic: authorization, Auditing, Transaction Enlist, Exception Handling, and Logging.
In PIAB, all CallHandler based on a Method are connected one by one to form a CallHandler Pipeline. The details are as follows:
We understand CallHandler Pipeline from the perspective of Class digoal (represented by Microsoft. Practices. EnterpriseLibrary. PolicyInjection. HandlerPipeline in PIAB ).
Public delegate IMethodReturnInvokeHandlerDelegate(IMethodInvocation input, GetNextHandlerDelegategetNext );
Public interfaceIMethodInvocation
{
// Methods
IMethodReturnCreateExceptionMethodReturn(Exception ex );
IMethodReturnCreateMethodReturn(Object returnValue, params object [] outputs );
// Properties
IParameterCollectionArguments{Get ;}
IParameterCollectionInputs{Get ;}
IDictionaryInvocationContext{Get ;}
MethodBaseMethodBase{Get ;}
ObjectTarget{Get ;}
}
Public delegate InvokeHandlerDelegateGetNextHandlerDelegate();
Public interfaceICallHandler
{
// Methods
IMethodReturnInvoke(IMethodInvocation input, GetNextHandlerDelegate getNext );
}
IMethodInvocationRepresents a call to a Method. It encapsulates information about a Method Invocation. For example, Parameter List, Invocation Context, and Target Object.InvokeHandlerDelegateIt indicates the call to CallHandler. Since all CallHandler is serialized into a CallHandler Pipeline, after calling the current CallHandler, you need to call the next CallHandler in Pipeline, call the next CallHandler through a Delegate,GetNextHandlerDelegateAnd the return value of the Delegate isInvokeHandlerDelegate. Combined with the chain structure of the CallHandler Pipeline above, it is not difficult to understand these.
Let's take a look at the definition of HandlerPipeline. All its CallHandler is represented by a List.
Public classHandlerPipeline
{
// Fields
Private List <ICallHandler>Handlers;
// Methods
PublicHandlerPipeline();
PublicHandlerPipeline(IEnumerable <ICallHandler> handlers );
Public IMethodReturnInvoke(IMethodInvocation input, InvokeHandlerDelegate target );
}
HandlerPipeline starts calling its CallHandler PipeLine through Invoke:
public IMethodReturn Invoke(IMethodInvocation input, InvokeHandlerDelegate target)
{
if (this.handlers.Count == 0)
{
return target(input, null);
}
int handlerIndex = 0;
return this.handlers[0].Invoke(input, delegate {
handlerIndex++;
if (handlerIndex < this.handlers.Count)
{
ICallHandler local1 = this.handlers[handlerIndex];
return new InvokeHandlerDelegate(local1.Invoke);
}
return target;
});
}
The logic is not complex. It is called one by one according to the order of CallHandler List, and then the Target Object is called. The second parameter in the method represents the target Object call.
Iv. Custom RealProxy in PIAB: InterceptingRealProxy
We have always stressed that PIAB is actually implemented through custom RealProxy. In the second section, we also tested the feasibility of this practice. Now let's take a look at PIAB's Custom RealProxy: Microsoft. Practices. EnterpriseLibrary. PolicyInjection. RemotingInterception. InterceptingRealProxy.
public class InterceptingRealProxy : RealProxy, IRemotingTypeInfo
{
// Fields
private Dictionary<MethodBase, HandlerPipeline> memberHandlers;
private readonly object target;
private string typeName;
// Methods
public InterceptingRealProxy(object target, Type classToProxy, PolicySet policies);
private void AddHandlersForInterface(Type targetType, Type itf);
private void AddHandlersForType(Type classToProxy, PolicySet policies);
public bool CanCastTo(Type fromType, object o);
public override IMessage Invoke(IMessage msg);
// Properties
public object Target { get; }
public string TypeName { get; set; }
}
The above is the definition of all its members, whereMemberHandlersIt is a Dictionary with MethodBase as the Key, indicating all CallHandler Pipeline applied to the Target Object. It is easy to obtain the Pipeline of a specific method call. Target is a real Target Object.
Let's take a look at the definition of Invoke:
public override IMessage Invoke(IMessage msg)
{
HandlerPipeline pipeline;
IMethodCallMessage callMessage = (IMethodCallMessage) msg;
if (this.memberHandlers.ContainsKey(callMessage.MethodBase))
{
pipeline = this.memberHandlers[callMessage.MethodBase];
}
else
{
pipeline = new HandlerPipeline();
}
RemotingMethodInvocation invocation = new RemotingMethodInvocation(callMessage, this.target);
return ((RemotingMethodReturn) pipeline.Invoke(invocation, delegate (IMethodInvocation input, GetNextHandlerDelegate getNext) {
try
{
object returnValue = callMessage.MethodBase.Invoke(this.target, invocation.Arguments);
return input.CreateMethodReturn(returnValue, invocation.Arguments);
}
catch (TargetInvocationException exception)
{
return input.CreateExceptionMethodReturn(exception.InnerException);
}
})).ToMethodReturnMessage();
}
The above code is not long. It is hard to understand it after reading it several times. In general, the above Code parses MethodBase Based on msg, obtains the corresponding CallHandler Pipeline, and finally calls Pipeline.
V. Policy Injection Transparent Proxy Factory
After introduction, the readers can realize that there are still two things to be solved: the initialization of CallHandler Pipeline and the creation of Transparent Proxy. These two tasks are completed by yyinject. Create and the method.
It should be noted that the Class using PIAB must have at least one of the two conditions:
· Class inherits System. externalbyrefobject.
· Class implements an Interface.
PolicyInjectiond provides two types of Create Methods: one needs to define the Interface, and the other does not need:
public static TInterface Create<TObject, TInterface>(params object[] args);
public static TObject Create<TObject>(params object[] args);
There are actually two additional loads, so we don't need to introduce them here. In the specific implementation, a PolicyInjector object is finally called for implementation.
Public static TObjectCreate<TObject> (Params object [] args)
{
Return defapolicpolicyinjector. Create <TObject> (args );
}
Public static TInterfaceCreate<TObject,TInterface> (Params object [] args)
{
Returndefapolicpolicyinjector. Create <TObject, TInterface> (args );
}
PolicyInjector is an Abstract Class. The Policies attribute indicates all Policies applied to the object (Policy = CallHandler + Matching Rule)
[CustomFactory (typeof (PolicyInjectorCustomFactory)]
Public abstract classPolicyInjector
{
// Fields
Private PolicySetPolicies;
// Methods
PublicPolicyInjector();
PublicPolicyInjector(PolicySet policies );
Public TInterface