Quando utilizziamo le Web API, per leggere e scrivere oggetti nel corpo di un messaggio Http utilizziamo delle classi particolari, le media-type formatters. "Gratis" Web API fornisce media-type formatters per JSON e XML, utilizzate secondo della richiesta da parte dei client (“Accept”). Se JSON e XML non sono i "formati" di cui abbiamo bisogno possiamo sempre creare la nostra classe derivata da MediaTypeFormatter o BufferedMediaTypeFormatter, rispettivamente per scenari asincroni o sincroni, ma non sono l’argomento di questo post promemoria.
Quando abbiamo a che fare con grafi di oggetti complessi, possono crearsi situazioni in cui abbiamo riferimenti circolari tra oggetti. Ad esempio in uno scenario di questo tipo:
Dove abbiamo due classi, Book ed Author , referenziate tramite collection (praticamente un’associazione molti-a-molti) l’una con l’altra.
Se abbiamo una Web API di questo tipo :
private BookshelfDb _db = new BookshelfDb();
public IEnumerable<Book> GetBooks()
{
return _db.Books.Include(b => b.Authors).AsEnumerable();
}
...
ed una configurazione standard delle Web API, ad eccezione dell’indentatura:
JsonMediaTypeFormatter json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.Indent = true;
In presenza di dati, invocando GetBooks() (ad esempio tramite browser), otterremo un’eccezione di questo tipo:
{
"Message": "An error has occurred.",
"ExceptionMessage": "The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": null,
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Self referencing loop detected with type 'BookshelfWebApi.Models.Book'. Path '[0].Authors[0].Books'.",
"ExceptionType": "Newtonsoft.Json.JsonSerializationException",
"StackTrace": " …
}
La soluzione al problema è abbastanza semplice in quanto è sufficiente aggiungere la riga:
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
Il caso Xml, non è immediato come nel caso JSON, in quanto è necessario decorare le classi con l’attributo DataContract impostando la proprietà IsReference a true. In questo caso però è necessario decorare anche le proprietà con l’attributo DataMember:
[DataContract(IsReference = true)]
public class Book
{
public Book()
{
this.Authors = new List<Author>();
}
[DataMember()]
public int Id { get; set; }
[DataMember()]
public int Paperback { get; set; }
[DataMember()]
public string Title { get; set; }
[DataMember()]
public string ISBN_10 { get; set; }
[DataMember()]
public string ISBN_13 { get; set; }
[DataMember()]
public string Language { get; set; }
[DataMember()]
public string Publisher { get; set; }
[DataMember()]
public ICollection<Author> Authors { get; set; }
}
Se non si vuole “sporcare” le classi “farcendole” di attributi possiamo optare per un DataContractSerializer. Le Web API possono essere configurate modificando il codice presente nella classe WebApiConfig.cs:
Quanto detto è sicuramente famigliare per chi espone entità collegate tramite Http, ad esempio utilizzando WCF.