Skip to content

Instantly share code, notes, and snippets.

@mcintyre321
Last active January 8, 2019 10:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mcintyre321/af68a899c30d014462e731864ae2b1a4 to your computer and use it in GitHub Desktop.
Save mcintyre321/af68a899c30d014462e731864ae2b1a4 to your computer and use it in GitHub Desktop.
Siren from IQueryable
namespace _10PrintHello.Apps.Things.SirenTables
{
public class Comment
{
public int postId { get; set; }
[Sortable]
public int id { get; set; }
[Sortable]
public string name { get; set; }
[Sortable]
public string email { get; set; }
public string body { get; set; }
}
}
namespace _10PrintHello.Apps.Things.SirenTables
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reflection;
using LinqToAnything;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using SirenDotNet;
static class Ext
{
public static IApplicationBuilder ExposeQueryable<T>(this IApplicationBuilder appBuilder, string path, IQueryable<T> queryable)
{
const int pageSize = 10;
var sortableProperties = typeof(T).GetTypeInfo().DeclaredProperties.Where(dp => dp.GetCustomAttribute<SortableAttribute>() != null).ToArray();
return appBuilder.Use(
(context, next) =>
{
if (context.Request.Path.Value.StartsWith("/comments"))
{
(T[] page, QueryInfo queryInfo, bool hasMoreData, string sortedBy) results = ProcessQueryString(context.Request.Query);
var pageNumber = 1 + results.queryInfo.Skip / pageSize;
var links = new List<(string, string)>()
{
("self", $"/{path}{context.Request.QueryString.ToUriComponent()}"),
("first", $"/{path}{context.Request.QueryString.Remove("$skip").ToUriComponent()}")
};
foreach (var sortableProperty in sortableProperties)
{
if (results.queryInfo.OrderBy?.Name == sortableProperty.Name) continue;
var queryString = context.Request.QueryString.Replace("$orderBy", sortableProperty.Name);
links.Add(
("sortedBy" + char.ToUpper(sortableProperty.Name[0]) + sortableProperty.Name.Substring(1),
$"/{path}{queryString.ToUriComponent()}"));
}
if (results.hasMoreData)
{
var queryString = context.Request.QueryString.Replace("$skip", (results.queryInfo.Skip + pageSize).ToString());
links.Add(("next", $"/{path}{queryString.ToUriComponent()}"));
}
if (results.queryInfo.Skip >= pageSize)
{
var queryString = context.Request.QueryString.Replace("$skip", (results.queryInfo.Skip - pageSize).ToString());
links.Add(("prev", $"/{path}{queryString.ToUriComponent()}"));
}
var doc = BuildSirenDoc($"{typeof(T).Name}s page {pageNumber}", pageNumber, results.sortedBy, results.page, links);
context.Response.ContentType = "application/vnd.siren+json";
var json = JsonConvert.SerializeObject(
doc,
Newtonsoft.Json.Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
return context.Response.WriteAsync(json);
}
return next();
(T[] page, QueryInfo queryInfo, bool hasMoreData, string sortedBy) ProcessQueryString(IQueryCollection query)
{
QueryInfo qiOut = null;
String sortedBy = null;
IQueryable<T> interceptedQueryable = new DelegateQueryable<T>(
qi =>
{
qiOut = qi;
var q = queryable;
if (sortableProperties.Any(sp => sp.Name == qi.OrderBy?.Name))
{
sortedBy = qi.OrderBy.Name;
q = q.OrderBy(qi.OrderBy.Name);
}
return q.Skip(qi.Skip).Take(pageSize + 1);
});
var eq = interceptedQueryable;
foreach (var kvp in query.SelectMany(x => x.Value.Select(value => new { x.Key, value })))
{
if (kvp.Key == "$orderBy") eq = eq.OrderBy(kvp.value);
if (kvp.Key == "$skip") eq = eq.Skip(int.Parse(kvp.value));
}
var resultsPlusOne = eq.Take(pageSize + 1).ToArray();
var hasMoreData = resultsPlusOne.Length == pageSize + 1;
return (resultsPlusOne.Take(pageSize).ToArray(), qiOut, hasMoreData, sortedBy);
}
Entity BuildSirenDoc(string title, int pageNumber, string sortedBy, T[] page, List<(string rel, string uri)> links)
{
var doc = new SirenDotNet.Entity() { Title = title, Properties = JObject.FromObject(new { page, pageNumber, sortedBy }) };
doc.Links = links.Select(l => new SirenDotNet.Link() { Rel = new[] { l.rel }, Href = new Uri(l.uri, UriKind.Relative) });
return doc;
}
});
}
}
}
namespace _10PrintHello.Apps.Things.SirenTables
{
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
static class QueryStringExtension
{
public static QueryString Replace(this QueryString qs, string key, string value)
{
return qs.Remove(key).Add(key, value);
}
public static QueryString Remove(this QueryString qs, params string[] keys)
{
return QueryHelpers.ParseQuery(qs.ToUriComponent()).Where(qh => keys.Contains(qh.Key) == false)
.SelectMany(qh => qh.Value.Select(value => new { qh.Key, value })).Aggregate(
QueryString.Empty,
(queryString, kvp) => queryString.Add(kvp.Key, kvp.value));
}
}
}
namespace _10PrintHello.Apps.Things.SirenTables
{
using System;
class SortableAttribute : Attribute
{
}
}
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
var commentsJson = new WebClient().DownloadString(@"https://jsonplaceholder.typicode.com/comments");
var comments = JsonConvert.DeserializeObject<Comment[]>(commentsJson).AsQueryable();
app.ExposeQueryable("comments", comments);
...
}
...
@mcintyre321
Copy link
Author

MIT Licence

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment