In the previous article, we have introduced that by specifying a custom channelsinkprovider in the configuration file, we can add our own channelsink to the pipeline. Now we can add our own information processing module, however, the objects we can operate on here are formatted messages (that is, data streams), and we cannot see the original message objects, which will inevitably affect the extended functions we can implement. In Figure 1 above, we can see that in addition to channelsink extension, we can also add custom messagesink, which is located before the formatter, that is to say, in messagesink, we can directly operate unformatted message objects. In this case, we will get a more powerful extension point. What does it mean to directly operate on message objects? To put it simply, we can implement method interception here. We can modify the parameters and return values of the method, and add our own processing logic before and after calling the method. Do you think it sounds familiar? That's right. This is exactly what AOP wants to achieve. Next, after learning about the background of remoting and the Expansion Mechanism of channelsink, we will further introduce the extension mechanism of messagesink.
Before the introduction, I would like to remind readers of the following points:
1. Are you sure you want to learn more about the internal mechanism of remoting;
2. Are you sure you can understand the previous article well;
3. if the content summarized in the previous article is large, most of the content in this article is the author's personal exploration. I think I have never introduced this content in other materials (including English materials, therefore, I do not guarantee the correctness of all opinions. If you think there is anything wrong with it, you are welcome to comment on it.
Let's start to have a big meal.
Use channelsinkprovider to extend messagesink
Messagesink extensions can be implemented in two ways. Let me start with a simple one. In the previous article, we have introduced that by specifying a custom channelsinkprovider in the configuration file, we can add our own channelsink to the pipeline. Is there an imessagesinkprovider similar to iclientchannelsinkprovider? Unfortunately, the answer is no. Can we insert a messagesink through iclientchannelsinkprovider? Can it play its role after being inserted?
First, implement a custom messagesink. In this case, you only need to create a new class and implement the syncprocessmessage method in the imessagesink interface (for the sake of simplicity, we only need to consider synchronous call mode). In the method, we can directly operate the message object, for example, we can add additional attributes to the message, as shown below:
1: public class CustomMessageSink:IMessageSink
2: {
3: public IMessage SyncProcessMessage( IMessage msg )
4: {
5: // Add some custom data into msg.
6: ((IMethodMessage)msg).LogicalCallContext.SetData("MyName" , "idior" );
7: return m_NextSink.SyncProcessMessage( msg );
8: }
9 :}
Code 1
In Figure 2 of the previous article, we can see that iclientchannelsinkprovider creates a channelsink using the following method.
1: public IClientChannelSink CreateSink(IChannelSender channel, string url,
2: Object remotechanneldata ){...}
Code 2
Note that the returned value is iclientchannelsink instead of imessagesink, so that we cannot insert custommessagesink that only implements the imessagesink interface. Therefore, we have custommessagesink also implement the iclientchannelsink interface, but when implementing the methods in the iclientchannelsink interface, we throw all exceptions to indicate that these methods should not be called. In this way, we can use channelsinkprovider to create a messagesink. Now the problem arises. Although this messagesink is created, is it inserted into the pipeline? In fact, we missed a problem in the previous article-How channelsinkprovider was inserted into the pipeline and understood its principles, the above problems are solved naturally.
Pipeline
What is pipeline? We have not explained this concept clearly. Is there an object called pipeline or similar name? Sorry to tell you, no! It can be said that pipeline is an abstract concept, which indicates a set of sinks that pass through from realproxy to stackbuildsink when we call a remote object, however, there is no separate linked list to link all these sinks. That is to say, there is no large sink linked list. After you trigger the remote method, we will extract the sinks from the linked list one by one and process the messages one by one. However, a linked list consisting of channelsink is maintained in the remote object proxy. However, you must note that it does not represent the entire pipeline, but is only part of it. Later we will see that pipeline includes many other types of sinks. This linked list is saved in the _ identity object of realproxy. The linked list is linked through the next attribute of iclientchannelsink. The first element of the linked list is saved in the _ identity object, other elements can be obtained through the next attribute, as shown in:
Next let's take a look at how this linked list is obtained. Whenever we call a remote method through transparentproxy, as shown in, the internalinvoke method in remotingproxy is finally called. It will create and link each Channel sink.
1: internal virtual IMessage InternalInvoke(IMethodCallMessage reqMcmMsg,
2: bool useDispatchMessage, int callType)
3 :{
4: //...
5: if (identity.ChannelSink == null)
6: {
7: IMessageSink envoySink = null;
8: IMessageSink channelSink = null;
9: if (!identity.ObjectRef.IsObjRefLite())
10: {
11: RemotingServices.CreateEnvoyAndChannelSinks(null,identity.ObjectRef,
12: out envoySink , out channelSink );
13: }
14: else
15: {
16: RemotingServices.CreateEnvoyAndChannelSinks(identity.ObjURI, null,
17: out envoySink , out channelSink );
18: }
19: RemotingServices.SetEnvoyAndChannelSinks(identity, envoySink,channelSink );
20: if (identity.ChannelSink == null)
21: {
22: throw new RemotingException("..."));
23: }
24: }
25: //...
26 :}
Code 3
The first judgment Statement (Line 5) It indicates that the work of creating and linking channelsink only takes place in the first call, and the first result will be reused in each call in the future. The second Judgment Statement (Line 9) for the moment, I only need to know that two sink chains will be created in the next step. One is the envoysinl chain, and the other is the channelsink chain, the former will be passed to the local variable channelsink through the out keyword. In the createenvoyandchannelsinks method, the creation task of the channelsink chain will be handed over to the channel object. As for how the channel object works with channelsinkprovider, we have already introduced it in the previous article.
I wonder if you have noticed that the local variable channelsink (line 8) is of the imessagesink type instead of iclientchannelsink type.Let's talk about the key points!Clearly we created a channelsink chain, but set the type of the Header element to imessagesink. Why? Do you know what an element of the channelsink chain is when using httpchannel? -- Soapclientformattersink. Do you think it should be a message sink or a Channel sink? It is responsible for the format of the message object as a data stream. The operation object is the original message and should naturally be a messagesink. Well, after a long time of remoting, there is an example of using iclientchannelsinkprovider to expand messagesink (you can find soapclientformattersinkprovider in the class library ). As described earlier, although soapclientformattersink is a messagesink, to use iclientchannelsinkprovider to insert it into pipeline, it also has to implement the iclientchannelsink interface, in addition, you can see that it throws all exceptions when implementing methods in the iclientchannelsink interface. As follows:
1: public class SoapClientFormatterSink :IMessageSink, IClientChannelSink//...
2: {
3: //...
4:
5: //Implement method in IMessageSink
6: public IMessage SyncProcessMessage(IMessage msg)
7: {
8: IMethodCallMessage message1 = (IMethodCallMessage) msg;
9: try
10: {
11: ITransportHeaders headers1;
12: Stream stream1;
13: Stream stream2;
14: ITransportHeaders headers2;
15: this.SerializeMessage(message1, out headers1, out stream1);
16: this._nextSink.ProcessMessage(msg, headers1, stream1,
17: out headers2, out stream2);
18: if (headers2 == null)
19: {
20: throw new ArgumentNullException("returnHeaders");
21: }
22: return this.DeserializeMessage(message1, headers2, stream2);
23: }
24: catch (Exception exception1)
25: {
26: return new ReturnMessage(exception1, message1);
27: }
28: catch
29: {
30: return new ReturnMessage(new Exception("...")), message1);
31: }
32: }
33:
34: //Implement method in IClientChannelSink
35: public void ProcessMessage(...)
36: {
37: throw new NotSupportedException();
38: }
39:
40: //...
41 :}
Code 4
Then in the internalinvoke method (Code 3), we use the setenvoyandchannelsinks method (line19) to assign the locally assigned variable channelsink to the _ channelsink variable of the Identity object in the remotingproxy object, in this way, a Channel sink is linked together and can be accessed by proxy objects.
Now we can determine that the new messagesink can be inserted into the pipeline through iclientchannelsinkprovider. Due to the existence of soapclientformattersink, we can fully believe that messagesink inserted into the Channel sink chain can work normally (that is, execute the methods in imessagesink instead of the methods in iclientchannelsink ), however, to make it clearer about the underlying implementation of remoting, we still want to explore how it calls the sink in the channelsink chain to process messages. Is the sequence diagram generated by calling a remote method:
View Source images
In, we can see that the callprocessmessage method will be called in the internalinvoke method, and it will send the message object to the first sink in the channelsink chain for processing. As follows:
Remotingproxy. callprocessmessage (identity. channelsink, reqmsg ,...);
Code 5
However, we can find that the first parameter of the callprocessmessage method is of the imessagesink type. That is to say, the first sink inserted to the pipeline through iclientchannelsinkprovider is imessagesink rather than iclientchannelsink. This also makes it clear that messagesink inserted into the Channel sink chain can work normally. It is precisely for this reason that soapclientformattersink can play its role.
In addition, when using iclientchannelsinkprovider to insert messagesink, it must be inserted before formattersink. Because the message can be processed through messagesink only before it is formmat, and the modification to the message by the sink after the format is invalid. This is reflected in the configuration file that the custom sinkprovider must be placed before the formatter. But this is for the client, the server side is just the opposite.
<channel ref="http"> <clientProviders> <provider type="CustomSinks.CustomSinkProvider,CustomSinks" /> <formatter ref="soap" /> </clientProviders></channel>
When the client inserts channelsink, the custom sinkprovider is placed behind the formatter. You can find this in figure 2 of the previous article.
Summary
This section describes how to use iclientchannelsinkprovider to add messagesink to the pipeline, so as to modify the message object in a remote method call, and implement more powerful functions. This article introduces the internal implementation mechanism of remoting to help you better understand the remoting framework.
The next section describes how to intercept and modify messages when the client and server objects are in the same appdomain. More types of sinks are involved.
Series of articles
A little nonsense:
The format of the article is very beautiful in Firefox. Why is it very wide in IE. Depressed, who knows how to fix it, please let us know.