Skip to content

v0.1.0 #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Aug 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
96a6570
add overridable controllers
jaredcnance Aug 29, 2016
5f8e176
add customizable resource expressions
jaredcnance Aug 29, 2016
c9e4b3f
add IJsonApiContext
jaredcnance Aug 29, 2016
91f29c1
update readme
jaredcnance Aug 29, 2016
61e75c5
update readme
jaredcnance Aug 29, 2016
a983d18
refactor UseController method
jaredcnance Aug 29, 2016
1d1ac7f
git merge master
jaredcnance Aug 30, 2016
df88765
add extension tests
jaredcnance Aug 30, 2016
e7a4466
add appveyor stuff
jaredcnance Aug 30, 2016
2de281e
move build file
jaredcnance Aug 30, 2016
80a56c4
add xproj files
jaredcnance Aug 30, 2016
456887c
update readme
jaredcnance Aug 30, 2016
a6a0e0e
update package versions
jaredcnance Aug 30, 2016
91fb64b
add travis ci
jaredcnance Aug 30, 2016
f8e159b
move travis yml
jaredcnance Aug 30, 2016
2bd1ccf
update readme
jaredcnance Aug 30, 2016
37ddbd9
fix ci build
jaredcnance Aug 30, 2016
abdfa5c
add singleOrDefault expression tree builder
jaredcnance Aug 30, 2016
7c0f6a8
add vscode launch.json and tasks.json
jaredcnance Aug 30, 2016
7418787
add GenericDataAccessAbstraction
jaredcnance Aug 30, 2016
690ea21
remove net451 dependency test proj
jaredcnance Aug 31, 2016
8830064
add SingleOrDefault test
jaredcnance Aug 31, 2016
5f276bb
attempt to fix travis.yml
jaredcnance Aug 31, 2016
1523214
update SingleOrDefault to test attr other than Id
jaredcnance Aug 31, 2016
4518436
fix travis yml syntax
jaredcnance Aug 31, 2016
526aa5f
add where data accessor
jaredcnance Aug 31, 2016
48ae63f
refactor(GenericDataAccess): consolidate query code
jaredcnance Aug 31, 2016
4673653
Add filtering
jaredcnance Aug 31, 2016
a464431
respond 404 if the route is not defined
jaredcnance Aug 31, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
language: csharp
sudo: required
dist: trusty
env:
- CLI_VERSION=latest
addons:
apt:
packages:
- gettext
- libcurl4-openssl-dev
- libicu-dev
- libssl-dev
- libunwind8
- zlib1g
mono:
- 4.2.3
os:
- linux
- osx
osx_image: xcode7.1
branches:
only:
- master
before_install:
- if test "$TRAVIS_OS_NAME" = "osx"; then brew update; brew install openssl; ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; fi
install:
- export DOTNET_INSTALL_DIR="$PWD/.dotnetcli"
- curl -sSL https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0/scripts/obtain/dotnet-install.sh | bash /dev/stdin --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR"
- export PATH="$DOTNET_INSTALL_DIR:$PATH"
script:
- ./build.sh
64 changes: 64 additions & 0 deletions Build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<#
.SYNOPSIS
You can add this to you build script to ensure that psbuild is available before calling
Invoke-MSBuild. If psbuild is not available locally it will be downloaded automatically.
#>
function EnsurePsbuildInstalled{
[cmdletbinding()]
param(
[string]$psbuildInstallUri = 'https://raw.githubusercontent.com/ligershark/psbuild/master/src/GetPSBuild.ps1'
)
process{
if(-not (Get-Command "Invoke-MsBuild" -errorAction SilentlyContinue)){
'Installing psbuild from [{0}]' -f $psbuildInstallUri | Write-Verbose
(new-object Net.WebClient).DownloadString($psbuildInstallUri) | iex
}
else{
'psbuild already loaded, skipping download' | Write-Verbose
}

# make sure it's loaded and throw if not
if(-not (Get-Command "Invoke-MsBuild" -errorAction SilentlyContinue)){
throw ('Unable to install/load psbuild from [{0}]' -f $psbuildInstallUri)
}
}
}

# Taken from psake https://github.com/psake/psake

<#
.SYNOPSIS
This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode
to see if an error occcured. If an error is detected then an exception is thrown.
This function allows you to run command-line programs without having to
explicitly check the $lastexitcode variable.
.EXAMPLE
exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed"
#>
function Exec
{
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
[Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd)
)
& $cmd
if ($lastexitcode -ne 0) {
throw ("Exec: " + $errorMessage)
}
}

if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse }

EnsurePsbuildInstalled

exec { & dotnet restore }

Invoke-MSBuild

$revision = @{ $true = $env:APPVEYOR_BUILD_NUMBER; $false = 1 }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
$revision = "{0:D4}" -f [convert]::ToInt32($revision, 10)

exec { & dotnet test .\JsonApiDotNetCoreTests -c Release }

exec { & dotnet pack .\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=$revision }
18 changes: 18 additions & 0 deletions JsonApiDotNetCore/Abstractions/IJsonApiContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Routing;
using Microsoft.AspNetCore.Http;

namespace JsonApiDotNetCore.Abstractions
{
public interface IJsonApiContext
{
JsonApiModelConfiguration Configuration { get; }
object DbContext { get; }
HttpContext HttpContext { get; }
Route Route { get; }
string GetEntityName();
Type GetEntityType();
Type GetJsonApiResourceType();
}
}
4 changes: 2 additions & 2 deletions JsonApiDotNetCore/Abstractions/JsonApiContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace JsonApiDotNetCore.Abstractions
{
public class JsonApiContext
public class JsonApiContext : IJsonApiContext
{
public HttpContext HttpContext { get; }
public Route Route { get; }
Expand All @@ -24,7 +24,7 @@ public JsonApiContext(HttpContext httpContext, Route route, object dbContext, Js

public Type GetJsonApiResourceType()
{
return Configuration.ResourceMapDefinitions[Route.BaseModelType];
return Configuration.ResourceMapDefinitions[Route.BaseModelType].Item1;
}

public string GetEntityName()
Expand Down
30 changes: 29 additions & 1 deletion JsonApiDotNetCore/Configuration/IJsonApiModelConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
using System;
using System.Collections.Generic;
using AutoMapper;
using JsonApiDotNetCore.Abstractions;

namespace JsonApiDotNetCore.Configuration
{
public interface IJsonApiModelConfiguration
{
/// <summary>
/// The database context to use
/// </summary>
/// <typeparam name="T"></typeparam>
/// <exception cref="ArgumentException"></exception>
void UseContext<T>();

/// <summary>
/// The request namespace.
/// </summary>
/// <param name="ns"></param>
/// <example>api/v1</example>
void SetDefaultNamespace(string ns);
void DefineResourceMapping(Action<Dictionary<Type,Type>> mapping);

/// <summary>
/// Define explicit mapping of a model to a class that implements IJsonApiResource
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TResource"></typeparam>
/// <param name="mappingExpression"></param>
/// <exception cref="ArgumentException"></exception>
void AddResourceMapping<TModel, TResource>(Action<IMappingExpression> mappingExpression);

/// <summary>
/// Specifies a controller override class for a particular model type.
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TController"></typeparam>
/// <exception cref="ArgumentException"></exception>
void UseController<TModel, TController>();
}
}
96 changes: 96 additions & 0 deletions JsonApiDotNetCore/Configuration/JsonApiConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Reflection;
using JsonApiDotNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper;
using JsonApiDotNetCore.Abstractions;
using JsonApiDotNetCore.Attributes;

namespace JsonApiDotNetCore.Configuration
{
public class JsonApiConfigurationBuilder
{
private readonly Action<IJsonApiModelConfiguration> _configurationAction;
private JsonApiModelConfiguration Config { get; set; }

public JsonApiConfigurationBuilder(Action<IJsonApiModelConfiguration> configurationAction)
{
Config = new JsonApiModelConfiguration();
_configurationAction = configurationAction;
}

public JsonApiModelConfiguration Build()
{
Config = new JsonApiModelConfiguration();
_configurationAction.Invoke(Config);
CheckIsValidConfiguration();
LoadModelRoutesFromContext();
SetupResourceMaps();
return Config;
}

private void CheckIsValidConfiguration()
{
if (Config.ContextType == null)
throw new NullReferenceException("DbContext is not specified");
}

private void LoadModelRoutesFromContext()
{
// Assumption: all DbSet<> types should be included in the route list
var properties = Config.ContextType.GetProperties().ToList();

properties.ForEach(property =>
{
if (property.PropertyType.GetTypeInfo().IsGenericType &&
property.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
{

var modelType = property.PropertyType.GetGenericArguments()[0];

var route = new RouteDefinition
{
ModelType = modelType,
PathString = RouteBuilder.BuildRoute(Config.Namespace, property.Name),
ContextPropertyName = property.Name
};

Config.Routes.Add(route);
}
});
}

private void SetupResourceMaps()
{
LoadDefaultResourceMaps();
var mapConfiguration = new MapperConfiguration(cfg =>
{
foreach (var definition in Config.ResourceMapDefinitions)
{
var mappingExpression = cfg.CreateMap(definition.Key, definition.Value.Item1);
definition.Value.Item2?.Invoke(mappingExpression);
}
});
Config.ResourceMapper = mapConfiguration.CreateMapper();
}

private void LoadDefaultResourceMaps()
{
var resourceAttribute = typeof(JsonApiResourceAttribute);
var modelTypes = Assembly.GetEntryAssembly().DefinedTypes.Where(t => t.GetCustomAttributes(resourceAttribute).Count() == 1);

foreach (var modelType in modelTypes)
{
var resourceType = ((JsonApiResourceAttribute)modelType.GetCustomAttribute(resourceAttribute)).JsonApiResourceType;

// do not overwrite custom definitions
if(!Config.ResourceMapDefinitions.ContainsKey(modelType.UnderlyingSystemType))
{
Config.ResourceMapDefinitions.Add(modelType.UnderlyingSystemType, new Tuple<Type, Action<IMappingExpression>>(resourceType, null));
}
}
}
}
}
59 changes: 22 additions & 37 deletions JsonApiDotNetCore/Configuration/JsonApiModelConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Linq;
using System.Reflection;
using AutoMapper;
using JsonApiDotNetCore.Abstractions;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.JsonApi;
using JsonApiDotNetCore.Routing;
using Microsoft.AspNetCore.Http;
Expand All @@ -16,59 +18,42 @@ public class JsonApiModelConfiguration : IJsonApiModelConfiguration
public IMapper ResourceMapper;
public Type ContextType { get; set; }
public List<RouteDefinition> Routes = new List<RouteDefinition>();
public Dictionary<Type, Type> ResourceMapDefinitions = new Dictionary<Type, Type>();
public Dictionary<Type, Tuple<Type, Action<IMappingExpression>>> ResourceMapDefinitions = new Dictionary<Type, Tuple<Type, Action<IMappingExpression>>>();
public Dictionary<Type, Type> ControllerOverrides = new Dictionary<Type, Type>();

public void SetDefaultNamespace(string ns)
{
Namespace = ns;
}

public void DefineResourceMapping(Action<Dictionary<Type,Type>> mapping)
public void AddResourceMapping<TModel, TResource>(Action<IMappingExpression> mappingExpression)
{
mapping.Invoke(ResourceMapDefinitions);
var resourceType = typeof(TResource);
var modelType = typeof(TModel);

var mapConfiguration = new MapperConfiguration(cfg =>
{
foreach (var definition in ResourceMapDefinitions)
{
cfg.CreateMap(definition.Key, definition.Value);
}
});
if (!resourceType.GetInterfaces().Contains(typeof(IJsonApiResource)))
throw new ArgumentException("Specified type does not implement IJsonApiResource", nameof(resourceType));

ResourceMapper = mapConfiguration.CreateMapper();
ResourceMapDefinitions.Add(modelType, new Tuple<Type, Action<IMappingExpression>>(resourceType, mappingExpression));
}

public void UseContext<T>()
{
// TODO: assert the context is of type DbContext
ContextType = typeof(T);
LoadModelRoutesFromContext();
}

private void LoadModelRoutesFromContext()
public void UseController<TModel, TController>()
{
// Assumption: all DbSet<> types should be included in the route list
var properties = ContextType.GetProperties().ToList();
var modelType = typeof(TModel);
var controllerType = typeof(TController);

properties.ForEach(property =>
{
if (property.PropertyType.GetTypeInfo().IsGenericType &&
property.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
{
if (!controllerType.GetInterfaces().Contains(typeof(IJsonApiController)))
throw new ArgumentException("Specified type does not implement IJsonApiController", nameof(controllerType));

var modelType = property.PropertyType.GetGenericArguments()[0];
ControllerOverrides[modelType] = controllerType;
}

var route = new RouteDefinition
{
ModelType = modelType,
PathString = RouteBuilder.BuildRoute(Namespace, property.Name),
ContextPropertyName = property.Name
};
public void UseContext<T>()
{
ContextType = typeof(T);

Routes.Add(route);
}
});
if (!typeof(DbContext).IsAssignableFrom(ContextType))
throw new ArgumentException("Context Type must derive from DbContext", nameof(T));
}

}
}
Loading