Skip to content

[WIP] The big rewrite #96

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 66 commits into from
Aug 22, 2015
Merged

[WIP] The big rewrite #96

merged 66 commits into from
Aug 22, 2015

Conversation

csantero
Copy link
Collaborator

@csantero csantero commented Jul 1, 2015

Summary

This is a rewrite of the JSONAPI.NET to improve compatibility with the 1.0 spec and to give API authors greater control over what goes into a response document, as well as greater access to information from the request document. This organization of the codebase is now vastly improved, with many smaller files (RIP the monolithic media type formatter). The library now fully embraces dependency injection, allowing API authors to provide their own handlers for pretty much any component in the system.

Rationale

When JSONAPI.NET was first created, the JSON API spec looked very different from how it does today. Polymorphism wasn't really well defined, nor were things like pagination and sorting. It was possible to define resource "linkage" without including the linked resources in the compound document. It was possible to query resources using multiple comma-separated ids. Attributes were found directly on the resource object root, instead of inside an attributes object. As a result of the changes that led to JSON API 1.0, some of the fundamental assumptions in JSONAPI.NET started to get in the way, and I felt that in order to move forward, we would have to break with the past.

Documents

One of the largest frustrations writing an API that uses JSONAPI.NET has been the lack of control over the response, as well as restricted access to data being sent in a PATCH or POST request. The problem is that JsonApiFormatter has been doing too much. Currently, it needs to know about all your registered types, and it determines how to serialize/deserialize them. This has the following consequences:

  1. Every resource type has to be known at registration time (app start-up). It is not possible to create temporary resource types later or decide what fields to serialize in a response at run-time.
  2. It is impossible to override the rules for (de)serializing a particular field.
  3. There is no access to metadata at all. At one point we were going to inject the MetadataManager into controllers, but this would have been a hacky and brittle solution.
  4. It is difficult to include related resources. Until this PR, this library's support for compound documents has been very poor.

My solution to these problems has been to introduce an intermediate data structure, called the Document, which is analogous to the JSON API document. There are three types of Document that JsonApiFormatter knows how to serialize or deserialize: ISingleResourceDocument, IResourceCollectionDocument, and IErrorDocument. It is now the controller action's responsibility to build these documents.

JsonApiController and IDocumentMaterializer

The generic ApiController<T> has been renamed to JsonApiController<T>, and the EntityFramework-specific ApiController<T, TDbContext> has been removed. JsonApiController defines the same actions it did before, but now it is no longer necessary to override those actions to provide your own functionality. These action methods now all delegate to methods on a new interface called IDocumentMaterializer<T>, which is the new main point of extensibility in JSONAPI.NET.

Your implementation of IDocumentMaterializer<T> is responsible for building document instances that the controller actions will return to the JsonApiFormatter. If you are using Entity Framework, there is an EntityFrameworkDocumentMaterializer<T> that should work for many use-cases. If you aren't using EF, or you need more control, you can write your own IDocumentMaterializer<T> implementation. SingleResourceDocumentBuilder, ResourceCollectionDocumentBuilder, and DefaultQueryableResourceCollectionDocumentBuilder are tools that can help you build your own document based on a registered POCO object.

Of course, you don't have to use JsonApiController if you don't want to. As long as your controller actions return ISingleResourceDocument, IResourceCollectionDocument, or IErrorDocument, then JsonApiFormatter can work with it. For POST and PATCH, your actions will have to take a parameter of ISingleResourceDocument or IResourceCollectionDocument.

Note: It is possible to return a POCO from an action like before, but this will not give you the ability to send metadata or to perform side-loading.

Configuration and Dependency injection

Previously, JSONAPI.NET has shied away from using ASP.NET Web API's DI system to supply controller constructor parameters, because we didn't want to step on the user's toes as they may have configured their own IDependencyResolver. Since the changes in this PR require services to be injected into controllers via the constructor, it's no longer possible to avoid DI inside this library. Therefore, JSONAPI.NET will start including integration packages for the most popular DI frameworks. My own project uses Autofac, so that is the first framework I have added support for. An example setup for configuring JSONAPI.NET with Autofac is in the JSONAPI.TodoMVC.API project. I am happy to accept pull requests that add support for other DI frameworks.

Registry Naming conventions

IModelManager still exists, in modified form. First off, it has been renamed to IResourceTypeRegistry. Second, the default implementation, ResourceTypeRegistry no longer concerns itself with how to name resource types and fields, but rather delegates to an INamingConventions service to do this. DefaultNamingConventions will dasherize all property names, unless those properties have a JsonProperty attribute. If you prefer camel-cased field names or something else, you can now provide your own INamingConventions.

Link templates

You can still specify link templates for relationship properties, but now there are two kinds: relationship links, and related resource links. You can now specify an ILinkConventions which decides how to format these URLs. The default implementation follows the JSON API recommendations for formatting relationship and related resource URLs.

SerializeAsAttribute

It's gone. Deciding what links or related resources to include for a particular resource's relationship is now a concern of document builders, not something to be set at compile-time by decorating properties with attributes.

Action filters

JsonApiQueryableAttribute is gone, because action filters are not meant to return IQueryable<T> anymore. Its functionality has been moved to DefaultQueryableResourceCollectionDocumentBuilder.

There are two new action filters, FallbackDocumentBuilderAttribute is responsible for the functionality that allows controller actions to return POCOs, and also ensures that the correct HTTP status code is set for error documents. JsonApiExceptionFilterAttribute catches exceptions and converts them to IErrorDocument. JsonApiHttpConfiguration will make sure that both these action filters are registered globally, along with the JsonApiFormatter

Still to do

  • Implement relationship actions (related resource actions already work)
  • Make sure there is an easy way to specify related resources when using default services.
  • DefaultQueryableResourceCollectionDocumentBuilder needs to send pagination links.
  • Ensure that BaseUrlService works with endpoints that have a path root. Such as /api/todos instead of just /todos
  • Update README

Chris Santero added 30 commits June 28, 2015 18:46
+ is no longer allowed for sorting ascending
Payload has been replaced with Document.
Serializer has been replaced with Formatter.
Purged the last few instances of Model Manager and Metadata Manager
Cleared up some Resharper warnings
also merged related resource tests into the FetchingResources test
class.
@GlennGeelen
Copy link
Contributor

I wanted to use this PR in my project to check if it worked. But I got lost at how to register all my model types, what I'd done:

  • In my webConfig I implemented this
ResourceTypeRegistrar registrar = new ResourceTypeRegistrar(new DefaultNamingConventions(new PluralizationService(pluralizationLib)));
var apiConfig = new JsonApiConfiguration(registrar);
apiConfig.RegisterResourceType<Models.Session>();

var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterApiControllers(Assembly.GetExecutingAssembly());

var container = containerBuilder.Build();

apiConfig.SetupHttpConfigurationUsingAutofac(config, container);

where config is HTTPConfiguration

  • In my ApiController I have a Post-method which is something like:
public IJsonApiDocument Post(Session session) {
   if(ok) {
      return SingleResourceDocument(session)
   } else {
      return ErrorDocument(errorArray, null);
   }
}

Do I need to extend IResourceObject in my Session model so JSONAPI recognizes the session like:

public class Session : IResourceObject {
   // class properties
}

Because I get an error something like: Session type cannot be deserialized.

So I have no idea how I can return my Session model as a SingleResourceDocument.

@csantero
Copy link
Collaborator Author

@GlennGeelen So, first I must apologize for the state of this PR. We've been furiously trying to get our product out the door and until we finish I can only contribute to JSONAPI.NET that which directly impacts our launch.

Secondly, after using the PR for a few weeks, I'm rather unhappy with the configuration and registration story. There is still far too much boilerplate required of the API developer, and writing document materializers is a real pain. I have some ideas for changes that will improve the situation, but I don't know when I'll have time to get to them.

As to solving your problem given the current state of the branch, if you look at the samples in JSONAPI.AcceptanceTests.EntityFrameworkTestWebApp, you'll see the way you're meant to set things up. I've gone with a single controller approach: all you have to do is create one controller called MainController that extends JsonApiController. That controller declares all the action methods you will need to serve up JSON API responses. The idea is that instead of writing your own action methods to return documents, you define document materializers and register them at configuration time. Startup.cs in the above-mentioned project has some good examples of how to do this, though like I said, the configuration system sucks, and as your experience makes clear, is not at all intuitive.

I should note that if you do want to use controllers with action methods, you can still do this. You can use an ISingleResourceDocumentBuilder, IResourceCollectionDocumentBuilder, or IQueryableResourceCollectionDocumentBuilder to prepare the intermediate document. Also, your action parameters for a POST or PATCH action must be of type ISingleResourceDocument, since that's all the JsonApiFormatter knows how to deserialize. If you want to convert the intermediate ISingleResourceDocument to a Session, then you need to do that yourself for now... This is one of the problem points with the current redesign.

@GlennGeelen
Copy link
Contributor

Ok, thanks @csantero for the examples and further explanation.

csantero added a commit that referenced this pull request Aug 22, 2015
@csantero csantero merged commit 718acb4 into master Aug 22, 2015
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.

4 participants