diff --git a/MapDataReader.Benchmarks/Program.cs b/MapDataReader.Benchmarks/Program.cs index d402e0c..bf3afe3 100644 --- a/MapDataReader.Benchmarks/Program.cs +++ b/MapDataReader.Benchmarks/Program.cs @@ -93,7 +93,7 @@ public static void Setup() } } - [GenerateDataReaderMapper] + [GenerateDataReaderMapper(AccessModifier = "internal")] public class TestClass { public string String1 { get; set; } diff --git a/MapDataReader.Tests/TestActualCode.cs b/MapDataReader.Tests/TestActualCode.cs index 76ccaca..2c71391 100644 --- a/MapDataReader.Tests/TestActualCode.cs +++ b/MapDataReader.Tests/TestActualCode.cs @@ -3,6 +3,7 @@ using System.Data; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -147,7 +148,7 @@ public void TestStringAssign() } [TestMethod] - public void TestDatatReader() + public void TestDataReader() { //create datatable with test data var dt = new DataTable(); @@ -227,6 +228,24 @@ public void TestWrongProperty() o.SetPropertyByName("Name", 123); //try to assign string prop to int Assert.IsTrue(o.Name == null); //wrong type. should be null } + + [TestMethod] + public void TestInternalAccessModifier() + { + var type = typeof(MapperExtensions); + var method = type.GetMethod("ToTestClassInternal", BindingFlags.Static | BindingFlags.NonPublic); + + Assert.IsNotNull(method, "Expected method 'ToTestClassInternal' to be 'internal'."); + } + + [TestMethod] + public void TestInternalAccessModifierNamed() + { + var type = typeof(MapperExtensions); + var method = type.GetMethod("ToTestClassInternalNamed", BindingFlags.Static | BindingFlags.NonPublic); + + Assert.IsNotNull(method, "Expected method 'ToTestClassInternalNamed' to be 'internal'."); + } } public class BaseClass @@ -239,5 +258,17 @@ public class ChildClass : BaseClass { public string Name { get; set; } } + + [GenerateDataReaderMapper("internal")] + internal class TestClassInternal + { + public int Id { get; set; } + } + + [GenerateDataReaderMapper(AccessModifier = "internal")] + internal class TestClassInternalNamed + { + public int Id { get; set; } + } } diff --git a/MapDataReader.Tests/TestGenerator.cs b/MapDataReader.Tests/TestGenerator.cs index 083790b..8ae70aa 100644 --- a/MapDataReader.Tests/TestGenerator.cs +++ b/MapDataReader.Tests/TestGenerator.cs @@ -29,6 +29,29 @@ public class MyClass public decimal Price {get;set;} } } +"; + var src = GetAndCheckOutputSource(userSource); + } + + [TestMethod] + public void TestAccessModifier() + { + string userSource = @" +using MapDataReader; + +namespace MyCode +{ + [GenerateDataReaderMapper(AccessModifier = ""internal"")] + public class MyClass + { + public string Name {get;set;} + public int Size {get;set;} + public bool Enabled {get;set;} + public System.DateTime Created {get;set;} + public System.DateTimeOffset Offset {get;set;} + public decimal Price {get;set;} + } +} "; var src = GetAndCheckOutputSource(userSource); } diff --git a/MapDataReader/MapperGenerator.cs b/MapDataReader/MapperGenerator.cs index 07e3b91..6863dee 100644 --- a/MapDataReader/MapperGenerator.cs +++ b/MapDataReader/MapperGenerator.cs @@ -11,6 +11,17 @@ namespace MapDataReader [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class GenerateDataReaderMapperAttribute : Attribute { + public string AccessModifier { get; set; } + + public GenerateDataReaderMapperAttribute() + { + AccessModifier = "public"; + } + + public GenerateDataReaderMapperAttribute(string access = "public") + { + AccessModifier = access; + } } [Generator] @@ -28,6 +39,8 @@ public void Execute(GeneratorExecutionContext context) var allProperties = typeNodeSymbol.GetAllSettableProperties(); + var accessModifier = GetAccessModifer(typeNode); + var src = $@" // #pragma warning disable 8019 //disable 'unnecessary using directive' warning @@ -40,7 +53,7 @@ namespace MapDataReader {{ public static partial class MapperExtensions {{ - public static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value) + {accessModifier} static void SetPropertyByName(this {typeNodeSymbol.FullName()} target, string name, object value) {{ SetPropertyByUpperName(target, name.ToUpperInvariant(), value); }} @@ -79,7 +92,7 @@ private static void SetPropertyByUpperName(this {typeNodeSymbol.FullName()} targ { src += $@" - public static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr) + {accessModifier} static List<{typeNodeSymbol.FullName()}> To{typeNode.Identifier}(this IDataReader dr) {{ var list = new List<{typeNodeSymbol.FullName()}>(); @@ -119,6 +132,44 @@ public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new TargetTypeTracker()); } + + private string GetAccessModifer(ClassDeclarationSyntax typeNode) + { + // Retrieve the attribute list + var attributeList = typeNode.AttributeLists + .SelectMany(al => al.Attributes) + .FirstOrDefault(attr => attr.Name.ToString() == "GenerateDataReaderMapper"); + + if (attributeList?.ArgumentList == null) + return "public"; + + var arguments = attributeList.ArgumentList.Arguments; + + if (arguments.Count == 0) + return "public"; + + if (arguments.Count == 1) + { + var argumentExpr = arguments[0].Expression as LiteralExpressionSyntax; + return argumentExpr?.Token.ValueText ?? "public"; + } + + foreach (var argument in arguments) + { + // Check if the argument is a named argument + if (argument is AttributeArgumentSyntax attributeArgument) + { + var nameEquals = attributeArgument.NameEquals; + if (nameEquals?.Name.Identifier.Text == "AccessModifier") + { + var argumentExpr = argument.Expression as LiteralExpressionSyntax; + return argumentExpr?.Token.ValueText ?? "public"; + } + } + } + + return "public"; + } } internal class TargetTypeTracker : ISyntaxContextReceiver diff --git a/README.md b/README.md index ab78f76..21c89e8 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,20 @@ Some notes for the above * Properly maps `DBNull` to `null`. * Complex-type properties may not work. +### Access Modifier: `public` or `internal` + +You can now specify the access modifer to be used with the mapping methods. By default, the methods will be `public` for backwards compatability. + +For example, to prevent exposure outside your assembly you'd set it to `internal`. This would hide the mapping methods outside your model project: + +``` csharp +[GenerateDataReaderMapper("internal")] +public class MyClass +{ + public int ID { get; set; } +... +``` + ## Bonus API: `SetPropertyByName` This package also adds a super fast `SetPropertyByName` extension method generated at compile time for your class.