Skip to content

Commit

Permalink
Merge pull request #148 from adamalesandro/master
Browse files Browse the repository at this point in the history
Move Wiki to /docs folder and update links
  • Loading branch information
joukevandermaas authored May 11, 2017
2 parents 0835dab + aa6e4ac commit d92ae45
Show file tree
Hide file tree
Showing 8 changed files with 618 additions and 0 deletions.
72 changes: 72 additions & 0 deletions docs/Customizing-the-serialization-process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

## Customizing the serialization process

[Back to home](index)

----

To add serialization and deserialization to your Web Api project,
add the following lines to your `Startup.cs`:

```csharp
public static void Register(HttpConfiguration config)
{
// ...
config.ConfigureJsonApi();

// ...
}
```

> **Note**: if you are using an older (< 1.4) version of Saule, add the following lines instead:
```csharp
public static void Register(HttpConfiguration config)
{
// ...
config.Formatters.Clear();
config.Formatters.Add(new JsonApiMediaTypeFormatter());

// ...
}
```

This will register Saule to serialize and deserialize every incoming
and outgoing request.

## Using `JsonConverter`

Saule allows you to specify any number of `JsonConverters`.
To e.g. serialize enums as strings, use:

```csharp
config.ConfigureJsonApi(new JsonApiConfiguration {
JsonConverters = { new StringEnumConverter() }
});
```

## Creating an `ExceptionFilter`

When creating an exception filter, the normal formatter pipeline is skipped.
This means you'll need to manually add the `JsonApiMediaTypeFormatter` when
creating your response:
```csharp
sealed class MyExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent(
context.Exception.GetType(),
context.Exception,
new JsonApiMediaTypeFormatter())
};
}
}
```
The media type formatter will automatically serialize subclasses of `System.Exception`
or `System.Web.Http.HttpError` as a Json Api error response. No other types are supported
at this time.
57 changes: 57 additions & 0 deletions docs/Generating-links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## Generating links

[Back to home](index)

----

> **Note**: You need at least Saule 1.2 for this to work.
Saule lets you customize the links that are generated during serialization. It
uses the `IUrlPathBuilder` interface to generate all urls. You can provide an
implementation of this interface to the `ConfigureJsonApi` extension method on `HttpConfiguration`:

```csharp
config.ConfigureJsonApi(new JsonApiConfiguration {
UrlPathBuilder = new DefaultUrlPathBuilder()
});
```

If you want to add a prefix to all urls, you can do so in the constructor:

```csharp
var prefixedUrls = new DefaultUrlPathBuilder("/api");
```

Saule comes with two implementations of `IUrlPathBuilder`:

Link type|`DefaultUrlPathBuilder`|`CanonicalUrlPathBuilder`
---|---|---
Collection of resources|`/people/`|`/people/`
Individual resource|`/people/123/`|`/people/123/`
Related resource|`/people/123/employer/`|`/companies/456/`
Related resource's self link|`/people/123/relationships/employer/`|`/people/123/relationships/employer/`

If you want to customize generated links beyond this, you can do so by extending
one of the above implementations or by implementing the interface from scratch.
The `IUrlPathBuilder` interface consists of four methods, which correspond to the
link types in the table above:

```csharp
public interface IUrlPathBuilder
{
// collection of resources
string BuildCanonicalPath(ApiResource resource);

// individual resource
string BuildCanonicalPath(ApiResource resource, string id);

// related resource
string BuildRelationshipPath(ApiResource resource, string id, ResourceRelationship relationship);

// related resource self link
string BuildRelationshipPath(ApiResource resource, string id, ResourceRelationship relationship, string relatedResourceId);
}
```

The included implementations use these methods internally as well; if you override
`BuildCanonicalPath(ApiResource)`, it will affect the result of all other methods.
35 changes: 35 additions & 0 deletions docs/Including-(or-not-including)-resources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Including (or not including) resources

[Back to home](index)

----

> **Note**: You need at least Saule 1.6 for this to work.
By default, Saule includes all related resources into the generated JSON. If you do not want this, you can add the `DisableDefaultIncludedAttribute` to your action method:

```cs
[DisableDefaultIncluded]
[ReturnsResource(typeof(PersonResource))]
[HttpGet]
public IEnumerable<Person> Get()
{
return GetThePeople();
}
```

This makes it so all resources are only included when requested using the [`include` query parameter](http://jsonapi.org/format/#fetching-includes). Clients can now specify explicitly which relationships to include (whitelist).

If you specify the `AllowsQueryAttribute`, clients can still specify the `include` query parameter to control what to include:

```cs
[AllowsQuery]
[ReturnsResource(typeof(PersonResource))]
[HttpGet]
public IEnumerable<Person> Get()
{
return GetThePeople();
}
```

A request to `/people?include=address,friends` will now *only* include the `address` and `friends` relationships, nothing else (such as `job`). However, if the client does not specify any `include` parameter, all related resources will still be included.
28 changes: 28 additions & 0 deletions docs/Pagination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Pagination

[Back to home](index)

----

> **Note**: You need at least Saule 1.1 for this to work.
Adding pagination to an action method's results is as easy as adding an attribute:

```csharp
public class PeopleController : ApiController
{
[Paginate(PerPage = 25)]
[ReturnsResource(typeof(PersonResource))]
public IQueryable<Person> Get()
{
return People.FindAll()
}
}
```

This will generate `next`, `prev` and `first` links in your responses, add interpret the page[number]
query parameter when appropriate.

Saule will use LINQ to query the `IQueryable<T>` you return, so pagination is not done in memory.
If you return an `IEnumerable<T>`, the query is executed in memory instead. Note that Saule does
not support the non-generic versions of `IQueryable` and `IEnumerable`.
186 changes: 186 additions & 0 deletions docs/Queryable-endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
## Queryable endpoints

[Back to home](index)

> **Note**: You need at least Saule 1.3 for this to work.
Saule supports queryable endpoints. These endpoints allow users of
your API to specify constraints on the results. Saule will automatically
apply the query to the `IQueryable<T>` or `IEnumerable<T>` you return from
your action methods.

Saule uses LINQ internally, so queries will be evaluated lazily. If you use
e.g. Entity Framework to generate the `IQueryable<T>`s, the query is executed
on the database, rather than in memory.

To enable queries on an endpoint, simply add the `AllowsQueryAttribute` to
the action method:

```csharp
[HttpGet]
[AllowsQuery]
[Route('api/people')]
public IQueryable<Person> GetPeople()
{
return Database.People.FindAll();
}
```

> **Note**: As of version 1.4, Saule only supports the `sort` and `filter` query parameters.
> The same attribute may support other queries in the future.
```
GET mywebsite.com/api/people?sort=last-name,-age
```

```json
{
"data": [
{
"type": "person",
"id": "0",
"attributes": {
"first-name": "Sheba",
"last-name": "Bockman",
"age": 20
},
"links": {
"self": "http://example.com/people/0/"
}
},
{
"type": "person",
"id": "3",
"attributes": {
"first-name": "Eugenie",
"last-name": "Bockman",
"age": 6
},
"links": {
"self": "http://example.com/people/3/"
}
},
{
"type": "person",
"id": "4",
"attributes": {
"first-name": "Larissa",
"last-name": "Summers",
"age": 70
},
"links": {
"self": "http://example.com/people/4/"
}
},
{
"type": "person",
"id": "1",
"attributes": {
"first-name": "Vergie",
"last-name": "Summers",
"age": 47
},
"links": {
"self": "http://example.com/people/1/"
}
},
{
"type": "person",
"id": "2",
"attributes": {
"first-name": "Francisco",
"last-name": "Summers",
"age": 41
},
"links": {
"self": "http://example.com/people/2/"
}
}
],
"links": {
"self": "http://example.com/api/people?sort=last-name,-age"
}
}
```

## Customizing filtering expressions

> **Note**: you need at least Saule 1.4 for this to work.
Sometimes you want to do something specific when a client specifies a filter query parameter.
For example, you might want to do case insensitive filtering for strings, so `/people?filter[name]=smith`
will not return an empty result set.

To do this in Saule, you can set *query filter expressions* for specific types in your Json Api configuration:

```csharp
public static void Register(HttpConfiguration config)
{
var jsonApiConfig = new JsonApiConfiguration();
jsonApiConfig.QueryFilterExpressions.Set(new CaseInsensitiveStringQueryFilterExpression());

config.ConfigureJsonApi(jsonApiConfig);
}
```

If you want to do something more specific, you can also directly specify a lambda expression. For example,
substring search can be implemented easily as follows:

```csharp
public static void Register(HttpConfiguration config)
{
var jsonApiConfig = new JsonApiConfiguration();
jsonApiConfig.QueryFilterExpressions.Set<string>((property, filter) => property.Contains(filter));

config.ConfigureJsonApi(jsonApiConfig);
}
```

If you set the query filter expression for a base class, it will also apply to all child classes. For example,
to overwrite the filter expression for all types, simply set it for `System.Object`:

```csharp
public static void Register(HttpConfiguration config)
{
var jsonApiConfig = new JsonApiConfiguration();
jsonApiConfig.QueryFilterExpressions.Set<object>((property, filter) => property != filter);

config.ConfigureJsonApi(jsonApiConfig);
}
```

This will make filters black list-like, rather than the default white list.

If you want even more control, you can implement the `IQueryFilterExpression` interface. In addition to all
of the above, this also gives you access to the `PropertyInfo` of the property that is being filtered on. This
way, you can even apply specific ways of filtering to specific filters.

Say for example that you want to do substring search for properties called `Name`, and the default equality
comparison otherwise:

```csharp
public class NameSubstringQueryFilterExpression : DefaultQueryFilterExpression<string>
{
public override Expression<Func<string, string, bool>> GetForProperty(PropertyInfo property)
{
if (property.Name == "Name")
{
return (prop, filter) => prop.Contains(filter);
}

return base.GetForProperty(property);
}
}
```

You can then use it as before:

```csharp
public static void Register(HttpConfiguration config)
{
var jsonApiConfig = new JsonApiConfiguration();
jsonApiConfig.QueryFilterExpressions.Set(new NameSubstringQueryFilterExpression());

config.ConfigureJsonApi(jsonApiConfig);
}
```
Loading

0 comments on commit d92ae45

Please sign in to comment.