Introduction
PresentationRequestor
is a library that helps implementing in the service layer by offering a generic class capable of querying business data with Linq expression based on presentation data.
For more information on this library, please read this article.
Now, we will talk about how it is possible to use this library over WCF (or any other similar means of communication).
Background
This biggest issue was to transfer the Linq queries over the network since they are not natively serializable. With the new release of PresentationRequestor
, the request is transferred under a special object "RequestDTO
". Additionally, one new class "ReaderServiceDTO
" will be used on the server side to read the request object and execute the request, and another class called "ClientReaderService
" will be used on the client side to create the requests and send them to WCF.
The Presentation Objects
At first, we need the contract objects (presentation objects). For this tutorial, we will use these classes:
[DataContract(IsReference=true)]
public class PresentationClass
{
[DataMember]
public virtual int Key { get; set; }
[DataMember]
public virtual string PresentationName { get; set; }
[DataMember]
public virtual PresentationClass2[] Class2s { get; set; }
}
[DataContract]
public class PresentationClass2
{
[DataMember]
public int SomeInt { get; set; }
[DataMember]
public bool? SomeBool { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public PresentationClass Parent { get; set; }
}
In this tutorial, we have decided to put them in a separate assembly called "PresentationRequestorDataContractTest
". We will see why later.
The Server Side
We need to create a WCF service application, that we call "PresentationRequestorWCFTest
". We need to reference the presentation objects library (right-click on the project, then Add Reference).
We need to create the business objects that match the presentation objects:
[Presentation(typeof(PresentationClass))]
public class BusinessClass
{
[Key]
public virtual int Key { get; set; }
[LinkTo("PresentationName")]
public virtual string BusinessName { get; set; }
public virtual IList<BusinessClass2> Class2s { get; set; }
}
[Presentation(typeof(PresentationClass2))]
public class BusinessClass2
{
[Key]
public int SomeInt { get; set; }
public string Name { get; set; }
public bool? SomeBool { get; set; }
public BusinessClass Parent { get; set; }
}
And the data context for Entity Framework:
public class DbTestContext : DbContext
{
public DbSet<BusinessClass> MyClasses { get; set; }
public DbSet<BusinessClass2> MyClasses2 { get; set; }
}
During startup, by default, the mapping between presentation objects and business objects are discovered by looking recursively at all the files in the current base directory of the application. For a WCF application, the base directory is the root folder of the application (/bin/debug for other types). In this case, many instances of the .dll can be found (/bin, /obj), which causes the application to throw an exception when we try to resolve a presentation type by its name. This problem occurs only for WCF application with attribute mapping mode.
The workaround is to call the initializer in a class that we place in the App_Code folder of the application (called only once at the first call of the server).
public class Initializer
{
public static void AppInitialize()
{
MapContext.InitializeAttributeMapping("bin\\PresentationRequestorWCFTest.dll");
}
}
This method indicates the libraries that contain the business objects that map the presentation objects.
Let's now create the interface IService1
that will be used to create the WCF service:
[ServiceContract]
public interface IService1
{
[OperationContract]
string GenericReaderService(RequestDTO request);
}
This interface will expose one method GenericReaderService
that the client will use to query his objects. RequestDTO
is a serializable object that will contain the user request.
Let's now create its implementation Service1.svc and Service1.svc.cs (Add WCF Service).
public class Service1 : IService1
{
public string GenericReaderService(RequestDTO request)
{
using (var context = new DbTestContext())
{
return ReaderServiceDTO.Proceed(request, context);
}
}
}
The implementation of the service is fairly simple. All we need to do is call the Proceed static
method of the ReaderServiceDTO
class and pass the RequestDTO
object as well as the data context. The returned string
will contain the desired objects (serialized).
The Client Side
Now, we can create the console application that will consume the service. Let's call it "PresentationRequestorConsoleTest
".
First, we need to add a service reference (Add Service Reference), but we need to make sure that "Reuse types in referenced assemblies" is checked. We can choose between "Reuse types in all referenced assemblies" or "Reuse types in specified referenced assemblies" as long as PresentationRequestor
and the assemblies containing the data contract (PresentationRequestorDataContractTest
in our tutorial) are selected.
Actually, only PresentationRequestor
is mandatory because we need to reference the RequestDTO
contained in this assembly and not a new object created by the proxy generator. Having a separate assembly containing the data contract that is referenced by both the server side and the client side has many benefits. One of them is that we can use the objects directly on the client side without having to expose the presentation objects in a service (they won't appear as proxy classes otherwise).
Once the service reference is added (let's call it ServiceReference1
), we need to create a method that calls the service given a RequestDTO
object.
static string CallService(RequestDTO request)
{
return new ServiceReference1.Service1Client().GenericReaderService(request);
}
This method will then be passed to the constructor of ClientReaderService
:
var reader = new PresentationRequestor.ClientReaderService<PresentationClass>(
invoke: CallService,
serializerType: PresentationRequestor.RequestDTO.SerializerType.DataContractSerializer
);
Note that we also have to pass the type of serializer that will be used to deserialized the result. In this tutorial, we will pass DataContractSerializer
because our presentation objects have a bidirectionnal relation and we need to use the "IsReference
" property of the DataContract
attribute. The other possible choice is the standard XML serializer.
Now that everything is setup, we can use the reader
object to request our data:
var result = reader.Where(x => x.Key > 1).OrderBy
(x => x.PresentationName).Include(x => x.Class2s).ToList();
History
- 12th June, 2015: Initial version