Remember
part 1 of Søren Trudsø Mahon's article of DXP?
Who's up for part two? Here goes:
In part 1 we saw how to use the API, in this post we’ll have a look at the implementation
behind this.
The interface of the API
To do this, we've created a fluent API for searching ADAM objects: inspired
by this series of blog posts:
http://www.lostechies.com/blogs/gabrielschenker/archive/2010/01/03/fluent-silverlight-fluent-api-and-inheritance.aspx
http://www.lostechies.com/blogs/gabrielschenker/archive/2010/01/03/fluent-silverlight-fluent-api-and-inheritance.aspx
To start off with we created an extension method (new feature in c# / .net 3.5)
on the Application object like this:
| C# |
1
2
3
4
5
6
7
8
9
10
|
namespace Adam.Core
{
public static class ApplicationSearchExtensions
{
public static AdamSearch Search(this Application @this)
{
return new AdamSearch(@this);
}
}
}
|
We have put this inside of the Adam.Core namespace, this way we don’t have
to remember to include our namespace wherever we want to call Search on the
Adam.Core.Application object. (We’ll probably end up putting the AdamSearch
behind an interface, and having a factory for creating the AdamSearch, enabling
us to mock our search). The AdamSearch class is our entry into searching,
in here we define the root types you can search on:
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class AdamSearch
{
private readonly Application application;
public AdamSearch(Application application)
{
this.application = application;
}
public IUserSearchRoot User
{
get { return new UserSearch(this.application); }
}
…
}
|
The class xxxSearch represents the adam object we are searching for, this is hidden
behind an interface IxxxSearchRoot. The keywords are defined in interface, IUserSearchKeywords
and ISearchKeywords:

| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public interface IUserSearchKeywords<TSearchParent, TItem, TCollection>
: ISearchKeywords<TSearchParent, TItem, TCollection>
{
TSearchParent Name(string name);
}
public interface ISearchKeywords<TSearchParent, TItem, TCollection>
{
IUserSearchKeywords<TSearchParent, TItem, TCollection> CreatedBy
{
get;
}
TSearchParent Id(Guid id);
TSearchParent Id(Operator @operator, Guid guid);
TSearchParent Id(IEnumerable<Guid> ids);
TSearchParent CreatedOn(Operator @operator, DateTime time);
}
|
We have methods and properties which correspond to the keywords in the Adam Help.
Properties are links to other objects. For example:
CreatedBy.Name("administrator") corresponds to createdby.name
= administrator
Methods are actual criteria. For example:
Id(user.Id) correspondents to id = 'C899EFCB-440E-487F-A007-09A9043E678F'
Pretty simple, but showing that our API correspondents to the Adam search.
The TSearchParent generic parameter is used as a placeholder for the kind
of search we are performing. That is, if we are performing a RecordSearch
this will refer back to an IRecordSearch where the keywords for searching
for records are defined. This enabled me to chain the keywords like this:
| C# |
1
2
3
|
Application.Search()
.User.CreatedBy.Name("dxp-stm")
.CreatedOn(Operator.LessThan, DateTime.Now);
|
Implementing the API:
We've created a “big” base class where all of the main loading functions and
common keywords are implemented:

This class has the responsibility of:
- All the search operators that are common to all Adam objects (Id, CreatedBy,
CreatedOn...)
- Executing queries (delegated to the SearchExecutor).
- Collection all search expressions (delegated to SearchExpressionProvider)
- Executing queries when we have no search expression yet (Load(Guid Id) and
Load(IEnumerable<Guid> ids).
Interesting bits are the implementation of keywords which lead to adding a search
criteria to our search expression. But also how we refer to other ADAM objects.
Implementation of keywords in SearchBase.cs:
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public TSearchParent Id(Guid id)
{
return AddEq("Id", id);
}
protected TSearchParent AddEq(string path, object value)
{
return Add(path, Operator.Equal, value);
}
protected TSearchParent Add(string path, Operator @operator, object value)
{
ExpressionProvider.Add(ParentPath + path, @operator, value);
return this.SearchParent;
}
|
Implementation of keywords in SearchBase.cs -> SearchExpressionProvider:
| C# |
1
2
3
4
5
6
7
8
9
10
11
|
public void Add(string expression, Operator @operator, object value)
{
expression = string.Format("{0} {1} ?", expression,
GetOperator(@operator));
Add(expression, value);
}
public void Add(string expression, params object[] parameters)
{
this.expressions.Add(new SearchExpression(expression, parameters));
}
|
Implementation of “references” to other objects go like this:
| C# |
1
2
3
4
5
6
7
8
9
|
public IUserSearchKeywords<TSearchParent, TItem, TCollection> CreatedBy
{
get
{
return new UserSearch<TSearchParent, TItem, TCollection>(
Application, ExpressionProvider, SearchParent,
ParentPath, "CreatedBy");
}
}
|
And specifically for User we have two classes called UserSearch and
UserSearch<TSearchParent,TItem, TCollection>:

The source for these are trivial, and the keywords are like in SearchBase,
so it’s mostly constructors, but a couple of things to note about UserSearch
is that this closes our chain of parents and inside the constructor it “closes”
our chain of parents by setting this to parent. (And this is maybe what I hate most
about our current implementation, I would like to put this responsibiliy within
my base class, but I just can get my head around that).
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class UserSearch
: UserSearch<IUserSearch, User, UserCollection>,
IUserSearchRoot, IUserSearch
{
public UserSearch(Application app)
: base(app)
{
this.SearchParent = this;
}
}
public class UserSearch<TSearchParent, TItem, TCollection>
: SearchBase<TSearchParent, TItem, TCollection>,
IUserSearchKeywords<TSearchParent, TItem, TCollection>,
IUserItemLoader<TItem>,
IUserSearchLoader<TItem, TCollection>
where TItem : ExtendedItemBase
where TCollection : ExtendedItemBaseCollection
{
public UserSearch(Application app)
: base(app)
{
}
public UserSearch(Application app, SearchExpressionProvider expressionProvider, TSearchParent searchParent, string parentPath, string path)
: base(app, expressionProvider, searchParent, parentPath, path)
{
}
#region Implementation of IUserSearchKeywords<TSearchParent,TItem,TCollection>
public TSearchParent Name(string name)
{
return AddEq("Name", name);
}
#endregion
}
|
And then we've got a lot of interfaces:

Basically we have interfaces for doing
Load(Guid id) and Load(IList<Guid> ids):
IUserItemLoader<TItem>: IExtendedItemLoader<TItem>
- A
Load() using searchexpressions:
IUserItemLoader<TItem>: IExtendedItemLoader<TItem>
- Keywords:
IUserSearchKeywords<TSearchParent, TItem, TCollection>: ISearchKeywords<TSearchParent,
TItem, TCollection>
These interfaces are all implemented by UserSearch<TSearchParent, TItem, TCollection>,
but we never expose that class in our API. That way we can control which operations
are available to the consumer at anytime.
Conclusion
That’s where we are now, we got the foundation, but we still need to add keywords,
but adding keywords is pretty easy, add it to the IXxxKeywords interface and then
implement it in the class that “complaints”, so it’s something we will add along
the way and whenever Adam adds new keywords.
And also we’ve created a Resharper template for creating a new Adam object for searching
because the object structure and the interfaces needed are very similar, they only
differ on the adam object name.