Skip to content

Commit

Permalink
Merge pull request #653 from sandervanteinde/main
Browse files Browse the repository at this point in the history
Add Orleans support
  • Loading branch information
SteveDunn authored Aug 6, 2024
2 parents ed99856 + 8bfadaf commit 0d37a05
Show file tree
Hide file tree
Showing 28 changed files with 5,132 additions and 18 deletions.
6 changes: 6 additions & 0 deletions Consumers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Onion", "Onion", "{48796EAD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infra", "samples\Onion\Infra\Infra.csproj", "{03EA902C-68D5-44B2-A8F0-2F3325B05448}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrleansExample", "samples\OrleansExample\OrleansExample.csproj", "{2F496E59-0462-431A-A70B-266B58A9A672}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -66,6 +68,10 @@ Global
{03EA902C-68D5-44B2-A8F0-2F3325B05448}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03EA902C-68D5-44B2-A8F0-2F3325B05448}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03EA902C-68D5-44B2-A8F0-2F3325B05448}.Release|Any CPU.Build.0 = Release|Any CPU
{2F496E59-0462-431A-A70B-266B58A9A672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F496E59-0462-431A-A70B-266B58A9A672}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F496E59-0462-431A-A70B-266B58A9A672}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F496E59-0462-431A-A70B-266B58A9A672}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
15 changes: 15 additions & 0 deletions samples/OrleansExample/CustomUrl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Vogen;

namespace OrleansExample;

[ValueObject<string>(Conversions.Default | Conversions.Orleans)]
public partial class CustomUrl
{
private static Validation Validate(string value)
{
// Just for example’s sake, a real Url validator should be more complex then this.
return value.StartsWith("http")
? Validation.Ok
: Validation.Invalid("A url should start with http");
}
}
7 changes: 7 additions & 0 deletions samples/OrleansExample/IUrlShortenerGrain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace OrleansExample;

public interface IUrlShortenerGrain : IGrainWithStringKey
{
Task SetUrl(CustomUrl fullUrl);
Task<CustomUrl> GetUrl();
}
22 changes: 22 additions & 0 deletions samples/OrleansExample/OrleansExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseLocallyBuiltPackage>true</UseLocallyBuiltPackage>
<NoWarn>$(NoWarn),NU1603,NU1903</NoWarn>
</PropertyGroup>

<ItemGroup Condition=" '$(UseLocallyBuiltPackage)' != ''">
<PackageReference Include="Vogen" Version="999.9.*" />
</ItemGroup>

<ItemGroup Condition=" '$(UseLocallyBuiltPackage)' == ''">
<PackageReference Include="Vogen" Version="999.9.10219943" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Server" Version="8.2.0" />
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions samples/OrleansExample/OrleansExample.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@OrleansExample_HostAddress = http://localhost:5242

### Create shortened URL
GET {{OrleansExample_HostAddress}}/shorten?url=https%3A%2F%2Fgithub.com%2FSteveDunn%2FVogen
Accept: application/json

### Create shortened URL that does not meet validation, this generated a 400 Bad Request
GET {{OrleansExample_HostAddress}}/shorten?url=invalidUrl
Accept: application/json
51 changes: 51 additions & 0 deletions samples/OrleansExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Orleans.Runtime;
using OrleansExample;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseOrleans(static siloBuilder =>
{
siloBuilder.UseLocalhostClustering();
siloBuilder.AddMemoryGrainStorage("urls");
});

var app = builder.Build();

app.MapGet("/", static () => "Welcome to the URL shortener, powered by Orleans!");

app.MapGet("/shorten",
static async (IGrainFactory grains, HttpRequest request, CustomUrl url) =>
{
var host = $"{request.Scheme}://{request.Host.Value}";

// Create a unique, short ID
var shortenedRouteSegment = Guid.NewGuid().GetHashCode().ToString("X");

// Create and persist a grain with the shortened ID and full URL
var shortenerGrain =
grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment);

await shortenerGrain.SetUrl(url);

// Return the shortened URL for later use
var resultBuilder = new UriBuilder(host)
{
Path = $"/go/{shortenedRouteSegment}"
};

return Results.Ok(resultBuilder.Uri);
});

app.MapGet("/go/{shortenedRouteSegment:required}",
static async (IGrainFactory grains, string shortenedRouteSegment) =>
{
// Retrieve the grain using the shortened ID and url to the original URL
var shortenerGrain =
grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment);

var url = await shortenerGrain.GetUrl();

return Results.Redirect(url.Value);
});

app.Run();
31 changes: 31 additions & 0 deletions samples/OrleansExample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59890",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5242",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
7 changes: 7 additions & 0 deletions samples/OrleansExample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Orleans example

This project showcases how Vogen can be utilized within the [Microsoft Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/) framework.

It showcases the [quickstart example](https://learn.microsoft.com/en-us/dotnet/orleans/quickstarts/build-your-first-orleans-app?tabs=visual-studio) but with Vogen value objects.

The [http file](./OrleansExample.http) can be utilized to test the API running Orleans.
11 changes: 11 additions & 0 deletions samples/OrleansExample/UrlDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OrleansExample;

[GenerateSerializer, Alias(nameof(UrlDetails))]
public sealed record UrlDetails
{
[Id(0)]
public required CustomUrl FullUrl { get; init; }

[Id(1)]
public required string ShortenedRouteSegment { get; init; }
}
23 changes: 23 additions & 0 deletions samples/OrleansExample/UrlShortenerGrain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace OrleansExample;

public class UrlShortenerGrain(
[PersistentState(stateName: "url", storageName: "urls")]
IPersistentState<UrlDetails> state
) : Grain, IUrlShortenerGrain
{
public async Task SetUrl(CustomUrl fullUrl)
{
state.State = new()
{
ShortenedRouteSegment = this.GetPrimaryKeyString(),
FullUrl = fullUrl
};

await state.WriteStateAsync();
}

public Task<CustomUrl> GetUrl()
{
return Task.FromResult(state.State.FullUrl);
}
}
8 changes: 8 additions & 0 deletions samples/OrleansExample/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions samples/OrleansExample/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
12 changes: 9 additions & 3 deletions src/Vogen.SharedTypes/Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,20 @@ public enum Conversions
/// Creates a LinqToDb ValueConverter for converting to and from the type
/// </summary>
LinqToDbValueConverter = 1 << 6,

/// <summary>
/// Sets the SerializeFn and DeSerializeFn members in JsConfig in a static constructor.
/// </summary>
ServiceStackDotText = 1 << 7,

/// <summary>
/// Creates a BSON serializer for each value object.
/// </summary>
Bson = 1 << 8
Bson = 1 << 8,

/// <summary>
/// Creates and registers a codec and copier for Microsoft Orleans.
/// This feature requires .NET 8 and C#12 and cannot be polly-filled.
/// </summary>
Orleans = 1 << 9
}
4 changes: 2 additions & 2 deletions src/Vogen/BuildConfigurationFromAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ private BuildConfigurationFromAttributes(AttributeData att)
}

public static VogenConfigurationBuildResult TryBuildFromValueObjectAttribute(AttributeData matchingAttribute) =>
new BuildConfigurationFromAttributes(matchingAttribute).Build(false);
new BuildConfigurationFromAttributes(matchingAttribute).Build(argsAreFromVogenDefaultAttribute: false);

public static VogenConfigurationBuildResult TryBuildFromVogenDefaultsAttribute(AttributeData matchingAttribute) =>
new BuildConfigurationFromAttributes(matchingAttribute).Build(true);
new BuildConfigurationFromAttributes(matchingAttribute).Build(argsAreFromVogenDefaultAttribute: true);

private VogenConfigurationBuildResult Build(bool argsAreFromVogenDefaultAttribute)
{
Expand Down
21 changes: 11 additions & 10 deletions src/Vogen/Util.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis;
Expand All @@ -21,9 +22,9 @@ public static class Util
new GenerateTypeConverterConversions(),
new GenerateDapperConversions(),
new GenerateEfCoreTypeConversions(),
new GenerateLinqToDbConversions(),
new GenerateLinqToDbConversions()
};

public static string SanitizeToALegalFilename(string input) => input.Replace('@', '_');

public static void TryWriteUsingUniqueFilename(string filename, SourceProductionContext context, SourceText sourceText)
Expand All @@ -49,7 +50,7 @@ public static void TryWriteUsingUniqueFilename(string filename, SourceProduction
}
}
}



public static string GenerateCallToValidationAndThrowIfRequired(VoWorkItem workItem)
Expand Down Expand Up @@ -90,7 +91,7 @@ public static string GenerateCallToValidationAndReturnFalseIfNeeded(VoWorkItem w

return string.Empty;
}

public static string GenerateNotNullWhenTrueAttribute() =>
"""
Expand Down Expand Up @@ -231,8 +232,8 @@ public static string GenerateDebuggerProxyForStructs(VoWorkItem item)
var createdWithMethod = item.Config.DisableStackTraceRecordingInDebug
? @"public global::System.String CreatedWith => ""the From method"""
: @"public global::System.String CreatedWith => _t._stackTrace?.ToString() ?? ""the From method""";
string code =

string code =
$$"""
internal sealed class {{item.VoTypeName}}DebugView
Expand Down Expand Up @@ -289,7 +290,7 @@ public static string GenerateDebuggerProxyForClasses(TypeDeclarationSyntax tds,
private static string GenerateToString(VoWorkItem item, bool isReadOnly)
{
string ro = isReadOnly ? " readonly" : string.Empty;

return item.UserProvidedOverloads.ToStringInfo.WasSupplied
? string.Empty
: $@"/// <summary>Returns the string representation of the underlying <see cref=""{item.UnderlyingTypeFullName}"" />.</summary>
Expand Down Expand Up @@ -339,7 +340,7 @@ causes Rider's debugger to crash.
*/";
}

return source;
}

Expand All @@ -349,7 +350,7 @@ public static string GenerateStackTraceFieldIfNeeded(VoWorkItem item)
{
return string.Empty;
}

return $"""
#if DEBUG
private readonly global::System.Diagnostics.StackTrace _stackTrace = null;
Expand All @@ -367,7 +368,7 @@ public static string GenerateMessageForUninitializedValueObject(VoWorkItem item)
if (item.Config.DisableStackTraceRecordingInDebug)
{
return $"""global::System.String message = "Use of uninitialized Value Object.";""";

}
return $"""global::System.String message = "Use of uninitialized Value Object at: " + _stackTrace ?? "";""";
}
Expand Down
2 changes: 2 additions & 0 deletions src/Vogen/ValueObjectGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ private static void Execute(
WriteEfCoreSpecs.WriteIfNeeded(spc, compilation, efCoreConverterSpecs);

WriteBsonSerializers.WriteIfNeeded(spc, compilation, workItems);

WriteOrleansSerializers.WriteIfNeeded(spc, workItems);

if (workItems.Count > 0)
{
Expand Down
Loading

0 comments on commit 0d37a05

Please sign in to comment.