Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
ascott18 committed Sep 16, 2024
2 parents 7fea229 + c971ea1 commit 179268c
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 13 deletions.
47 changes: 47 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "C# (.NET)",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/github-cli:1": {
"version": "2"
},
"ghcr.io/devcontainers/features/powershell:1": {
"version": "latest"
},
"ghcr.io/azure/azure-dev/azd:0": {
"version": "latest"
},
"ghcr.io/devcontainers/features/common-utils:2": {},
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "8.0",
"dotnetRuntimeVersions": "8.0",
"aspNetCoreRuntimeVersions": "8.0"
},
"ghcr.io/devcontainers/features/node:1.3.1": {
"version": "20"
}
},
"postCreateCommand": "dotnet restore && cd ./src && cd coalesce-vue && npm ci && cd ../coalesce-vue-vuetify2 && npm ci && cd ../coalesce-vue-vuetify3 && npm ci",

"customizations": {
"vscode": {
"extensions": [
"ms-vscode.vscode-node-azure-pack",
"GitHub.vscode-github-actions",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csdevkit",
"ms-dotnetools.csharp",
"streetsidesoftware.code-spell-checker",
"dbaeumer.vscode-eslint",
"vue.volar",
"vue.vscode-typescript-vue-plugin",
"esbenp.prettier-vscode",
"antfu.goto-alias"
]
}
}
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[Documentation](https://intellitect.github.io/Coalesce) · [Get Started](#Get-Started) · [Builds](#Builds)

Check out [The Coalesce Podcast](https://www.youtube.com/playlist?list=PLRjft3wXvK_srWUHS4w_lVrIfB4uNqfSD) for some step-by-step tutorials about Coalesce features.

Coalesce is a framework for rapid-development of ASP.NET Core + Vue.js web applications. It works from the Entity Framework Core data model that you design, automating the creation of the glue - DTOs, API Controllers, and TypeScript - that sit between your data and the UI of your application.

## Fundamentals
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/linkcheck-skip-file.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ github.com/intellitect/coalesce/edit
https://www.npmjs.com/package
https://github.com/IntelliTect/Coalesce
https://intellitect.com
https://www.intellitect.com
6 changes: 5 additions & 1 deletion docs/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"IntelliTect",
"mkdir",
"nameof",
"overridable",
"discoverability",
"netcoreapp",
"netstandard",
"overridable",
Expand All @@ -37,7 +39,9 @@
"Vitest",
"Vuetify",
"webp",
"wwwroot"
"wwwroot",
"Swashbuckle",
"swashbuckle"
],
"ignorePaths": [
"node_modules"
Expand Down
52 changes: 50 additions & 2 deletions docs/modeling/model-components/attributes/many-to-many.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ViewModel.
The named specified in the attribute will be used as the name of a collection of the objects on the other side of the relationship in the generated [TypeScript ViewModels](/stacks/vue/layers/viewmodels.md#model-data-properties).

## Example Usage

In this example, we have a Person entity and an Appointment entity that share a many-to-many relationship. The PersonAppointment entity serves as the required middle table.
``` c#
public class Person
{
Expand All @@ -22,6 +22,26 @@ public class Person
[ManyToMany("Appointments")]
public ICollection<PersonAppointment> PersonAppointments { get; set; }
}

public class Appointment
{
public int AppointmentId { get; set; }
public DateTime AppointmentDate { get; set; }

[ManyToMany("People")]
public ICollection<PersonAppointment> PersonAppointments { get; set; }
}

public class PersonAppointment
{
public int PersonAppointmentId { get; set; }

public int PersonId { get; set; }
public Person Person { get; set; }

public int AppointmentId { get; set; }
public Appointment Appointment { get; set; }
}
```

## Properties
Expand All @@ -33,4 +53,32 @@ The name of the collection that will contain the set of objects on the other sid

<Prop def="public string FarNavigationProperty { get; set; }" />

The name of the navigation property on the middle entity that points at the far side of the many-to-many relationship. Use this to resolve ambiguities when the middle table of the many-to-many relationship has more than two reference navigation properties on it.
The name of the navigation property on the middle entity that points at the far side of the many-to-many relationship. Use this to resolve ambiguities when the middle table of the many-to-many relationship has more than two reference navigation properties on it.

``` c#
public class Person
{
...

[ManyToMany("Appointments", FarNavigationProperty = nameof(PersonAppointment.Appointment))]
public ICollection<PersonAppointment> PersonAppointments { get; set; }
}

public class Appointment
{
...

[ManyToMany("People", FarNavigationProperty = nameof(PersonAppointment.Person))]
public ICollection<PersonAppointment> PersonAppointments { get; set; }
}

public class PersonAppointment
{
...

// Adding a third reference navigation property in the middle table requires
// the use of FarNavigationProperty in order to resolve ambiguity.
public int WaiverId { get; set; }
public Waiver Waiver { get; set; }
}
```
41 changes: 32 additions & 9 deletions docs/modeling/model-types/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

In a Coalesce application, you are likely to end up with a need for some API endpoints that aren't closely tied with your regular data model. While you could stick [Static Methods](/modeling/model-components/methods.md#static-methods) on one of your entities, to do so is detrimental to the organization of your code.

Instead, Coalesce allows you to generate API Controllers and a TypeScript client from a service. A service, in this case, is nothing more than a C# class or an interface with methods on it, annotated with `[Coalesce, Service]`. An implementation of this class or interface must be injectable from your application's service container, so a registration in Startup.cs is needed.
Instead, Coalesce allows you to generate API Controllers and a TypeScript client from a service. A service, in this case, is nothing more than a C# class or an interface with methods on it, annotated with `[Coalesce, Service]`. An implementation of this class or interface must be injectable from your application's service container, so a registration in Program.cs is needed.

The instance methods of these services work just like other custom [Methods](/modeling/model-components/methods.md) in Coalesce, with one notable distinction: Instance methods don't operate on an instance of a model, but instead on a dependency injected instance of the service.

Expand Down Expand Up @@ -44,21 +44,44 @@ public class WeatherService : IWeatherService
return response.Body.SerializeTo<WeatherData>();
}

public void MethodThatIsNotExposedBecauseItIsNotOnTheExposedInterface() { }
// This method is not exposed because it is not defined on the interface
public void MethodThatIsNotExposed() { }
}
```

And a registration:

``` c#
public class Startup
// In Program.cs
builder.Services.AddCoalesce<AppDbContext>();
builder.Services.AddScoped<IWeatherService, WeatherService>();
```

## Using Interfaces With Services
Interfaces annotated with `[Coalesce, Service]` will automatically expose all methods on that interface. Your interfaces should precisely define the service you intend to expose through Coalesce. Any members you do not want to expose should not be included in the interface.

Although it is not required to use an interface (you can generate endpoints directly from the implementation), it is highly recommended. Interfaces improve testability and reduce the risk of inadvertently changing the signature of a published API.

If you choose to generate directly from the implementation, annotate the class itself with `[Coalesce, Service]` rather than the interface. Unlike interfaces, each method you want to expose on the class must be explicitly annotated with the `[Coalesce]` attribute.

``` c#
[Coalesce, Service]
public class WeatherService
{
public void ConfigureServices(IServiceCollection services)
public WeatherService(AppDbContext db)
{
services.AddCoalesce<AppDbContext>();
services.AddScoped<IWeatherService, WeatherService>();
this.db = db;
}
}
```

While it isn't required that an interface for your service exist (you can generate directly from the implementation), it is highly recommended that an interface be used. Interfaces increase testability and reduce risk of accidentally changing the signature of a published API, among other benefits.
[Coalesce]
public WeatherData GetWeather(string zipCode)
{
// Assuming some magic HttpGet method that works as follows...
var response = HttpGet("http://www.example.com/api/weather/" + zipCode);
return response.Body.SerializeTo<WeatherData>();
}

// This method is not exposed because it lacks the [Coalesce] attribute
private void MethodThatIsNotExposed() { }
}
```
2 changes: 1 addition & 1 deletion docs/topics/audit-logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Add a reference to the Nuget package `IntelliTect.Coalesce.AuditLogging` to your

``` xml:no-line-numbers{3}
<ItemGroup>
<PackageReference Include="IntelliTect.Coalesce.Vue" Version="$(CoalesceVersion)" />
<PackageReference Include="IntelliTect.Coalesce" Version="$(CoalesceVersion)" />
<PackageReference Include="IntelliTect.Coalesce.AuditLogging" Version="$(CoalesceVersion)" />
</ItemGroup>
```
Expand Down
Binary file added docs/topics/coalesce-swashbuckle-with.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/topics/coalesce-swashbuckle-without.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions docs/topics/coalesce-swashbuckle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# OpenAPI/Swagger

When using Coalesce to generate API endpoints, the default OpenAPI generation _(sometimes referred to as its pre-2015 name "Swagger")_ can sometimes result in verbose and confusing API definitions, especially when dealing with DataSources and Behaviors. To address these issues, the `IntelliTect.Coalesce.Swashbuckle` package offers enhancements for OpenAPI definitions, making your Coalesce-generated APIs clearer and more manageable.

## Setup

In this setup process, we're going to add an additional Coalesce NuGet package, configure OpenAPI in your ASP.NET Core application, and specify a Coalesce-specific config property to improve the OpenAPI documentation for Coalesce-generated APIs.

### 1. Add the NuGet Package

Add a reference to the `IntelliTect.Coalesce.Swashbuckle` NuGet package to your web project:

```xml:no-line-numbers{3}
<ItemGroup>
<PackageReference Include="IntelliTect.Coalesce.Vue" Version="$(CoalesceVersion)" />
<PackageReference Include="IntelliTect.Coalesce.Swashbuckle" Version="$(CoalesceVersion)" />
</ItemGroup>
```

### Configure OpenAPI in Program.cs

Update your Program.cs file to configure OpenAPI and include Coalesce-specific enhancements. This involves [setting up OpenAPI as usual](https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-8.0&tabs=visual-studio) and then applying the Coalesce configuration (Note: You do not need to install the `Swashbuckle.AspNetCore` package if you are using the Coalesce one).

```c#:no-line-numbers
builder.Services.AddSwaggerGen(config =>
{
config.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
config.AddCoalesce(); // Add coalesce specific configuration
});
```

```c#
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
```

## Improvements

### Default OpenAPI Generation

By default, OpenAPI in ASP.NET Core offers a simple way to document APIs. It generates API documentation based on the structure of your controller actions and data models. While this default setup is functional for many scenarios, it may fall short in representing more complex cases, especially when dealing with Coalesce-generated endpoints that include DataSources and Behaviors. These scenarios can lead to verbose and sometimes confusing OpenAPI documentation.

### Coalesce Enhancements

The `IntelliTect.Coalesce.Swashbuckle` package addresses the limitations of the default OpenAPI generation by providing custom OpenAPI filters. These filters enhance the readability and usability of your OpenAPI documentation for Coalesce-generated APIs.

The primary effect is an adjustment of parameter definitions to account for Coalesce's custom model binders that create instances of Data Sources and Behaviors on each request. These parameters will be updated in the OpenAPI document to account for data source parameters, filter parameters, and other model-specific customizations.

## Visual Comparison

To illustrate the impact of the `IntelliTect.Coalesce.Swashbuckle` package, let's examine the Patient model and its representation in OpenAPI.

```c#
public class Patient
{
public int PatientId { get; init; }
public DateTime NextAppointment { get; set; }
// Additional properties
[DefaultDataSource]
public class PatientDataSource(CrudContext<AppDbContext> context) : StandardDataSource<Patient, AppDbContext>(context)
{
// ...
}

public class PatientsWithUpcomingAppointmentsDataSource(CrudContext<AppDbContext> context) : StandardDataSource<Patient, AppDbContext>(context)
{
[Coalesce]
public int MonthsOut { get; set; }

// ...
}
}
```

#### Without `IntelliTect.Coalesce.Swashbuckle`
In the default OpenAPI configuration, DataSource and Behavior parameters are represented as generic objects. DataSource names are also shown as plain strings, hindering the discoverability of available data sources.
![](./coalesce-swashbuckle-without.jpg)

#### With `IntelliTect.Coalesce.Swashbuckle`
With the `IntelliTect.Coalesce.Swashbuckle` package, OpenAPI can interpret the DataSource as a dropdown menu and provides individual fields for each DataSource property. Additionally, it eliminates unnecessary behavior parameters.
![](./coalesce-swashbuckle-with.jpg)

0 comments on commit 179268c

Please sign in to comment.