A .NET analyzer that validates classes implementing the Dapr Actor base class to ensure they follow proper serialization rules for strongly-typed >.NET Dapr Actor clients.
This analyzer helps developers follow the serialization guidelines outlined in the Dapr Actor Serialization documentation by providing compile-time validation and code fixes.
Severity: Error
Interfaces implemented by Actor classes should inherit from IActor interface.
Bad:
public interface IWeatherActor
{
Task<string> GetWeatherAsync();
}
public class WeatherActor : Actor, IWeatherActor
{
public WeatherActor(ActorHost host) : base(host) { }
public Task<string> GetWeatherAsync() => Task.FromResult("Sunny");
}Good:
public interface IWeatherActor : IActor
{
Task<string> GetWeatherAsync();
}
public class WeatherActor : Actor, IWeatherActor
{
public WeatherActor(ActorHost host) : base(host) { }
public Task<string> GetWeatherAsync() => Task.FromResult("Sunny");
}Severity: Warning
Enum members used in Actor types should use [EnumMember] attribute for consistent serialization.
Bad:
public enum Season
{
Spring,
Summer,
Fall,
Winter
}Good:
public enum Season
{
[EnumMember]
Spring,
[EnumMember]
Summer,
[EnumMember]
Fall,
[EnumMember]
Winter
}Severity: Information
Properties in Actor classes used with weakly-typed clients should consider [JsonPropertyName] attribute for consistent property naming.
Example:
public class WeatherData
{
[JsonPropertyName("temperature")]
public double Temperature { get; set; }
[JsonPropertyName("humidity")]
public int Humidity { get; set; }
}Severity: Warning
Complex types used as parameters or return types in Actor methods should have proper serialization attributes.
Bad:
public class WeatherData
{
public double Temperature { get; set; }
public int Humidity { get; set; }
}
public interface IWeatherActor : IActor
{
Task<WeatherData> GetWeatherAsync(); // WeatherData lacks serialization attributes
}Good:
[DataContract]
public class WeatherData
{
[DataMember]
public double Temperature { get; set; }
[DataMember]
public int Humidity { get; set; }
}
public interface IWeatherActor : IActor
{
Task<WeatherData> GetWeatherAsync();
}Severity: Warning
Parameters in Actor methods should use types with proper serialization attributes for reliable data transfer.
Bad:
public class LocationData
{
public string City { get; set; }
public string Country { get; set; }
}
public interface IWeatherActor : IActor
{
Task<string> GetWeatherAsync(LocationData location); // LocationData lacks serialization attributes
}Good:
[DataContract]
public class LocationData
{
[DataMember]
public string City { get; set; }
[DataMember]
public string Country { get; set; }
}
public interface IWeatherActor : IActor
{
Task<string> GetWeatherAsync(LocationData location);
}Severity: Warning
Return types in Actor methods should have proper serialization attributes for reliable data transfer.
Severity: Warning
Collection types used in Actor methods should contain elements with proper serialization attributes.
Bad:
public class WeatherReading
{
public DateTime Timestamp { get; set; }
public double Value { get; set; }
}
public interface IWeatherActor : IActor
{
Task<List<WeatherReading>> GetHistoryAsync(); // WeatherReading lacks serialization attributes
}Good:
[DataContract]
public class WeatherReading
{
[DataMember]
public DateTime Timestamp { get; set; }
[DataMember]
public double Value { get; set; }
}
public interface IWeatherActor : IActor
{
Task<List<WeatherReading>> GetHistoryAsync();
}Severity: Warning
Record types used as parameters or return types in public IActor interface methods should have [DataContract] attribute and [DataMember] attributes on all properties for reliable serialization. Records that are not part of an IActor contract are not flagged.
Bad:
public record WeatherData(double Temperature, int Humidity);
public interface IWeatherActor : IActor
{
Task<WeatherData> GetWeatherAsync(); // WeatherData is used here — triggers DAPR008
}Good:
[DataContract]
public record WeatherData([property: DataMember] double Temperature, [property: DataMember] int Humidity);
public interface IWeatherActor : IActor
{
Task<WeatherData> GetWeatherAsync();
}Severity: Error
Actor class implementations should implement an interface that inherits from IActor for proper Actor pattern implementation.
Bad:
public class WeatherActor : Actor
{
public WeatherActor(ActorHost host) : base(host) { }
public Task<string> GetWeatherAsync() => Task.FromResult("Sunny");
}Good:
public interface IWeatherActor : IActor
{
Task<string> GetWeatherAsync();
}
public class WeatherActor : Actor, IWeatherActor
{
public WeatherActor(ActorHost host) : base(host) { }
public Task<string> GetWeatherAsync() => Task.FromResult("Sunny");
}Severity: Error
Types used as parameters or return types in public IActor interface methods must either expose a public parameterless constructor or be decorated with the [DataContract] attribute for reliable serialization. Types that are not part of an IActor contract are not flagged.
Bad:
public class WeatherData
{
public WeatherData(double temperature)
{
Temperature = temperature;
}
public double Temperature { get; }
}Good (Option 1 - Parameterless Constructor):
public class WeatherData
{
public WeatherData() { }
public WeatherData(double temperature)
{
Temperature = temperature;
}
public double Temperature { get; set; }
}Good (Option 2 - DataContract):
[DataContract]
public class WeatherData
{
public WeatherData(double temperature)
{
Temperature = temperature;
}
[DataMember]
public double Temperature { get; }
}Install the analyzer via NuGet:
<PackageReference Include="Analyzers.Dapr" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>You can configure the severity of each rule in your .editorconfig file:
[*.cs]
# Dapr Actor Analyzer Rules
dotnet_diagnostic.DAPR001.severity = error
dotnet_diagnostic.DAPR002.severity = warning
dotnet_diagnostic.DAPR003.severity = suggestion
dotnet_diagnostic.DAPR004.severity = warning
dotnet_diagnostic.DAPR005.severity = warning
dotnet_diagnostic.DAPR006.severity = warning
dotnet_diagnostic.DAPR007.severity = warning
dotnet_diagnostic.DAPR008.severity = warning
dotnet_diagnostic.DAPR009.severity = error
dotnet_diagnostic.DAPR010.severity = errorThe analyzer provides automatic code fixes for:
- Adding inheritance from
IActorto Actor interfaces (DAPR001) - Adding
[EnumMember]attribute to enum members (DAPR002) - Adding
[DataContract]attribute to complex types (DAPR004, DAPR005, DAPR006) - Adding
[DataMember]attribute to properties (DAPR004, DAPR005, DAPR006) - Adding
[DataContract]and[DataMember]attributes to record types (DAPR008) - Adding parameterless constructors to types (DAPR010)
To build the analyzer:
dotnet build Analyzers.Dapr.csprojTo run tests:
dotnet test Analyzers.Dapr.Tests.csprojThis project uses GitHub Actions for continuous integration and deployment:
-
CI (
ci.yml): Runs on every push and pull request tomainordevelop- Builds in Release configuration
- Runs tests with code coverage (reported to Codecov)
- Packs and uploads the NuGet package as a build artifact
-
Release (
release.yml): Triggered by av*.*.*tag push or manual dispatch- Builds and packs the NuGet package
- Publishes to NuGet.org
- Requires
NUGET_API_KEYsecret to be configured
NUGET_API_KEY: API key for publishing to NuGet.org
When contributing to this analyzer, please ensure:
- All new rules have corresponding tests
- Code fixes are provided where applicable
- Documentation is updated for new rules
- Follow the existing code style and patterns
This project is licensed under the MIT License.