"Open framework that encourages
convention
over
configuration
, using C# code,
not
XML files
, to model at the
right level of abstraction
and achieve
deadlines
.
...but also has a full Linq provider, and syncs the schema for you!"
Search
Back
History
Server Contracts
== Introduction == Signum.Service is the folder in Signum.Entities that contains the [http://msdn.microsoft.com/en-us/magazine/cc163647.aspx|WCFService Contracts] of the server required by Signum.Windows. {s:SF2| 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 [EntityControls|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 {s:SF2| ILoginServer and IExcelReportServer has been moved out to Signum.Extensions (not public yet).} All of these interfaces share a common philosophy. They use [http://www.pluralsight.com/community/blogs/aaron/archive/2006/04/21/22284.aspx|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 [TutorialServer|Tutorial: Writing the Server Code] Let's focus on all these interfaces. [anchor|#IBaseServer] == 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 [EntityControls|Entity Controls] and [EntityWindows|Entity Windows]. <code lang="cs"> [¬ServiceContract(SessionMode = ¬SessionMode.Required)] public interface ¬IBaseServer { //Is used when you need to navigate to an entity, or when you need to retrieve a Lazy. [¬OperationContract, ¬NetDataContract] ¬IdentifiableEntity Retrieve(¬Type type, int id); //Used by [EntityWindows|NormalWindow] to save an entity. [¬OperationContract, ¬NetDataContract] ¬IdentifiableEntity Save(¬IdentifiableEntity entidad); //Retrives all the entities of a given type. Expensive operation, used by AdminWindows for administrating simple types with few instances on the database. [¬OperationContract, ¬NetDataContract] ¬List<¬IdentifiableEntity> RetrieveAll(¬Type type); //Saves all the entities in a list. Used by AdminWindows when clicking save. [¬OperationContract, ¬NetDataContract] ¬List<¬IdentifiableEntity> SaveList(¬List<¬IdentifiableEntity> list); //Retrieves the lazy version of all the entities of a given type, used by EntityCombo. When the EntityCombo has Implementations, types is filled. [¬OperationContract, ¬NetDataContract] ¬List<¬Lite> RetrieveAllLite(¬Type liteType, ¬Type[] types); //Retrieves the lazy version of the top ''count'' entities of a given type which ToStr looks like the subString provided, used by EntityLine when auto-complete is enabled. //When the EntityList has Implementations, types parameter is set. [¬OperationContract, ¬NetDataContract] ¬List<¬Lazy> FindLiteLike(¬Type liteType, ¬Type[] types, string subString, int count); //Given the type of an IdentitiableEntity, and path of MemberInfo (presumably PropertyInfo) returns the Type[] //with the different implementations of the ImplementedBy reference the path points to, otherwise null. [¬OperationContract, ¬NetDataContract] ¬Type[] FindImplementations(¬Type type, ¬MemberInfo[] implementations); //Return the list of all the server types (entity controls use it to avoid calling FindImplementations). New in SF 2.0 [¬OperationContract, ¬NetDataContract] ¬Dictionary<¬Type, ¬TypeDN> ServerTypes(); //Returns DateTime.Now in the server. Handy for custom client logic. New in SF 2.0 [¬OperationContract, ¬NetDataContract] ¬DateTime ServerNow(); //Returns the registered types in the server that are assignable to 'type'. Handy for custom client logic. New in SF 2.0 [¬OperationContract, ¬NetDataContract] ¬List<¬Lite<¬TypeDN>> TypesAssignableFrom(¬Type type); } </code> Implementing these methods is trivial, most of them have an equivalent with the same name in [Database|Database class]. All but FindLazyLike and RetrieveAllLazy have an implementation on DynamicQueryUtils, and you can find FindImplementations on your current [Schema]. {s:SF2| 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: <code lang="cs"> [¬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)); } (...) } </code> [anchor|#IQueryServer] == IQueryServer == IQueryServer interface contains the main methods you need to implement in order to enable [Search|search windows] in the client application. <code lang="cs"> [¬ServiceContract] public interface ¬IQueryServer { //returns the QueryDescription (columns and filter names and types). [¬OperationContract, ¬NetDataContract] ¬QueryDescription GetQueryDescription(object queryName); //returns the QueryResult (actual data) given a queryName, some filters, and an optional limit to reduce the number of results. [¬OperationContract, ¬NetDataContract] ¬QueryResult GetQueryResult(object queryName, ¬List<¬Filter> filters, int? limit); //Used by CountSearchControl [¬OperationContract, ¬NetDataContract] int GetQueryCount(object queryName, List<Filter> filters); //Handy to retrieve an associated entity using a view. Used bu FindUnique [¬OperationContract, ¬NetDataContract] Lite GetUniqueEntity(object queryName, List<Filter> filters, UniqueType uniqueType); //Return the names of all the available queries in the server. [¬OperationContract, ¬NetDataContract] ¬List<object> GetQueryNames(); } </code> {s:Note| 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: <code lang="cs"> public static class ¬Starter { //Call this method when start application in Global.asax, after Connection.Default is set-up 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 }; //Add as many queries as you want ¬ConnectionScope.Default = new ¬Connection(connectionString, sb.Schema, dqm); } } </code> {s:Note| 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: <code lang="cs"> 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()); } (...) </code> 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.
Signum Framework Site by
Signum Software
is licensed under a
Creative Commons Attribution 3.0 License
.
Powered by
ScrewTurn Wiki
version 2.0.35.