Last active
January 8, 2019 10:03
-
-
Save mcintyre321/af68a899c30d014462e731864ae2b1a4 to your computer and use it in GitHub Desktop.
Siren from IQueryable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
}); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace _10PrintHello.Apps.Things.SirenTables | |
{ | |
using System; | |
class SortableAttribute : Attribute | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
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); | |
... | |
} | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
MIT Licence