Skip to content

Commit eb8fece

Browse files
authored
Merge pull request #124 from Research-Institute/develop
v2.0.8
2 parents 0077cc1 + 91fcbb9 commit eb8fece

File tree

13 files changed

+580
-41
lines changed

13 files changed

+580
-41
lines changed

appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ deploy:
4343
server: https://www.myget.org/F/research-institute/api/v2/package
4444
api_key:
4545
secure: 6CeYcZ4Ze+57gxfeuHzqP6ldbUkPtF6pfpVM1Gw/K2jExFrAz763gNAQ++tiacq3
46-
skip_symbols: true
46+
skip_symbols: false
4747
on:
4848
branch: develop
4949
- provider: NuGet
5050
server: https://www.myget.org/F/jadnc/api/v2/package
5151
api_key:
5252
secure: 6CeYcZ4Ze+57gxfeuHzqP6ldbUkPtF6pfpVM1Gw/K2jExFrAz763gNAQ++tiacq3
53-
skip_symbols: true
53+
skip_symbols: false
5454
on:
5555
branch: unstable
5656
- provider: NuGet
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Threading.Tasks;
5+
using JsonApiDotNetCore.Controllers;
6+
using JsonApiDotNetCore.Internal;
7+
using Microsoft.AspNetCore.Mvc.Filters;
8+
9+
namespace JsonApiDotNetCore.Controllers
10+
{
11+
public abstract class HttpRestrictAttribute : ActionFilterAttribute, IAsyncActionFilter
12+
{
13+
protected abstract string[] Methods { get; }
14+
15+
public override async Task OnActionExecutionAsync(
16+
ActionExecutingContext context,
17+
ActionExecutionDelegate next)
18+
{
19+
var method = context.HttpContext.Request.Method;
20+
21+
if(CanExecuteAction(method) == false)
22+
throw new JsonApiException("405", $"This resource does not support {method} requests.");
23+
24+
await next();
25+
}
26+
27+
private bool CanExecuteAction(string requestMethod)
28+
{
29+
return Methods.Contains(requestMethod) == false;
30+
}
31+
}
32+
33+
public class HttpReadOnlyAttribute : HttpRestrictAttribute
34+
{
35+
protected override string[] Methods { get; } = new string[] { "POST", "PATCH", "DELETE" };
36+
}
37+
38+
public class NoHttpPostAttribute : HttpRestrictAttribute
39+
{
40+
protected override string[] Methods { get; } = new string[] { "POST" };
41+
}
42+
43+
public class NoHttpPatchAttribute : HttpRestrictAttribute
44+
{
45+
protected override string[] Methods { get; } = new string[] { "PATCH" };
46+
}
47+
48+
public class NoHttpDeleteAttribute : HttpRestrictAttribute
49+
{
50+
protected override string[] Methods { get; } = new string[] { "DELETE" };
51+
}
52+
}

src/JsonApiDotNetCore/Formatters/JsonApiReader.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.IO;
3+
using System.Text;
34
using System.Threading.Tasks;
5+
using JsonApiDotNetCore.Internal;
46
using JsonApiDotNetCore.Serialization;
57
using JsonApiDotNetCore.Services;
68
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -50,6 +52,13 @@ public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
5052
context.HttpContext.Response.StatusCode = 422;
5153
return InputFormatterResult.FailureAsync();
5254
}
55+
catch(JsonApiException jex)
56+
{
57+
_logger?.LogError(new EventId(), jex, "An error occurred while de-serializing the payload");
58+
context.HttpContext.Response.StatusCode = jex.GetStatusCode();
59+
context.HttpContext.Response.Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(jex.GetError())));
60+
return InputFormatterResult.FailureAsync();
61+
}
5362
}
5463

5564
private string GetRequestBody(Stream body)

src/JsonApiDotNetCore/Internal/JsonApiExceptionFactory.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,19 @@ public static JsonApiException GetException(Exception exception)
1515
case "InvalidCastException":
1616
return new JsonApiException("409", exception.Message);
1717
default:
18-
return new JsonApiException("500", exception.Message);
18+
return new JsonApiException("500", exception.Message, GetExceptionDetail(exception.InnerException));
1919
}
2020
}
21+
22+
private static string GetExceptionDetail(Exception exception)
23+
{
24+
string detail = null;
25+
while(exception != null)
26+
{
27+
detail = $"{detail}{exception.Message}; ";
28+
exception = exception.InnerException;
29+
}
30+
return detail;
31+
}
2132
}
2233
}
Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
using System;
2+
using System.Reflection;
23

34
namespace JsonApiDotNetCore.Internal
45
{
56
public static class TypeHelper
67
{
78
public static object ConvertType(object value, Type type)
89
{
9-
if(value == null)
10-
return null;
10+
try
11+
{
12+
if (value == null)
13+
return null;
1114

12-
type = Nullable.GetUnderlyingType(type) ?? type;
15+
type = Nullable.GetUnderlyingType(type) ?? type;
1316

14-
var stringValue = value.ToString();
15-
16-
if(type == typeof(Guid))
17-
return Guid.Parse(stringValue);
17+
var stringValue = value.ToString();
1818

19-
return Convert.ChangeType(stringValue, type);
19+
if (type == typeof(Guid))
20+
return Guid.Parse(stringValue);
21+
22+
if (type == typeof(DateTimeOffset))
23+
return DateTimeOffset.Parse(stringValue);
24+
25+
return Convert.ChangeType(stringValue, type);
26+
}
27+
catch (Exception e)
28+
{
29+
throw new FormatException($"{ value } cannot be converted to { type.GetTypeInfo().Name }", e);
30+
}
2031
}
2132
}
2233
}

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<VersionPrefix>2.0.7</VersionPrefix>
3+
<VersionPrefix>2.0.8</VersionPrefix>
44
<TargetFrameworks>netstandard1.6</TargetFrameworks>
55
<AssemblyName>JsonApiDotNetCore</AssemblyName>
66
<PackageId>JsonApiDotNetCore</PackageId>

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class JsonApiDeSerializer : IJsonApiDeSerializer
1616
private readonly IJsonApiContext _jsonApiContext;
1717
private readonly IGenericProcessorFactory _genericProcessorFactor;
1818

19-
public JsonApiDeSerializer(
19+
public JsonApiDeSerializer(
2020
IJsonApiContext jsonApiContext,
2121
IGenericProcessorFactory genericProcessorFactory)
2222
{
@@ -26,9 +26,16 @@ public JsonApiDeSerializer(
2626

2727
public object Deserialize(string requestBody)
2828
{
29-
var document = JsonConvert.DeserializeObject<Document>(requestBody);
30-
var entity = DocumentToObject(document.Data);
31-
return entity;
29+
try
30+
{
31+
var document = JsonConvert.DeserializeObject<Document>(requestBody);
32+
var entity = DocumentToObject(document.Data);
33+
return entity;
34+
}
35+
catch (Exception e)
36+
{
37+
throw new JsonApiException("400", "Failed to deserialize request body", e.Message);
38+
}
3239
}
3340

3441
public object Deserialize<TEntity>(string requestBody)
@@ -38,26 +45,40 @@ public object Deserialize<TEntity>(string requestBody)
3845

3946
public object DeserializeRelationship(string requestBody)
4047
{
41-
var data = JToken.Parse(requestBody)["data"];
48+
try
49+
{
50+
var data = JToken.Parse(requestBody)["data"];
4251

43-
if(data is JArray)
44-
return data.ToObject<List<DocumentData>>();
52+
if (data is JArray)
53+
return data.ToObject<List<DocumentData>>();
4554

46-
return new List<DocumentData> { data.ToObject<DocumentData>() };
55+
return new List<DocumentData> { data.ToObject<DocumentData>() };
56+
}
57+
catch (Exception e)
58+
{
59+
throw new JsonApiException("400", "Failed to deserialize request body", e.Message);
60+
}
4761
}
4862

4963
public List<TEntity> DeserializeList<TEntity>(string requestBody)
5064
{
51-
var documents = JsonConvert.DeserializeObject<Documents>(requestBody);
65+
try
66+
{
67+
var documents = JsonConvert.DeserializeObject<Documents>(requestBody);
68+
69+
var deserializedList = new List<TEntity>();
70+
foreach (var data in documents.Data)
71+
{
72+
var entity = DocumentToObject(data);
73+
deserializedList.Add((TEntity)entity);
74+
}
5275

53-
var deserializedList = new List<TEntity>();
54-
foreach (var data in documents.Data)
76+
return deserializedList;
77+
}
78+
catch (Exception e)
5579
{
56-
var entity = DocumentToObject(data);
57-
deserializedList.Add((TEntity)entity);
80+
throw new JsonApiException("400", "Failed to deserialize request body", e.Message);
5881
}
59-
60-
return deserializedList;
6182
}
6283

6384
private object DocumentToObject(DocumentData data)
@@ -66,7 +87,7 @@ private object DocumentToObject(DocumentData data)
6687
_jsonApiContext.RequestEntity = contextEntity;
6788

6889
var entity = Activator.CreateInstance(contextEntity.EntityType);
69-
90+
7091
entity = SetEntityAttributes(entity, contextEntity, data.Attributes);
7192
entity = SetRelationships(entity, contextEntity, data.Relationships);
7293

@@ -106,8 +127,8 @@ private object SetEntityAttributes(
106127
}
107128

108129
private object SetRelationships(
109-
object entity,
110-
ContextEntity contextEntity,
130+
object entity,
131+
ContextEntity contextEntity,
111132
Dictionary<string, RelationshipData> relationships)
112133
{
113134
if (relationships == null || relationships.Count == 0)
@@ -117,18 +138,18 @@ private object SetRelationships(
117138

118139
foreach (var attr in contextEntity.Relationships)
119140
{
120-
entity = attr.IsHasOne
121-
? SetHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships)
141+
entity = attr.IsHasOne
142+
? SetHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships)
122143
: SetHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships);
123144
}
124145

125146
return entity;
126147
}
127148

128-
private object SetHasOneRelationship(object entity,
129-
PropertyInfo[] entityProperties,
130-
RelationshipAttribute attr,
131-
ContextEntity contextEntity,
149+
private object SetHasOneRelationship(object entity,
150+
PropertyInfo[] entityProperties,
151+
RelationshipAttribute attr,
152+
ContextEntity contextEntity,
132153
Dictionary<string, RelationshipData> relationships)
133154
{
134155
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id");
@@ -142,7 +163,7 @@ private object SetHasOneRelationship(object entity,
142163
{
143164
var relationshipAttr = _jsonApiContext.RequestEntity.Relationships
144165
.SingleOrDefault(r => r.PublicRelationshipName == relationshipName);
145-
166+
146167
var data = (Dictionary<string, string>)relationshipData.ExposedData;
147168

148169
if (data == null) return entity;
@@ -159,9 +180,9 @@ private object SetHasOneRelationship(object entity,
159180
}
160181

161182
private object SetHasManyRelationship(object entity,
162-
PropertyInfo[] entityProperties,
163-
RelationshipAttribute attr,
164-
ContextEntity contextEntity,
183+
PropertyInfo[] entityProperties,
184+
RelationshipAttribute attr,
185+
ContextEntity contextEntity,
165186
Dictionary<string, RelationshipData> relationships)
166187
{
167188
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName);
@@ -179,7 +200,7 @@ private object SetHasManyRelationship(object entity,
179200

180201
var genericProcessor = _genericProcessorFactor.GetProcessor(attr.Type);
181202
var ids = relationshipData.ManyData.Select(r => r["id"]);
182-
genericProcessor.SetRelationships(entity, attr, ids);
203+
genericProcessor.SetRelationships(entity, attr, ids);
183204
}
184205

185206
return entity;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using JsonApiDotNetCore.Controllers;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace JsonApiDotNetCoreExample.Controllers.Restricted
5+
{
6+
[Route("[controller]")]
7+
[HttpReadOnly]
8+
public class ReadOnlyController : Controller
9+
{
10+
[HttpGet]
11+
public IActionResult Get() => Ok();
12+
13+
[HttpPost]
14+
public IActionResult Post() => Ok();
15+
16+
[HttpPatch]
17+
public IActionResult Patch() => Ok();
18+
19+
[HttpDelete]
20+
public IActionResult Delete() => Ok();
21+
}
22+
23+
[Route("[controller]")]
24+
[NoHttpPost]
25+
public class NoHttpPostController : Controller
26+
{
27+
[HttpGet]
28+
public IActionResult Get() => Ok();
29+
30+
[HttpPost]
31+
public IActionResult Post() => Ok();
32+
33+
[HttpPatch]
34+
public IActionResult Patch() => Ok();
35+
36+
[HttpDelete]
37+
public IActionResult Delete() => Ok();
38+
}
39+
40+
[Route("[controller]")]
41+
[NoHttpPatch]
42+
public class NoHttpPatchController : Controller
43+
{
44+
[HttpGet]
45+
public IActionResult Get() => Ok();
46+
47+
[HttpPost]
48+
public IActionResult Post() => Ok();
49+
50+
[HttpPatch]
51+
public IActionResult Patch() => Ok();
52+
53+
[HttpDelete]
54+
public IActionResult Delete() => Ok();
55+
}
56+
57+
[Route("[controller]")]
58+
[NoHttpDelete]
59+
public class NoHttpDeleteController : Controller
60+
{
61+
[HttpGet]
62+
public IActionResult Get() => Ok();
63+
64+
[HttpPost]
65+
public IActionResult Post() => Ok();
66+
67+
[HttpPatch]
68+
public IActionResult Patch() => Ok();
69+
70+
[HttpDelete]
71+
public IActionResult Delete() => Ok();
72+
}
73+
}

0 commit comments

Comments
 (0)