It's straight forward and uses LINQ to XML. So, let's see how it's done.
First, I created a method that get me some namespaces to work with.
1: protected static void GetWorkingNameSpaces(string AssembliesDir)
2: {
3: Namespaces = new HashSet<string>();
4: DirectoryInfo di = new DirectoryInfo(AssembliesDir);
5: var workingAssemblies = di.Exists ?
6: di.EnumerateFiles()
7: .Where(s=>s.Extension.ToLower() ==".dll" ||
8: s.Extension.ToLower() == ".exe" ) : null;
9: Parallel.ForEach(workingAssemblies, a =>
10: {
11: Assembly reference = Assembly.ReflectionOnlyLoadFrom(a.FullName);
12: try
13: {
14: HashSet<string> refernceNamespaces =
15: new HashSet<string>(reference.GetTypes()
16: .Select(f => f.Namespace)
17: .Where(n => n != null));
18: Namespaces.UnionWith(refernceNamespaces);
19: }
20: catch (ReflectionTypeLoadException) { }
21: });
22: }
So, not a whole lot going on, just newing up an instance of a HashSet and doing reflection over a list of assemblies and extracting some namespaces, and storing them in a HashSet collection so that i can work with them later.
Next, I created a method that will process the wildcard token. It takes in a namespace root or sub-n-root and returns nested namespaces.
1: protected static HashSet<string> ProcessWildCard(string Token)
2: {
3: HashSet<string> namespaces = new HashSet<string>();
4: int index = default(int);
5: string Name = default(string);
6: if (Token.Contains("."))
7: index = Token.LastIndexOf('.');
8: else return namespaces;
9: Name = Token.Substring(0, index).Trim();
10: foreach (var t in from c in Namespaces
11: where c.StartsWith(Name)
12: select c)
13: {
14: namespaces.Add(t);
15: }
16: return namespaces;
17: }
Now that i got this far, i need to build an update method that takes in an attribute Name and an XElement - since I am going to be updating both the Forbidden Namespace Dependencies and Forbidden Namespaces.
1: protected static void UpdateForiddenAttribute(ref string
2: forbiddenAttributeName,
3: XElement t)
4: {
5: List<string> proccessed = new List<string>();
6: string _forbiddenAttributeValue = default(string);
7: if (t.Attribute(forbiddenAttributeName) != null)
8: {
9: _forbiddenAttributeValue =
10: t.Attribute(forbiddenAttributeName).Value;
11: string[] elements = default(string[]);
12: if (_forbiddenAttributeValue.Contains(";"))
13: {
14: elements = _forbiddenAttributeValue.Split(new[] { ";" },
15: StringSplitOptions.RemoveEmptyEntries);
16: }
17: else
18: {
19: elements = new[] { _forbiddenAttributeValue };
20: }
21: foreach (var s in elements)
22: {
23: if (s.Contains("*") && !(s.Contains(",") || s.Contains("|")))
24: {
25: var hash = ProcessWildCard(s);
26: if (hash.Count > 0)
27: foreach (var h in hash)
28: {
29: proccessed.Add(h);
30: }
31: else
32: {
33: proccessed.Add(s);
34: }
35: }
36: else
37: {
38: proccessed.Add(s);
39: }
40: }
41: }
42: string formattedAttributeValue = default(string);
43: if (proccessed.Count > 1)
44: {
45: formattedAttributeValue = String.Join(";", proccessed);
46: }
47: else
48: {
49: if (proccessed.Count == 1)
50: formattedAttributeValue = proccessed[0];
51: else
52: formattedAttributeValue = null;
53: }
54: t.SetAttributeValue(forbiddenAttributeName, formattedAttributeValue);
55: }
So far so good, all i did is get some attribute value, do some processing on it and update the .layerdiagram file with the newly constructed list of namespaces. So, the next step is the actual method that runs and collects data about the layerdiaram file/s in the modeling project, calls the update method and finally saves each layerdiagram file to complete the process.
1: static void RunTool(string layerdiagramDirectoryPath,
2: string AssembliesDirectory)
3: {
4: string forbiddenNamespaceDependenciesName =
5: "forbiddenNamespaceDependencies";
6: string forbiddenNamespaceName = "forbiddenNamespace";
7: GetWorkingNameSpaces(AssembliesDirectory);
8:
9: var q= Directory.EnumerateFiles(layerdiagramDirectoryPath,
10: "*.layerdiagram",
11: SearchOption.AllDirectories)
12: .Select(x => new{
13: s = XDocument.Load(x),
14: p = x
15: })
16: .Where(d => d.s.Root
17: .Elements()
18: .Descendants()
19: .Any(v => v.Attributes(
20: forbiddenNamespaceDependenciesName)
21: .Any())
22: ||d.s.Root
23: .Elements()
24: .Descendants()
25: .Any(v => v.Attributes(forbiddenNamespaceName)
26: .Any())
27: );
28: foreach (var x in q)
29: {
30: bool DocumentHasChanges = default(bool);
31: IEnumerable<XElement> xElements =
32: ( from c in x.s.Root
33: .Elements()
34: .Descendants()
35: where c.Attributes(forbiddenNamespaceDependenciesName)
36: .Any() ||
37: c.Attributes(forbiddenNamespaceName).Any()
38: select c
39: );
40: Parallel.ForEach(xElements, t =>
41: {
42: if (t.Attribute(forbiddenNamespaceDependenciesName) != null)
43: {
44: UpdateForiddenAttribute(ref
45: forbiddenNamespaceDependenciesName, t);
46: DocumentHasChanges = true;
47: }
48: if (t.Attribute(forbiddenNamespaceName) != null)
49: {
50: UpdateForiddenAttribute(ref forbiddenNamespaceName, t);
51: DocumentHasChanges = true;
52: }
53: });
54: if (DocumentHasChanges)
55: x.s.Save(x.p);
56: }
57: }
And that's all the code needed to now have an out of box wildcard functionality. To get this working just hook it up to the main entry point of your program and pass in 2 arguments (modeling project directory and assemblies directory) and wrap it up with a try catch block. Would look something like this:
1: #region Program Entry
2: public static void Main(string[] args)
3: {
4: try
5: {
6: RunTool(args[0], args[1]);
7: }
8: catch (Exception g)
9: {
10: Console.WriteLine("Error :{0} ", g.Message);
11: }
12: }
13: #endregion
So, to use this tool, right click on the modeling project and choose Edit project - add an MSBUILD Exec task and give it the location of batch file under your solution items or where ever you like to keep .bat files related to your solution and that's all. Next time you build the solution and if there are any broken forbidden namespace dependencies, they will show up as build errors.