Olá pessoal, tudo certo?
Dando sequência a esta série sobre REST, mostro como implementar um Streaming Provider, somente leitura, baseado no protocolo OData, usando WCF Data Services.
Vou usar como exemplo o código demonstrado no post anterior. A missão de hoje será poder vincular uma foto para cada “Person” provida pelo serviço (só isso).
Em um próximo post veremos como implementar upload de dados binários para serviços OData.
Sem mais delongas…
OData e dados binários
OData permite desenvolvimento de serviços que forneçam dados binários (binary large object [BLOB]), tais como fotos, vídeos e documentos.
Um BLOB sempre está associado a um recurso “comum”. Entretanto, no lugar de ser provido diretamente, no feed, é provido em separado. Este “recurso binário independente” é conhecido media resource (MR). O recurso “comum”, chamado media link entry (MLE), possui um link que “aponta” para o recurso binário.
OData “herdou” esse comportamento do AtomPub protocol.
Dados binários usando WCF Data Services
WCF Data Services implementa completamente a manipulação de recursos binários. Para isso, define uma interface, chamada IDataServiceStreamProvider, que, quando implementada no serviço, é usada pelo runtime para prover acesso a Streams usadas para leitura e gravação de Media Resources.
As classes que definem os recursos que possuem Media Resources associados devem estar sinalizadas com o atributo HasStreamAttribute.
Preparando o Model para prover recursos binários
Como disse no início, nosso objetivo é fazer com que cada “Person” possua uma foto associada. Preparar o Model para fazer isso é simples, basta marcar a classe com o atributo HasStreamAttribute. Observe:
[DataServiceKey("Id")] [HasStream()] public class Person { public int Id { get; set; } public string Name { get; set; } public Enterprise EmployerField = null; public Enterprise Employer { get { return this.EmployerField; } set { if (value == this.EmployerField) return; if (this.EmployerField != null && this.EmployerField.Employees.Contains(this)) this.EmployerField.Employees.Remove(this); this.EmployerField = value; if (this.EmployerField != null && !this.EmployerField.Employees.Contains(this)) this.EmployerField.Employees.Add(this); } } }
Esse código é idêntico àquele apresentado no post anterior, exceto pelo atributo.
Iniciando a construção de um provedor de recursos binários
Depois de marcar as classes que fornecem media resources com o atributo HasStream, há a necessidade de criar uma classe que proverá os recursos binários para nosso serviço. Essa classe deverá realizar a interface IDataServiceStreamProvider.
Eis nosso código inicial:
public class MRProvider : IDataServiceStreamProvider { public void DeleteStream(object entity, DataServiceOperationContext operationContext) { throw new NotImplementedException(); } public Stream GetReadStream( object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext ) { throw new NotImplementedException(); } public Uri GetReadStreamUri(object entity, DataServiceOperationContext operationContext) { throw new NotImplementedException(); } public string GetStreamContentType(object entity, DataServiceOperationContext operationContext) { throw new NotImplementedException(); } public string GetStreamETag(object entity, DataServiceOperationContext operationContext) { throw new NotImplementedException(); } public Stream GetWriteStream(object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext ) { throw new NotImplementedException(); } public string ResolveType(string entitySetName, DataServiceOperationContext operationContext) { throw new NotImplementedException(); } public int StreamBufferSize { get { throw new NotImplementedException(); } } }
Para vincular esse provider com o serviço, basta realizar a interface IServiceProvider na implementação do DataService. Observe:
public class ODataService : DataService, IServiceProvider { public static void InitializeService(IDataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.All); } public object GetService(Type serviceType) { if (serviceType == typeof(IDataServiceStreamProvider)) return new MRProvider(); return null; } }
Colocando o serviço “no ar”, iremos conferir que uma exception será lançada assim que um recurso Person for evocado. O primeiro método “sem código” de nosso provider que é requisitado é … GetStreamEtag
Método GetStreamETag
Este método é “chamado” pelo runtime do data servce para retornar o eTag do stream que está associado a entidade especificada. Esta informação é utilizada para gestão de concorrência em dados binários. Retornando null, assumimos que não há qualquer gestão de concorrência.
Para nosso serviço, por hora, não faremos gestão de concorrência (esse seria um bom tema para outro post), por isso, retornamos null. Observe:
public string GetStreamETag(object entity, DataServiceOperationContext operationContext) { return null; }
Mudanças feitas, vamos ver o que ocorre ao requisitar os dados de um recurso Person. A próxima exception disparada está em …
Método GetReadStreamUri
Este método é chamado pelo runtime para determinar a URI que será usada para requisitar o media resource. Quando esse método retorna null, caberá ao método determinara essa URI.
Por hora, retornemos null e confiemos no Runtime
public Uri GetReadStreamUri(object entity, DataServiceOperationContext operationContext) { return null; }
Mudanças feitas, vamos ver o que ocorre ao requisitar os dados de um recurso Person. A próxima exception disparada está em …
Método GetStreamContentType
Este método é chamado pelo runtime para determinar o tipo de recurso correspondente ao media resource. Diferente do que fizemos até aqui, precisamos retornar algo.
Para nosso serviço, assumo que todos os binários são JPEG:
public string GetStreamContentType(object entity, DataServiceOperationContext operationContext) { return "image/jpeg"; }
Mudanças feitas…
Voltamos a ter um recurso representado. Como pode ser observado no XML, há uma indicação para nosso MR para “People( 0)/$value”. Entretanto, ao solicitar esse recurso, temos, outra vez, um exception. Agora em…
Método GetReadStream
Este método é “chamado” pelo runtime para retornar o media resource como um stream. Quando implementamos a interface IDataServiceStreamProvider, este método contem o código que retornará a stream que será utilizada pelo data service para retornar o media resource que está vinculado a uma media link entry
Eis a nossa implementação:
public Stream GetReadStream( object entity, string etag, bool? checkETagForEquality, DataServiceOperationContext operationContext ) { if (checkETagForEquality != null) throw new DataServiceException(500, "ETag header is not supported"); var person = entity as Person; if (person == null) throw new DataServiceException(500, "Only Person is supported"); var file = string.Format("{0}.jpg", person.Id); if (!File.Exists(file)) throw new DataServiceException(500, "Image file not found."); return new FileStream(file, FileMode.Open); }
Algumas considerações:
- Deixo explicito a ausência de suporte a ETag;
- O runtime do WCF Data Services encaminha uma instância da entidade sendo processada como parâmetro. Nesse serviço, suporto apenas um tipo de entidade;
- Assumo que a imagem estará disponível em um arquivo, cujo o nome corresponde ao id. Poderia utilizar outras fontes (considere blobs no azure);
Executando o serviço, temos… outro exception, agora …
Propriedade StreamBufferSize
Essa propriedade define o tamanho dos buffers internos utilizados pelo WCF Data Services para manipular streams. Para nosso exemplo, utilizarei um buffer de 64Kb. Observe:
public int StreamBufferSize { get { return 65536; } }
Executando o serviço, temos…
Sucesso!
Por hoje, era isso!
Posted on março 30, 2011 by elemarjr
0