Skip to content

Commit

Permalink
Documentation changes
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveDunn committed Apr 29, 2024
1 parent 60c8037 commit 6beae1c
Show file tree
Hide file tree
Showing 15 changed files with 97 additions and 33 deletions.
2 changes: 1 addition & 1 deletion docs/site/Writerside/hi.tree
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<toc-element topic="Overriding-methods.md"/>
<toc-element topic="EfCoreIntegrationHowTo.md"/>
<toc-element topic="Use-in-Swagger.md"/>
<toc-element topic="efcore-tips.md"/>
</toc-element>
<toc-element topic="Reference.md">
<toc-element topic="Configuration.md"/>
Expand All @@ -48,6 +49,5 @@
<toc-element topic="Parsing.md"/>
</toc-element>
<toc-element topic="FAQ.md">
<toc-element topic="How-to-identify-a-type-that-is-generated-by-Vogen.md"/>
</toc-element>
</instance-profile>
4 changes: 1 addition & 3 deletions docs/site/Writerside/topics/how-to-guides.topic
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@
<secondary>
<title>Exploring more advanced features</title>
<a href="EfCoreIntegrationHowTo.md"/>
<a href="NormalizationHowTo.md" summary="With a custom card summary"/>
<a href="Instances.md">Custom card title</a>
<a href="Casting.md" summary="With a custom card summary">Custom card title</a>
<a href="NormalizationHowTo.md" summary="How to normalize (sanitize) input values"/>
</secondary>
</section-starting-page>

Expand Down
2 changes: 0 additions & 2 deletions docs/site/Writerside/topics/how-to/How-to-guides.md

This file was deleted.

5 changes: 3 additions & 2 deletions docs/site/Writerside/topics/how-to/Parsing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Vogen 'hoists' (copies up to the wrapper) certain functionality from the underlying primitive. For instance, any method named `Parse` or `TryParse` from the underlying primitive is hoisted.

The `IParsable` family of interfaces (including `ISpanParsable` and `IUtf8SpanParsable`) that are **implemented
publicly** by the primitive are hoisted to the wrapper and the generic parameter is changed to that of the wrapper. The methods that are generated delegate back to the underlying implementation of primitive.
publicly** by the primitive are hoisted to the wrapper, and the generic parameter is changed to that of the wrapper.
The methods that are generated delegate back to the underlying implementation of primitive.

Some primitive types, such as `bool`, explicitly implement `ISpanParsable<bool>` **privately**, so the _interface_ is not
hoisted to wrapper, but the _non-explicit_ methods *are* hoisted.
Expand All @@ -14,7 +15,7 @@ value, or, for structs, a `default` value object that will throw if you try to a

`string`s are a special case. It is useful to have a Parse/TryParse methods on these, for instance, when value objects represent parameters in ASP.NET Core endpoints.

If `IParsable<>` is not available, e.g. in versions before .NET 7, then the interfaces are not generated for the wrapper. For `strings`, the `Parse` and `TryParse` methods are still generated though.
If `IParsable<>` is not available, e.g., in versions before .NET 7, then the interfaces are not generated for the wrapper. For `strings`, the `Parse` and `TryParse` methods are still generated though.

<note>
Beginning with V4.0, the behaviour of `TryParse` has changed. In previous versions, `TryParse` would throw a `ValueObjectValidationException`.
Expand Down
2 changes: 1 addition & 1 deletion docs/site/Writerside/topics/how-to/Use-in-Swagger.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public IEnumerable<WeatherForecast> Get(CityName cityName)
}
```

... You can use it (e.g. at `http://localhost:5053/WeatherForecast/London`) without having to do anything else,
... You can use it (e.g., at `http://localhost:5053/WeatherForecast/London`) without having to do anything else,
but Swagger will show the field as JSON instead of the underlying type:

<img border-effect="rounded" alt="swagger-json-parameter.png" src="swagger-json-parameter.png"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
<note>
This topic is incomplete and is currently being improved.
</note>
# Tips for working with EFCore

<toc></toc>

## Identifying types that are generated by Vogen

Thank you to [@jeffward01](https://github.com/jeffward01) for this item.

# Identifying types that are generated by Vogen
The goal of this is to identify types that are generated by Vogen.

## Use Case
### Use Case
I use Vogen alongside EfCore, I like to programmatically add ValueConverters by convention, I need to identity which properties on my entities are Vogen Generated value objects.

## Solution
### Solution
Vogen decorates the source it generates with the `GeneratedCodeAttribute`. This provides metadata about the tool which generated the code, this is what we'll use as an identifier.

Note: the code snippets use:
Expand Down Expand Up @@ -98,9 +100,68 @@ public class VogenStronglyTypedIdTests
}
```

## Summary
### Summary

Jeff says:
<note>
I think this is outside the scope of the library, but I imagine a large majority of Vogen users have some use-case where this will be helpful.
</note>

## Adding all value objects to the `ModelConfigurationBuilder`

Thank you to [CheloXL](https://github.com/CheloXL) for this tip

Just to add another snippet that I'm using in my solutions:
Add all VOs that have EfCoreValueConverter to the `ModelConfigurationBuilder`:

```C#
internal static class VogenExtensions
{
public static void ApplyVogenEfConvertersFromAssembly(this ModelConfigurationBuilder configurationBuilder, Assembly assembly)
{
var types = assembly.GetTypes();

foreach (var type in types)
{
if (IsVogenValueObject(type) && TryGetEfValueConverter(type, out var efCoreConverterType))
{
configurationBuilder.Properties(type).HaveConversion(efCoreConverterType);
}
}
}

private static bool TryGetEfValueConverter(Type type, [NotNullWhen(true)]out Type? efCoreConverterType)
{
var inner = type.GetNestedTypes();

foreach (var innerType in inner)
{
if (!typeof(ValueConverter).IsAssignableFrom(innerType) || !"EfCoreValueConverter".Equals(innerType.Name, StringComparison.Ordinal))
{
continue;
}

efCoreConverterType = innerType;
return true;
}

efCoreConverterType = null;
return false;
}

private static bool IsVogenValueObject(MemberInfo targetType)
{
var generatedCodeAttribute =targetType.GetCustomAttribute<GeneratedCodeAttribute>();
return "Vogen".Equals(generatedCodeAttribute?.Tool, StringComparison.Ordinal);
}
}
```

Usage: In your `dbcontext`:
```C#
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.ApplyVogenEfConvertersFromAssembly(typeof(YOURDBCONTEXT).Assembly);
}
```

Thank you to [@jeffward01](https://github.com/jeffward01) for this item.
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ e.g. `var vo2 = vo1 with { Value = 42 }` - but initializing via this doesn't set
would promote using a public constructor (even though the analyzer will still cause a compilation error)
* the generated code for records still overrides `ToString` as the default enumerates fields, which we don't want

Something not yet implemented is primary constructor analysis for classes in C#12, and how they will fit in with Vogen.
Something not yet implemented is primary constructor analysis for classes in C# 12, and how they will fit in with Vogen.
This is covered in [this issue](https://github.com/SteveDunn/Vogen/issues/563).
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,8 @@ From what I've read, it's not recommended to share the DB Context across threads
I generally use an 'anti-corruption layer' to translate between the infrastructure and domain;
it's a layer for converting/mapping/validation.
Yes, it's more code, but it's explicit.

<note title="Users' tips">
<a href="efcore-tips.md">This page</a> has some handy tips provided by the community.

</note>
6 changes: 3 additions & 3 deletions docs/site/Writerside/topics/reference/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ correct.
## How do I identify types that are generated by Vogen?
_I'd like to be able to identify types that are generated by Vogen so that I can integrate them in things like EFCore._

This is described in [this how-to page](How-to-identify-a-type-that-is-generated-by-Vogen.md)
This is described in [this how-to page](efcore-tips.md)

### What versions of .NET are supported?

Expand Down Expand Up @@ -329,9 +329,9 @@ See [the how-to page](NormalizationHowTo.md) for more information.

### Can I create custom Value Object attributes with my own defaults?

No. It used to be possible, but it impacts the performance of Vogen.
No, it used to be possible, but it impacts the performance of Vogen.
A much better way is
to use [type alias feature C# 12](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/using-alias-types).
to use [type alias feature](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive#using-alias).
```
NOTE: *custom attributes must extend a ValueObjectAttribute class; you cannot layer custom attributes on top of each other*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ Here is what is hoisted:

## Parsing

Any method named `Parse` or `TryParse` from the underlying primitive is hoisted. Also, the `IParsable` family of interfaces (including `ISpanParsable` and `IUtf8SpanParsable`) that are **implemented publicly** by the primitive, are hoisted.
Any method named `Parse` or `TryParse` from the underlying primitive is hoisted.
Also, the `IParsable`
family of interfaces (including `ISpanParsable` and `IUtf8SpanParsable`)
that are **implemented publicly** by the primitive are hoisted.

Please see the [Parsing](Parsing.md) documentation for more information.
Please see the [Parsing](../how-to/Parsing.md) documentation for more information.

## IComparable

Expand Down
4 changes: 2 additions & 2 deletions docs/site/Writerside/topics/reference/Performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ This topic is incomplete and is currently being improved.
</note>


(to run these yourself: `dotnet run -c Release --framework net7.0 -- --job short --filter *` in the `Vogen.Benchmarks` folder)
(to run these yourself: `dotnet run -c Release --framework net8.0 -- --job short --filter *` in the `Vogen.Benchmarks` folder)

As mentioned previously, the goal of Vogen is to achieve very similar performance compared to using primitives themselves.
Here's a benchmark comparing the use of a validated Value Object with an underlying type of `int` vs using an `int` natively (*primitively* 🤓)
Here's a benchmark comparing the use of a validated Value Object with an underlying type of `int` vs. using an `int` natively (*primitively* 🤓)

``` ini
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22621.1194)
Expand Down
1 change: 0 additions & 1 deletion docs/site/Writerside/topics/tutorials/Casting.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
This topic is incomplete and is currently being improved.
</note>


It is recommended not to use casting operators, either explicit or implicit.

It might seem like a handy way to use the underlying primitive natively, but the goal of strong-typing primitives is to differentiate them from the underlying type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public partial struct CustomerId
}
```

We know, from the validation tutorial that this throws an exception. This means that users can't create one with
a zero value. All well and good. But **we** (the author of the type), want to create one with a zero.
We know from the validation tutorial the above code throws an exception.
This means that users can't create one with a zero value. All well and good. But **we** (the author of the type), want to create one with a zero.

We can do this with a known-instance:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Working with databases

<note>
This topic is incomplete and is currently being improved.
This topic is incomplete and is being improved.
</note>

There are other converters/serializer for:
Vogen has converters and serializers for databases, including:

* Dapper
* EFCore
* [LINQ to DB](https://github.com/linq2db/linq2db)

They are controlled by the `Conversions` enum. The following has serializers for NSJ and STJ:
They are controlled by the `Conversions` enum. The following specifies Newtonsoft.Json and System.Text.Json converters:

```c#
[ValueObject(conversions: Conversions.NewtonsoftJson | Conversions.SystemTextJson, underlyingType: typeof(float))]
Expand Down
4 changes: 2 additions & 2 deletions src/Vogen/Rules/DoNotDeriveFromVogenAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public class DoNotDeriveFromVogenAttributesAnalyzer : DiagnosticAnalyzer
// "error RS2002: Rule 'XYZ123' is part of the next unshipped analyzer release, but is not a supported diagnostic for any analyzer"
private static readonly DiagnosticDescriptor _rule = new DiagnosticDescriptor(
RuleIdentifiers.DoNotDeriveFromVogenAttributes,
"Deriving from a Vogen attribute will be disallowed in a future release, use a type alias instead if you're using C# 12 or greater",
"Deriving from a Vogen attribute will be disallowed in a future release, use a type alias instead",
"Type '{0}' should not derive from a Vogen attribute",
RuleCategories.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description:
"The value object is created with a new expression. This can lead to invalid value objects in your domain. Use the From method instead.");
"It is better to use a type alias than derive from the ValueObject attribute. Future versions of Vogen will use faster APIs to discover this attribute, and code that derives from it will prohibit Vogen from using newer APIs. Use a type alias instead of deriving from the attribute.");

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_rule);

Expand Down

0 comments on commit 6beae1c

Please sign in to comment.