Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ tab_width = 4
indent_size = 4
charset = utf-8

# Build scripts
[*.{yml,yaml}]
indent_style = spaces
indent_size = 2

# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2

# Code files
[*.cs]

### Naming rules: ###

Expand Down Expand Up @@ -52,8 +63,7 @@ dotnet_naming_rule.static_fields_should_be_pascal_case.style = static_field_styl
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static

dotnet_naming_style.static_field_style.capitalization = camel_case
dotnet_naming_style.static_field_style.required_prefix = s_
dotnet_naming_style.static_field_style.capitalization = pascal_case

# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
Expand Down Expand Up @@ -150,20 +160,6 @@ dotnet_style_readonly_field = true:warning
# New-line preferences
dotnet_style_allow_multiple_blank_lines_experimental = false:warning
dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:warning
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning

# Build scripts
[*.{yml,yaml}]
indent_style = spaces
indent_size = 2

# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2

# Code files
[*.cs]

## C# style settings:

Expand Down Expand Up @@ -195,7 +191,7 @@ csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none

# Prefer local method constructs to have a block body
csharp_style_expression_bodied_local_functions = true:suggestion
csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion

# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:suggestion
Expand Down Expand Up @@ -288,16 +284,17 @@ dotnet_diagnostic.CS1998.severity = none # CS1998: Async method l
dotnet_diagnostic.CS4014.severity = error # CS4014: Because this call is not awaited, execution of the current method continues before the call is completed
dotnet_diagnostic.CA2007.severity = none # CA2007: Consider calling ConfigureAwait on the awaited task

# Immediate.Handlers relies on nested types
dotnet_diagnostic.CA1034.severity = none # CA1034: Nested types should not be visible

# No need for cryptographically secure anything in this project
dotnet_diagnostic.CA5394.severity = none # CA5394: Random is an insecure random number generator

# Dispose things need disposing
dotnet_diagnostic.CA2000.severity = error # CA2000: Dispose objects before losing scope

dotnet_diagnostic.CA1034.severity = none # CA1034: Nested types should not be visible
dotnet_diagnostic.CA1515.severity = none # CA1515: Consider making public types internal
dotnet_diagnostic.CA1708.severity = none # CA1708: Identifiers should differ by more than case
dotnet_diagnostic.CA1716.severity = none # CA1716: Identifiers should not match keywords
dotnet_diagnostic.IDE0390.severity = none # IDE0390: Make method synchronous

# Meziantou.Analyzers
MA0053.public_class_should_be_sealed = true
Expand All @@ -307,13 +304,24 @@ dotnet_diagnostic.MA0004.severity = none
dotnet_diagnostic.MA0048.severity = none
dotnet_diagnostic.MA0051.severity = none
dotnet_diagnostic.MA0053.severity = warning
dotnet_diagnostic.MA0190.severity = none

[src/Immediate.Cache.Shared/**.cs]

# XML Documentation
dotnet_diagnostic.CS0105.severity = error # CS0105: Using directive is unnecessary.
dotnet_diagnostic.CS1573.severity = error # CS1573: Missing XML comment for parameter
dotnet_diagnostic.CS1591.severity = error # CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1712.severity = error # CS1712: Type parameter has no matching typeparam tag in the XML comment (but other type parameters do)

# Async
dotnet_diagnostic.CA2007.severity = error # CA2007: Consider calling ConfigureAwait on the awaited task

[tests/**.cs]

dotnet_diagnostic.CA1707.severity = none # CA1707: Identifiers should not contain underscores
dotnet_diagnostic.CA1724.severity = none # CA1724: Type names should not match namespaces
dotnet_diagnostic.CA1822.severity = none # CA1822: Mark members as static
dotnet_diagnostic.CA1873.severity = none # CA1873: Evaluation of this argument may be expensive and unnecessary if logging is disabled

dotnet_diagnostic.xUnit2029.severity = none # xUnit2029: Do not use Assert.Empty to check if a value does not exist in a collection
8 changes: 8 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
<PropertyGroup Label="Enabled Polyfills">
<!--https://github.com/meziantou/Meziantou.Polyfill?tab=readme-ov-file#supported-polyfills-->
<Polyfill></Polyfill>
<Polyfill>$(Polyfill)|T:System.Index</Polyfill>
<Polyfill>$(Polyfill)|T:System.Range</Polyfill>
<Polyfill>$(Polyfill)|T:System.Diagnostics.CodeAnalysis</Polyfill>
<Polyfill>$(Polyfill)|T:System.Runtime.CompilerServices.CallerArgumentExpressionAttribute</Polyfill>
<Polyfill>$(Polyfill)|T:System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute</Polyfill>
<Polyfill>$(Polyfill)|T:System.Runtime.CompilerServices.IsExternalInit</Polyfill>
<Polyfill>$(Polyfill)|T:System.Runtime.CompilerServices.RequiredMemberAttribute</Polyfill>
<Polyfill>$(Polyfill)|T:System.Runtime.CompilerServices.SkipLocalsInitAttribute</Polyfill>
<Polyfill>$(Polyfill)|T:System.Threading.Lock</Polyfill>
<MeziantouPolyfill_IncludedPolyfills>$(Polyfill)</MeziantouPolyfill_IncludedPolyfills>
</PropertyGroup>
Expand Down
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
</PropertyGroup>

<ItemGroup>
<PackageVersion Include="AssemblyMetadata.Generators" Version="2.2.0" />
<PackageVersion Include="Immediate.Handlers" Version="3.8.0" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
Expand All @@ -13,6 +14,9 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<!-- Locked until https://github.com/microsoft/codecoverage/issues/221 resolved -->
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="[18.4.1]" />
<PackageVersion Include="Scriban" Version="7.2.3" />
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0" />
<PackageVersion Include="Verify.XunitV3" Version="31.18.0" />
<PackageVersion Include="xunit.v3.mtp-v2" Version="3.2.2" />
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions Immediate.Cache.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</Folder>
<Folder Name="/Source/">
<Project Path="src/Immediate.Cache.Analyzers/Immediate.Cache.Analyzers.csproj" />
<Project Path="src/Immediate.Cache.Generators/Immediate.Cache.Generators.csproj" />
<Project Path="src/Immediate.Cache.Shared/Immediate.Cache.Shared.csproj" />
<Project Path="src/Immediate.Cache/Immediate.Cache.csproj" />
</Folder>
Expand Down
34 changes: 11 additions & 23 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ Immediate.Cache is a collection of classes that simplify caching responses from

### Creating a Cache

Create a subclass of `ApplicationCacheBase`, which will serve as the cache for a particular handler. An example:
Create a class and apply the `[CacheFor<>]` attribute, targeting a handler. Add a `TransformKey` method to transform a
request into a cache key. For example:

```cs
[Handler]
public static partial class GetValue
Expand All @@ -33,13 +35,8 @@ public static partial class GetValue
) => ValueTask.FromResult(new Response(query.Value));
}

public sealed class GetValueCache(
IMemoryCache memoryCache,
Owned<IHandler<GetValue.Query, GetValue.Response>> ownedHandler
) : ApplicationCacheBase<GetValue.Query, GetValue.Response>(
memoryCache,
ownedHandler
)
[CacheFor<GetValue>]
public sealed class GetValueCache
{
protected override string TransformKey(GetValue.Query request) =>
$"GetValue(query: {request.Value})";
Expand All @@ -48,24 +45,15 @@ public sealed class GetValueCache(

In this case, the `GetValueCache` class will serve as a cache for the `GetValue` IH handler.

### Register the Cache with DI

In your `Program.cs` file:
### Adding generated caches to the `IServiceCollection` collection

* Ensure that Memory Cache is registered, by calling:
```cs
services.AddMemoryCache();
```
In your `Program.cs`, add a call to `services.AddXxxCaches()`, where Xxx is the application identifier. By default,
this is the short form of the assembly name. For example:

* Register `Owned<>` as a singleton
```cs
services.AddSingleton(typeof(Owned<>));
```
* For a project named `Web`, it will be `services.AddWebCaches()`
* For a project named `Application.Web`, it will be `services.AddApplicationWebCaches()`

* Register your cache service(s) as a singleton(s)
```cs
services.AddSingleton<GetValueCache>();
```
However, this name can be overridden using `[assembly: ImmediateAssemblyIdentifierAttribute("SomeIdentifier")]`.

### Retrieve Data From the Cache

Expand Down
114 changes: 114 additions & 0 deletions src/Common/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;

namespace Immediate.Cache;

internal static class ITypeSymbolExtensions
{
extension([NotNullWhen(true)] ITypeSymbol? typeSymbol)
{
public bool IsImmediateAssemblyIdentifierAttribute =>
typeSymbol is INamedTypeSymbol
{
Arity: 0,
Name: "ImmediateAssemblyIdentifierAttribute",
ContainingNamespace.IsImmediateHandlersShared: true,
};

public bool IsHandlerAttribute =>
typeSymbol is INamedTypeSymbol
{
Name: "HandlerAttribute",
ContainingNamespace.IsImmediateHandlersShared: true,
};
}

extension(INamedTypeSymbol typeSymbol)
{
public bool GetValidHandleMethod([NotNullWhen(true)] out ITypeSymbol? requestType, [NotNullWhen(true)] out ITypeSymbol? responseType)
{
requestType = null;
responseType = null;

if (!typeSymbol.GetAttributes().Any(a => a.AttributeClass.IsHandlerAttribute))
return false;

if (typeSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.Where(m => m.Name is "Handle" or "HandleAsync")
.Take(2)
.ToList() is not [var handleMethod])
{
return false;
}

// must have request type
if (handleMethod.Parameters is not [{ Type: ITypeSymbol parameterType }, ..])
return false;

if (handleMethod.ReturnType is not INamedTypeSymbol
{
Arity: 1,
Name: "ValueTask",
ContainingNamespace.IsSystemThreadingTasks: true,
TypeArguments: [ITypeSymbol returnType],
})
{
return false;
}

requestType = parameterType;
responseType = returnType;
return true;
}
}

extension(INamespaceSymbol namespaceSymbol)
{
public bool IsImmediateCacheShared =>
namespaceSymbol is
{
Name: "Shared",
ContainingNamespace:
{
Name: "Cache",
ContainingNamespace:
{
Name: "Immediate",
ContainingNamespace.IsGlobalNamespace: true,
},
},
};

public bool IsImmediateHandlersShared =>
namespaceSymbol is
{
Name: "Shared",
ContainingNamespace:
{
Name: "Handlers",
ContainingNamespace:
{
Name: "Immediate",
ContainingNamespace.IsGlobalNamespace: true,
},
},
};

public bool IsSystemThreadingTasks =>
namespaceSymbol is
{
Name: "Tasks",
ContainingNamespace:
{
Name: "Threading",
ContainingNamespace:
{
Name: "System",
ContainingNamespace.IsGlobalNamespace: true,
},
},
};
}
}
13 changes: 13 additions & 0 deletions src/Common/SyntaxExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Immediate.Cache;

internal static class SyntaxExtensions
{
extension(MemberDeclarationSyntax mds)
{
public bool IsStatic => mds.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword));
}
}
12 changes: 12 additions & 0 deletions src/Common/Utility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.CodeAnalysis;

namespace Immediate.Cache;

internal static class Utility
{
public static string? NullIf(this string value, string check) =>
value.Equals(check, StringComparison.Ordinal) ? null : value;

public static IncrementalValuesProvider<T> WhereNotNull<T>(this IncrementalValuesProvider<T?> values)
where T : class => values.Where(x => x is not null)!;
}
16 changes: 10 additions & 6 deletions src/Immediate.Cache.Analyzers/Immediate.Cache.Analyzers.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<Compile Include="../Common/*.cs" Link="Common/%(Filename).cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>

</Project>
10 changes: 1 addition & 9 deletions src/Immediate.Cache.Analyzers/OwnedDisposableScopeSuppressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,7 @@ public override void ReportSuppressions(SuppressionAnalysisContext context)
{
Arity: 1,
Name: "Owned",
ContainingNamespace:
{
Name: "Cache",
ContainingNamespace:
{
Name: "Immediate",
ContainingNamespace.IsGlobalNamespace: true,
},
},
ContainingNamespace.IsImmediateCacheShared: true,
})
{
continue;
Expand Down
Loading