Simple Service Abstraction

tukwilagleefulInternet and Web Development

Oct 31, 2013 (3 years and 9 months ago)

79 views

Simple Service Abstraction

By David Catherman

Overview

This presentation is about abstracting the service layer of an N
-
Tiered application.


The technique is to

create one generic

WCF service that uses reflection to pass any
business object across the serv
ice tier and then abstract

the whole layer

so the
developers build the application as if it were all running on one machine.



Part of the problem with

the service layer is that the proxy object generated for the
presentation layer does not match exactly
the business object on the business layer.


Therefore, developers must code differently depending on whether they are coding
the presentation layer or the business layer.


With
this

strategy, the same business
object can be used in exactly the same way on
either side of the service layer and
the application can be built as if it were running locally but then push it to a
distributed environment without much change.


This presentation will include an
overview of enterprise N
-
tiered architecture
and

the advan
tages of separating out the business
tier on an application server.

Background

N
-
Tiered Enterprise Applications

The concept of separating an application into tiers
and layers has been around for many years and is
largely a result of correcting the problems

encountered in the Client
-
Server era. Isolating
specific functionality into a layer of an application
helps abstract it so it can be managed separately
without knowledge of the whole application

and
can easily be reused by other similar applications
.

The
terminology might be confusing here. The
layers of an application are abstract
implementations of architectural principles to
group and segregate

specific types of functionality.
A tier defines how an application can be separated
into different computers
. The Data Access Layer
actually spans the tier separation between the application server and the database
server. The Service Layer spans the tier separation between the application server
and the client PC or Web Sever. The ability to separate layers o
f an application to
different platforms

is the definition of enterprise applications.

Business Tier

So why are enterprise applications important? Is the added complexity

worth the
effort? Basically, the whole point is to add a business tier to the applica
tion. In
Client
-
Server applications, the business logic
was either in the client code, or added
to stored procedures in the data layer. Both of these options cause problems for
application maintainability.


Figure 1
-

Layers

in an N
-
tiered
architecture

Consider an application that has both a Web and
Windows user interface. Putting
the business logic in the presentation layer would require duplicating the code in two
different environments. Creating a separate business tier allows the logic to be
consolidated and referenced by different presentation
layers.

Business Objects

Business objects are a critical part of an N
-
tiered architecture, building the
foundation of the Business
tier
. Business objects
encapsulate data structures and
provide all the components necessary to accomplish a set of tightly re
lated specific
business functions. Business objects interact with the data access layer to persist the
data and also interact with the service layer to make the data and functions available
to the presentation layer or other applications. Business objects
provide the business
logic which may include working with Business Workflow and Business Rules and
abstracts the complexity from the presentation layer.

A business object is made up of three parts: a domain model with data entity
structures and relationshi
ps; the
data access (
CRUD
)

methods necessary to persist
to a data store; and the methods to implement business logic.

The domain model allows the business object to work with structured data that can
be manipulated easily.
There are many different struc
tures available to model the
data such as arrays, lists, tables, etc. In .Net, Microsoft has provided tools such as
data tables and datasets, data context for use with LINQ which is extended in
the
entity

frameworks

architecture
. Many developers choose to

create specific classes
with properties to model the data objects and use generic lists to
consolidate several
together.

The business logic portion of the business object is a set of methods that wrap the
calls to the data access layer and also implement
specific business functionality such
as data validation
.

The main point of this discussion is to be careful not to skip layers in the
architectural model. Specifically, the presentation layer should not have access to
the data access layer. The presenta
tion layer should instantiate the business object
and call methods on it to do data access.

Domain Driven Architecture

I highly endorse the
Domain Driven model for defining business
objects

(see books
by
Jimmy Nilsson

and others)
. The pattern elevates
the domain model part of the
business object and allows access from several points in the application (Figure 2).


Figure 2


aomain ariven Architecture diagram

In a single tier application, it makes sense for the presentation layer to use the same
business object as the business layer, but in distri
buted applications, it is a little
harder to
make
use the same business objects
, especially if they are encapsulated
.
While the reuse and consistency of code is a major advantage, there are also
complications

that need to be designed around
.

The Problems
with Encapsulation

In object oriented programming

(OOP)
, the concept of encapsulation applies to
business objects by creating a single class that has both data structures and the
business logic together in one class so that the object can operate on itself
. A good
example
is the

Validate function

telling an object to go validate itself

and make sure
that the properties are all correct
.

While this is a handy
pattern for building business
objects
, it does offer many other problems (the details here is anothe
r whole article).

Encapsulation of the methods
with the domain model
presents a problem with
circular references when the data access layer needs to reference the business object
data structures

to fill them with data

and the business object needs to ref
erence the
data access methods

to save the data
.
The solution is to either encapsulate all of the
data access layer into the business object, or don’t use encapsulation and keep all
three layers in separate projects.
In summary,
the best solution is a com
bination of
OOP principles:
keep the domain model portion of the business object in a separate
assembly (project) from the business logic, but then use inheritance to
bring them
back together to provide the benefits of encapsulation.

In the first project,
define the
objects with properties and collections (for related business objects). Then in a
separate business logic project, define another class that inherits from the domain
class and implements all the methods needed for functionality. This also allow
s the
data access layer to be in its own project.

The Problems with Tier Separation

In addition to the problems mentioned above,
more problems are encountered when
the presentation and business tiers are separated on different platforms.
Using WCF
Services

is the primary way of communicating across the tier separation

but care is
required to use consistent patterns on both sides of the tier
.
M
aking the
same
business

object available in

the presentation layer
can be tricky
.

A
gain
, the problem
is

mostly with

the methods:
the methods

of the business object

in the presentation
layer call the services, whereas the methods on the business layer actually
implement the logic
.
S
eparating the domain model from the methods is the
answer

t
he same domain model can be r
eused by both tiers, but the logic method

project
s need to be different.

A WCF Utility (svcutil.exe) code generates a proxy object that
attempts to
duplicate
the domain model and uses delegates for each of the methods to call the services.
Unfortunately,

it takes some manipulation to get everything to line up so the coding
can be identical from either perspective.

Discussion

Back to the goal of this
architecture
,
there seems to be difference between fro
nt
-
end
and back
-
end developers i
n the way business ob
jects are used. Back
-
end
applications use the business objects as they were designed and front
-
end
applications use the proxy objects as WCF generates them, which is usually slightly
different. It is the role of the architecture to establish patterns of
how the
application should be built and I think I have found a solution to the problem.

Separating the Tiers

Architecting enterprise applications mainly about
scalability

how to design
applications that can grow to support many users.

This is usually done

by running
different parts of the application on different systems.

The idea of separating the business tier and the database tier has been around for a
long time. The database runs best on a dedicated server

that is setup for
performance
.

Having a dedic
ated server also allows the database application to be
clustered across several servers when maximum performance is needed.

Creating a separate business tier allows for consolidation of common business logic
while supporting
access from several different c
lients. Microsoft provides a couple
different methods of communicating between the presentation tier and the business
tier across the tier separation. These include web services, .Net
R
emoting
,
Enterprise

S
ervices, but the most popular

and most flexible

is
the
Windows
Communication Foundation (WCF) services.

The Service Layer

Architecting for distributed enterprise applications

requires careful design of the
communication between tiers.

The service layer of this architecture attempts to
abstract the ti
er separation between the presentation layer and the business layer
using WCF services.

Normally, a separate service is created for each business object and all the business
logic implemented through methods. The service layer is quite thick and require
s a
separate special type of project, an interface object to act as contract, the actual
service code with wrappers for each method, a request object that wraps all the
parameters for each method, and a response object to package the result of the
method.

On the client side, a proxy object is generated for each service with a class
that mimics the original business object and any other objects that are referenced
(collections of child objects for example) as well as the request and response objects.
This r
esults in over 1000 lines of code (mostly generated) for a typical business
object with 5
-
6 methods.

The binding configuration, transport protocol, and endpoint definition for the service
can be defined in code, but is usually defined in config files so th
e services can be
moved to different environments easily (Development to QA to Production). WCF is
very sophisticated and the config files have many parameters that need to be
tweaked making the process very complicated and prone to problems that are hard
to diagnose and debug.

Simplifying the Complexity

Adding a service layer to an application increases the complexity exponentially.
Becoming proficient at understanding and implementing services is a highly sought
after quality in a developer and can lead

to a much higher paying job. But few
people really master the concept and usually end up copying someone else
’s

working
example and breathing a sigh of relief when it

finally

actually works.

The frustration at dealing with services is another motivation
for this presentation.
My goal is to abstract the process to the point that developers need not worry about
implementing services

just plug in this reference and it all happens automatically.
Yes, I dream big, but it does seem to come together fairly well
.

The other motivating factor was mentioned earlier

the need to deal with business
objects
in exactly the same way in both the presentation layer and the business
layer. With this technique, a developer should be able to build the application as if it
wer
e all running on one system and then implement it in a distributed environment
without many changes.

Single Generic Service

The concept of the Single Generic Service is to create one WCF Service that will pass
back and forth a
ny

business object

and execute

the desired method
. This requires
that each business object
can be serialized

using

the proper attributes on each of the
properties and methods

and conform
s

to a standard

(inherits from a base object)
.
The service will have a generic request/response obj
ect that will be able to package
any parameters and return the serialized object.

Once the client proxy has packaged the parameters and called the service, the back
-
end service will un
-
package the parameters and use reflection to instantiate the
business

object and call the specified method. On the return, the resulting object
will be serialized and packaged in the response object
, and returned to the client.
The client proxy will un
-
package and de
-
serialize the object and return the
resulting
business
object

(List)
to the client.

Creating the Service

To create a new service in Visual Studio (currently based on 2008), first create a new
WCF Project.
With Visual Studio open to a specific solution:

1.

In the Solution Explorer, r
ight click on the
s
olution

nam
e

or
specific
solution folder where you want the new project and select New Project.

2.

From the dialog, select
Visual C# (or Visual Basic) and then select the
WCF Service Application template. (NOTE: ironically, this option is not
available under the WCF sub

heading.)


3.

Enter the name of the project as BizObjServices (usually the project name
is plural and the individual service is named singular), and the path where
the project will be created. Then click the OK button.

4.

Visual Studio will create a default s
ervice
that we can use for building the
new service, but it needs to be renamed in several places
.

It may be
easier to delete this default service and add it back in with the proper
name. But do take note of the default constructs as we will be using the
m
and we don’t get the same template when adding a new service.

5.

Before creating any objects in the new project, be sure to get the
Namespace right. VS defaults to just the name of the project, but best
practice is to use a namespace with the format
Compan
yName.ApplicationName.ProjectName. For my project, I use the
namespace BTT.Common.BizObjServices since this project will be reused
between applications. Right click on the Project in Solution Explorer and
select

Properties. Under the Application tab, e
nt
er the namespace for both
the Assembly Name and the Default Namespace
.


6.

Optionally, I also like to show the assembly name in Visual Studio. Right
click on the project name in solution explorer and select Rename. Enter
the namespace and press Enter.

7.

Righ
t click on the new project and select Add / New Item. From the dialog,
select the WCF Service template. In the Name control, enter the name
BizObjService.svc.


8.

VS will create 3 files for you: BizObjService.svc and under it
BizObjServices.svc.cs, and IBiz
ObjService.cs

with template code inserted

and also updates the Web.config file with the service parameters
.

Building the Service objects

Start with the interface class (IBizObjService.cs) and add some code to define the
contracts needed for our new service
.

Using the sample construct, notice that the
default
interface for our service has

the attribute [ServiceContr
act] before it. This
identifies the class as one that will be exposed by the service. Each method in the
class that is to be exposed has the a
ttribute [OperationContract]. The class that
define the data structures have the attribute [DataContract] and each property that
is exposed has the attribute [DataMember]. Here is the template code provided:


[ServiceContract]


public

interface

ISer
vice1


{



[OperationContract]


string

GetData(
int

value);



[OperationContract]


CompositeType GetDataUsingDataContract(CompositeType composite);



// TODO: Add your service operations here


}




// Use a data c
ontract as illustrated in the sample below to add composite types to
service operations.


[DataContract]


public

class

CompositeType


{


bool

boolValue =
true
;


string

stringValue =
"Hello "
;



[DataMember]


public

bool

BoolValue


{


get

{
return

boolValue; }


set

{ boolValue =
value
; }


}



[DataMember]


public

string

StringValue


{


get

{
return

stringValue; }


set

{ stringValue =
value
; }



}


}

}


Our service will only have one method and it fits the sample given by the
GetDataUsingDataContract method which takes as a paramter the CompositeType
request object and returns another CompositeType response object.

In the second class
in this file (it could be separated into another file if desired) is
the CompositeType object definition

that will act as both the request and response
objects
. It has a set of properties that will be used to
package

the parameters
of the
methods and retu
rn the result. In this case the service is generic and the
parameters count and type will vary, so we will add a structure that can handle
anything.

The parameters of a method are usually strongly typed, so we can use a
series of
Dictionary object to pa
ss a collection of
parameter values

t
hat can be referenced by
type and then by name. Create a separate property of type Dictionary<string,
datatype
> for each data type to be supported for the business objects and give it the
name Param
Datatype
.

Remember t
o precede each with the [DataMember] attribute.

The actual business objects could also be packaged this way, but we would have
create separate dictionary for each business object. This might be more efficient, but
will make the service dedicated to a speci
fic application and take away the flexibility.
In order to support any business object, just create an XML object parameter and
then serialize the business object that needs to be passed across the service.

There are also some parameters to define the spec
ifics about the business object and
method being called and another list to return any error messages.

The resulting code should look like this:

namespace

BTT.Common.BizObjServices

{


[
ServiceContract
]


public

interface

IBizObjService


{


[
OperationContract
]


CompositeType

GetDataUsingDataContract(
CompositeType

composite);


}



[
DataContract
]


public

class

CompositeType


{


[
DataMember
]


public

string

AssemblyName {
get
;
set
;}


[
DataMember
]


publ
ic

string

BizObjName{
get
;
set
;}


[
DataMember
]


public

string

MethodName{
get
;
set
;}


[
DataMember
]


public

System.
String

BizObjXml{
get
;
set
;}


[
DataMember
]


public

List
<System.
String
> MessageList{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, System.
Int32
> ParamInt32{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, System.
Int64
> ParamInt64{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, Syst
em.
Double
> ParamDouble{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, System.
Decimal
> ParamDecimal{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, System.
String
> ParamString{
get
;
set
;}


[
DataMembe
r
]


public

IDictionary
<System.
String
, System.
Byte
[]> ParamByteArray{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, System.
Boolean
> ParamBoolean{
get
;
set
;}


[
DataMember
]


public

IDictionary
<System.
String
, Syst
em.
DateTime
> ParamDateTime{
get
;
set
;}


}

}

If your business objects have any other native data types as parameters, you may
need to add other dictionary parameters to match. Even if the method returns a list
of objects, the XML field should be able to h
andle the result once the object list has
been serialized.

Coding the Generic Method Call

Now open the other code file for the service called BizObjService.svc.cs (you may
have to expand the BizObjService.svc file).
Notice that the class inherits from the

interface defined in the previous section. Since reflection is used to instantiate the
business object, add a “using” statement for the System.Reflection namespace.

Delete the GetData method and start to work on the GetDataUsingDataContract
method. Noti
ce that it has a request parameter using the CompositeType data
structure and returns another CompositeType object.


The values needed for reflection are the assembly name, the type (business object
name), and the method name which are retrieved from the p
roperties of the request
object.

Namespace

BTT.Common.BizObjServices

{


public

class

BizObjService

:
IBizObjService


{


public

CompositeType

GetDataUsingDataContract(
CompositeType

request)


{


CompositeType

response =
new

C
ompositeType
();


try


{



if

(request.AssemblyName ==
null
)


throw

new

ApplicationException
(
"Missing AssemblyName"
);


if

(request.BizObjName ==
null
)


throw

new

Appli
cationException
(
"Missing BizObjName"
);


if

(request.MethodName ==
null
)


throw

new

ApplicationException
(
"Missing MethodName"
);



//get assembly


Assembly

a =
Assembly
.Load(request.AssemblyName
);


//get business object


Type

t = a.GetType(request.BizObjName );
//reference type to logic classes


//get the method


MethodInfo

mi = t.GetMethod(request.MethodName);

The parameters are passed t
o the method using an object array with the correct
sequence.
Reflection is also used to get a MethodInfo object for the desired method
and then a list of the parameters using the GetParameters method. First use the
parameter type to get the cor
rect dicti
onary property and then use the method name
to pull the correct parameter value out of the dictionary. Add this value to the object
array that will be passed to the business object method.

If the parameter is a
complex object instead of a value, use the De
serialize (see below) method to pull the
object out of the XML property.


//get the parameter values in object list


List
<
object
> parms =
new

List
<
object
>();


foreach

(
ParameterInfo

pi
in

mi.GetParameters())



{


//get the value of the parameter from the proper type


if

(pi.ParameterType ==
typeof
(System.
Int32
))


{


parms.Add(request.ParamInt32[pi.Name]);



}


else

if

(pi.ParameterType ==
typeof
(System.
Int64
))


{


parms.Add(request.ParamInt64[pi.Name]);


}


else

if

(pi.ParameterType ==
typeof
(System.
String
)
)


{


parms.Add(request.ParamString[pi.Name]);


}


else

if

(pi.ParameterType ==
typeof
(System.
Decimal
))


{


parms.Add(request.ParamDec
imal[pi.Name]);


}


else

if

(pi.ParameterType ==
typeof
(System.
Boolean
))


{


parms.Add(request.ParamBoolean[pi.Name]);


}


else

if

(pi.Par
ameterType ==
typeof
(System.
DateTime
))


{


parms.Add(request.ParamDateTime[pi.Name]);


}


else

if

(pi.ParameterType ==
typeof
(System.
Byte
[]))


{



parms.Add(request.ParamByteArray[pi.Name]);


}


else

if

(pi.Name.EndsWith(
"Object"
) && request.BizObjXml !=
null
)


{


parms.Add(Deserialize(request.BizObjXml, pi.P
arameterType));



}


else


throw

new

ApplicationException
(
"invalid parameter type
encountered
-

"

+ pi.ParameterType.ToString());


}

Now that everything is unpackaged, use the Invo
keMember reflection method to
execute the desired method on the business object. Serialize (see below) the result
to XML and put it back in the response object. If there are any error messages, add
them to the MessageList property and then return the res
ponse object.


//execute the method with the parameter list


object

obj = t.InvokeMember(mi.Name,
BindingFlags
.Default |
BindingFlags
.InvokeMethod,
null
,
null
, parms.ToArray());


if

(obj ==
null
)



throw

new

ApplicationException
(
"Error executing "

+ mi.Name +
"
method on object "

+ t.Name);


if

(mi.ReturnType.Name ==
""
)


throw

new

ApplicationException
(
""
);


//serialize return object



response.BizObjXml = Serialize(obj );




return

response;


}


catch

(
Exception

ex)


{


response.MessageList.Add(ex.Message);


return

response;


}


}

The Seri
alize and Deserialize methods are straight out of the book and could be
isolated to a core library project.


//serialize the current instantiation to xml string


private

static

string

Serialize(
object

obj)


{


try



{


//create a new stream


System.IO.
MemoryStream

memStream =
new

System.IO.
MemoryStream
();



System.Xml.Serialization.
XmlSerializer

x =
new

System.Xml.Serialization.
XmlSerializer
(obj.GetType());



x.Serialize(memStream, obj);



//memStream is now contains the seralized XML version of the object


System.Text.
ASCIIEncoding

enc =
new

System.Text.
ASCIIEncoding
();


return

enc.GetString(memStream.ToArray());



}


catch

(
Exception

ex)


{


throw

new

ApplicationException
(
"Error Serializing object "

+
obj.GetType() +
"
\
r"

+ ex.Message);


}


}



//deserialize the xml string back to an object (
doesn't need an instance)


private

static

object

Deserialize(
string

xml,
Type

ObjectType)


{


System.Text.
ASCIIEncoding

enc =
new

System.Text.
ASCIIEncoding
();


byte
[] buffer = enc.GetBytes(xml);


System.IO.
Mem
oryStream

memStream =
new

System.IO.
MemoryStream
(buffer);



memStream.Position = 0; //make sure we are at the beginning


System.Xml.Serialization.
XmlSerializer

x =
new

System.Xml.Serialization.
XmlSerializer
(ObjectType);



r
eturn

x.Deserialize(memStream);


}


}

}

That is all there is to the generic service. To finish spanning the tier separation, a
proxy class is added to the presentation layer of the application.

Modifying the Config file

VS generated the config fi
le needed, but it adds everything needed for a full scale
Web app. Much of it can be cut out since only one service is needed. Most business
objects need to access a database, so add a ConfigurationString section and then the
rest is for the service. The

default config only supports small objects and several
parameters need to be increased in order to pass large business objects. The
following shows the config that is needed with the added parts highlighted as bold.

<?
xml

version
="1.0"?>

<
configuration
>


<
connectionStrings
>


<
add

name
="
BTT_BDS
"
connectionString
="
server=SCRBDDBUSCNC08
\
SQL01;database=BTT_BDS;Integrated Security=SSPI
"


providerName
="System.Data.SqlClient" />


</
connectionStrings
>



<
system.serviceModel
>


<
services
>


<
servi
ce

behaviorConfiguration
="BTT.Common.BizObjServices.BizObjServiceBehavior"


name
="BTT.Common.BizObjServices.BizObjService">


<
endpoint

address
=""
binding
="wsHttpBinding"




bindingConfiguration
="LargeMessageBinding"


contr
act
="BTT.Common.BizObjServices.IBizObjService">


<
identity
>


<
dns

value
="localhost" />


</
identity
>


</
endpoint
>


<
endpoint

address
="mex"
binding
="mexHttpBinding"
contract
="IMetadataExchange" />


</
service
>



</
services
>


<
behaviors
>


<
serviceBehaviors
>


<
behavior

name
="BTT.Common.BizObjServices.BizObjServiceBehavior">


<
serviceMetadata

httpGetEnabled
="true" />


<
serviceDebug

includeExceptionDetailInFaults
="
true
" />



<
dataContractSerializer

maxItemsInObjectGraph
="1048576" />


</
behavior
>


</
serviceBehaviors
>


</
behaviors
>


<
bindings
>


<
wsHttpBinding
>


<
binding

name
="LargeMessageBinding"
receiveTimeout
="24.0:00:00"



maxRec
eivedMessageSize
="16777216">


<
readerQuotas

maxStringContentLength
="16777216" />


</
binding
>


</
wsHttpBinding
>


</
bindings
>


</
system.serviceModel
>

</
configuration
>

The Bindings section was added to override the default size on th
e
maxReceivedMessageSize. This web.config

should be deployed along with the .svc
file where ever the service ends up being hosted.

Create a Client Project

To demonstrate the client side of the service, create another project in the solution

for now we wil
l use a Windows Forms, but it could be any client project

and name it
SingleServiceTest.

Generating
the Proxy Class for the Service

There are two ways to generate the proxy class for the client to have access to the
service. The most common way is to add
a Service Reference, but there is an
advanced tool called SvcUtil.exe that will generate a code object to
encapsulate the
domain model and the service calls

(may be covered in a future article)
.

To add a service reference, right click on the project and se
lect Add Service
Reference. In right side of the dialog, drop down the Discover list and choose
Services in Solution. It will fill the list of available services.


Select the BizObjService, change the default name to BizObjServiceReference and
click the

OK button. The service will show under the Service Reference category in
the Solution Explorer.


In the background, VS has generated the proxy code

including the domain object
and the methods necessary to call the service
. Now we need encapsulate the co
de
back into our business object.

Extending the Proxy Class for the Generic Service

Add a new C# Class object to the
client
project and name it BizObjServiceClient. This
class will have one public static method to execute the business object method and
the

same Serialize and Deserialize methods as
in the service project.

With a good code generation system, a more efficient could be generated that
explicitly defined each parameter for each method. But the goal here is flexibility
and reusable code, so
Refle
ction is used again to handle the parameters of the
method in a generic manner (therefore another “using

System.Reflection
” statement
is needed).

using

SingleServiceTest.BizObjServiceReference;


namespace

BTT.Common.BizObjServices

{


public

partial

class

SingleServiceClient

{

public static object ExecuteBizObjMethod(string AssemblyName,


string BizObjName, string MethodName, object[] ParameterArray)


{


try


{


//get an instance of the proxy object that was generated


BizObjServiceClient

client = new
BizObjServiceClient
();


//get an instance of the request object


CompositeType

composite = new
CompositeType
();


composite.AssemblyName = AssemblyName;


composite.BizO
bjName = BizObjName;


composite.MethodName = MethodName;


//use reflection to get the parameter list


Assembly

a =
Assembly
.Load(AssemblyName);


//get business object reference type to the logic classes



Type

t = a.GetType(BizObjName);


//get the method


MethodInfo

mi = t.GetMethod(MethodName);


//get the parameter values in object list


List
<object> parms = new
List
<object>();


ParameterInfo
[] pi =

mi.GetParameters();


for (int i = 0; i < pi.Length; i++)


{


//get the value of the parameter from the proper type


if (pi[i].ParameterType == typeof(System.
Int32
))


{


composite.ParamInt32.Add(pi[i].Name,
(System.
Int32
)ParameterArray[i]);


}


else if (pi[i].ParameterType == typeof(System.
Int64
))


{


composite.ParamInt64.Add(pi[i].Name,
(System.
Int64
)Paramete
rArray[i]);


}


else if (pi[i].ParameterType == typeof(System.
String
))


{


composite.ParamString.Add(pi[i].Name,
(System.
String
)ParameterArray[i]);


}


else if (p
i[i].ParameterType == typeof(System.
Decimal
))


{


composite.ParamDecimal.Add(pi[i].Name,
(System.
Decimal
)ParameterArray[i]);


}


else if (pi[i].ParameterType == typeof(System.
Boolean
))



{


composite.ParamBoolean.Add(pi[i].Name,
(System.
Boolean
)ParameterArray[i]);


}


else if (pi[i].ParameterType == typeof(System.
DateTime
))


{


composite.ParamDateTi
me.Add(pi[i].Name,
(System.
DateTime
)ParameterArray[i]);


}


else if (pi[i].ParameterType == typeof(System.
Byte
[]))


{


composite.ParamByteArray.Add(pi[i].Name,
(System.
Byte
[])ParameterArray[i]
);


}


else if (pi[i].Name.EndsWith(
"Object"
) && ParameterArray[i] != null)
//could be an object


{


composite.BizObjXml = Serialize(ParameterArray[i]);


}


else


throw new
ApplicationException
(
"invalid parameter type encountered
-

"

+ pi[i].ParameterType.ToString());



}


BTT.Common.BizObjServices.
CompositeType

response =
client.GetDataUsingDataContract(composite);



if (response.MessageList != null && response.MessageList.Count > 0)


{


throw new
ApplicationException
(response.MessageList[0]);


}


return Deserialize(response.BizObjXml, mi.ReturnType);



}



catch (
Exception

ex)


{


throw new
ApplicationException
(
"Error in
SingleServiceProxy.ExecuteBizObjMethod
-

"

+ ex.Message);


}



}


//serialize the current instantiation to xml string


public static string Seria
lize(object obj)


{


try


{


//create a new stream


System.IO.
MemoryStream

memStream = new System.IO.
MemoryStream
();



//convert object, into xml stream. This can be any object. This

works
because all data is correctly attributed for services


System.Xml.Serialization.
XmlSerializer

x = new
System.Xml.Serialization.
XmlSerializer
(obj.GetType());


x.Serialize(memStream, obj);



//memStream is
now a stream containing the seralized XML version of the
object


System.Text.
ASCIIEncoding

enc = new System.Text.
ASCIIEncoding
();


return enc.GetString(memStream.ToArray());



}


catch (
Exception

ex)



{


throw new
ApplicationException
(
"Error Serializing object "

+
obj.GetType() +
"
\
r"

+ ex.Message);


}


}

Client Side Business Logic Class

The last step is to build logic class
for the business object class that mir
rors the
business logic class in the business layer. The methods will have the same signature
as the primary class, but will call the services instead of implementing the logic.

Each
method will build a request object, call the service, and convert the re
sult back
to
the business object list. This business object has three methods
:

GetAll,
GetByPrimaryKey, and Save.


using

SingleServiceTest.BizObjServiceReference;


namespace

SingleServiceTest

{


#region

CustomerType Logic Methods Class



public

part
ial

class

CustomerTypeLogic

: BTT.TRUSS.Services.
CustomerType


{



#region

Wrap DAL Get Methods





public

static

CustomerTypeList

GetAll


(


)


{


try



{


object

response =
BTT.Common.BizObjServices.
SingleServiceClient
.ExecuteBizObjMethodShort(


"BTT.TRUSS.BusinessLogic"
,
"BTT.TRUSS.Services.CustomerTypeLogic"
,
"GetAll"
,
new

object
[]{});


CustomerTypeL
ist

ctList =
new

CustomerTypeList
();


ctList.AddRange(response
as

List
<
CustomerType
>);


return

ctList;


}


catch

(
Exception

ex)


{


TrussLogger
.Error(
"Error in GetAll
-

"

+ ex.Me
ssage,
"CustomerTypeLogic"
);


throw

new

ApplicationException
(
"Error in CustomerTypeLogic GetAll
-

"

+
ex.Message, ex);


}



}



public

static

CustomerTypeList

GetByPrimaryKey


(



System.
Int32

CustomerTypeId


)


{


try


{


object

response =
BTT.Common.BizObjServices.
SingleServiceClient
.ExecuteBizObjMethodShort(


"BTT.TR
USS.BusinessLogic"
,
"BTT.TRUSS.Services.CustomerTypeLogic"
,
"GetAll"
,


new

object
[] {CustomerTypeId })
as

CustomerType
[];


CustomerTypeList

ctList =
new

CustomerTypeList
();


ctList.AddRange(response
as

List
<
CustomerType
>);


return

ctList;


}


catch

(
Exception

ex)


{


TrussLogger
.Error(
"Error in GetByPrimaryKey
-

"

+ ex.Message,
"CustomerTypeLogic"
);


throw

new

ApplicationExcept
ion
(
"Error in CustomerTypeLogic
GetByPrimaryKey
-

"

+ ex.Message, ex);


}



}




#endregion

//Get Methods



#region

Wrap DAL Save Methods



public

static

CustomerTypeList

Save(
CustomerType

obj)


{


t
ry


{


object

response =
BTT.Common.BizObjServices.
SingleServiceClient
.ExecuteBizObjMethodShort(


"BTT.TRUSS.BusinessLogic"
,
"BTT.TRUSS.Services.CustomerTypeLogic"
,
"GetAll"
,


new

obj
ect
[] {obj })
as

CustomerType
[];


CustomerTypeList

ctList =
new

CustomerTypeList
();


ctList.AddRange(response
as

List
<
CustomerType
>);


return

ctList;


}


catch

(
Exception

ex)


{


TrussLogger
.Error(
"Error in Save
-

"

+ ex.Message,
"CustomerTypeLogic"
);


throw

new

ApplicationException
(
"Error in CustomerTypeLogic Save
-

"

+
ex.Message, ex);


}


}





#endregion

//DAL Save Methods






}



#endregion

//CustomerType Logic Class



[
Serializable
()]


public

class

CustomerTypeList

:
List
<
CustomerType
>


{


[
DataMember
]


public

virtual

List
<
TrkPlusMessage
> MessageList


{


get

{
return

_Messag
eList; }


set

{ _MessageList =
value
; }


}


private

List
<
TrkPlusMessage
> _MessageList;



public

CustomerType

FindFirst(
string

DataFieldName,
string

value)


{


foreach

(
CustomerType

obj
in

this
)


{


if

(obj.GetPropertyValue(DataFieldName).ToString() == value)


return

obj;


}


return

null
;


}



}




//
------------------------------------------------------------------------

}


Or c
ould forget the generic client (SingleServiceClient) and code all the logic in each
of the methods for the business logic class.

Each method is longer, but less reflection
required. If Metadata Code Generation is available, this might be a better option.


using

System;

using

System.Collections;

using

System.Collections.Generic;

using

System.Text;

using

System.Data;

using

System.Xml;

using

System.Runtime.Serialization;

using

System.ServiceModel;


using

BTT.TRUSS.Libraries.CoreLibrary.Error;

using

BTT.TRUSS.
Libraries.CoreLibrary.Logging;

using

BTT.TRUSS.Libraries.ServiceLibrary;

using

BTT.TRUSS.Services;


namespace

SingleServiceTest

{


#region

CustomerType Logic Methods Class



public

partial

class

CustomerTypeLogic

: BTT.TRUSS.Services.
CustomerType



{



#region

Wrap DAL Get Methods





public

static

CustomerTypeList

GetAll ()


{


try


{


BizObjServiceReference.
BizObjServiceClient

client =
new

BizObjServiceReference.
BizObjServiceClient
()
;


BizObjServiceReference.
CompositeType

request =
new

BizObjServiceReference.
CompositeType
();


request.AssemblyName =
"BTT.TRUSS.BusinessLogic"
;


request.BizObjName =
"BTT.TRUSS.Services.CustomerTypeLogic"
;



request.MethodName =
"GetAll"
;


BizObjServiceReference.
CompositeType

response =
client.GetDataUsingDataContract(request);


if

(response.MessageList !=
null

&& response.MessageList.Count > 0)


thr
ow

new

ApplicationException
(response.MessageList[0]);


CustomerType
[] ctArray =
BTT.Common.BizObjServices.
BizObjServiceClient
.Deserialize(response.BizObjXml,


typeof
(BTT.TRUSS.Services.
CustomerType
[]))
as

CustomerType
[];



return

new

CustomerTypeList
(ctArray);


}


catch

(
Exception

ex)


{


TrussLogger
.Error(
"Error in GetAll
-

"

+ ex.Message,
"CustomerTypeLogic"
);


throw

new

ApplicationException
(
"Err
or in CustomerTypeLogic GetAll
-

"

+
ex.Message, ex);


}



}



public

static

CustomerTypeList

GetByPrimaryKey


(System.
Int32

CustomerTypeId)


{


try


{


BTT.Comm
on.BizObjServices.
BizObjServiceClient

client =
new

BTT.Common.BizObjServices.
BizObjServiceClient
();


BTT.Common.BizObjServices.
CompositeType

request =
new

BTT.Common.BizObjServices.
CompositeType
();


request.AssemblyName =
"BTT
.TRUSS.BusinessLogic"
;


request.BizObjName =
"CustomerType"
;


request.MethodName =
"GetByPrimaryKey"
;


request.ParamInt32.Add(
"CustomerTypeId"
,CustomerTypeId);


BTT.Common.BizObjServices.
Composite
Type

response =
client.GetDataUsingDataContract(request);


if

(response.MessageList !=
null

&& response.MessageList.Count > 0)


throw

new

ApplicationException
(response.MessageList[0]);


CustomerType
[] ctArra
y =
BTT.Common.BizObjServices.
BizObjServiceClient
.Deserialize(response.BizObjXml,


typeof
(BTT.TRUSS.Services.
CustomerType
[]))
as

CustomerType
[];


return

new

CustomerTypeList
(ctArray);


}


catch

(
Excep
tion

ex)


{


TrussLogger
.Error(
"Error in GetByPrimaryKey
-

"

+ ex.Message,
"CustomerTypeLogic"
);


throw

new

ApplicationException
(
"Error in CustomerTypeLogic
GetByPrimaryKey
-

"

+ ex.Message, ex);


}




}




#endregion

//Get Methods



#region

Wrap DAL Save Methods



public

static

CustomerTypeList

Save(
CustomerType

obj)


{


try


{


BTT.Common.BizObjServices.
BizObjServiceClient

client =
ne
w

BTT.Common.BizObjServices.
BizObjServiceClient
();


BTT.Common.BizObjServices.
CompositeType

request =
new

BTT.Common.BizObjServices.
CompositeType
();


request.AssemblyName =
"BTT.TRUSS.BusinessLogic"
;


request.Bi
zObjName =
"CustomerType"
;


request.MethodName =
"Save"
;


request.BizObjXml =
BTT.Common.BizObjServices.
BizObjServiceClient
.Serialize(obj);


BTT.Common.BizObjServices.
CompositeType

response =
client.GetDataUsing
DataContract(request);


if

(response.MessageList !=
null

&& response.MessageList.Count > 0)


throw

new

ApplicationException
(response.MessageList[0]);


CustomerType
[] ctArray =
BTT.Common.BizObjServices.
BizOb
jServiceClient
.Deserialize(response.BizObjXml,


typeof
(BTT.TRUSS.Services.
CustomerType
[]))
as

CustomerType
[];


return

new

CustomerTypeList
(ctArray);


}


catch

(
Exception

ex)


{



TrussLogger
.Error(
"Error in Save
-

"

+ ex.Message,
"CustomerTypeLogic"
);


throw

new

ApplicationException
(
"Error in CustomerTypeLogic Save
-

"

+
ex.Message, ex);


}


}





#endregion

//DAL Save Methods



}




#endregion

//CustomerType Logic Class



[
Serializable
()]


public

class

CustomerTypeList

:
List
<
CustomerType
>


{


[
DataMember
]


public

virtual

List
<
TrkPlusMessage
> MessageList


{


get

{
return

_MessageList; }



set

{ _MessageList =
value
; }


}


private

List
<
TrkPlusMessage
> _MessageList;



public

CustomerType

FindFirst(
string

DataFieldName,
string

value)


{


foreach

(
CustomerType

obj
in

this
)


{



if

(obj.GetPropertyValue(DataFieldName).ToString() == value)


return

obj;


}


return

null
;


}


public

CustomerTypeList()


{


}


public

CustomerTypeList(
CustomerType
[] ctArray)



{


this
.AddRange(ctArray);


}


}

}

Conclusion

The simple service is a good way to hide the complexity of the service layer from
developers, but does require some custom code generation.