WCF: Hur kan jag returnera en DataTable?

Jag har fått frågan om hur det går i Windows Communication Foundation att returnera en DataTable från en tjänst till en klient, oavsett innehåll och schema på datat. Jag tänker inte gå in i dialogen om det här verkligen är att rekommendera utan istället fokusera på hur det skulle kunna implementeras.

Det går inte helt enkelt att säga att DataTable är returtypen för det kommer inte kunna hanteras av WCF på ett bekvämt sätt, istället är det lättare att "wrappa" den strukturen och då kan vi också äntligen få titta på lite mer generell hantering och användning av WCF.

Det första vi får göra är att skapa vårt tjänstekontrakt, lite mer detaljerat än vad vi kanske har blivit vana med (beroende på hur avancerade tjänster du har byggt tidigare), i och med att jag inte heller vill ha redundans i sträng-hanteringen så väljer jag att referera mina Action och ReplyAction till konstanter i service-typen (implementationen):

 [ServiceContract()]
public interface IMyService
{
[OperationContract(Action = MyService.RequestAction, ReplyAction = MyService.ReplyAction)]
Message ProcessMessage(Message request);
}

public class MyService : IMyService
{
public const String ReplyAction = "https://demos.microsoft.se/Message_ReplyAction"
public const String RequestAction = "https://demos.microsoft.se/Message_RequestAction"
...
}

 

Vad bör vi då nämna mer om koden ovan, för det första så blir vår metod/operation mycket generell och kommer alltså ta emot ett Message och returnera detsamma, därför väljer jag i det här fallet att också kalla metoden för ProcessMessage eftersom logiken i metoden kommer att kunna förändras baserat på innehållet i meddelandet, det enda som egentligen kan beskriva den här metoden ytterligare är dess RequestAction och ReplyAction.

Message är en klass som finns implementerad i namnrymden System.ServiceModel.Channels och inte att förväxla med dess motsvarighet i exempelvis MSMQ eller System.Web.Services.Description. Message inkapslar nämligen en hel del logik för att hantera "obskyra" datatyper som exempelvis en DataTable. Så hur används då den klassen och hur kommer logiken för vår implementation av ProcessMessage se ut? Jag gör en mycket generell implementation där jag inte ens bryr mig om att gå till DAL'en för att populera datatabellen utan det är inkapslingen och klienten som jag är mer nyfiken på.

 public Message ProcessMessage(Message request)
{
DataTable table = new DataTable("Customers");
table.Columns.Add("ID");
table.Rows.Add("1000");
table.Rows.Add("1001");
table.Rows.Add("1002"); 

Message msg = Message.CreateMessage(request.Version, ReplyAction, table);
return msg;
} 

 

Så vad är det som sker, jo först skapar vi den datatabell som ska returneras och sedan skapar vi det meddelande som kommer att bifoga datatabellen, här använder vi en Factory-metod för att skapa denna instans med parametrar som baseras på inkommande meddelandes typ av SOAP-version och sedan specificerar vi också vår ReplyAction och slutligen innehållet, som i den här metodsignaturen är av datatyper object, alltså vad som helst.

Då kanske du undrar, hur kommer WSDL-kontraktet och kanske ännu mer intressant XSD-kontraktet se ut för denna tjänst. Ja XSD'n som genereras av WCF ser ut som följer:

 <?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" 
                    targetNamespace="https://schemas.microsoft.com/Message"
                    xmlns:xs=https://www.w3.org/2001/XMLSchema 
                    xmlns:tns="https://schemas.microsoft.com/Message">
  <xs:complexType name="MessageBody">
    <xs:sequence>
      <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema> 

 

Vi kan alltså skicka vad som helst både som MessageBody.

På klientsidan automatgenererar jag en proxy och använder sedan på föjande sätt för att iterera igenom samlingen av rader i tabellen, observera här att om jag vill ha en mer utförlig logik och validering så måste jag implementera den själv, i det här fallet så är strukturen på datan i tabellen välkänd eftersom det är jag själv som gjort både tjänst och klient, men så är inte alltid fallet, eller hur, det är här dialogen kan uppstå om det här verkligen är rätt att göra eller fel, men som sagt, det var inte min mening att diskutera det utan istället kommer här lite mer kod, klienten alltså:

 static void Main(string[] args)
{
MyServiceClient clientProxy = new MyServiceClient();
Message request = Message.CreateMessage(MessageVersion.Soap11, "https://demos.microsoft.se/Message_RequestAction");
Message response = clientProxy.ProcessMessage(request); 

DataTable table = response.GetBody<DataTable>(); 

foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["ID"].ToString());
}
} 

 

Hur sedan bindningen ser ut är inte intressant, jag använde mig av en BasicHttpBinding eftersom det är den som erbjuder absolut bästa interoperabiliteten, och det är i fokus den här veckan :)

Jag bifogar en zippad version av de projekt som jag implementerade det här exemplet i så kan du prova själv.

DataTable.zip