Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Rules for CLR Add-Ins Contract and View Assemblies

 

The CLR Add-In team
recently released on CodePlex a set of 49 FxCop rules
to verify View and Contract assemblies. If you don’t don’t
know what is View and Contract assemblies, I suggest reading
this excellent CLR Inside-Out MSDN Magazine article
by Jack Gudenkauf and Jesse Kaplan. It is about how to harness System.AddIn facilities.

 

I spent an hour implementing these
rules with CQL
and came to the following result. Notice that these rules can be readily adapted
if you have sereval contract or view assemblies.

 

 

Rules on Contract assemblies

// <Name>Contract assemblies should not have references to non-framework assemblies</Name>
WARN IF Count > 0 IN SELECT ASSEMBLIES WHERE 
  
IsUsedBy "MyContractAssembly" AND 
  
// Framework assemblies are mscorlib and thos whose names begin with System.
  (!NameIs "mscorlib" AND 
   
!NameLike "^System.")

// Don't reference non-framework assemblies from the contract assembly unless you can 
// be sure that the assembly can be loaded on both sides of the contract boundary.


// <Name>Contract types that represent add-ins should be marked with the AddInContractAttribute</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
IsInterface AND // Limiting search to interfaces only.
  !HasAttribute "OPTIONAL:System.AddIn.Pipeline.AddInContractAttribute"

// Contracts that represent add-ins and are intended to be activated should be marked 
// with the AddInContractAttribute. Contracts that only represent objects passed between 
// hosts and add-ins do not need this attribute; if all of the types in this assembly 
// fall under this category then this rule can be ignored.


// <Name>Value types defined in a contract assembly should be serializable</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
IsStructure AND !IsSerializable 

// Value types (structs) are only useful in the contract assembly if 
// they can be serialized across the boundary.

// <Name>Contract interfaces must implement IContract</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE 
  
IsInterface AND
  
!Implement "OPTIONAL:System.AddIn.Contract.IContract"

// All interfaces defined in the contract assembly must implement IContract, 
// or another interface that implements IContract. Using an interface that 
// doesn’t implement IContract can cause problems with lifetime management.


// <Name>Contract assemblies should not define types that derive from MarshalByRefObject</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
DeriveFrom "OPTIONAL:System.MarshalByRefObject"

// MarshalByRefObjects can cause problems with lifetime management. If this type 
// represents an object you want to marshal by reference, you should define an 
// interface implementing IContract to represent it. If it is intended to be copied 
// (and thus passed by value), then you should define a simple serializable value 
// type (struct) to represent it.


// <Name>Exception types must implement ISerializable and be marked with the Serializable attribute</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
DeriveFrom "OPTIONAL:System.Exception" AND
  
(!Implement "OPTIONAL:System.Runtime.Serialization.ISerializable" OR
   
!HasAttribute "OPTIONAL:System.SerializableAttribute")

// Exception must implement ISerializable and be marked with the SerializableAttribute 
// in order to function well across AppDomain boundaries.


// <Name>Contract assemblies should not define static types</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE IsStatic

// The state of static types is stored per AppDomain and changes to state 
// in one AppDomain are not reflected in others.


// <Name>Contract assemblies should not define reference types other than exception types</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
IsClass AND !DeriveFrom "OPTIONAL:System.Exception"

// The only valid reference type is an Exception type. If this type represents an 
// object you want to marshal by reference, you should define an interface implementing 
// IContract to represent it. If it is intended to be copied (and thus passed by value),
// then you should define a simple serializable value type (struct) to represent it.


// <Name>Contract assemblies should not define delegate types</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE IsDelegate 

// Delegates do no marshal well across AppDomain or Process boundaries 
// and so should not be used in contracts.


// <Name>Contract assemblies should not define exception types</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
DeriveFrom "OPTIONAL:System.Exception"

// If possible, you should use one of the framework defined exceptions rather 
// than defining your own. If you decide to define your own exceptions, 
// they become part of your object model and you will need to define corresponding 
// Exception types in the views and perform the conversion in your adapters.


// <Name>Non-exception types should prefer [Serializable] attribute over ISerializable</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE
  
!DeriveFrom "OPTIONAL:System.Exception" AND
  
Implement "OPTIONAL:System.Runtime.Serialization.ISerializable"

// Implementing ISerializable adds a significant performance overhead compared 
// to standard serialization. If possible, use the built in serialization 
// engine instead of customizing it with ISerializable.


// <Name>All interfaces used in contract assemblies should implement IContract</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE 
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
IsInterface AND
  
!Implement "OPTIONAL:System.AddIn.Contract.IContract"

// All interfaces used in the contract assembly should implement IContract, or 
// another interface that implements IContract. Using an interface that doesn’t 
// implement IContract can cause problems with lifetime management.


// <Name>Contract assemblies should not use types that derive from MarshalByRefObject</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
DeriveFrom "OPTIONAL:System.MarshalByRefObject"

// MarshalByRefObjects can cause problems with lifetime management. If this
// type represents an object you want to marshal by reference, you should define
// an interface implementing IContract to represent it. If it is intended to be
// copied (and thus passed by value), then you should define a simple serializable
// value type (struct) to represent it.


// <Name>Contract assemblies should only use types that are either serializable or are interfaces implementing IContract</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE 
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
(!IsSerializable OR
   
(IsInterface AND !Implement "OPTIONAL:System.AddIn.Contract.IContract"))

// This type is neither serializable nor is it an interface implementing IContract. 
// If it represents an object you want to marshal by reference, you should define an 
// interface implementing IContract to represent it. If it is intended to be copied 
// (and thus passed by value), then you should define a simple serializable value 
// type (struct) to represent it.


// <Name>Contract assemblies should not contain any static members</Name>
WARN IF Count > 0 IN SELECT FIELDS FROM ASSEMBLIES "MyContractAssembly" WHERE IsStatic

// Static state is stored per AppDomain, and changes to state in one 
// AppDomain are not reflected in others.


// <Name>Contract assemblies should not use delegates</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
IsDelegate 

// Delegates do not marshal well across AppDomain or Process boundaries, 
// and so should not be used in contracts.


// <Name>Contract assemblies should not use arbitrary types that implement ICollection<T> or ICollection</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
(Implement "OPTIONAL:System.Collections.Generic.ICollection<T>" OR
   
Implement "OPTIONAL:System.Collections.ICollection")


// <Name>Contract assemblies should not use a type defined simply as System.Object</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
FullNameIs "System.Object"

// Using the System.Object type makes it very difficult to ensure that this
// will version well over time. If this is intended to represent an arbitrary
// contract, type you should use IContract instead.


// <Name>Contract assemblies should not use a type defined simply as System.Type</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
FullNameIs "System.Type"

// Passing a System.Type across the boundary causes the assembly containing it 
// to be loaded in the other domain as well. This greatly lessens the value of 
// the isolation boundary for versioning, security, unloadability, and reliability.


// <Name>Contract assemblies should not use types from the System.Reflection namespace other than AssemblyName</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
FullNameLike "System.Reflection." AND
  
!NameIs "AssemblyName"

// Passing most reflection types across the boundary causes the assembly 
// containing it to be loaded in that domain as well. This greatly lessens 
// the value of the isolation boundary for versioning, security, unloadability, 
// and reliability.


// <Name>Contract assemblies should not use System.AddIn.Contract.Collections.IListContract<T></Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
FullNameIs "OPTIONAL:System.AddIn.Contract.Collections.IListContract<C>"

// System.AddIn contains helper methods for using System.AddIn.Contract.IList<T> and 
// should be preffered over the version in System.AddIn.Contracts.Collections. 
// System.AddIn.Contract.Collections.IList<T> is in place largely for compatibility reasons.


// <Name>Contract assemblies should not use System.AddIn.Contract.Collections.*</Name>
WARN IF Count > 0 IN SELECT NAMESPACES WHERE
  
IsDirectlyUsedBy "MyContractAssembly" AND
  
NameIs "OPTIONAL:System.AddIn.Contract.Collections"

// System.AddIn.Contract.Collections.* should not be used. If you use 
// System.AddIn.Contract.Collections, you will need to write your own adapters for them.


// <Name>Contract assemblies should not declare non-public types</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyContractAssembly" WHERE !IsPublic

// The purpose of a contract assembly is to allow adapters to represent the 
// types across isolation and versioning boundaries. Making a type in this 
// assembly non-public prevents it from being used in this fashion.

 

 

Rules on View assemblies 


// <Name>Activatable add-in types should be marked with the AddInBaseAttribute</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
(IsInterface OR IsAbstract) AND 
  
HasAttribute "OPTIONAL:System.AddIn.Pipeline.AddInBaseAttribute"

// No type in the assembly is marked with the AddInBaseAttribute. For this assembly 
// to be used (without modification) as an AddInView, the types that represent 
// add-ins need to be marked with the AddInBaseAttribute. Even if the type will not 
// be used directly through System.AddIn today, applying this attribute now will 
// make it easier to migrate while still maintaining compatibility.

// <Name>View types should not implement IContract or an interface that implements IContract</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
Implement "OPTIONAL:System.AddIn.Contract.IContract"

// Exposing a contract directly in the view can cause a variety of problems. It makes it
// very difficult to version over time since it strongly binds the consumer of the view 
// to a particular version of the contract assembly.


// <Name>View types should not be marked serializable</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
Implement "OPTIONAL:System.Runtime.Serialization.ISerializable" OR
  
HasAttribute "OPTIONAL:System.SerializableAttribute"

// Types defined in views should never have to directly cross any isolation 
// boundary, and so shouldn’t need to be serializable. You can mark these 
// serializable if you need to store them to disk but you shouldn’t do so in
// order to pass them across boundaries.


// <Name>View types should not derive from MarshalByRefObject</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
DeriveFrom "OPTIONAL:System.MarshalByRefObject"

// Types defined in views should never have to directly cross any isolation
// boundary and so shouldn’t need to be a MarshalByRefObject.


// <Name>View types should not have generic parameters</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
IsGeneric 

// Types with generic parameters are difficult to isolate, since their 
// pipeline components cannot be generated automatically.


// <Name>There should be no exception types defined in the view assembly</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
DeriveFrom "OPTIONAL:System.Exception"

// Defining your own exception makes that exception type part of your object model. 
// This means it will require a different exception type to cross the isolation 
// boundary, and adapting logic on either side to do the conversion. If at all 
// possible, you should use one of the existing framework exception types.


// <Name>View types should not be marked static</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
IsStatic 

// The state of static types is stored per AppDomain, and changes to state 
// in one AppDomain are not reflected in others. Static types in the view 
// are only OK if they provide simple helper functionality, and are stateless.


// <Name>View types should not inherit from FrameworkElement</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
DeriveFrom "OPTIONAL:System.Windows.FrameworkElement" 

// Building adapters for types that inherit from FrameworkElement is difficult, 
// since you have to hand-code them..


// <Name>View types should not inherit from Control</Name>
WARN IF Count > 0 IN SELECT TYPES FROM ASSEMBLIES "MyViewAssembly" WHERE 
  
DeriveFrom "OPTIONAL:System.Windows.Forms.Control" 

// Building adapters for types that inherit from Control is difficult, 
// since you have to hand-code them.


// <Name>Types used in views should not implement IContract</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
Implement "OPTIONAL:System.AddIn.Contract.IContract"

// Exposing a contract directly in the view can cause a variety of problems. 
// It makes it very difficult to version over time since it strongly binds 
// the consumer of the view to a particular version of the contract assembly.


// <Name>Concrete reference types used in views should be serializable</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
IsClass AND
  
!IsSerializable 

// Concrete types are very difficult isolate down the road. They should only 
// be in this assembly if they inherit/implement an abstract base class or interface 
// in this assembly. Other types should reference the abstract base class or 
// interface, and not the concrete helper type.


// <Name>Concrete reference types used in views should belong to a framework assembly</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
IsClass AND
  
!FullNameLike "^System." // The namespace name must begin with System. for framework type.

// Concrete types are very difficult isolate down the road. They should only be in 
// this assembly if they inherit/implement an abstract base class or interface in this 
// assembly. Other types should reference the abstract base class or interface, and not 
// the concrete helper type. You can pass types across the boundary directly only if 
// they are serializable and can be loaded in both sides of an isolation boundary. 
// This is only true for serializable framework types.


// <Name>Events should be of the generic type EventHandler<T></Name>
WARN IF Count > 0 IN SELECT FIELDS FROM ASSEMBLIES "MyViewAssembly" WHERE
  
IsEventDelegateObject AND 
  
!IsOfType "OPTIONAL:System.EventHandler<TEventArgs>"

// There are no tools that automatically generate adapters for events other
// than EventHandler<T> which would make building adapters for this member 
// very difficult.

// <Name>IList<T> should be used instead of arbitrary ICollections</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
(Implement "OPTIONAL:System.Collections.Generic.ICollection<T>" OR
   
Implement "OPTIONAL:System.Collections.ICollection")
  
AND !FullNameIs "OPTIONAL:System.Collections.Generic.IList<T>"

// There are no pre-built contracts or adapters for collections other than IList<T>. 
// Thus, if you use these you will have to write your own adapters.


// <Name>Non-concrete types used in views should be defined in the view assembly</Name>
WARN IF Count > 0 IN SELECT TYPES OUT OF ASSEMBLIES "MyViewAssembly" WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
(IsInterface OR IsAbstract)

// If you are using an interface/abstract base class defined in a different assembly,
// you need to ensure that that type follows the same rules that govern types in this assembly.


// <Name>Serializable types used by views should be defined in a framework assembly</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
IsSerializable AND 
  
!FullNameLike "^System." // The namespace name must begin with System. for framework type.

// If you intend to serialize this type across an isolation boundary, you need to be 
// sure that the type is going to be available to load in both sides of the isolation 
// boundary. This is hard to ensure if it is not a framework type.


// <Name>View members should not have open generic parameters</Name>
WARN IF Count > 0 IN SELECT METHODS FROM ASSEMBLIES "MyViewAssembly" WHERE
  
IsGeneric 

// There are no tools to automatically generate pipelines for open generic types. 
// Thus, if you decide to isolate this type later you will have to do the adapter 
// logic by hand.


// <Name>Views should not use a type defined simply as System.Object</Name>
WARN IF Count > 0 IN SELECT TYPES WHERE
  
IsDirectlyUsedBy "MyViewAssembly" AND
  
FullNameIs "System.Object"

// Defining the type as System.Object makes it very difficult to ensure that
// it versions well over time. If this is intended to represent an arbitrary 
// contract type, you should use a more specific type instead.


// <Name>View members should not be marked static</Name>
WARN IF Count > 0 IN SELECT FIELDS FROM ASSEMBLIES "MyViewAssembly" WHERE
  
IsStatic 

// The state of static types is stored per AppDomain, and changes to state 
// in one AppDomain are not reflected in others. Static types in the view are OK 
// as long as they provide simple helper functionality and are stateless.


42 on 49 rules can readily be
expressed with CQL. Here are the ones that cannot be written with the current
version of CQL :

 

(Contract)Arrays should not contain
types that implement IContract ; (Contract)Arrays should only contain
serializable types ; (View) Arrays should only contain serializable types
 :
While you can constraint some methods to use some array or not with some regex
on method signature and the term [], CQL cannot yet constraint element types of
an array.

(Contract)Contract interfaces should
not implement non-IContract interfaces
 : This rules implies 2 composite
queries, the first one to get contract interfaces and the second one takes the
result of the first one and check if it implements non-IContract interfaces. Composing
queries this way is not yet possible with CQL but it is a major feature for the
future.

(View) Concrete Reference Types Should
Derive From A View Type Defined In The Current Assembly
 : Here also
this rule need 2 composite queries to be implemented.

(Contract)Contract
assemblies should not define events
 ; (View) Events should only be declared on
Interfaces, not AbstractBaseClasses
: While you can check if a class or a
structure define some event by checking fields with the condition
IsEventDelegateObject, CQL cannot so far check if an interface define some
events.

 
This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.