Skip to content

Commit e5adb87

Browse files
committed
[ENHANCEMENT] Change default IsolationLevel in ADO.StartTransaction #13
[ENHANCEMENT] Extend Active Directory to return Manager property #10 [ENHANCEMENT] Add script to create default database #14
1 parent 00361d5 commit e5adb87

File tree

19 files changed

+110
-39
lines changed

19 files changed

+110
-39
lines changed

db/database.sql

5.89 KB
Binary file not shown.

rls/API.Library.dll

3 KB
Binary file not shown.

rls/API.Library.dll.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
<add key="API_AD_USERNAME" value="" />
5959
<!-- Active Directory - Username -->
6060
<add key="API_AD_PASSWORD" value="" />
61+
<!-- Active Directory - Custom Properties (comas separated, case sensitive) -->
62+
<add key="API_AD_CUSTOM_PROPERTIES" value="" />
6163

6264
<!--
6365
**********************************************************************

rls/API.Library.pdb

2 KB
Binary file not shown.

src/API.Library/App.config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
<add key="API_AD_USERNAME" value="" />
5959
<!-- Active Directory - Username -->
6060
<add key="API_AD_PASSWORD" value="" />
61+
<!-- Active Directory - Custom Properties (comas separated, case sensitive) -->
62+
<add key="API_AD_CUSTOM_PROPERTIES" value="" />
6163

6264
<!--
6365
**********************************************************************

src/API.Library/Entities/ADO.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Configuration;
3-
using System.Linq;
4-
using System.Data.SqlClient;
54
using System.Data;
6-
using System.Collections.Generic;
7-
using System.Dynamic;
5+
using System.Data.SqlClient;
86
using System.Diagnostics;
7+
using System.Dynamic;
8+
using System.Linq;
99

1010
namespace API
1111
{
@@ -199,9 +199,10 @@ private bool CheckTransaction()
199199

200200
/// <summary>
201201
/// Start a SQL Server transaction
202+
/// Consider setting "Is Read Committed Snapshot On" to TRUE in the Database options for better performance
202203
/// </summary>
203204
/// <param name="transactionIsolation"></param>
204-
public void StartTransaction(IsolationLevel transactionIsolation = IsolationLevel.Snapshot)
205+
public void StartTransaction(IsolationLevel transactionIsolation = IsolationLevel.ReadCommitted)
205206
{
206207
// Check if a transaction already exists
207208
if (!CheckTransaction())
@@ -747,4 +748,4 @@ public ADO_readerOutput()
747748
data = new List<dynamic>();
748749
}
749750
}
750-
}
751+
}

src/API.Library/Entities/ActiveDirectory.cs

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,30 @@ public class ActiveDirectory
3232
/// Active Directory Password for Querying
3333
/// </summary>
3434
private static string adPassword = ConfigurationManager.AppSettings["API_AD_PASSWORD"];
35+
36+
/// <summary>
37+
/// Active Directory custom properties for Querying
38+
/// </summary>
39+
private static List<string> adCustomProperties = (ConfigurationManager.AppSettings["API_AD_CUSTOM_PROPERTIES"]).Split(',').ToList<string>();
3540
#endregion
3641

3742
#region Methods
3843
/// <summary>
3944
/// This method returns the entire Active Directory list for the configure Domain
4045
/// </summary>
4146
/// <returns></returns>
42-
private static IDictionary<string, dynamic> GetDirectory()
47+
private static IDictionary<string, dynamic> GetDirectory<T>() where T : UserPrincipal
4348
{
4449
Log.Instance.Info("AD Domain: " + adDomain);
4550
Log.Instance.Info("AD Path: " + adPath);
4651
Log.Instance.Info("AD Username: " + adUsername);
4752
Log.Instance.Info("AD Password: ********"); // Hide adPassword from logs
4853

49-
// Initilise a new dynamic object
50-
dynamic adUsers = new ExpandoObject();
51-
// Implement the interface for handling dynamic properties
52-
var adUsers_IDictionary = adUsers as IDictionary<string, dynamic>;
54+
// new ExpandoObject() and implement the interface for handling dynamic properties
55+
var adUsers_IDictionary = new ExpandoObject() as IDictionary<string, dynamic>;
5356

54-
MemCachedD_Value adCache = MemCacheD.Get_BSO<dynamic>("API", "ActiveDirectory", "GetDirectory", null);
57+
var inputDTO = Utility.JsonSerialize_IgnoreLoopingReference(Activator.CreateInstance(typeof(T), new object[] { new PrincipalContext(ContextType.Domain) }) as T);
58+
MemCachedD_Value adCache = MemCacheD.Get_BSO<dynamic>("API", "ActiveDirectory", "GetDirectory", inputDTO);
5559
if (adCache.hasData)
5660
return adCache.data.ToObject<Dictionary<string, dynamic>>();
5761

@@ -60,11 +64,20 @@ private static IDictionary<string, dynamic> GetDirectory()
6064
// Get to the Domain
6165
using (var context = new PrincipalContext(ContextType.Domain, adDomain, String.IsNullOrEmpty(adPath) ? null : adPath, String.IsNullOrEmpty(adUsername) ? null : adUsername, String.IsNullOrEmpty(adPassword) ? null : adPassword))
6266
{
63-
// Get to the Search, filtering by Enabled accounts, exclude accounts with blank properties
64-
using (var searcher = new PrincipalSearcher(new UserPrincipal(context) { Enabled = true, SamAccountName = "*", EmailAddress = "*", GivenName = "*", Surname = "*" }))
67+
// Crete the query filterusing enabled accounts and excluding those with blank basic properties
68+
var queryFilter = Activator.CreateInstance(typeof(T), new object[] { context }) as T;
69+
queryFilter.Enabled = true;
70+
queryFilter.SamAccountName = "*";
71+
queryFilter.EmailAddress = "*";
72+
queryFilter.GivenName = "*";
73+
queryFilter.Surname = "*";
74+
75+
// Run the search
76+
using (var searcher = new PrincipalSearcher(queryFilter))
6577
{
6678
// Loop trough the results and sort then by SamAccountName
67-
foreach (var result in searcher.FindAll().Cast<UserPrincipal>().OrderBy(x => x.SamAccountName))
79+
// Cast to dynamic to get all properties including any custom one
80+
foreach (var result in searcher.FindAll().Cast<dynamic>().OrderBy(x => x.SamAccountName))
6881
{
6982
// Check for duplicate accounts
7083
if (adUsers_IDictionary.ContainsKey(result.SamAccountName))
@@ -74,23 +87,30 @@ private static IDictionary<string, dynamic> GetDirectory()
7487
}
7588
else
7689
{
77-
// Create a shallow copy of the UserPrincipal with the main proprieties for caching/serialising it later on
78-
dynamic userPrincipal_ShallowCopy = new ExpandoObject();
79-
userPrincipal_ShallowCopy.SamAccountName = result.SamAccountName;
80-
userPrincipal_ShallowCopy.UserPrincipalName = result.UserPrincipalName;
81-
userPrincipal_ShallowCopy.DistinguishedName = result.DistinguishedName;
82-
userPrincipal_ShallowCopy.DisplayName = result.DisplayName;
83-
userPrincipal_ShallowCopy.Name = result.Name;
84-
userPrincipal_ShallowCopy.GivenName = result.GivenName;
85-
userPrincipal_ShallowCopy.MiddleName = result.MiddleName;
86-
userPrincipal_ShallowCopy.Surname = result.Surname;
87-
userPrincipal_ShallowCopy.EmailAddress = result.EmailAddress;
88-
userPrincipal_ShallowCopy.EmployeeId = result.EmployeeId;
89-
userPrincipal_ShallowCopy.VoiceTelephoneNumber = result.VoiceTelephoneNumber;
90-
userPrincipal_ShallowCopy.Description = result.Description;
90+
// Create a shallow copy of AD with the mandatory proprieties for caching/serialising it later on
91+
var userPrincipal_ShallowCopy = new ExpandoObject() as IDictionary<string, Object>;
92+
93+
userPrincipal_ShallowCopy.Add("SamAccountName", result.SamAccountName);
94+
userPrincipal_ShallowCopy.Add("UserPrincipalName", result.UserPrincipalName);
95+
userPrincipal_ShallowCopy.Add("DistinguishedName", result.DistinguishedName);
96+
userPrincipal_ShallowCopy.Add("DisplayName", result.DisplayName);
97+
userPrincipal_ShallowCopy.Add("Name", result.Name);
98+
userPrincipal_ShallowCopy.Add("GivenName", result.GivenName);
99+
userPrincipal_ShallowCopy.Add("MiddleName", result.MiddleName);
100+
userPrincipal_ShallowCopy.Add("Surname", result.Surname);
101+
userPrincipal_ShallowCopy.Add("EmailAddress", result.EmailAddress);
102+
userPrincipal_ShallowCopy.Add("EmployeeId", result.EmployeeId);
103+
userPrincipal_ShallowCopy.Add("VoiceTelephoneNumber", result.VoiceTelephoneNumber);
104+
userPrincipal_ShallowCopy.Add("Description", result.Description);
105+
106+
// Add the cusotm properties to the shallow copy if any
107+
foreach (string property in adCustomProperties)
108+
{
109+
userPrincipal_ShallowCopy.Add(property, result.GetType().GetProperty(property)?.GetValue(result, null));
110+
}
91111

92112
// Add user to the dictionary, serialise/deserialise to avoid looping references
93-
adUsers_IDictionary.Add(result.SamAccountName, userPrincipal_ShallowCopy);
113+
adUsers_IDictionary.Add(result.SamAccountName, userPrincipal_ShallowCopy as ExpandoObject);
94114
}
95115
}
96116
}
@@ -103,7 +123,7 @@ private static IDictionary<string, dynamic> GetDirectory()
103123
}
104124

105125
// Set the cache to expire at midnight
106-
MemCacheD.Store_BSO<dynamic>("API", "ActiveDirectory", "GetDirectory", null, adUsers_IDictionary, DateTime.Today.AddDays(1));
126+
MemCacheD.Store_BSO<dynamic>("API", "ActiveDirectory", "GetDirectory", inputDTO, adUsers_IDictionary, DateTime.Today.AddDays(1));
107127

108128
return adUsers_IDictionary;
109129
}
@@ -115,7 +135,12 @@ private static IDictionary<string, dynamic> GetDirectory()
115135
public static IDictionary<string, dynamic> List()
116136
{
117137
// Get the full directory
118-
return GetDirectory();
138+
return List<UserPrincipal>();
139+
}
140+
public static IDictionary<string, dynamic> List<T>() where T : UserPrincipal
141+
{
142+
// Get the full directory
143+
return GetDirectory<T>();
119144
}
120145

121146
/// <summary>
@@ -124,9 +149,13 @@ public static IDictionary<string, dynamic> List()
124149
/// <param name="username"></param>
125150
/// <returns></returns>
126151
public static dynamic Search(string username)
152+
{
153+
return Search<UserPrincipal>(username);
154+
}
155+
public static dynamic Search<T>(string username) where T : UserPrincipal
127156
{
128157
// Get the full director
129-
IDictionary<string, dynamic> adDirectory = GetDirectory();
158+
IDictionary<string, dynamic> adDirectory = GetDirectory<T>();
130159

131160
if (adDirectory.ContainsKey(username))
132161
return adDirectory[username];
@@ -167,4 +196,30 @@ public static bool IsPasswordValid(dynamic userPrincipal, string password)
167196

168197
}
169198

199+
/// <summary>
200+
/// Template to implement a extended UserPrincipal to retrieve custom AD properties (i.e. Sample)
201+
/// </summary>
202+
/*
203+
[DirectoryRdnPrefix("CN")]
204+
[DirectoryObjectClass("Person")]
205+
public partial class UserPrincipalExtended : UserPrincipal
206+
{
207+
public UserPrincipalExtended(PrincipalContext context) : base(context) { }
208+
209+
210+
// Create the "Sample" property.
211+
[DirectoryProperty("sample")]
212+
public string Sample
213+
{
214+
get
215+
{
216+
if (ExtensionGet("sample").Length != 1)
217+
return string.Empty;
218+
219+
return (string)ExtensionGet("sample")[0];
220+
}
221+
set { ExtensionSet("sample", value); }
222+
}
223+
}
224+
*/
170225
}

src/API.Library/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
// You can specify all the values or you can default the Build and Revision Numbers
3232
// by using the '*' as shown below:
3333
// [assembly: AssemblyVersion("1.0.*")]
34-
[assembly: AssemblyVersion("4.5.0")]
35-
[assembly: AssemblyFileVersion("4.5.0")]
34+
[assembly: AssemblyVersion("4.6.0")]
35+
[assembly: AssemblyFileVersion("4.6.0")]
3636

3737
// Configure log4net using the Web.config file by default
3838
[assembly: log4net.Config.XmlConfigurator(Watch = true)]

test/API.Test/API.Test.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
7373
</PropertyGroup>
7474
<ItemGroup>
75-
<Reference Include="API.Library, Version=4.5.0, Culture=neutral, processorArchitecture=MSIL">
75+
<Reference Include="API.Library, Version=4.6.0, Culture=neutral, processorArchitecture=MSIL">
7676
<SpecificVersion>False</SpecificVersion>
77-
<HintPath>..\packages\API.Library.4.5.0\API.Library.dll</HintPath>
77+
<HintPath>..\packages\API.Library.4.6.0\API.Library.dll</HintPath>
7878
</Reference>
7979
<Reference Include="Enyim.Caching, Version=2.16.0.0, Culture=neutral, PublicKeyToken=cec98615db04012e, processorArchitecture=MSIL">
8080
<HintPath>..\packages\EnyimMemcached.2.16.0\lib\net35\Enyim.Caching.dll</HintPath>
@@ -106,6 +106,7 @@
106106
<Private>True</Private>
107107
</Reference>
108108
<Reference Include="System.Data.DataSetExtensions" />
109+
<Reference Include="System.DirectoryServices.AccountManagement" />
109110
<Reference Include="System.ValueTuple">
110111
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
111112
<Private>True</Private>

test/API.Test/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@
3030
//
3131
// You can specify all the values or you can default the Revision and Build Numbers
3232
// by using the '*' as shown below:
33-
[assembly: AssemblyVersion("4.5.0")]
34-
[assembly: AssemblyFileVersion("4.5.0")]
33+
[assembly: AssemblyVersion("4.6.0")]
34+
[assembly: AssemblyFileVersion("4.6.0")]

0 commit comments

Comments
 (0)