Custom OData Provider Without Underlying DB

25 September 2014 - OData

Today I will do my OData session at BASTA conference in Mainz. This time I have a bit more time so I will add a demo of creating a custom OData provider without any underlying database. The result is generated based on the OData query on the fly. In this blog article I share the code.

You can download the entire source code from my GitHub Samples repository.

The OData Controller

Let's start with the important part of the sample: The ODataController.

using Microsoft.OData.Core.UriParser.Semantic;
using Microsoft.OData.Core.UriParser.TreeNodeKinds;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Http;
using System.Web.OData;
using System.Web.OData.Query;
using System.Web.OData.Routing;

namespace CustomODataProvider.Provider.Controller
{
[ODataRoutePrefix("Customers")]
public class CustomerController : ODataController
{
// Helpers for generating customer names
private readonly char[] letters1 = "aeiou".ToArray();
private readonly char[] letters2 = "bcdfgklmnpqrstvw".ToArray();
private Random random = new Random();

private const int pageSize = 100;

[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.Filter | AllowedQueryOptions.Top | AllowedQueryOptions.Skip | AllowedQueryOptions.OrderBy)]
[ODataRoute]
public IHttpActionResult Get(ODataQueryOptions<Customer> options)
{
// Calculate number of results based on $top
var numberOfResults = pageSize;
if (options.Top != null)
{
numberOfResults = options.Top.Value;
}

// Analyze $filter
string equalFilter = null;
if (options.Filter != null)
{
// We only support a single "eq" filter
var binaryOperator = options.Filter.FilterClause.Expression as BinaryOperatorNode;
if (binaryOperator == null || binaryOperator.OperatorKind != BinaryOperatorKind.Equal)
{
return InternalServerError();
}

// One side has to be a reference to CustomerName property, the other side has to be a constant
var propertyAccess = binaryOperator.Left as SingleValuePropertyAccessNode ?? binaryOperator.Right as SingleValuePropertyAccessNode;
var constant = binaryOperator.Left as ConstantNode ?? binaryOperator.Right as ConstantNode;
if (propertyAccess == null || propertyAccess.Property.Name != "CustomerName" || constant == null)
{
return InternalServerError();
}

// Save equal filter value
equalFilter = constant.Value.ToString();

// Return between 1 and 2 rows (CustomerName is not a primary key)
numberOfResults = Math.Min(random.Next(1, 3), numberOfResults);
}

// Generate result
var result = new List<Customer>();
for (var i = 0; i < numberOfResults; i++)
{
result.Add(new Customer() { CustomerID = Guid.NewGuid(), CustomerName = equalFilter ?? GenerateCustomerName() });
}

return Ok(result.AsQueryable());
}

private string GenerateCustomerName()
{
var length = random.Next(5, 8);
var result = new StringBuilder(length);
for (var i = 0; i < length; i++)
{
var letter = (i % 2 == 0 ? letters1[random.Next(letters1.Length)] : letters2[random.Next(letters2.Length)]).ToString();
result.Append(i == 0 ? letter.ToUpper() : letter);
}

return result.ToString();
}
}
}

Configuration the OData Endpoint

using Microsoft.OData.Edm;
using System.Net.Http.Formatting;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using System.Web.OData.Routing.Conventions;

namespace CustomODataProvider.Provider
{
public static class ODataConfiguration
{
public static void RegisterOData(HttpConfiguration config)
{
config.Formatters.Clear();
config.Formatters.Add(new JsonMediaTypeFormatter());

var routeConventions = ODataRoutingConventions.CreateDefault();
config.MapODataServiceRoute("odata", "odata", GetModel());
}

private static IEdmModel GetModel()
{
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
return modelBuilder.GetEdmModel();
}
}
}

The Self Host

using CustomODataProvider.Provider;
using Microsoft.Owin.Hosting;
using Owin;
using System;
using System.Web.Http;

namespace CustomODataProvider.Hosting
{
class Program
{
static void Main(string[] args)
{
using (WebApp.Start<Startup>("http://localhost:5000"))
{
Console.WriteLine( "Server ready... Press Enter to quit.");
Console.ReadLine();
}
}
}

public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ODataConfiguration.RegisterOData(config);
app.UseWebApi(config);
}
}
}