You are not logged in.
Thanks for the explanation, that clears it up. ![]()
It looks like there is a bug with RecordLoadJSON.
program JsonBug;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
SynCommons;
type
TTestRec = packed record
Id: Integer;
Name: string;
end;
const
__TTestRec = 'Id: Integer; Name: string;';
var
Rec: TTestRec;
Json1, Json2: RawUTF8;
begin
try
Rec.Id := 55;
Rec.Name := 'This is the name.';
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TTestRec), __TTestRec);
Json1 := RecordSaveJSON(Rec, TypeInfo(TTestRec));
Rec := Default(TTestRec);
RecordLoadJSON(Rec, @Json1[1], TypeInfo(TTestRec));
Json2 := RecordSaveJSON(Rec, TypeInfo(TTestRec));
Assert(SameTextU(Json1, Json2));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.The following assertion fails, because RecordSaveJSON modified the contents of the Json1 parameter:
Assert(SameTextU(Json1, Json2));
Json1 BEFORE call to RecordLoadJSON:
'{"Id":55,"Name":"This is the name."}' Json1 AFTER call to RecordLoadJSON:
'{"Id'#0#0'55'#0'"Name'#0#0'"This is the name.'#0#0 Right now, the text based record serialization is almost perfect for my needs except for these cases. I really don't want to release my web service API with integers instead of named enumerations, as changing it later will cause customer confusion/problems (and it's not very readable). I could revert back to using classes with the interface based services, but using records for DTO operations (as you suggested in another response) is certainly preferable. Now that I have seen the record based functionality, I don't want to go back to using classes for my interface based services. ![]()
While Mormot is extremely powerful, fast, and obviously well thought out, IMHO it could benefit even more by introducing support for the newer RTTI features. This would probably open doors for some easier coding styles in Mormot (e.g., generics, property and method attributes, etc.) and would probably increase the options for JSON serialization with classes too. For those who have existing projects in versions < Delphi 2010, I can certainly understand that existing code should remain stable, however for new features I think it makes sense to consider support for newer language features.
Perhaps to make code maintenance easier, new units could be created that will encapsulate the new features without disrupting the existing code base and without littering the code with $IFDEFS, but I'm sure you have ideas on how to best address that.
In any case, taking one step at a time, I would really like to see support for newer RTTI starting with text based record serialization. How can I help to move this forward?
Thanks!
The text based definition for record serialization is a very powerful and cool feature, however most of my DTOs use enumerated types.
When I tried to define a record with an enumerated type, I got a failure when trying to register it:
ESynException with message TJSONCustomParserCustom.Create(unknown "TCONTACTTYPE" type)
type
TContactType = (ctIndividual, ctCompany);
TContactData = array of Integer;
TContact = packed record
Name: string;
Address: string;
ContactType: TContactType;
Data: TContactData;
end;
const
__TContact = 'Name: string; Address: string; ContactType: TContactType; Data: TContactData;';
procedure RegisterContact;
begin
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TContact), __TContact);
end;I also noticed that 'TContactData' won't work either, because it's an unknown type to the parser (even though it's just a redefined type of a dynamic integer array).
Is there a way to define custom types for a text based record definition without having to resort to manually creating a custom serializer via TTextWriter.RegisterCustomJSONSerializer for the entire record?
IMHO, it would be very useful to be able to do this to avoid having to convert enums to/from integers and to define dynamic arrays with meaningful names.
For example, maybe something like:
TTextWriter.RegisterCustomJSONSerializerType(TypeInfo(TContactType), 'TContactType');
TTextWriter.RegisterCustomJSONSerializerType(TypeInfo(TContactData), 'TContactData');This would prevent having to write a custom reader/writer for every record that uses these types, so they could be reused in other record definitions.
Maybe I could modify the code to implement something like this with some guidance?
Thanks
I've modified the calculator demo to include a method that takes an object as a parameter:
type
TIntArray = array of Integer;
TAddress = class(TPersistentWithCustomCreate)
private
FStreet: string;
FCity: string;
FZipCode: string;
FCountry: string;
FData: TIntArray;
public
constructor Create; override;
destructor Destroy; override;
published
property Street: string read FStreet write FStreet;
property City: string read FCity write FCity;
property ZipCode: string read FZipCode write FZipCode;
property Country: string read FCountry write FCountry;
property Data: TIntArray read FData write FData;
end;
TContact = class(TPersistentWithCustomCreate)
private
FId: Integer;
FFirstName: string;
FLastName: string;
FAddress: TAddress;
public
constructor Create; override;
destructor Destroy; override;
published
property Id: Integer read FId write FId;
property FirstName: string read FFirstName write FFirstName;
property LastName: string read FLastName write FLastName;
property Address: TAddress read FAddress write FAddress;
end;
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
function Add(n1,n2: integer): integer;
function GetContact(Id: string; var Contact: TContact): Boolean;
end;
implementation
{ TAddress }
constructor TAddress.Create;
begin
inherited;
SetLength(FData, 10);
end;
destructor TAddress.Destroy;
begin
inherited;
end;
{ TContact }
constructor TContact.Create;
begin
inherited;
FAddress := TAddress.Create;
end;
destructor TContact.Destroy;
begin
FAddress.Free;
inherited;
end;
end.In the Project 14 Client, I've added the following code:
if Client.Services['Calculator'].Get(I) then
begin
lblResult.Caption := IntToStr(I.Add(a,b));
Contact := TContact.Create;
I.GetContact('100', Contact);
end;And in the Project 14 HTTP Server:
TServiceCalculator = class(TInterfacedObject, ICalculator)
private
public
function Add(n1,n2: integer): integer;
function GetContact(Id: string; var Contact: TContact): Boolean;
end;
function TServiceCalculator.GetContact(Id: string; var Contact: TContact): Boolean;
begin
Contact := TContact.Create;
try
Contact.FirstName := 'Joe';
Contact.LastName := 'Smith';
Contact.Address.Street := '1234 Main St.';
Result := True;
finally
end;
end;However, the call to
I.GetContact('100', Contact);in the Client (using HTTP / TCP-IP) results in the following error:
EInterfaceFactoryException:
Invalid fake ICalculator.GetContact interface call: : {
"ErrorCode":400,
"ErrorText":"Bad Request"
}
I must be doing something obviously wrong, but I can't see what...
BTW, I'm using the nightly build of 1 week ago.
Thanks in advance.
Thanks, I needed to inherit from TPersistentWithCustomCreate *AND* specify the override directive in TContact.Create(). ![]()
However, regarding the documentation, perhaps I don't have the latest?
Synopse mORMot Framework
Software Architecture Design 1.18
Date: November 12, 2013
The only places I see TPersistentWithCustomCreate referenced are pages: 801, 803, 902
There is no mention of this in the usage section in "10.1.6. TObject serialization"
Here is a complete test program, but it still fails even after using M+ and descending from TPersistent. I'm using Delphi XE 2 Update 4 Hotfix 1, along with the nightly Mormot build from April 20, 2014.
{$M+}
program JsonTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, Classes, SynCommons, Mormot;
type
TAddress = class(TPersistent)
private
FStreet: string;
FCity: string;
FZipCode: string;
FCountry: string;
published
property Street: string read FStreet write FStreet;
property City: string read FCity write FCity;
property ZipCode: string read FZipCode write FZipCode;
property Country: string read FCountry write FCountry;
end;
TContact = class(TPersistent)
private
FId: Integer;
FFirstName: string;
FLastName: string;
FAddress: TAddress;
public
constructor Create;
destructor Destroy; override;
published
property Id: Integer read FId write FId;
property FirstName: string read FFirstName write FFirstName;
property LastName: string read FLastName write FLastName;
property Address: TAddress read FAddress write FAddress;
end;
{ TContact }
constructor TContact.Create;
begin
inherited;
FAddress := TAddress.Create;
end;
destructor TContact.Destroy;
begin
FAddress.Free;
inherited;
end;
var
C1, C2: TContact;
Json: RawUTF8;
JsonPtr: PUTF8Char;
IsValidJson: Boolean;
Ch: Char;
begin
try
TJSONSerializer.RegisterClassForJSON([TContact, TAddress]);
// C1
C1 := TContact.Create;
C1.FirstName := 'Joe';
C1.LastName := 'Smith';
C1.Address.Street := '1234 Main St.';
Json := ObjectToJson(C1, [woStoreClassName, woHumanReadable]);
Writeln('=== C1 ===');
Writeln(Json);
C1.Free;
Writeln;
Writeln('Press ENTER to continue...');
Readln;
// C2
JsonPtr := @Json[1];
C2 := TContact(JSONToNewObject(JsonPtr, IsValidJson, [j2oIgnoreUnknownProperty]));
Assert(IsValidJson);
Writeln('=== C2 ===');
Writeln(C2.FirstName);
Writeln(C2.LastName);
Writeln(C2.Address.Street);
C2.Free;
Writeln;
Writeln('Press ENTER to continue...');
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.I'm sure this is simple, but I can't seem to get this to work:
type
TAddress = class
private
FStreet: string;
FCity: string;
FZipCode: string;
FCountry: string;
published
property Street: string read FStreet write FStreet;
property City: string read FCity write FCity;
property ZipCode: string read FZipCode write FZipCode;
property Country: string read FCountry write FCountry;
end;
TContact = class
private
FId: Integer;
FFirstName: string;
FLastLame: string;
FAddress: TAddress;
FLastName: string;
public
constructor Create;
destructor Destroy; override;
published
property Id: Integer read FId write FId;
property FirstName: string read FFirstName write FFirstName;
property LastName: string read FLastName write FLastName;
property Address: TAddress read FAddress write FAddress;
end;
{ TContact }
constructor TContact.Create;
begin
inherited;
FAddress := TAddress.Create;
end;
destructor TContact.Destroy;
begin
FAddress.Free;
inherited;
end;
procedure TForm1.btnTestClick(Sender: TObject);
var
C1, C2: TContact;
Json: RawUTF8;
JsonPtr: PUTF8Char;
IsValidJson: Boolean;
begin
TJSONSerializer.RegisterClassForJSON([TContact, TAddress]);
// C1
C1 := TContact.Create;
C1.FirstName := 'Joe';
Json := ObjectToJson(C1, [woStoreClassName, woHumanReadable]);
C1.Free;
// C2
Memo.Lines.Add(Json);
JsonPtr := @Json[1];
C2 := TContact(JSONToNewObject(JsonPtr, IsValidJson, [j2oIgnoreUnknownProperty]));
Assert(IsValidJson); // <-------- ASSERTION FAILS HERE
C2.Free;
end;The assertion in the test function above fails, but I don't see why, considering I've registered both classes.
If I comment out the 'Address' property in TContact, everything works fine.
Thanks
ab,
I'm having success with the help you provided in http://synopse.info/forum/viewtopic.php?id=1555 for AJAX calls from Javascript in the browser, so thanks again.
Now, I'm trying to understand the authentication process and get it to work. I've read about authentication in the v1.18 documentation, but I'm still not clear on how to add credentials in the server for users and user group membership (I'll need to update the server periodically when new or existing user credentials are created/updated in my existing database). I've looked at the authentication classes and forum, but do you have any other examples (perhaps I overlooked them or missed key forum replies) on using and extending/customizing them?
I'm also not completely clear on the difference between the two functions below (Project 14) and what is different between 'Weak' and 'regular' HTTP /TCP-IP, except that one must be using a weaker authenticaion mechanism.:
'Weak HTTP / TCP-IP'
TSQLRestServerAuthenticationNone.ClientSetUser(Client, 'User');and
'HTTP / TCP-IP'
Client.SetUser('User', 'synopse');On an unrelated note, I'm wondering if the following page should reference v1.18 instead of v1.17 of the documentation, because it wasn't clear at first that there was an update:
http://synopse.info/fossil/wiki?name=Do … umentation
This might help others like me, who are new to the framework. Also, is there a nightly build of the latest updated PDF documentation too (or updated wiki version)?
I've just committed some enhancements.
TSQLRestRoutingREST will now recognize several URI schemes.
See http://synopse.info/fossil/info/7b4dd9f2d3
and blog article http://blog.synopse.info/post/2014/01/0 … d-servicesThe new root/Calculator/Add?n1=1&n2=2 alternative could be pretty convenient to be consumed from your REST clients.
Hope it helps.
Yes, that definitely helps
. Thank you very much for the effort to implement this functionality, it is greatly appreciated!
I have further questions:
4. For interface-based services, errors are usually triggered by raising an exception.
How is this exception returned to the caller? Is there a way to generate a specific exception to return the error to the caller in a JSON format as is provided with Ctxt.Error()?
2. Interface-based services URL routing is defined in a KISS principle in mORMot, i.e. fixed to some schemes, which are defined by classes.
For instance, TSQLRestRoutingREST defines /root/interfacename.methodname[/ClientDrivenID].
Or TSQLRestRoutingJSON_RPC defines /root/interfacename with the method name within the JSON input.
You can define your own class, inheriting from TSQLRestServerURIContext. But it won't be direct.
If you want custom routing, use method-based services, which gives access to all execution context.
It looks like both of these classes don't support standard REST URL behavior because they require the method names to be defined in either the body or the URL with the 'dot' notation. Unfortunately, this won't work for me as I'm creating an API for other developers to access our system and I want the URLs to conform to the generally accepted standard REST URL format.
If I override TSQLRestServerURIContext, how will that help me? I'm not clear on what you mean by it won't be direct, unless you mean that the mapping between this class and the interface implementation won't be automatic. If so, is there a way to manually create the mapping between the URL and the methods that get called (i.e., is there a way to gain control at the point of method dispatch so I can route the URL to the correct method?).
Ideally, I'd like to map specific HTML verbs to specific method signatures and handle the parameters via the URL, like GET_Customer, POST_Customer, etc. or whatever convention I choose (I assume this is already being done somewhere in the code anyway). If I can override TSQLRestServerURIContext to create custom instance behavior, that would be great. If so, any example code (and/or which methods to override, etc.) would be greatly appreciated.
Perhaps the only way to do this is by using method based services as you mentioned, but I'd prefer to use the interface based architecture if at all possible. ![]()
5. sicPerThread is about the class implementing the interface lifetime: one class instance will be available per running thread.
Since mORMot uses a thread pool internally, you should override TSQLRestServer.BeginCurrentThread and TSQLRestServer.EndCurrentThread methods to initialize your thread resources (e.g. initialize external database access).
Just so I understand, sicPerThread means that on the server side, an instance of my service class that implements the methods will be created per thread in the pool. If this is the case, then can I simply create a class variable within the service class that references my database connection for each instance. If so, does the service class instance lifetime get managed in the BeginCurrentThread/EndCurrentThread methods? I obviously don't want the threads to be terminated until my server application is shutdown, so I want to be sure that I understand the lifetime management of the instance. Also, are there any limits with the thread pool or does it expand as needed? Can I specify how many threads are initially in the pool?
Thanks again for the help!
I'm new to the mORMot framework and I'm interested in using it to develop web services that can be called from Javascript applications.
I'm not interested in ORM at this point, as I only need to leverage existing code to provide the web services (similar to what C# provides).
I modified the Project 14 calculator demo interface to include an extra function as a test, like so:
function LookupCustomer(ACustomerNumber: string): RawUTF8;which is implemented like this:
function TServiceCalculator.LookupCustomer(ACustomerNumber: string): RawUTF8;
var
Customer: TCustomer;
begin
Customer := TCustomer.Create;
try
Customer.FirstName := 'First';
Customer.LastName := 'Last';
Result := ObjectToJSON(Customer);
finally
Customer.Free;
end;
end;When called from the client application, the JSON is returned as expected. However, I have the following questions:
When calling the service from the client, these following tests are performed:
if not Client.ServerTimeStampSynchronize then begin
ShowMessage(UTF8ToString(Client.LastErrorMessage));
exit;
end;
case ComboProtocol.ItemIndex of
2: TSQLRestServerAuthenticationNone.[b]ClientSetUser(Client,'User')[/b];
else
Client.SetUser('User','synopse');
end;If, for example, I need to call the service from a Javascript application, how would I perform these tests from Javascript? Alternatively, how to I exclude them if I don't need them?
How can I route specific URLs to a specific method? (e.g., if I perform a GET /root/v1/customers/100, how can I route that to my 'LookupCustomer' method call in the server?)
When I route a URL, how can I determine whether it was a GET, POST, PUT or DELETE so I know what behavior to implement on the server side?
If I need to return an error, the documentation states that for method based services I can use Ctxt.Error(). Is there something similar for interface based services (so I can return the TCustomer as a function result instead of RawUTF8 and still be able to return meaningful JSON errors like Ctxt.Error('Customer does not exist', [HTML_NOTFOUND]')?
I need every call to occur in a new thread, so I assume that sicPerThread is the option I need to use. However, does mORMot support a thread pool? If so, how can I associate a custom object instance with each thread, which needs to be pre-allocated with database connections so it can be reused on subsequent service calls on that thread. I saw the TServiceRunningContext class, but I'm not sure if I could use it for this purpose.
Thanks in advance for any help provided!