Skip to content
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

opt into JsonApi per webapi endpoint #187

Merged

Conversation

barsh
Copy link
Contributor

@barsh barsh commented Apr 4, 2018

For existing WebApi projects, that don't use JsonApi/Saule we need a way to preserve backwards compatibility and opt into JsonApi on a case by case basis.

For example: api/v2/companies uses the attribute filter JsonApi which configures it to return a JsonAPI response instead of the default response.

image

With this change, it's not necessary to call ConfigureJsonApi during application startup:

image

@joukevandermaas
Copy link
Owner

@barsh Thanks so much for contributing!

A few notes:

  1. The way this is implemented now, it will not support all of Saule's features. A lot of that depends on the delegating handler that is also registered in the configure methods.
  2. Saule does support a way of optionally processing only certain requests (at least in the current pre-release). You can use this overload of the configure method:
config.ConfigureJsonApi(new JsonApiConfiguration(), FormatterPriority.AddFormatterToEnd);

Now only requests with the accept request header value of application/vnd.api+json will be processed:

Accept: application/vnd.api+json

That effectively enables clients to opt in to JSON API, which may not be what you need. To enable the server to opt in for specific endpoints, I think we need a solution that at least does the same amount of processing that the regular setup does (i.e. supports all features). Ideally we would find a way to make it possible to implement this JsonApiAttribute as a user of Saule, without needing to add it to the library itself.

Perhaps a class JsonApiProcessor and a method with a signature like this:

void ProcessRequest(HttpRequestMessage request, HttpResponseMessage response)

which does the same thing as the delegating handler. Then when a user of Saule implements the JsonApiAttribute action filter, they can call this ProcessRequest method before using the media-type formatter.

What do you think?

@barsh
Copy link
Contributor Author

barsh commented Apr 6, 2018

@joukevandermaas I'll have a go at it over the weekend and update this PR


var value = context.Response.Content as ObjectContent;

var config = new JsonApiConfiguration();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joukevandermaas, I couldn't figure out how to get the configuration, so I created a new one. Is that OK? Is there a way to get the configuration?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, perhaps we can also create an overload that takes it as a parameter.

var formatter = new JsonApiMediaTypeFormatter(config).GetPerRequestFormatterInstance(
typeof(string),
context.Request,
new MediaTypeHeaderValue(Constants.MediaType));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joukevandermaas this works but it feels hacky. Is there a better way to get the formatter?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the constructor overload:

internal JsonApiMediaTypeFormatter(HttpRequestMessage request, JsonApiConfiguration config)

That one is called internally by GetPerRequestFormatterInstance.

Copy link
Owner

@joukevandermaas joukevandermaas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making these changes! I think this will enable more fine-grained control over what Saule does, which is great.

There are some tests for the delegating handler. Perhaps they can be copied and changed so we cover both paths?

Other than that I had a few minor questions, but the approach is looking really solid!

/// process request as JSON API response
/// </summary>
/// <param name="context">context</param>
public static void ProcessRequest(HttpActionExecutedContext context)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this take the HttpRequestMessage and HttpResponseMessage (or similar) as parameters? The ActionExecutedContext is very closely tied to action filters.


var value = context.Response.Content as ObjectContent;

var config = new JsonApiConfiguration();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, perhaps we can also create an overload that takes it as a parameter.

var formatter = new JsonApiMediaTypeFormatter(config).GetPerRequestFormatterInstance(
typeof(string),
context.Request,
new MediaTypeHeaderValue(Constants.MediaType));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the constructor overload:

internal JsonApiMediaTypeFormatter(HttpRequestMessage request, JsonApiConfiguration config)

That one is called internally by GetPerRequestFormatterInstance.

/// <summary>
/// Processes JSON API responses
/// </summary>
public class JsonApiProcessor
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this class should be static or sealed.

/// An optional attribute that can be used to opt an api into returning a JsonApi response.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class JsonApiAttribute : ActionFilterAttribute
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this still be sealed?

/// <summary>
/// Processes JSON API responses
/// </summary>
public class JsonApiProcessor
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we can call this from the delegating handler? Now the code is duplicated. I think since this is public, it would be awesome if the delegating handler used the same code-path.

@barsh
Copy link
Contributor Author

barsh commented Apr 9, 2018

@joukevandermaas I have incorporated all of your feedback.

The delegating handler and JsonApiAttribute are now using the same method JsonApiProcessor.ProcessRequest, which means we might not need to duplicate the tests, but I think we do need to add the following test:

JsonApiAttribute should respond with json api even when client does not request media type 'application/vnd.api+json'

Can you give me some direction on where this test should go and point me towards an example that I can use as a reference?

@joukevandermaas
Copy link
Owner

@barsh awesome! I will review your changes as soon as possible. I think these tests have some setup that should allow you to write the test you propose. They create an http client that talks to some of the endpoints in an example application (see here). You could add a controller or action method there.

Alternatively you could create a new file that just tests the new process method you created (whichever you find easier to do).

@barsh
Copy link
Contributor Author

barsh commented Apr 9, 2018

@joukevandermaas see added test 0f1b7b2

@joukevandermaas joukevandermaas merged commit 1c90bf1 into joukevandermaas:master Apr 10, 2018
@joukevandermaas
Copy link
Owner

@barsh thanks so much for seeing this through! I'll publish a new pre-release with these changes.

@barsh barsh deleted the opt-into-json-api-per-webapi branch April 19, 2018 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants