Thursday, July 21, 2011

Autonomous and Intelligent Object

I insist on that the object must be autonomous, that means it should own its data and behavior that can express its features. It will responsible for managing its data and state. The autonomous object reflects the basic principle of OO, that is "The data and the related behaviors should be encapsulated together." The object which owns the behaviors is smart, it is like it has a general sense, to judge for itself, without other objects informing. Other hand, the object should be an expert who can handle and manage its data.

For example, we need handle the parameters which are submitted by the customer to fetch the parameter objects we want to get in our project. The values of these parameters are placed in the web request. In advance we have get the information of parameter type according to the configuration. We provide three kinds of parameter:
1. Simple Parameter
2. Item Parameter
3. Table Parameter

I have defined the ParameterGraph class which can read the configuration info and create the concrete parameter subclass object by the parameter type. These parameter classes extends the same abstract class ParameterBase, and the ParameterBase implements the Parameter interface. The class diagram is as below:

parameter Because the data of parameters are stored in the web request, so we must parse the request. Essentially, the parameters in the web request are stored in the map, we should distinguish the parameters according to their name, then fill the data into the corresponding parameter object. We think it is the process of collecting the data of request and assembling parameter objects. This behavior is defined in the ReportParameterAction class. At first, I implement this function like this:

private Map<String,Parameter> collectParameters(ServletRequest request, ParameterGraph parameterGraph) {
for (Parameter para : parameterGraph.getParmaeters()) {
Map
<String,Parameter> paraMap = new HashTable<String, Parameter>();
if (para instanceOf SimpleParameter) {
String[] values
= request.getParameterValues(para.getName());
para.setValues(values);
paraMap.put(para.getName(),para);
}
else {
if (para instanceOf ItemParameter) {
ItemParameter itemPara
= (ItemParameter)para;
for (Item item : itemPara.getItems()) {
String[] values
= request.getParameterValues(item.getName());
item.setValues(values);
}
paraMap.put(itemPara.getName(),itemPara);
}
else {
TableParameter tablePara
= (TableParameter)para;
String[] rows
=
request.getParameterValues(para.getRowName());
String[] columns
=
request.getParameterValues(para.getColumnName());
String[] dataCells
=
request.getParameterValues(para.getDataCellName());

int columnSize = columns.size;
for (int i=0; i < rows.size;i++) {
for (int j=0;j < columns.size;j++) {
TableParameterElement element
= new TableParameterElement();
element.setRow(rows.get(i));
element.setColumn(columns.get(j);
element.setDataCell(dataCells[columnSize
* i + j]);
tablePara.addElement(element);
}
paraMap.put(tablePara.getName(),tablePara);
}
}
}
return parameterGraph.getParameters();
}

There are different logic to handle the different kind of parameter. For instance, if we want to collect the TableParameter, we must recoginize the row, column and data cells of table, and let their names to be key of map, and the value of map is the String array in fact. When I was writting these code like this, my intuition tell me that it exists bad smell. Maybe something is going wrong? In the collectParameters() method, there is very terrible branch statement. It will determine the runtime type of parameter to choose the differant processing logic. Why should we do like this? According to the class diagram, the parameter subclasses have the different method to process the value of paramter, such as getValue(), getItems(), getElement(). These methods can not be generized into the abstract super class. So that we must downcast the concrete type to invoke the corresponding method.


Now, let’s consider the issue from two perspective. First, who own the data which be processed by branch statement? These data are belong to the related parameter object except the request as the datasource for parameters. So, according to the OO principle described above, we should encapsulate these processing logic into the corresponding parameter subclass.


Second, we might consider about the abstraction of parameter. Although the different parameter has the different value, and the behaviors of process these value are different also, so we can’t generalize these logic. In fact, if you agree this opinion, it means you don’t really understand the meaning of abstract. Because we focused on the details, but not analyze the interface from the perspective of common features.  In the previous description, I have referred to this common feature, that is: whatever value you want to process, you want to fill these value into the parameter object essentially, as for how to fill, it belongs to the scope of implementation details. So we can define such method in the paramter interface:


public interface Parameter {
public void fillData(ParameterRequest request);
}

Note, I play a trick here. Comparing between the fillData() and collectParameters() methods, they accept the different argument. In fillData() method, I provide the ParameterRequest to replace the ServletRequest. What happen?


Two reasons. First, the ServletRequest interface is defined in the Servlet. If Parameter object want to use ServletRequest, it must depends on the Servlet package(Parameter and ReportParameterAction belong to two different packages, we can’t involve the dependency in the package that Parameter belong to). Second, ServletRequest interface is a huge interface, and it is difficult to implement. Consider about this situation: we want to do unit test for Parameter, it is very difficult to mock ServletRequest interface. In fact, we just want to invoke the getParameterValues() operation here. So, I provide the ParameterRequest interface:



public interface ParameterRequest {
public String[] getParameterValues(String name);
}
Let’s continue to look at the processing of parameter. We move the processing logic into the fillData() method of each parameter subclass:
public class SimpleParameter extends ParameterBase {
public void fillData(ParameterRequest request) {
this.setValues(request.getParameterValues(this.getName()));
}
}
public class ItemParameter extends ParameterBase {
public void fillData(ParameterRequest request) {
for (Item item : this.getItems()) {
String[] values
request.getParameterValues(item.getName());
item.setValues(values);
}
}
}
public class TableParameter extends ParameterBase {
public void fillData(ParameterRequest request) {
String[] rows
=
request.getParameterValues(
this.getRowName());
String[] columns
=
request.getParameterValues(
this.getColumnName());
String[] dataCells
=
request.getParameterValues(
this.getDataCellName());

int columnSize = columns.size;
for (int i=0; i < rows.size;i++) {
for (int j=0;j < columns.size;j++) {
TableParameterElement element
=
new TableParameterElement();
element.setRow(rows.get(i));
element.setColumn(columns.get(j);
element.setDataCell(dataCells[columnSize
* i + j]);
this.addElement(element);
}
}
}
}

Now, these parameter objects all are autonomous objects, they can handle their data, don’t need the other object tell them. They have the ability of judge there behavior intelligently. Let’s look at the code snippet of client:


private Map<String,Parameter> collectParameters(ServletRequest request, ParameterGraph parameterGraph) {
for (Parameter para : parameterGraph.getParmaeters()) {
Map
<String,Parameter> paraMap = new HashTable<String, Parameter>();
para.fillData(
new ParameterRequest() {
public String[] getParameterValues(String name) {
return request.getParameterValues(name);
}
);
}
return parameterGraph.getParameters();
}

Because the parameter itself can process its data from the request, so ReportParameterAction doesn’t consider about these complex situations. The structure of the code becomes more clear, and the responsibilities of these objects becomes more accurate. It improves the extensibility of the program due to we do the correct encapsulation to remove the branch statement. That is a benifit of autonomous object. Please keep in your mind that autonomous principle and expert pattern while you want to assign the responsibilities.

Tuesday, June 21, 2011

Design decides on performance

In our legacy system, we found a stupid design decision. We provide the service of business suite for our customer.  The relationship between customer and business suite is stored in the user_busi_suite table. When the administrator of system want to delete the specific business suite, the program should check whether the business suite was subscribed by user at first.  If the business suite had been subscribed, it will popup the alert window to give the noticement.
Guess how to implement this function in the legacy system? It fetched the all user who subscribed this suite, then draw a dicision whether delete it depending on the size of user list:
List users = busiSuiteRepository.fetchSubsribersWith(suiteId);
if (users == null || users.size() == 0) {
    busiSuiteRepository.remove(suiteId);
}
Its implementation will effect on the performance seriously. The query of the user with suite is not necessary. We should better define the property for BusinessSuite which indicates whether it was subscribed by user. Like this:
public class BusinessSuite {
    private boolean subscriptionFlag = false;
    public boolean isSubscribed() {
        return subscriptionFlag;
    }
    public void subscribe() {
        subscriptionFlag = true;
    }
}

BusinessSuite bs = busiSuiteRepository.with(suiteId);
if (!bs.isSubscribed()) {
    busiSuiteRepository.remove(suiteId);
}
Don't need to query the list of user, we only get the value of subsription flag to draw a decision. That's easy!

Friday, July 23, 2010

SessionStore in Rails

Recently, I am learning how to use Rails( the version is 2.3.5,ruby version is 1.8.6). I need to use session  in Rails。Considering about the scalability, we should store the session to database(MySQL 5.x). The default value of session_store in Rails is File, so we need to modify the configuration of environment.rb file which is located in config foler. The setting is:

config.action_controller.session_store = :active_record_store

But, unexpected progress is in the matter, when I modified the setting value, the website can not be open(Firefox), the error page is as below:imageI know it must be session issue. After checking the session table in database and the configuration of session in rails, no problem was found. But when I checked the log, I found the following error information:

Status: 500 Internal Server Error
  Mysql::Error: Data too long for column 'session_id' at row 1: INSERT INTO `sessions` (`updated_at`, `session_id`, `data`, `created_at`) VALUES('2010-07-23 01:17:05', 'BAh7CDoPc2Vzc2lvbl9pZCIlMDNlNTg0ZDg2NjJkZmFjZTdiMjUwN2ExNmYyNGNjNjAiCmZsYXNoSUM6J0FjdG
lvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA6EF9jc3JmX3Rva2VuIjFWNStxMUc
4bThmUmJHQ3VHRGQ3Q2RJR2p1aStUejdtRmRtc0xJY1RIOUNrPQ==--6f29ea421cd0f14d5e8de70935515c26021c254f', 'BAh7AA==\n', '2010-07-23 01:17:05')

Obviously, the crux of problem is clearly that the session_id is too long instead of configuration of session. So I checked the columns of sessions table. Its definition is:

image However, I had setted the value of secret of session, like this:

config.action_controller.session = {
  :key => '_session',
  :secret      => 'f914e9b1bbdb829688de8512f5fea7d8e83fb35bfe2b56bcf1e6438d1d1b7837c532f8c2ece2a2
d0e37812e9b210824089b1810a4e238a61dfd922dc9dd62521'
}

Despite I changed it into shorter value, the error is still.

Finnally, I found the answer.It is incredible but simple. That is clearing cookie in the explorer. That's OK! Now, let's query the session table:

image

The length of session_id becomes normal and the system becomes normal too. It is really a trap that is tend to be ingored. Mark it!

Friday, July 16, 2010

Installing RadRails Plug-in in the MyEclipse

Preparation: Before installing it, I suggest you'd better install ruby, rails and MySQL at first. The installer package for ruby that I choose is rubyinstaller for windows version, you can download it from http://rubyinstaller.org. The process of installing is very simple, you only need to follow the wizard and do it(Notice: During installing, you must make sure the checkbox of Enable RubyGems option is checked. It's the default value):

image

After installing completed, you can open the command window and input the command line: Ruby –v. If the version of ruby can be displayed normally, it proved the installing was sucessful!(The installer would add the system path for ruby automatically. And my version of ruby is 1.8.6.)

Next, we should update Gem system. You can execute the command on the command windows: gem update –system; then we should execute the command "gem install -v=2.3.5 rails" to install Rails. After installed, you can execute the command "rails –v" to check the version of Rails.

Now, you need to install MySQL. Please pay attention to the version number of MySQL. It seams like the MySQL adapter that ruby provided is just for 5.0 version. The version of MySQL DB that I installed is 5.0.18 for windows. You can download it from MySQL official website. Here I omitted the process of installing for MySQL. If you want to verify whether the installing is sucessful, you might input the command line as below (Suppose the username and password all are "root"):

02

Then we need to install the gem for mysql. The gem I choosed is mysql-2.8.1-x86-mswin32.gem. After you fetch this file and save it in the installing directory for ruby(Suppose it is c:\ruby), you can switch the current directory to c:\ruby, and execute command line:

gem install mysql-2.8.1-x86-mswin32.gem

03 The system will give the result it showed "Sucessfully installed". But I don't know the specfic reason that the gem installer cann't install the related document and give the error message "No definition for …".

Now, we are going to install the RadRails plug-in, it is my topic of this article. The version of MyEclipse is 8.5. The installing steps are as below:

1. Start MyEclipse application, and choose "MyEclipse" menu, go to "MyEclipse Configuration Center" item.

04

2. Choose "Software" tag, and click the "add site" on the left side:

05 It will pop up the dialog box:

06

3. Input the name of site(The naming is up to you, I choose the name "Rails") and URL:

http://download.aptana.com/tools/radrails/plugin/install/radrails-bundle

4. Now, the section which name is "Personal Site" emerges from the left side. There is the name of new site "Rails":

07 5. Click the site name, the system will connect to the URL and download the remoted data. It will last about one minute, then it will show the treeview structure of Rails-Aptana RadRails. Now you can click it, there should be show the message "Managed Changes:1 Change" in the Pending Changes section which locates in the bottom and right of the screen. 

08

6. Click "Apply 1 Change" button, the system will validate the MyEclipse at first:

 0910 Then, It will pop up the dialog box "Accept Software License". We might select it and click "Next" button:

11Then, click "Update" button, it is executing update operation:

12 

The process will progress slowly. It is very strange that it is normal at the beginning of update operation, but the error occurs frequently at the end of operation:

13 Don't worry! Please wait for a few minutes, it will pop up the dialog box to show the error information:

14 At this time, you must click "Back" button to back the dialog box, and click "update" button again, it will restart to update. It will skip the sucessful operation and repeat the wrong operation. Unfortunally, only one wrong operation can be corrected every time, the error diaglog box is popped up as ghost. I don't know how to handle this issue, the one way that I know is to repeat the process until it corrected all wrong operation. I try to find out the reason, but I failed. The error is not related to bandwidth of network.

This terrible process will be lasted for about two hours andsuccessful final. The system requires to restart MyEclipse.

After installing the plug-in, the system maybe notices the user to auto-install gems, the diagram is as below:

15 Although I didn't try these, but I strongly suggest not to install these gems because these gems with the latest version will be conflicted with the existed version.

To validate whether the plug-in had been installed sucessfully, and integrated with ruby, rails and MySQL, we might create the sample project to check it. At first, we will change the view in MyEclipse into RadRails:

16 Then, you can create a new rails project on the ruby explorer:

17 Input the project name "Sample" in the pop-up dialog box, and select mysql database:

18Click the "Finish" button, it will execute the rails command automaticlly and generate the related folders and files with Rails. The directory structure is as below:

19  Now, please open the database.yaml file in the directory with "config", and modify the configuration like this(The precondition is that I had create the database "oa" in the MySQL):

20 Then switch to the server window in MyEclipse, and start the server of Sample project(using WebRick server) with the default port number 3000. After server is running, you can open the explorer such as Internet Explorer or Firefox, and input the URL:http://localhost:3000:

21 Click the link "About your application's environment", if the information related to ruby can be display correctly, it proved the plug-in had been installed sucessfully and the development environment was prepared normally.

Tuesday, July 13, 2010

Art of Object Design on MPDay

Last sunday, I gave a speaking as a speaker in the msup open day conference in ShangHai. My topic was Art of Object Design, mainly focused on low level design in the software development lifecycle. I want to dig the essence of software design deeply. As we know, there are amount of principles and patterns for software achitecting and design. It is impossible to grasp all of these. You know more, understand less. Through by analyzing the essence and core idea of design patterns and architecture patterns, I summarized seven principles including Reusability, Extensibility, Separation, Change, Simplicity, Consistency, Indirection.

   

Reusability

The most evil enemy in the software development is repeat. It results in developing repeaded so that we cann't reuse some components and functions effeciency. It would generate the bad smell of solution sprawl. How to avoid repeat? We must keep the objects to be fine-grained and high cohesion. It needs to use encapsulation reasonable.  We can improve the reusability of software based on three-level: method, class and module. For instance, we can extract method or super class, define some helper class, devide modules by dependency.  The following diagram demonstrates how to apply template method pattern to reuse some codes in JUnit Framework:

image

Extensibility

The excellence structure of software should be extensible so that we can add functions without modifying the source code. There are two meaning of extensibility. First we don't add new features to expose it, that is an extensiblity internal.  We can decorate the target object or control it by providing a proxy object. Second is an extensiblity external. The inheritance and composition are the common approach. Of course, we don't forget the abstraction. For example, the Runnable interface in Java supports the extensiblity of business when you want to write the multi-thread program.

class MyThreadStart implements Runnable {
     public void run()  {
        //do something
    }
}

Thread controller = new Thread(new ThreadStart());
controller.start();

Separation

Concern separation is the most important principle for architecting architecture. The classic patterns which embody the concern separtion are layered architecture pattern and MVC pattern. The key element of separtion is that seperating changeful resposiblity from changeless object. That is core value of SRP also. Of course, we must focus on the collaboration between objects too. The following diagrams list my opinon about separation:  imageimage image

Change

One of the inevitable thing is change in the software development. So we must find the change point when we analyze the requirement. In my experience, I think these points always are changed as below:

1. Business Rule (Solution: Specification Pattern)

2. Algorithm and Strategy (Solution: Strategy Pattern)

3. Command and Request (Solution: Command Pattern)

4. Hardware Environment (Solution: Abstraction Layer or Gateway Pattern)

5. Protocol and Standard (Solution: Metadata)

6. Data Schema (Solutioin: Encapsulate Data or Information Hide)

7. Business Flow (Solution: Customize Workflow)

8. System Configuration (Solution: Metadata or Database)

9. User Interface and Presentation (Solution: Layered Pattern, MVC Pattern)

10. External Services (Solution: Abstraction Layer or Service Facade)

Simplicity

There are two important principles we have always to keep in mind. These are KISS(Keep it simple and stupid) and YAGNI(You aren't gonna need it). How to simplify the complex implemention? We can use encapsulation to hide the complex implementation. The abstraction is the other way. It might unify model, and elimilate the difference. As architect, most of them want to pursue the perfect solution. It's wrong! Many anti-patterns related with this idea, such as Analysis Paralysis, Accidental Complexity.

Consistency

So called "Consistency" includes interface, format, invoking way and solution. If we have consistant interface, so the implementation can be substituted. If we have consistant format, we can infer the overall design from the part of design. Consistant invoking way would help client understand the intention of service provider. Consistant solution is the basic of cooperation of team. For example, we can achieve the consistant invoking way by using composit pattern:

image Indirection

David Wheeler said:"All problems in computer science can be solved by another level of indirection". Indirection in software programming is achieved by delegation, abstraction and collaboration. Indirection can reduce dependence, hide the detail and simplify the client call. Many pattens are reflect the indirection thought, such as Facade Pattern, Mediator Pattern, Adapter Pattern, Strategy Pattern, Service Locator Pattern.

Friday, July 9, 2010

Hunt a new Job

I changed my job recently. When I worked in a new company, I found everything is not ok. I don't like my job responsibility. As current role, I need to go out on business often, and communication with some fresh guys who don't understand what is software developing. I felt I am a cheater. That's terrible.

I need change, but the new job can't give me new opportunity and challedge. Of course, the new job can't help me improve my ability. Don't hesitate, I want to hunt a new job.

I am back

Don't ask me the reason why I don't update my blog these days. Maybe I am lazy. In fact, I can't visit the wordpress normally in China. Fortunately, one of my freinds introduce one way to handle this issue, so I am back.

These days, I don't stop keeping my moving. I wrote and posted some articles about software in my chinese blog(http://www.agiledon.com). Well, I'll continue to write these with english version. Wait for me. Thanks.

Monday, June 1, 2009

Stream operation in WCF

WCF provides the support for Stream object. It typically recommends the developer to handle the message which size is too large as Stream object for the sake of high performance.

However, there are some constraints on Stream operation to note:
1. The constraint of Binding

The valid bindings include BasicHttpBinding, NetTcpBinding and NetNamePipeBinding for Stream operation. In addition, we can’t use Reliable Messaging while handling the Stream object. If you are considering about the security of message, this way is not a good choice.

2. The constraint of Stream object

The object, you want to transport as a parameter with WCF Operation, must be serializable. Unfortunately, FileStream class can’t be serialized. We have to use Stream, MemoryStream. The Stream class is the main option for handling a stream object.

It is very interesting of transform between FileStream and Stream class. For example, the following implementation of the operation in a service:
public Stream TransferDocument(Document document)
{
     FileStream stream
= new FileStream
                             (document.LocalPath, FileMode.Open, FileAccess.Read);
    
return stream;
}

Note, the type of return value of TransferDocument() method is Stream. But the ture type should be FileStream. Due to FileStream is the subclass of Stream, so it is no problem according to polymorphism of OO. When the client want to invoke TransferDocument() method, we can’t assign the return value to FileStream object in fact:
FileStream stream = m_service.TransferDocument(doc);

The value of stream object is null now. So we must do like this:
Stream stream = m_service.TransferDocument(doc);

It is strange that WCF can’t serialize the Length property of Stream object. On the client side, we can not use the Length property. If you want to do, it will throw a NotSupportedException.

3. The constraint of TransferMode

The default value of TransferMode is setted to Buffered. If you want to use Stream operation, you must change the default setting of TransferMode. We can set it to Streamed, StreamedRequest or StreamedResponse according to the different cases.

4. The constraint of MaxReceiveMessage

The default value of MaxReceiveMessage property is 64kb. If the size of transported stream object exceeds the setting value of MaxReceiveMessage, It will throw a CommunicationException during the client invokes the operation of service to handle this stream object. So we should change the value depending on the specific situation. The value ranges from 1 to 9223372036854775807(i.e. Int32.MaxValue). If the setting value is outside the range, the program can not be compiled successfully. Set its value in programmatic:
binding.MaxReceivedMessageSize = 120000;

And set it in administrative:
<binding …… maxReceivedMessageSize=120000/>

5. The constraint of Operation Parameters

WCF applies the strict constraint on the parameters of operation including stream objects. There can be only one stream object as the parameter(in, out, ref parameter or return value) in the method signature. So these definitions of the method are all invalid as below:
void Transfer(Stream s1, Stream s2);
void Transfer(Stream s1, out Stream s2);
void Transfer(Stream s1, ref Stream s2);
Stream Transfer(Stream stream);

If you define the method like above, it will occur the run-time error.

6. The constraint of Instance Activation

Because we can only use the BasicHttpBinding, NetTcpBinding or NetNamedPipeBinding in the stream operaiton, it will impact on the mode of instance activation, in particular Session mode. First, BasicHttpBinding doesn’t support Session mode. Secondly, although the other bindings(NetTcpBinding or NetNamedPipeBinding) support Session mode, we can’t set the value of ReliableSession to the true because the stream operation doesn’t support reliable messaging. So if you set the value of SessionMode to SessionMode.Required for the service, it will throw an exception.

In fact, the stream operation(i.e. the value of TransferMode is not Buffered) itself doesn’t support Session mode. Even we set the value of SessionMode to Allowed, and set the value of InstanceContextMode to PerSession while using NetTcpBinding, the behavior of service is still PerCall mode. And the value of SessionId(get it through by OperationContext.Current.SessionId) should be null at this time.

Finally, I recommend you increase the value of SendTimeOut property because the calling a large stream object will last too long time. For example, set its value to 10 minutes in programmatic:
binding.SendTimeout = TimeSpan.FromMinutes(10);

Or set it in administrative:
<binding …… sendTimeout=00:10:00/>

Note, the configuration of Binding on the service and client side must keep in consistent.

This article on C# Corner.

Sunday, May 17, 2009

Focus on the Extension of WCF Behavior

WCF provides the flexible and extensible architecture for the developer. The most common situation is to customize the extension of behavior. It is not complex, but some issues should be noticed. This article is prepare to discuss how to extend the behavior in WCF.

On the service side, if we want to extend the behavior, we need to extend the DispatchRuntime and DispatchOperation. The points of extension include inspecting parameters, messages and invoker of operations. The corresponding interfaces are IParameterInspector (to inspect parameters), IDispatchMessageInspector(to inspect messages) and IOperationInvoker(to invoke the operation). On the client side, we should extend the ClientRuntime and ClientOperation, and the points of extension include inspecting parameters and messages. The corresponding interfaces are IParameterInspector and IClientMessageInspector. All interfaces are placed in System.ServiceModel.Dispatcher namespace. Note please that IParameterInspector can be applied both service side and client side.

It seems like implementation of AOP (Aspect Oriented Programming) to implement these interfaces. We can inject some additional logic before and after invoking the related methods, so we call these extensions “Listener”. For example, There are some methods in IParameterInspector interface as below:

void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState);

object BeforeCall(string operationName, object[] inputs);

 

BeforeCall() method will be invoked before we invoke the target method of the service object, and AfterCall() method will be occured after the target method is invoked. For instance, we can validate if the value of parameter is less than zero before the method is invoked. If yes, it will throw an exception:

public class CalculatorParameterInspector : IParameterInspector

{

    public void BeforeCall(string operationName, object[] inputs)

    {

        int x = inputs[0] as int;

        int y = inputs[1] as int;

        if (x < 0 || y < 0)

        {

            throw new FaultException(“The number can not be less than zero.”);

        }

        return null;

    }

 

    public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)

    {

        //empty;

    }

}

 

It distinguishs between the service and client side to inspect the parameter, and the methods of interface are quite converse to the order of messaging(Note: IDispatchMessageInspector interface includes BeforeSendReply() and AfterReceiveRequest(); and IClientMessageInspector interface includes BeforeSendRequest() and AfterReceiveReply()). We might handle the message through by the methods of this interface, for example, printing the message header:

public class PrintMessageInterceptor : IDispatchMessageInspector

{

    #region IDispatchMessageInspector Members

 

    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)

    {

        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);

        request = buffer.CreateMessage();

 

        Console.WriteLine(“After Receive Request:”);

        foreach (MessageHeader header in request.Headers)

        {

            Console.WriteLine(header);

        }

        Console.WriteLine(new string(‘*’, 20));

        return null;

    }

 

    public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)

    {

        MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);

        reply = buffer.CreateMessage();

 

        Console.WriteLine(“Before Send Request:”);

        foreach (MessageHeader header in reply.Headers)

        {

            Console.WriteLine(header);

        }

        Console.WriteLine(new string(‘*’, 20));

    }

 

    #endregion

}

 

There are four different kinds of behaviors including Service Behavior, Endpoint Behavior, Contract Behavior and Operation Behavior. Their corresponding interfaces are IServiceBehavior, IEndpointBehavior, IContractBehavior and IOperationBehavior. Although they are different interface by nature, but their methods are almost similar including: AddBindingParameters(), ApplyClientBehavior() and ApplyDispatchBehavior().

 

Note: Because IServiceBehavior is only used on the service side, so it has no ApplyClientBehavior() method.

 

We can customize our class to implement these interface, but some key elements should be underlined:

1. The scope of the behavior. Table 1 describes all situations:

Behavior Type

Interface

Scope

Service

Endpoint

Contract

Operation

Service

IServiceBehavior

Y

Y

Y

Y

Endpoint

IEndpointBehavior

 

Y

Y

Y

Contract

IContractBehavior

 

 

Y

Y

Operation

IOperationBehavior

 

 

 

Y

 

2. We can add the extension of service behavior, contract behavior and operation behavior by applying on custom attribute, but can not add the extension of endpoint behavior in this way.  We can add the extension of service behavior and endpoint behavior by using config file, but can not add the extension of contract behavior and operation behavior in this way. All behaviors can be added by ServiceDescription.

 

To add the extended behavior by applying on custom attribute, we can let the custom behavior derived from Attribute class. Then we can apply it on service, contract or operation:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]

public class MyServiceBehavior:Attribute, IServiceBehavior

{}

 

[MyServiceBehavior]

public interface IService

{ }

 

If you want to add the extended behavior by using config file, you must define a class derived from BehaviorExtensionElement (It belongs to System.ServiceModel.Configuration namespace) class, then override the BehaviorType property and CreateBehavior() method. BehaviorType property returns the type of extended behavior, and CreateBehavior() is responsible for creating the instance of the extended behavior:

public class MyBehaviorExtensionElement : BehaviorExtensionElement

{

    public MyBehaviorExtensionElement() { }

    public override Type BehaviorType

    {

        get { return typeof(MyServiceBehavior); }

    }

 

    protected override object CreateBehavior()

    {

        return new MyServiceBehavior();

    }

}

 

If the element which should be configured add the new property, we must apply the ConfigurationPropertyAttribute on this new one:

[ConfigurationProperty("providerName", IsRequired = true)]

public virtual string ProviderName

{

    get

    {

        return this["ProviderName"] as string;

    }

    set

    {

        this["ProviderName"] = value;

    }

}

 

The detail of config file like this:

<configuration>

  <system.serviceModel>

    <services>

      <service name=MessageInspectorDemo.Calculator>

        <endpoint behaviorConfiguration=messageInspectorBehavior

                  address=http://localhost:801/Calculator

                  binding=basicHttpBinding

                  contract=MessageInspectorDemo.ICalculator/>

      </service>

    </services>

    <behaviors>

      <serviceBehaviors>

        <behavior name=messageInspectorBehavior>

          <myBehaviorExtensionElement providerName=Test/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

    <extensions>

      <behaviorExtensions>

        <add name=myBehaviorExtensionElement

            type=MessageInspectorDemo.MyBehaviorExtensionElement,

            MessageInspectorDemo,

            Version=1.0.0.0,

            Culture=neutral,

            PublicKeyToken=null/>

      </behaviorExtensions>

    </extensions>

  </system.serviceModel>

</configuration>

 

Please notes the contents which font are bold. <myBehaviorExtensionElement> is our extended behavior, and providerName is the new property of MyBehaviorExtensionElement. If you extended IEndpointBehavior, <serviceBehaviors> section should be replaced with <endpointBehaviors>. The extensions of custom behaviors will be placed in the <extensions></extensions> section. The value of name attribute must match the configuration of <behavior> section, both are “myBehaviorExtensionElement”.

 

The value of type inside the <behaviorExtensions> section you want to add must be the full name of type. The first part of the full name is the full type name, and the second part of the name is the namespace. Version, Culture and PublicKeyToken are also indispensable elements. The string of type name use the comma as a splitter. After the comma, it must left a space, otherwise we can not add the configuration of extended behavior normally. Why does it give the awful constraint here? Because the value is prepared for reflect technology. I agree that it is a defect. I hope microsoft will solve this problem in the next release of WCF.

 

3. In the body of related methods, we need to add the extensions of checking parameters, messages and operation invoker. The relationship between the extension of them exists here. For checking parameters, the logic of extensions might be added in ApplyClientBehavior() and ApplyDispatchBehavior() of IOperationBehavior interface. For example, we can define a CalculatorParameterValidation class for CalculatorParameterInspector:

public class CalculatorParameterValidation : Attribute, IOperationBehavior

{

    #region IOperationBehavior Members

    public void AddBindingParameters(OperationDescription operationDescription,

        BindingParameterCollection bindingParameters)

    {

    }

 

    public void ApplyClientBehavior(OperationDescription operationDescription,

        ClientOperation clientOperation)

    {

        CalculatorParameterInspector inspector = new CalculatorParameterInspector();

        clientOperation.ParameterInspectors.Add(inspector);

    }

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription,

        DispatchOperation dispatchOperation)

    {

        CalculatorParameterInspector inspector = new CalculatorParameterInspector();

        dispatchOperation.ParameterInspectors.Add(inspector);

    }

 

    public void Validate(OperationDescription operationDescription)

    {

    }

    #endregion

}

 

If it is not necessary to seperate the inspector from the extended behavior, a better solution is to let a custom class implement both IParameterInspector and IOperationBehavior. For example:

public class CalculatorParameterValidation : Attribute, IParameterInspector, IOperationBehavior

{

    #region IParameterInspector Members

    public void BeforeCall(string operationName, object[] inputs)

    {

        int x = inputs[0] as int;

        int y = inputs[1] as int;

        if (x < 0 || y < 0)

        {

            throw new FaultException(“The number can not be less than zero.”);

        }

        return null;

    }

 

    public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)

    {

        //empty;

    }

    #endregion

 

 

    #region IOperationBehavior Members

    public void AddBindingParameters(OperationDescription operationDescription,

        BindingParameterCollection bindingParameters)

    {

    }

 

    public void ApplyClientBehavior(OperationDescription operationDescription,

        ClientOperation clientOperation)

    {

        CalculatorParameterInspector inspector = new CalculatorParameterInspector();

        clientOperation.ParameterInspectors.Add(this);

    }

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription,

        DispatchOperation dispatchOperation)

    {

        CalculatorParameterInspector inspector = new CalculatorParameterInspector();

        dispatchOperation.ParameterInspectors.Add(this);

    }

 

    public void Validate(OperationDescription operationDescription)

    {

    }

    #endregion

}

 

While operation invoker is associated with IOperationBehavior, but in fact, it will do with Invoker property of DispatchOperation. Assume that we have defined a MyOperationInvoker class which implement the IOperationInvoker interface, the solution is:

public class MyOperationInvoker : IOperationInvoker

{

    //some implementation

}

 

public class MyOperationInvokerBehavior : Attribute, IOperationBehavior

{

    #region IOperationBehavior Members

    public void AddBindingParameters(OperationDescription operationDescription,

        BindingParameterCollection bindingParameters)

    {

    }

    public void ApplyClientBehavior(OperationDescription operationDescription,

        ClientOperation clientOperation)

    {

    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription,

        DispatchOperation dispatchOperation)

    {

        dispatchOperation.Invoker = new MyOperationInvoker(dispatchOperation.Invoker);

    }

    public void Validate(OperationDescription operationDescription)

    {

    }

    #endregion

}

 

As far as message inspecting with Dispatch are concerned, we can add it using MessageInspectors property in DispatchRuntime owned by  IServiceBehavior, or IEndpointBehavior, or IContractBehavior. For message inspecting with Client, we can add in using MessageInspectors property in ClientRuntime owned by IEndpointBehavior or IContractBehavior(IServiceBehavior can not be used on the client side, so it’s not IServiceBehavior’s business). For example:

public class PrintMessageInspectorBehavior : IDispatchMessageInspector, IEndpointBehavior

{

    #region IEndpointBehavior Members

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)

    {

        //empty;

    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)

    {

        clientRuntime.MessageInspectors.Add(this);

    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)

    {

        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);

    }

    public void Validate(ServiceEndpoint endpoint)

    {

        //empty;

    }

    #endregion

 

    //The implemenation of DispatchMessageInspector; Omitted

}

 

If our behavior implement the IServiceBehavior, we must iterate the ServiceHostBase object in the ApplyDispatchBehavior() method:

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)

{

    foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)

    {

        foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)

        {

            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);

        }

    }

}

 

This article on CSharp Corner.