-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #148 from adamalesandro/master
Move Wiki to /docs folder and update links
- Loading branch information
Showing
8 changed files
with
618 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
``` |
Oops, something went wrong.