Skip to content

Fix/#620 #621

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 5 commits into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public virtual void Configure(
AppDbContext context)
{
context.Database.EnsureCreated();
app.EnableDetailedErrors();
app.UseJsonApi();
}

Expand Down
4 changes: 2 additions & 2 deletions src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public class JsonApiOptions : IJsonApiOptions
/// <summary>
/// Whether or not stack traces should be serialized in Error objects
/// </summary>
public static bool DisableErrorStackTraces { get; set; }
public static bool DisableErrorStackTraces { get; set; } = true;

/// <summary>
/// Whether or not source URLs should be serialized in Error objects
/// </summary>
public static bool DisableErrorSource { get; set; }
public static bool DisableErrorSource { get; set; } = true;

/// <summary>
/// Whether or not ResourceHooks are enabled.
Expand Down
33 changes: 19 additions & 14 deletions src/JsonApiDotNetCore/Controllers/BaseJsonApiController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Extensions;
Expand Down Expand Up @@ -106,7 +104,11 @@ public virtual async Task<IActionResult> GetAsync(TId id)
if (_getById == null) throw Exceptions.UnSupportedRequestMethod;
var entity = await _getById.GetAsync(id);
if (entity == null)
return NotFound();
{
// remove the null argument as soon as this has been resolved:
// https://github.com/aspnet/AspNetCore/issues/16969
return NotFound(null);
}

return Ok(entity);
}
Expand All @@ -117,7 +119,11 @@ public virtual async Task<IActionResult> GetRelationshipsAsync(TId id, string re
throw Exceptions.UnSupportedRequestMethod;
var relationship = await _getRelationships.GetRelationshipsAsync(id, relationshipName);
if (relationship == null)
return NotFound();
{
// remove the null argument as soon as this has been resolved:
// https://github.com/aspnet/AspNetCore/issues/16969
return NotFound(null);
}

return Ok(relationship);
}
Expand All @@ -141,7 +147,7 @@ public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
return Forbidden();

if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid)
return UnprocessableEntity(ModelState.ConvertToErrorCollection<T>(GetAssociatedResource()));
return UnprocessableEntity(ModelState.ConvertToErrorCollection<T>());

entity = await _create.CreateAsync(entity);

Expand All @@ -155,12 +161,17 @@ public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)
return UnprocessableEntity();

if (_jsonApiOptions.ValidateModelState && !ModelState.IsValid)
return UnprocessableEntity(ModelState.ConvertToErrorCollection<T>(GetAssociatedResource()));
return UnprocessableEntity(ModelState.ConvertToErrorCollection<T>());

var updatedEntity = await _update.UpdateAsync(id, entity);

if (updatedEntity == null)
return NotFound();
{
// remove the null argument as soon as this has been resolved:
// https://github.com/aspnet/AspNetCore/issues/16969
return NotFound(null);
}


return Ok(updatedEntity);
}
Expand All @@ -180,14 +191,8 @@ public virtual async Task<IActionResult> DeleteAsync(TId id)
return NotFound();
return NoContent();
}

internal Type GetAssociatedResource()
{
return GetType().GetMethod(nameof(GetAssociatedResource), BindingFlags.Instance | BindingFlags.NonPublic)
.DeclaringType
.GetGenericArguments()[0];
}
}

public class BaseJsonApiController<T>
: BaseJsonApiController<T, int>
where T : class, IIdentifiable<int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCore.Extensions
Expand All @@ -20,7 +21,6 @@ public static class IApplicationBuilderExtensions
/// <returns></returns>
public static void UseJsonApi(this IApplicationBuilder app)
{
DisableDetailedErrorsIfProduction(app);
LogResourceGraphValidations(app);
using (var scope = app.ApplicationServices.CreateScope())
{
Expand All @@ -38,14 +38,14 @@ public static void UseJsonApi(this IApplicationBuilder app)
app.UseEndpoints(endpoints => endpoints.MapControllers());
}

private static void DisableDetailedErrorsIfProduction(IApplicationBuilder app)
/// <summary>
/// Configures your application to return stack traces in error results.
/// </summary>
/// <param name="app"></param>
public static void EnableDetailedErrors(this IApplicationBuilder app)
{
var webHostEnvironment = (IWebHostEnvironment) app.ApplicationServices.GetService(typeof(IWebHostEnvironment));
if (webHostEnvironment.EnvironmentName == "Production")
{
JsonApiOptions.DisableErrorStackTraces = true;
JsonApiOptions.DisableErrorSource = true;
}
JsonApiOptions.DisableErrorStackTraces = false;
JsonApiOptions.DisableErrorSource = false;
}

private static void LogResourceGraphValidations(IApplicationBuilder app)
Expand Down
4 changes: 2 additions & 2 deletions src/JsonApiDotNetCore/Extensions/ModelStateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace JsonApiDotNetCore.Extensions
{
public static class ModelStateExtensions
{
public static ErrorCollection ConvertToErrorCollection<T>(this ModelStateDictionary modelState, Type resourceType)
public static ErrorCollection ConvertToErrorCollection<T>(this ModelStateDictionary modelState) where T : class, IIdentifiable
{
ErrorCollection collection = new ErrorCollection();
foreach (var entry in modelState)
Expand All @@ -19,7 +19,7 @@ public static ErrorCollection ConvertToErrorCollection<T>(this ModelStateDiction
continue;
}

var targetedProperty = resourceType.GetProperty(entry.Key);
var targetedProperty = typeof(T).GetProperty(entry.Key);
var attrName = targetedProperty.GetCustomAttribute<AttrAttribute>().PublicAttributeName;

foreach (var modelError in entry.Value.Errors)
Expand Down
43 changes: 25 additions & 18 deletions src/JsonApiDotNetCore/Formatters/JsonApiWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Serialization.Server;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
Expand Down Expand Up @@ -32,33 +33,39 @@ public async Task WriteAsync(OutputFormatterWriteContext context)
throw new ArgumentNullException(nameof(context));

var response = context.HttpContext.Response;
using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
using var writer = context.WriterFactory(response.Body, Encoding.UTF8);
string responseContent;

if (_serializer == null)
{
responseContent = JsonConvert.SerializeObject(context.Object);
}
else
{
response.ContentType = Constants.ContentType;
string responseContent;
if (_serializer == null)
try
{
responseContent = JsonConvert.SerializeObject(context.Object);
}
else
{
try
if (context.Object is ProblemDetails pd)
{
responseContent = _serializer.Serialize(context.Object);
}
catch (Exception e)
{
_logger?.LogError(new EventId(), e, "An error ocurred while formatting the response");
var errors = new ErrorCollection();
errors.Add(new Error(400, e.Message, ErrorMeta.FromException(e)));
errors.Add(new Error(pd.Status.Value, pd.Title, pd.Detail));
responseContent = _serializer.Serialize(errors);
response.StatusCode = 400;
} else
{
responseContent = _serializer.Serialize(context.Object);
}
}

await writer.WriteAsync(responseContent);
await writer.FlushAsync();
catch (Exception e)
{
_logger?.LogError(new EventId(), e, "An error ocurred while formatting the response");
var errors = new ErrorCollection();
errors.Add(new Error(500, e.Message, ErrorMeta.FromException(e)));
responseContent = _serializer.Serialize(errors);
response.StatusCode = 500;
}
}
await writer.WriteAsync(responseContent);
await writer.FlushAsync();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ public bool ShouldSerializeData()
/// <summary>
/// Internally used for "single" primary data.
/// </summary>
internal T SingleData { get; private set; }
[JsonIgnore]
public T SingleData { get; private set; }

/// <summary>
/// Internally used for "many" primary data.
/// </summary>
internal List<T> ManyData { get; private set; }
[JsonIgnore]
public List<T> ManyData { get; private set; }

/// <summary>
/// Internally used to indicate if the document's primary data is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,31 @@ public async Task GetResources_NoDefaultPageSize_ReturnsResources()
// Assert
Assert.True(result.Data.Count >= 20);
}

[Fact]
public async Task GetSingleResource_ResourceDoesNotExist_ReturnsNotFoundWithNullData()
{
// Arrange
var context = _fixture.GetService<AppDbContext>();
context.TodoItems.RemoveRange(context.TodoItems);
await context.SaveChangesAsync();

var builder = new WebHostBuilder()
.UseStartup<NoDefaultPageSizeStartup>();
var httpMethod = new HttpMethod("GET");
var route = $"/api/v1/todoItems/123";
var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// Act
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
var document = JsonConvert.DeserializeObject<Document>(body);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
Assert.Null(document.Data);
}
}
}