Introduction
Signum.Service is the folder in Signum.Entities that contains the
WCFService Contracts of the server required by Signum.Windows.
In SF 2.0 we have merged Signum Services into Signum Entities to simplify the architecture. This contracts are not needed for Web Applications thought.
The great benefit of Signum.Windows is that it provides
controls that allow working with entities in a generic way, reducing dramatically the amount of code you have to write. For this to work the server has to provide a generic way of saving and retrieving
any entity, this way is defined in IBaseServer interface.
There are other useful contracts:
- IQueryServer: necessary for Search.
- IAlertsServer: for Alerts widget support.
- INotesServer: for Notes widget support
ILoginServer and IExcelReportServer has been moved out to Signum.Extensions (not public yet).
All of these interfaces share a common philosophy. They use
NetDataContractAttribute.
NetDataContractAttribute is like WCF's Cinderella. A hidden gem completely despised by Microsoft. It allows SharedType communication between client and server. That means that the same types are going to be used on both sides of the channel, using old binary or xml soap serialization, so the assembly where these entities are defined has to be available on both sides as well.
No need to generate a proxy class for your services, neither for the entities being sent, it behaves like old Remoting.
Microsoft is trying to discourage this approach because it tightly couples client and server: they have to use the same technology (.Net Framework) and have to evolve together. They are not wrong, but we were already prepared for that:
- We are using .Net on both sides
- Almost any change in an Entitiy will affect client and server (i.e. Validation). Very often it will impact Database also (adding field) and the UI (adding an imput box). So they were indirectly coupled already.
If you are looking for a practical example of how to write the Server, look at
Tutorial: Writing the Server CodeLet's focus on all these interfaces.
IBaseServer
IBaseServes is the only mandatory interface you need to implement in the server in order to use Signum.Windows on the client side. It provides general purpose operations for Saving and Retrieving mainly used by
Entity Controls and
Entity Windows.
[ServiceContract(SessionMode = SessionMode.Required)]
public interface IBaseServer
{
[OperationContract, NetDataContract]
IdentifiableEntity Retrieve(Type type, int id);
[OperationContract, NetDataContract]
IdentifiableEntity Save(IdentifiableEntity entidad);
[OperationContract, NetDataContract]
List<IdentifiableEntity> RetrieveAll(Type type);
[OperationContract, NetDataContract]
List<IdentifiableEntity> SaveList(List<IdentifiableEntity> list);
[OperationContract, NetDataContract]
List<Lite> RetrieveAllLite(Type liteType, Type[] types);
[OperationContract, NetDataContract]
List<Lazy> FindLiteLike(Type liteType, Type[] types, string subString, int count);
[OperationContract, NetDataContract]
Type[] FindImplementations(Type type, MemberInfo[] implementations);
[OperationContract, NetDataContract]
Dictionary<Type, TypeDN> ServerTypes();
[OperationContract, NetDataContract]
DateTime ServerNow();
[OperationContract, NetDataContract]
List<Lite<TypeDN>> TypesAssignableFrom(Type type);
}
Implementing these methods is trivial, most of them have an equivalent with the same name in
Database class. All but FindLazyLike and RetrieveAllLazy have an implementation on DynamicQueryUtils, and you can find FindImplementations on your current
Schema.
In SF 2.0 you can find a ServiceBasic class that already implements IBaseServer, IQueryServer, INotesServer and IAlertsServer
It's up to you to add any Security, error logging or trace performance on the Service implementation.
Trick: If you are planning to repeat this additional authorization/logging/tracing code on any Service operation, instead you can refactor the code using lambdas this way:
[ServiceContract(SessionMode = SessionMode.Required)]
public interface IMyCustomServer: IBaseServer
{
}
public class ServerBugs : IMyCustomServer
{
#region Internal Methods
protected T Return<T>(MethodBase mi, Func<T> function)
{
return Return(mi, mi.Name, function);
}
protected virtual T Return<T>(MethodBase mi, string description, Func<T> function)
{
try
{
return function();
}
catch (Exception e)
{
throw new FaultException(e.Message);
}
}
protected void Execute(MethodBase mi, Action action)
{
Return(mi, mi.Name, () => { action(); return true; });
}
protected void Execute(MethodBase mi, string description, Action action)
{
Return(mi, description, () => { action(); return true; });
}
#endregion
public IdentifiableEntity Retrieve(Type type, int id)
{
return Return(MethodInfo.GetCurrentMethod(), "Retrieve {0}".Formato(type.Name),
() => Database.Retrieve(type, id));
}
public IdentifiableEntity Save(IdentifiableEntity entidad)
{
return Return(MethodInfo.GetCurrentMethod(), "Save {0}".Formato(entidad.GetType()),
() => { Database.Save(entidad); return entidad; });
}
public List<Lite> RetrieveAllLite(Type liteType, Type[] types)
{
return Return(MethodInfo.GetCurrentMethod(), "RetrieveAllLite {0}".Formato(liteType),
() => AutoCompleteUtils.RetriveAllLite(liteType, types));
}
(...)
}
IQueryServer
IQueryServer interface contains the main methods you need to implement in order to enable
search windows in the client application.
[ServiceContract]
public interface IQueryServer
{
[OperationContract, NetDataContract]
QueryDescription GetQueryDescription(object queryName);
[OperationContract, NetDataContract]
QueryResult GetQueryResult(object queryName, List<Filter> filters, int? limit);
[OperationContract, NetDataContract]
int GetQueryCount(object queryName, List<Filter> filters);
[OperationContract, NetDataContract]
Lite GetUniqueEntity(object queryName, List<Filter> filters, UniqueType uniqueType);
[OperationContract, NetDataContract]
List<object> GetQueryNames();
}
As you see, object (not string) is used for queryName. This is very convenient since we use the System.Type to identify the default view used for a type, and you can define a enum type, shared by client and server, for all the other special views so you don't have to rely on nasty string to identify views.
DynamicQueryManager class is designed to be the back-end implementation of these methods. Usually we fill it in your starter method like this:
public static class Starter
{
public static void Start()
{
SchemaBuilder sb = (...)
DynamicQueryManager dqm = new DynamicQueryManager();
dqm[typeof(ProjectDN)] = from p in Database.Query<ProjectDN>()
select new
{
Entity = p.ToLazy(),
p.Name,
p.IsInternal
};
ConnectionScope.Default = new Connection(connectionString, sb.Schema, dqm);
}
}
As you see, DynamicQueryManager is meant to be implemented with Linq to Signum queries. It is possible to make an implementation that works with plain sql views but we discourage it so we don't provide it: While you stay writing Linq to Signum queries you get refactoring and compile time checking of your views, making it easier for your application to grow and evolve. Also you have Lite objects support.
Then, implementing the interface is as easy as:
public QueryDescription GetQueryDescription(object queryName)
{
return Return(MethodInfo.GetCurrentMethod(),
() => DynamicQueryManager.Current.QueryDescription(queryName));
}
public QueryResult GetQueryResult(object queryName, List<Filter> filters, int? limit)
{
return Return(MethodInfo.GetCurrentMethod(),
() => DynamicQueryManager.Current.DynamicQueryManager.ExecuteQuery(queryName, filters, limit));
}
public List<object> GetQueryNames()
{
return Return(MethodInfo.GetCurrentMethod(),
() => DynamicQueryManager.Current.DynamicQueryManager.GetQueryNames());
}
(...)
It provides two methods, one for logging-in that is expected to throw an exception if no user is found with this username and password, and another for logging-out that closes the WCF session.
Actually there's no control in Signum.Windows that requires this interface, so you are free to implement your log-in/out strategy in a different way.