WebAPI / WCF / XML Webservices support

Important!

This section is for .NET Framework and is considered legacy. It's given here for completeness. Going forward, users of .Net core 3.x / .Net 5+ are adviced to use derived models and Asp.net core with WebAPI.

This section describes how to use the entity and entity collection classes of the generated code in a webservice scenario. Every entity and entity collection class implements IXmlSerializable, which makes it possible to transparently use these classes with webmethods, without the necessity to first produce XML from them using their WriteXml() methods.

The preferred way to implement services in .NET is by using WebAPI, or the more low level variant WCF (Windows Communication Foundation). LLBLGen Pro entities are fully usable with WebAPI and WCF similar to webservices.

By default, LLBLGen Pro entity classes aren't marked with DataContract and DataMember attributes. If you want to have these defined on classes and members, use the LLBLGen Pro designer's attribute feature to define the DataContract and DataMember attributes on entities and fields. Define these attributes in the Project Settings of the LLBLGen Pro designer. A step-by-step guide is given below in the WebAPI section.

WebAPI

To write a WebAPI using LLBLGen Pro, you can use any query API the runtime supports: Linq, QuerySpec or the low-level API, if you're going to return IEnumerable entities. By default, WebAPI serializes the data to XML, though you can opt for JSON if you want to. The following steps, make sure that your entities are properly serialized from a WebAPI in XML or JSON.

Decorating elements with DataContract and DataMember attributes.

To decorate fields, entities and navigators with the proper attributes, please follow these steps:

  • Load your LLBLGen Pro project into the LLBLGen Pro Designer and select from the main menu Project -> Settings. A dialog opens. Navigate to General -> Conventions -> Code Generation -> Attributes.
  • At the top, 'Entity' is selected. Add below Attribute definition: DataContract
  • Press enter.
  • Select at the top 'NormalField'. Add below Attribute definition: DataMember
  • Press enter.
  • Repeat the action you did with NormalField also for NavigatorSingleValue and NavigatorCollection.
  • Click OK to close the settings dialog. You have now decorated all entities, the fields of the entities and the navigator properties with the required attributes.
  • Regenerate your code.

You now have entities which are decorated with the right attributes and which are serializable from the WebAPI interface you're writing. To start with a WebAPI, simply follow one of the many tutorials given at the main WebAPI site. LLBLGen Pro doesn't come with a scaffolding add-in like Entity Framework does, but in general you'd want to start with an empty WebAPI service anyway.

An example of a WebAPI method, which returns all products together with the category entities pre-fetched:

public IEnumerable<ProductEntity> GetAllProducts()  
{  
    using(var adapter = new DataAccessAdapter())  
    {  
        var metaData = new LinqMetaData(adapter);  
        return metaData.Product.WithPath(p=>p.Prefetch(x=>x.Category)).ToList();       
    }  
}  

JSON support

Now, you might think that with the DataContract/DataMember attributes, it's ok to use the DataContractJsonSerializer, but that's not the case. Instead, we're going to use the default JSON serializer of WebAPI, which is the Newtonsoft JSON.NET serializer. By default it doesn't do what we want, so we have to configure it. However this is very simple:

Open WebApiConfig.cs which is located in the App_Start folder. Now add the following lines to the Register method:

var json = config.Formatters.JsonFormatter;  
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;  
json.SerializerSettings.ContractResolver = new DefaultContractResolver()   
        {   
            IgnoreSerializableInterface = true,   
            IgnoreSerializableAttribute = true   
        }; 
    

After this, your entities will serialize to JSON as expected. The above example method will, after using the configuration as describe above, serialize the data to JSON, including reference tracking. Pulling the data as XML by specifying application/xml in the Accept header element of the request will make things fail as WebAPI chooses a DataContract serializer by default if it sees a [DataContract] attribute on the objects to serialize. This is OK, as both sides have to work with the entity data anyway, so mutual agreement of what's sent is required.

The LLBLGen Pro Runtime Framework will keep both sides of a relationship in sync and this creates cycles in references, e.g. order.Customer references the 'Customer' instance which owns the Orders collection the 'Order' instance is part of. When serializing a graph of entity instances to JSon, using JSon.NET, it can be you'll run into an exception as JSon.NET will see the cycle and will throw an exception telling you there's a 'Self referencing loop detected'. To avoid that, set the SerializerSettings.ReferenceLoopHandling property of the JSon.NET formatter to Newtonsoft.Json.ReferenceLoopHandling.Ignore.

For web services and WCF, which both use the IXmlSerializable implementations of the entity and entity collection classes, LLBLGen Pro uses its own Compact25 format, which is very lightweight and very fast to consume and produce as it contains almost no overhead. See for a description of the Compact25 format: generated code - Xml support.

Example usage

The example discussed here is pretty simple. It offers a service with three methods: GetCustomer, SaveCustomer and GetCustomers. The client consumes the service to retrieve a customer to have it edited in a winforms application and saves the changed data back into the database using the webservice, plus it uses the service to display all customers available.

The service project has references to both generated projects: the database generic and the database specific project. The client only has a reference to the database generic project, as it uses the service for database specific activity, namely the persistence logic to work with the actual data. Because both client and service have references to the database generic project, they both can use the same types for the entities, in this case the CustomerEntity.

The code below uses our low-level API, but you can also use Linq to LLBLGen Pro or QuerySpec to fetch the entities. The GetCustomers method returns a non-generic EntityCollection object as webservices can't deal with generics.

The service

Below is the service code, simplified. As you can see, the code works directly with entity objects and entity collection objects.

[WebService(Namespace="http://www.llblgen.com/examples")]
public class CustomerService : System.Web.Services.WebService
{
    [WebMethod]
    public CustomerEntity GetCustomer(string customerID)
    {
        CustomerEntity toReturn = new CustomerEntity(customerID);
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntity(toReturn);

            return toReturn;
        }
    }

    [WebMethod]
    public EntityCollection GetCustomers()
    {
        EntityCollection customers = new EntityCollection(new CustomerEntityFactory());
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntityCollection(customers, null);
            return customers;
        }
    }

    [WebMethod]
    public bool SaveCustomer(CustomerEntity toSave)
    {
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            return adapter.SaveEntity(toSave);
        }
    }
}
<WebService(Namespace="http://www.llblgen.com/examples")> _
Public Class CustomerService 
    Inherits System.Web.Services.WebService

    <WebMethod> _
    Public Function GetCustomer(customerID As String) As CustomerEntity
        Dim toReturn As New CustomerEntity(customerID)
        Dim adapter As New DataAccessAdapter()
        Try
            adapter.FetchEntity(toReturn)
            Return toReturn
        Finally
            adapter.Dispose()
        End Try
    End Function

    <WebMethod> _
    Public Function GetCustomers() As EntityCollection
        Dim customers As New EntityCollection(New CustomerEntityFactory())
        Dim adapter As New DataAccessAdapter()
        Try
            adapter.FetchEntityCollection(customers, Nothing)
            Return customers
        Finally
            adapter.Dispose()
        End Try
    End Function

    <WebMethod> _
    Public Function SaveCustomer(toSave As CustomerEntity) As Boolean
        Dim adapter As New DataAccessAdapter()
        Try
            Return adapter.SaveEntity(toSave)
        Finally
            adapter.Dispose()
        End Try
    End Function
End Class

This code forms the code behind of the .asmx file which forms the service entry point.

Custom Member serialization/deserialization

If you add your own member variables and properties to entity classes, you probably also want these values to be serialized and deserialized into the XML stream. Normally, a custom member exposed as a read/write property is serialized as a string using the ToString() method of the value of the custom property.

In some cases this isn't sufficient and you want to perform your own custom xml serialization/deserialization on the value of this custom property, for example if this custom property represents a complex object.

To signal that the LLBLGen Pro runtime framework has to call custom xml serialization code for a given property, the property has to be annotated with a CustomXmlSerializationAttribute attribute. When a property is seen with that attribute, LLBLGen Pro will call the entity method entity.PerformCustomXmlSerialization to produce valid XML for the custom property.

Likewise, when deserializing an XML stream into entities, the LLBLGen Pro runtime framework will call, when it runs into a property annotated with a CustomXmlSerializationAttribute, the method entity.PerformCustomXmlDeserialization** to deserialize the xml for the property into a valid object. You should override these methods in a partial class of the entity which contains the custom properties.

Custom property serialization/deserialization is a feature of the Compact25 xml format, which is used by Adapter in Webservices/WCF scenarios.

Windows Communication Foundation (WCF) support

To be able to send entities from service to client and back using WCF, you have to define a ServiceContract. This ServiceContract defines the types involved in the service. It's recommended to define an interface onto which the ServiceContract is defined. Both client and service now know which types are involved in the service and no stub classes are created anymore nor necessary.

If you want to have a fixed DataContract instead, it might be better to send Data Transfer Objects (DTO)'s over the wire which are more or less dumb buckets with data back and forth instead of entity class instances. The reason is that a DataContract can't change however an entity might change over time, which then would violate the DataContract. LLBLGen Pro's powerful projection framework can help you with projecting fetched data onto DTO classes to send them over the wire.

If you want to return Entity class instances from a WCF service, use the LLBLGen Pro designer to add DataContract and DataMember attributes to entity, fields and navigator elements. The best way to do this is in the Project Settings. See the LLBLGen Pro designer manual for details.

Below is a small example of a simple WCF service and client. Its main purpose is to illustrate what to do to get LLBLGen Pro generated code working with WCF. You should check the MSDN library for information about WCF, configuration of WCF services and other WCF documentation to get a WCF service up and running in your environment.

Info

When sending entities over the wire using WCF, the 'IsNew' flag is not passed along as it is determinable on the service side for new entities. If you use the trick where you set the IsNew flag manually on an entity and then send the entity over the wire to the service, the IsNew flag is set to false at the service during deserialization, so you have to set it back to true in that special case scenario.

Interface with ServiceContract

Below is the service interface definition with the ServiceContract. The client code will use this interface to refer to the service and the service will use this interface to implement a common interface for clients to connect to.

[ServiceContract]
[ServiceKnownType(typeof(CustomerEntity))]
[ServiceKnownType(typeof(EntityCollection))]
public interface IWCFExample
{
    [OperationContract]
    IEntity2 GetCustomer(string customerID);

    [OperationContract]
    IEntityCollection2 GetCustomers();
}

Server implementation

Below is the implementation of the IWCFExample interface to be used as a WCF service. As with the other examples above, the collection is a non-generic variant of EntityCollection, as WCF services can't deal with generic collection classes.

// class to implement the service logic
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFExampleService : IWCFExample
{
    public IEntity2 GetCustomer(string customerID)
    {
        CustomerEntity toReturn = new CustomerEntity(customerID);
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntity(toReturn);
        }
        return toReturn;
    }

    public IEntityCollection2 GetCustomers()
    {
        EntityCollection toReturn = new EntityCollection(new CustomerEntityFactory());
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntityCollection(toReturn, null);
        }
        return toReturn;
    }
}

// class to actually run the service:
public class WCFExampleServerHost
{
    public WCFExampleServerHost()
    {
        WCFExampleService server = new WCFExampleService();
        ServiceHost host = new ServiceHost(server);
        host.Open();
    }
}

Client usage of the service

Below is the code snippet to consume the service defined above. It illustrates the usage of the service.

// C#
ChannelFactory<IWCFExample> channelFactory =
        new ChannelFactory<IWCFExample>("WCFExampleServer");
IWCFExample server = channelFactory.CreateChannel();

// Fetch an entity
IEntity2 c = server.GetCustomer("CHOPS");

// Fetch a collection
IEntityCollection2 customers = serverTest.GetCustomers();

Configuration of the service

Below is the serviceModel element of the service config file. The settings below are illustrative and your own production service likely will use different values for various WCF settings. Please consult the WCF documentation in the MSDN library for details on the user elements.

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="RemoteConfig"
                closeTimeout="infinite"
                openTimeout="infinite"
                sendTimeout="infinite"
                receiveTimeout="infinite"
                maxBufferSize="65536000"
                maxReceivedMessageSize="65536000" />
        </netTcpBinding>
    </bindings>
    <services>
        <service name="Service.WCFExampleServer">
            <endpoint address="" binding="netTcpBinding" name="WCFExampleServer"
                      bindingConfiguration="RemoteConfig"
              contract="Interfaces.IWCFExample" />
            <host>
                <baseAddresses>
                    <add baseAddress="net.tcp://localhost:6543/WCFExampleServer" />
                </baseAddresses>
            </host>
        </service>
    </services>
</system.serviceModel> 

Configuration of the client

Below is the serviceModel element of the client config file. The settings below are illustrative and your own production service likely will use different values for various WCF settings. Please consult the WCF documentation in the MSDN library for details on the user elements.

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="RemoteConfig"
                closeTimeout="infinite"
                openTimeout="infinite"
                sendTimeout="infinite"
                receiveTimeout="infinite"
                maxBufferSize="65536000"
                maxReceivedMessageSize="65536000" />
        </netTcpBinding>
    </bindings>
    <client>
        <endpoint address="net.tcp://localhost:6543/WCFExampleServer"
                  name="WCFServer" binding="netTcpBinding"
                  bindingConfiguration="RemoteConfig"
                  contract="Interfaces.IWCFExample" />
    </client>
</system.serviceModel>