Astoria - next generation dataservices

by DotNetNerd 2. November 2007 15:53

Astoria Data Services - Data in the cloud.

Microsoft arbejder for tiden på et projekt under kodenavnet "Astoria" som skal gøre det muligt at lave dataservices der lader applikationer tilgå og manipulere data via almindelige HTTP requests ved hjælp af et URI format. Idéen er at man skal kunne arbejde med ren data i sine klientapps - altså Ajax og Silverlight implementationer. Data kan efterspørges som XML eller JSON der er simple dataformater, som kan beskrive datastrukturer på en generisk måde.

Standard HTTP verber som GET, POST, PUT og DELETE definerer operationen der skal afvikles - altså om det er en select, update, insert eller delete operation.

"Under kølerhjelmen" består Astoria Dataservices af en Windows Comminication Foundation Service der stiller data til rådighed, som er defineret ved hjælp af entities frameworket over en database. Astoria er altså bygget på eksisterende teknologier, hvor der er bygget en HttpHandler over som tillader at et query defineres som en URI.

JavaScript Object Notation vs Extensible Markup Language.

Som nævnt kan data efterspørges i formaterne JSON og XML. Overordnet set kan man sige at JSON sigter imod Ajax implementationer og XML imod Silverlight, da det er "the format of choice" for tiden for de to domæner.

Da JSON er knap så kendt som XML vil jeg lige beskrive det kort. Formatet er fordelagtigt at bruge i Ajax applikationer, da det er et meget kompakt format der er velunderstøttet i Javascript, idet man kan hente en JSON.js eller bruge Microsofts implementation der følger med frameworket fra .NET 3.5.

Kort fortalt defineres et objekt således

{name:value,name:value}

og et array således

[value,value]

Ud fra disse definitioner kan man definere arrays hvor værdierne er objekter og omvendt, og det er derved muligt at beskrive komplekse datastrukturer med meget lidt overhead.

JSON serialisering.

Ved hjælp af ASP.NET Ajax er det meget enkelt at arbejde med JSON, hvilket jeg vil illustrere med et lille eksempel.

<asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
<script>

var value = new Person();
value.Name = "Jens";

var result = Sys.Serialization.JavaScriptSerializer.serialize(value);

alert(result);

//Objektet kan derefter gendannes enten ved at deserialisere eller ved hjælp af eval
var obj = Sys.Serialization.JavaScriptSerializer.deserialize(result);
obj = eval("(" + result + ")");

function Person()
{
    this.Name;
}
</script>

På samme måde kan man naturligvis også serialisere objekter til JSON i .NET også.
using System.Web.Script.Serialization;

...

JavaScriptSerializer serializer = new JavaScriptSerializer();

Person person = new Person();
person.Name = "Hans";

string result = serializer.Serialize(person);

Response.Write(result);

Person person2 = serializer.Deserialize<Person>(result);

Response.Write(person2.Name);
Querying via URI.

Den URL-baserede query syntax understøtter en del forskellige operatorer, men lad os lige først se på et par eksempler som jeg har fra et screencast på MSDN der bygger på Northwind databasen.

Helt enkelt kan man tilgå data ved at "slashe" ('/') sig igennem en struktur og bruge hårde klammer ('[]') til at angive nøgler som sådan:

northwind.svc/Customers[ALKFI]/Orders

Næste skridt er at man kan bruge simple operatorer, som her hvor jeg efterspørger Kunder fra London:

northwind.svc/Customers[City eq 'London']

Og slutteligt er der nogle mere advancerede operatorer - den nedenstående gør at ordrene fra kunden "ALKFI" flettes ind i de data der returneres. Det vil sige at man effektivt set kan hente kunde og ordre oplysninger som ét kald.

northwind.svc/Customers[ALKFI]?$expand=Orders

Simple query operatorer.

Operatorerne er vidst så selvbeskrivende at jeg vil nøjes med at liste dem.

eq (Equal)  /Customers[City eq 'London'] 
ne (Not equal)  /Customers[City ne 'London'] 
gt (Greater than)  /Orders[OrderDate gt '1998-5-1'] 
gteq (Greater than or equal)  /Orders[Freight gteq 800] 
lt (Less than)  /Orders[Freight lt 1] 
lteq (Less than or equal)  /Orders[OrderDate lteq '1999-5-4'] 


Advancerede query operatorer.

De advancerede operatorer giver mulighed for at hente sammensat data og lave paging. Derved er en de mest almindelige scenarier understøttet "ud af boksen".

?$expand=Orders  inkluderer ordrer inline, således at man undergår at lave to kald. 
?$orderby=ProductName  Sorterer efter ProductName. 
?$top=2  Returnerer de 2 første rækker. 
?$skip=2  Springer de 2 første rækker over. 


Custom Service Methods.

Hvis man ønsker at definere et query som er for advanceret til at kunne løses med de eksisterende operatorer er det muligt selv at skrive metoder der også kan kaldes ved hjælp af Astorias URI format.

Eksempelvis kan man lave så det er muligt at hente kunder ud fra hvad stat de bor i - hvor queriet kunne se sådan her ud:

/CustomersByState?state=WA

Implementationen af servicen er enormt simpel, da der er en Astoria template i Visual Studio man kan tage udgangspunkt i. Derefter kan metoden skrives sådan her.


[WebGet]
public static ObjectQuery<Custumers> CustomersByCity(NorthWindEntities db, string city)
{
 if (city == null || city.length < 3) {throw new Exception("Bad city");}
  
 return db.Customers.where("it.City = @city", new ObjectParameter("City", city));
}


Hvordan ser den payload jeg modtager ud?

For at give syn for sagen kan du her se det der returneres fra det simple kald til Customers[ALKFI] hvis man efterspørger XML.
<DataService xml:base="http://myserver/data.svc">
 <Customers>
  <Customer uri="Customers[ALFKI]">
   <CustomerID>ALFKI</CustomerID>
   <CompanyName>Alfreds Futterkiste</CompanyName>
   <ContactName>Maria Anders</ContactName>
   <ContactTitle>Sales Representative</ContactTitle>
   <Address>Obere Str. 57</Address>
   <City>Berlin</City>
   <Region />
   <PostalCode>12209</PostalCode>
   <Country>Germany</Country>
   <Phone>030-0074321</Phone>
   <Fax>030-0076545</Fax>
   <Orders href="Customers[ALFKI]/Orders" />
  </Customer>
 </Customers>
</DataService>

Og hvis man efterspørger JSON får man det her tilbage.

[
    {
        __metadata: {
            Type: "Customer",
            Base: "http://myserver/data.svc",
            Uri: "Customers[ALFKI]"
        },
        CustomerID: "ALFKI",
        CompanyName: "Alfreds Futterkiste",
        ContactName: "Maria Anders",
        ContactTitle: "Sales Representative",
        Address: "Obere Str. 57",
        City: "Berlin",
        Region: null,
        PostalCode: "12209",
        Country: "Germany",
        Phone: "030-0074321",
        Fax: "030-0076545",
        Orders: {
            __metadata: {
                Uri: "Customers[ALFKI]/Orders"
            }
        }
    }
]

Hvordan forbruger jeg så en service?

Næste skridt er så naturligvis at kalde servicen, hvilket jeg vil demonstrere ud fra både en Ajax og en Silverlight kontekst. Ved at bruge Microsofts Scripting Library, som er en del af ASP.NET Ajax, kan en service tilgås på den her måde:
function GetCustomer(id)
{
 var req = newe Sys.Net.WebRequest();
 req.set_url("northwind.svc/Customers[" + id + "]/Orders");
 req.get_headers()["Accept"] = "application/json";
 req.add_completed(OrdersCompleted);
 req.invoke();
}

function OrdersCompleted(response)
{
 if (response.get_statuscode() != 200){//Error}
 else
 {
  var orders = response.get_object();
  var html = Sys.StringBuilder("<ul>");

  for (var i = 0; i<orders.length; i++)
  {
   html.append("<li>" + orders[i].OrderDate + "</li>");
  }

  html.append("</ul>");

  $get(Orders).innerHTML = html.toString();
 }
}
Ligeledes kan man i Silverlight udforme et query, og der er endvidere muligt ved hjælp af et tool generere proxy klasser så man kan arbejde med strongly typed data.
WebDataContext dataContext = new WebDataContext(txtDataserviceUrl.Text);

foreach (Customer customer in dataContext.CreateQuery<Customer>("/Customers"))
{
 cmbCustomers.Items.Add(customer.Name);
}
if (cmbCustomers.Items.Count > 0)
{
 cmbCustomers.SelectedIndex = 0;
}
Hvis man vil lave inserts er det også rimeligt enkelt, hvilket jeg har fundet et eksempel på som viser hvordan en Note føjes til en Artikel i et typisk Artikel system.
Article article = (Article)lstArticles.SelectedItem;

Note note = new Note();
note.Comments = txtNewNote.Text;
note.Article.RelatedObject = article;

dataContext.Add(note, "Notes");
dataContext.SaveChanges();

lstNotes.Items.Add(note.Comments);
txtNewNote.Text = "";

Blod på tanden?

Idet trenden går imod at webapplikationer skal minde mere og mere om "rigtige" applikationer, er RIAS (Rich Interactive Applications) et af de mest hotte emner verden over inden for webudvikling. I den forbindelse er teknologier som Astoria en vigtig brik, da de gør det muligt at lave generiske applikationer der direkte fra klienten selv kan tilgå og vise data.

Jeg finder derfor emnet meget interessant og jeg håber artiklen her har giver dig blod på tanden, da jeg selv glæder mig til at teknologien bliver færdigudviklet så jeg selv kan komme til at anvende den i mine løsninger.

Tags:

Who am I?

My name is Christian Holm Diget, and I work as an independent consultant, in Denmark, where I write code, give advice on architecture and help with training. On the side I get to do a bit of speaking and help with miscellaneous community events.

Some of my primary focus areas are code quality, programming languages and using new technologies to provide value.

Microsoft Certified Professional Developer

Microsoft Most Valuable Professional

Month List

bedava tv izle