diff --git a/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/RoleSecuredTypes.cs b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/RoleSecuredTypes.cs
new file mode 100644
index 000000000..a48ad4770
--- /dev/null
+++ b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/RoleSecuredTypes.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Cratis.Arc.Authorization;
+
+namespace Cratis.Arc.ProxyGenerator.ControllerBased.for_MethodInfoExtensions;
+
+///
+/// Holds the shapes roles can be declared in, for reading them back.
+///
+///
+/// Both Cratis.Arc.Authorization and Microsoft.AspNetCore.Authorization declare an
+/// Authorize attribute, so the ASP.NET Core one is written out in full where the named-argument form is needed.
+///
+public class RoleSecuredTypes
+{
+ [Roles("Librarian")]
+ public void SingleRoleFromConstructor() { }
+
+ [Roles("Librarian", "Admin")]
+ public void MultipleRolesFromConstructor() { }
+
+ [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Librarian")]
+ public void RoleFromNamedArgument() { }
+
+ [Roles("Librarian")]
+ [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Librarian")]
+ public void RoleFromBothForms() { }
+}
diff --git a/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_both_forms_combined.cs b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_both_forms_combined.cs
new file mode 100644
index 000000000..46d7b4b47
--- /dev/null
+++ b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_both_forms_combined.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+
+namespace Cratis.Arc.ProxyGenerator.ControllerBased.for_MethodInfoExtensions.when_extracting_roles;
+
+public class from_both_forms_combined : Specification
+{
+ MethodInfo _method;
+ IEnumerable _result;
+
+ void Establish() => _method = typeof(RoleSecuredTypes).GetMethod(nameof(RoleSecuredTypes.RoleFromBothForms));
+
+ void Because() => _result = _method.GetRoles();
+
+ [Fact] void should_deduplicate_the_roles() => _result.ShouldContainOnly(["Librarian"]);
+}
diff --git a/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_constructor_form_with_multiple_roles.cs b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_constructor_form_with_multiple_roles.cs
new file mode 100644
index 000000000..a539b2c75
--- /dev/null
+++ b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_constructor_form_with_multiple_roles.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+
+namespace Cratis.Arc.ProxyGenerator.ControllerBased.for_MethodInfoExtensions.when_extracting_roles;
+
+public class from_constructor_form_with_multiple_roles : Specification
+{
+ MethodInfo _method;
+ IEnumerable _result;
+
+ void Establish() => _method = typeof(RoleSecuredTypes).GetMethod(nameof(RoleSecuredTypes.MultipleRolesFromConstructor));
+
+ void Because() => _result = _method.GetRoles();
+
+ [Fact] void should_yield_all_roles() => _result.ShouldContainOnly(["Librarian", "Admin"]);
+}
diff --git a/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_constructor_form_with_single_role.cs b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_constructor_form_with_single_role.cs
new file mode 100644
index 000000000..fcfaf30df
--- /dev/null
+++ b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_constructor_form_with_single_role.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+
+namespace Cratis.Arc.ProxyGenerator.ControllerBased.for_MethodInfoExtensions.when_extracting_roles;
+
+public class from_constructor_form_with_single_role : Specification
+{
+ MethodInfo _method;
+ IEnumerable _result;
+
+ void Establish() => _method = typeof(RoleSecuredTypes).GetMethod(nameof(RoleSecuredTypes.SingleRoleFromConstructor));
+
+ void Because() => _result = _method.GetRoles();
+
+ [Fact] void should_yield_the_role() => _result.ShouldContainOnly(["Librarian"]);
+}
diff --git a/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_named_argument_form.cs b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_named_argument_form.cs
new file mode 100644
index 000000000..0649e53bd
--- /dev/null
+++ b/Source/DotNET/Tools/ProxyGenerator.Specs/ControllerBased/for_MethodInfoExtensions/when_extracting_roles/from_named_argument_form.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+
+namespace Cratis.Arc.ProxyGenerator.ControllerBased.for_MethodInfoExtensions.when_extracting_roles;
+
+public class from_named_argument_form : Specification
+{
+ MethodInfo _method;
+ IEnumerable _result;
+
+ void Establish() => _method = typeof(RoleSecuredTypes).GetMethod(nameof(RoleSecuredTypes.RoleFromNamedArgument));
+
+ void Because() => _result = _method.GetRoles();
+
+ [Fact] void should_yield_the_role() => _result.ShouldContainOnly(["Librarian"]);
+}
diff --git a/Source/DotNET/Tools/ProxyGenerator/MethodInfoExtensions.cs b/Source/DotNET/Tools/ProxyGenerator/MethodInfoExtensions.cs
index b624143c4..a46588e50 100644
--- a/Source/DotNET/Tools/ProxyGenerator/MethodInfoExtensions.cs
+++ b/Source/DotNET/Tools/ProxyGenerator/MethodInfoExtensions.cs
@@ -105,6 +105,7 @@ static IEnumerable GetRolesFromAttributesData(IEnumerable a.MemberName == "Roles");
if (rolesArg != default && rolesArg.TypedValue.Value is string rolesStr && !string.IsNullOrEmpty(rolesStr))
{
@@ -113,6 +114,16 @@ static IEnumerable GetRolesFromAttributesData(IEnumerable 0 &&
+ attr.ConstructorArguments[0].Value is IReadOnlyCollection roleArgs)
+ {
+ foreach (var role in roleArgs.Select(a => a.Value as string).Where(r => !string.IsNullOrEmpty(r)))
+ {
+ yield return role!;
+ }
+ }
}
}