Type rules
editA type rule selects types, then asserts something about them: the types they use with Assert.Architecture, or their names and shape with Assert.All.
Select the types
ArchitectureModel.Types is a list of TypeElement: select the types with LINQ. The failure messages show the expression you pass to the assertion, so a variable with a clear name, such as domain, gives a clear message.
These members of TypeElement cover the common filters:
| Member | Is true when the type |
|---|---|
| IsInNamespace | is in the namespace, not in one of its child namespaces |
| IsInNamespaceOrChildren | is in the namespace or one of its child namespaces: MyApp.Domain matches MyApp.Domain.Events, not MyApp.DomainEvents |
| IsClass, IsInterface, IsStruct, IsEnum, IsDelegate, IsRecord | is of a kind |
| IsAbstract, IsSealed, IsStatic, IsPublic, IsNested | has a modifier |
| InheritsFrom, Implements, IsAssignableTo | inherits from a class or implements an interface, directly or not |
| HasAttribute | has an attribute |
Name is the name without the namespace and the generic arity (Repository for Repository<T>), so t.Name.EndsWith("Repository", StringComparison.Ordinal) filters by name.
The members that take a type, such as Implements<T>(), compare full names. Pass a generic type definition, such as typeof(Repository<>), to match all its instantiations. The overloads that take a string expect the name as written in metadata: MyApp.Repository`1, MyApp.Outer+Inner.
Combine sets with LINQ (Except, Concat) or with a collection expression: [.. application, .. web].
Check the dependencies
| Assertion | Fails when a type |
|---|---|
| DoesNotDependOn(types, forbiddenTypes) | uses one of the forbidden types. The dependencies between the checked types are ignored |
| DoesNotDependOn(types, predicate) | uses a type that matches the predicate, including a type of .NET or of a package |
| DoesNotDependOn<T>(types) | uses T. A generic type definition, such as typeof(List<>), forbids all its instantiations |
| DependsOnlyOn(types, allowedTypes) | uses a type of the loaded assemblies that is neither allowed nor checked. The types of .NET and of the packages are not checked |
| DependsOnlyOn(types, predicate) | uses a type that is not checked and does not match the predicate. Add System to allow the types of .NET |
| DoesNotAccessMember<T>(types, memberName) | calls a method or a constructor of T, or reads or writes a field, a property or an event |
The predicates take a TypeReference, which has the same IsInNamespace and IsInNamespaceOrChildren members as TypeElement:
var architecture = ArchitectureModel.FromAssemblyContaining<Order>();
var domain = architecture.Types.Where(t => t.IsInNamespaceOrChildren("SampleApp.Domain"));
// The types of the domain may use each other and the types of .NET, nothing else
Assert.Architecture.DependsOnlyOn(domain, t => t.IsInNamespaceOrChildren("System"));DoesNotAccessMember bans a member of a type that is otherwise allowed, such as the current time:
var architecture = ArchitectureModel.FromAssemblyContaining<Order>();
var types = architecture.Types.Where(t => t.IsInNamespaceOrChildren("SampleApp"));
Assert.Architecture.DoesNotAccessMember<DateTime>(types, "Now", "the current time comes from TimeProvider");
Assert.Architecture.DoesNotAccessMember<DateTime>(types, nameof(DateTime.UtcNow), "the current time comes from TimeProvider");The model explains what counts as a dependency.
Check the names and the shape
Assert.All from Meziantou.Framework.Assertions checks a predicate on each type, with the members of TypeElement:
var architecture = ArchitectureModel.FromAssemblyContaining<Order>();
var repositories = architecture.Types.Where(t => t.IsClass && t.Implements<IOrderRepository>());
Assert.NotEmpty(repositories);
Assert.All(repositories, t => t.Name.EndsWith("Repository", StringComparison.Ordinal));
Assert.All(repositories, t => t.IsSealed);
Assert.All(architecture.Types.Where(t => t.IsInNamespaceOrChildren("SampleApp.Domain") && !t.IsInterface), t => t.IsImmutable);Assert.Allpasses when the collection is empty: addAssert.NotEmptywhen a typo in the filter must fail.Assert.Allreports the first type that does not match the predicate.- IsImmutable is
truewhen the instance fields arereadonlyor constant, and the instance properties have no setter other thaninit.
The members of the types are in the model too, so the same pattern checks methods, properties or fields:
var architecture = ArchitectureModel.FromAssemblyContaining<Order>();
var handlers = architecture.Types.Where(t => t.IsInNamespace("SampleApp.Application") && t.Name.EndsWith("Handler", StringComparison.Ordinal));
Assert.All(handlers.SelectMany(t => t.Methods).Where(method => method.Accessibility == ElementAccessibility.Public), method => method.IsAsync);