REST 101 – Parte 5 – Implementando um Streaming Provider (read-only) OData usando IDataServiceStreamProvider

Posted on março 30, 2011 by elemarjr

0


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…

image

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:

  1. Deixo explicito a ausência de suporte a ETag;
  2. 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;
  3. 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…

image

Sucesso!

Por hoje, era isso!

Smiley piscando

Posted in: C#, REST