How to hide standard interfaces from the default page WebService - web-services

Some time ago I've been thinking about how to hide from default page of the web service the IAppServer and IAppServerSOAP interfaces and that appear by default. I know that my Webservice interface, has these interfaces as ancestors, but I believe pointless "see" these interfaces on the default page, as the client programs do not use them directly.
Is there any way to hide these interfaces and just keep our interface and others that were created?

You should be able to alter the WSDL returned by the service. I think there's a WSDL control, where you can override the WSDL response to either edit it, or substitute whatever you want.
Specifically, add a TWSDLHTMLPublish component to your WebModule form. Use the OnBeforePublishingWSDL to write your own WSDL, like this:
procedure TWebModule2.WSDLHTMLPublish1BeforePublishingWSDL(
const IntfName: WideString; var WSDL: WideString; var Handled: Boolean);
begin
WSDL := '<foo>bar</foo>';
Handled := true;
end;

Thanks Carlos!
But finally I found other approach . . . simply unregister the interface
InvRegistry.UnRegisterInterface(TypeInfo(IAppServer));
InvRegistry.UnRegisterInterface(TypeInfo(IAppServerSOAP));
InvRegistry.UnRegisterInterface(TypeInfo(IWSDLPublish));

If your client applications do not need server to implement IAppServer (or IAppServerSOAP) then it is pointless to implement these. I expect you have implemented them - as you already said - because they are already implemented in ancestors of your objects - I expect it to be TSOAPDataModule.
So, instead of hiding them in the WSDL, i would suggest descending your server objects from a class that does not already introduce IAppServerxxxx. Which might be simple TDataModule (if you need a "container" object) or TInvokableClass.

Finally I got it!
To do so, all I had to do was edit the WebModule2DefaultHandlerAction method, i.e., the OnAction event handler of the DefaultHandler WebActionItem.
The final event handler looks like this now:
procedure TWEBMWebService.WebModule2DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Conteudo: String;
begin
WSHPWebService.ServiceInfo(Sender, Request, Response, Handled);
Conteudo := Response.Content;
try
HideInterfaces(Conteudo,['IAppServer','IAppServerSOAP']);
finally
Response.Content := Conteudo;
end;
end;
The HideInterfaces procedure follows:
procedure HideInterfaces(var aContent: String; aInterfaces: array of string);
var
Intf: String;
i: Integer;
begin
if Length(aInterfaces) = 0 then
Exit;
with TStringList.Create do
try
{ Remove todos os enters }
aContent := StringReplace(aContent,#13#10,' ',[rfreplaceAll]);
{ Separa tudo baseando-se nos TR }
Text := StringReplace(aContent,'<tr>',#13#10'<tr>'#13#10,[rfreplaceAll,rfIgnoreCase]);
Text := StringReplace(Text,'</tr>',#13#10'</tr>'#13#10,[rfreplaceAll,rfIgnoreCase]);
{ Neste ponto, cada linha do StringList contém ou <TR>, ou </TR>, ou o que
houver entre os dois, então circulamos por cada interface que precisa ser
ocultada }
for Intf in aInterfaces do
begin
for i := 0 to Pred(Count) do
if Pos(LowerCase(Intf),LowerCase(Strings[i])) > 0 then
Break;
{ Se achou a interface, oculta a linha inteira de tabela, removendo do
StringList i, i-1 e i+1 }
if i < Count then
begin
Delete(i+1);
Delete(i);
Delete(i-1);
end;
end;
aContent := Text;
finally
Free;
end;
end;
The comments are in Portuguese, sorry, but it's easy to understand the code. If you like it and use it, please let me know and give me some credits, right ;)
I would like to thank you all for the valuable answers. Without your help I would never find the solution! Thank You All!

Related

Record being modified but can't find where even exporting all objects to txt

In Dynamics Nav 2018, I'm using the selected action in this page rows, when I click "Validar", all selected rows will change it's status to "Pendiente".
After some time, the row will change to status "Error":
I can see in debugger when it changes from "Registrando" to "Pendiente". But I can't see when it's changing to "Error" and when it's inserting data to column "Descripción Estado":
Validar - OnAction()
UserSetup.GET(USERID);
UserSetup.TESTFIELD("Superusuario SII",TRUE);
//++SII V.08
CurrPage.SETSELECTIONFILTER(SII);
WITH SII DO BEGIN
IF FINDSET(TRUE,TRUE) THEN
REPEAT
//--SII V.08
//++SII JUL-2018
IF SII."Pendiente de validar" THEN BEGIN
TempHistSII.RESET;
TempHistSII.SETCURRENTKEY("N Doc NAV","Fecha Registro");
TempHistSII.SETRANGE("N Doc NAV",SII."N Doc NAV");
TempHistSII.SETRANGE("Fecha Registro",SII."Fecha Registro");
TempHistSII.SETRANGE(TempHistSII."Nº Mov",SII."Nº Mov");
//++SII JUL-2018 12/06/18
//IF TempHistSII.FINDFIRST THEN BEGIN
IF TempHistSII.FINDLAST THEN BEGIN
//--SII JUL-2018 12/06/18
HistRegSII.INIT;
HistRegSII.TRANSFERFIELDS(TempHistSII);
HistRegSII."Usuario modificación" := USERID;
HistRegSII."Fecha modificación" := TODAY;
HistRegSII."Hora modificación" := TIME;
HistRegSII.INSERT;
//++SII JUL-2018 12/06/18
//Inserto en tabla temporal
TempHistSII.INIT;
TempHistSII.TRANSFERFIELDS(SII);
TempHistSII."Nº línea registro" := HistRegSII."Nº línea registro" + 10000;
TempHistSII.INSERT;
//--SII JUL-2018 12/06/18
END;
SII."Pendiente de validar" := FALSE;
END;
//--SII JUL-2018
CASE Estado OF
//++SII JUL-2018
//Estado::"Registrada con error",Estado::Registrada: //++SII V.07
Estado::"Registrada con error",Estado::Registrada,Estado::"Error por inmuebles adicionales":
//--SII JUL-2018
BEGIN
"Tipo comunicacion" := "Tipo comunicacion"::A1;
Estado := Estado::Pendiente;
"Descripción Estado" := '';
END;
//++SII V.06
//Estado::Error:
Estado::Error,Estado::"Error configuración":
//--SII V.06
BEGIN
Estado := Estado::Pendiente;
"Descripción Estado" := '';
END;
//++SII V.07
Estado::Pendiente,Estado::"Pendiente Devengo":
"Descripción Estado" := '';
//--SII V.07
ELSE
ERROR(Text001,Estado);
END;
MODIFY;
//++SII V.08
UNTIL NEXT = 0;
END;
//--SII V.08
So I can see there when it's changing the status when clicking "Validar" button... but I can't find the process that is changing later the status to "Error"...
I'm checking the Job Queue Entries and I don't see anything that affects this columns.
Using the tool whereused I can't find anything.
I've also checked the table triggers and there's nothing, all empty.
I don't know how it's possible that I can't find anything exporting all objects to txt... I should be able to search "Descripción Estado" := or No existe el Registro
What could write to this column that it's not displayed in txt objects and not triggering the debugger?
Table can still be modified through SQL. This you will only find using SQL Server Profiler.
THEORETICALLY your version supports extensions v1, which you will not see when exporting objects to txt. So check if you have any extensions installed.
Maybe you just searching wrong. Try using Statical Prism. This is a greater tool for code exploring.
There is a trick I like to use. Your version supports subscriptions. Create a codeunit, put a subscriber function to it, subscribe to table's OnModify event and just throw an error when record is being modified and the value of the field is error. This will break some processes so do this on test DB. Then reproduce the behavior. Then check windows logs on server where nav instance is installed. Find your error in the log. Along with the error you will find stack trace. By stack trace you will see which object caused record modification.

Inno Setup: Inherited OLE-Object properties not accessible?

I followed this question's accepted answer to query the machines network adapters. It finally worked but I still face a problem reading the values of these properties:
Win32_NetworkAdapterConfiguration.Caption
Win32_NetworkAdapterConfiguration.Description
Every time the code reaches this line where networkAdapter.Caption is called a runtime error is produced saying:
Runtime error (at 60:8952): Unknown method.
This is my code, adopted from the above mentioned Stack Overflow question:
Log('Querying WMI for network adapter data...');
query := 'SELECT IPEnabled, IPAddress, MACAddress, InterfaceIndex FROM Win32_NetworkAdapterConfiguration';
networkAdapters := wbemServices.ExecQuery(query);
if not VarIsNull(networkAdapters) then
begin
for i := 0 to networkAdapters.Count - 1 do
begin
networkAdapter := networkAdapters.ItemIndex(i);
if (not VarIsNull(networkAdapter.MACAddress)) and networkAdapter.IPEnabled and (not VarIsNull(networkAdapter.IPAddress)) then
begin
SetArrayLength(sysInfo.networkAdapters, GetArrayLength(sysInfo.networkAdapters) + 1);
nicRec := sysInfo.networkAdapters[adapterIndex];
{ Adapter name }
nicRec.name := defaultIfNull(networkAdapter.Caption, Format('Adapter %d', [i]));
Log(Format(' NIC[%d] name........: %s', [adapterIndex, nicRec.name]));
{ Adapter index }
nicRec.index := defaultIfNull(networkAdapter.InterfaceIndex, adapterIndex);
Log(Format(' NIC[%d] index.......: %d', [adapterIndex, nicRec.index]));
{ Adapter MAC address }
nicRec.macAddress := defaultIfNull(networkAdapter.MACAddress, '');
Log(Format(' NIC[%d] MAC address.: %s', [adapterIndex, nicRec.macAddress]));
{ Adapter ip address(es) }
nicRec.ipAddresses := TStringList.Create;
if not VarIsNull(networkAdapter.IPAddress) then
begin
ips := networkAdapter.IPAddress;
for j := 0 to GetArrayLength(ips) - 1 do
begin
nicRec.ipAddresses.Add(ips[j]);
Log(Format(' NIC[%d] IPv4 address: %s', [adapterIndex, nicRec.ipAddresses.Strings[j]]));
end;
end;
adapterIndex := adapterIndex + 1;
end;
end;
end;
After some reading in the Microsoft docs, I came across the description of these properties. It states, that the Win32_NetworkAdapterConfiguration class extends the CIM_Setting class. The properties Caption and Description are defined there. Is this an issue with the Inno Setup compiler (I am using the latest 6.0.2) or do I have to apply some sort of cast to may variant variable?
Of course that inherited properties are accessible. Actually Inno Setup does not even care what class is that. It uses late binding. Property name resolution is delegated to the class itself.
But you are not working with Win32_NetworkAdapterConfiguration. IWbemServices.ExecQuery returns IEnumWbemClassObject, which in turn enumerates IWbemClassObject. And that contains results of your query. Your query does not ask for Caption and Description properties, so the result set naturally does not contain them.
Add the properties to the query:
Query := 'SELECT IPEnabled, IPAddress, MACAddress, InterfaceIndex, Caption, Description FROM Win32_NetworkAdapterConfiguration';

Improper exception handling / object freeing probably causes memory leak (Cause: unnoticed "variable might not be assigned" warning)

Full source of example with same problem
Full source of example with same problem
Continuation with all code inside single EXE
I have moved everything to single EXE
var SenderInstance: AutoGeneratedWebserviceUnit.Sender;
...
procedure TForm1.FormCreate(Sender: TObject);
begin
SenderInstance := AutoGeneratedWebserviceUnit.GetSender(False, 'http://invalid_URL'); // => there is no exception here
end;
...
procedure TForm1.Button1Click(Sender: TObject);
var
req: AutoGeneratedWebserviceUnit.Request;
res: AutoGeneratedWebserviceUnit.Response;
begin
try
req := Request.Create;
try
with req do
begin
ID := 0;
param := 'trash';
end;
res := SenderInstance.Request('Login', 'Pass', req); // => ESOAPHTTPException + EAccesViolation !
ShowMessage(res.status);
finally
req.Free;
res.Free; // ### MOST POSSIBLE PROBLEM CAUSE ###
end;
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
I understand ESOAPHTTPException, but AV? WHY?...
Problem
Strange things happens after raising ESOAPHTTPException...
I suppose that there is something like a memory leak that I can't figure out or I did something really really stupid...
Any settings? Maybe I forgot something?
Clue
I've found something
It's about "res.Free;" line inside try/finally block
Disabling this line causes no AV
But Assigned(req) returns True
Assigned(res) returns True too...
What the....?
The problem causing the AV lies in your try..finally clause in the Button1Click event handler.
You are trying to free your res variable (response), which has not been assigned anything yet, since the call to the Requestmethod fails with an exception. This means it contains garbage and by calling res.Free you are accessing memory positions you should not, which can provoke almost any kind of strange error.
To solve it set res to nil before entering the try..finally and check if it is assigned before calling res.Free.
On a side note, this happens because res is a local variable. If it would be a member of an instance of a class the compiler would have assigned it a nil value automatically.
UPDATE
As #RobKennedy stated, it is much better to use nested try..finally blocks than assigning nil as I first told you (which can potentially lead to problems if some destructor fails).
So you would do something like following:
req := Request.Create;
try
req.ID := 0;
req.param := 'trash';
res := SenderInstance.Request('Login', 'Pass', req);
try
ShowMessage(res.status);
finally
res.Free;
end;
finally
req.Free;
end;

Get clients ip address in a RemObjects webservice

I have a webservice implemented using RemObjects over Delphi XE and I want to know the ip address of the clients petitions. My service inherits from TRORemoteDataModule and I haven't found any method or object to do that.
Any suggestion?
Thanks
Note: I think that the information that I need is returning in the method self.transport.GetTransportObject() but it returns a TObject and I don't know how to extract this information
This is how I get it from a SuperChannel:
procedure TMyInterface.RORemoteDataModuleGetDispatchInfo(const aTransport: IROTransport; const aMessage: IROMessage);
var
tcpinfo: IROTCPTransport;
Session: TCustomSession;
szClientIP : String;
begin
Session := TCustomSession(Self.Session);
if Supports(aTransport, IROTCPTransport, tcpinfo) then
begin
szClientIP := tcpinfo.ClientAddress;
if (not Session.ShownTCP) or (Session.TCPAddress <> szClientIP) then
begin
Session.TCPAddress := szClientIP;
Session.Report(leInformation, 'TCP address ' + szClientIP);
Session.ShownTCP := True;
end;
end
else
begin
Session.Report(leInformation, 'TCP address not available');
end;
end;
The specifics of what you do with it are up to you, but you have to get it as it is set up, and store it in the session object if you want to pick it up later. I implemented a custom session to hold the client Ip so that I could get it any time in further calls.
here it is how to do it
http://wiki.remobjects.com/wiki/Server_FAQs_%28RemObjects_SDK%29#How_can_I_get_IP_address_of_the_remote_client.3F

Avoiding Dialog Boilerplate in Delphi and /or C++

I often need to design a dialog in Delphi/C++Builder that allows various properties of an object to be modified, and the code to use it typically looks like this.
Dialog.Edit1.Text := MyObject.Username;
Dialog.Edit2.Text := MyObject.Password;
// ... many more of the same
if (Dialog.ShowModal = mrOk)
begin
MyObject.Username := Dialog.Edit1.Text;
MyObject.Password := Dialog.Edit2.Text;
// ... again, many more of the same
end;
I also often need similar code for marshalling objects to/from xml/ini-files/whatever.
Are there any common idioms or techniques for avoiding this kind of simple but repetitive code?
well, something that I feel completely invaluable is the GExperts plugin wizard "Reverse Statement" which is invoked after installing GExperts by pressing Shift + ALT + R
What it does is automatically switch the assignments around for the highlighted block. For example:
edit1.text := dbfield.asString;
becomes
dbField.asString := edit1.text;
Not exactly what your looking for, but a huge time saver when you have a large number of assignments.
Here's my variation on this. What I did, having got fed up with the same repetitive code, was to name all the edit boxes according to the XML node names I wanted, then iterate around the components and output their values. The XML code should be obvious, and I only have an edit and checkbox, but you should be able to see the idea.
procedure TfrmFTPSetup.LoadFromXML(szFileName : string);
var
xComponent : TComponent;
nLoop : Integer;
xMainNode : TXmlNode;
xDocument : TNativeXml;
begin
inherited;
xDocument := TNativeXml.Create;
try
xDocument.LoadFromFile(szFileName);
xMainNode := xml_ChildNodeByName(xDocument.Root, 'options');
for nLoop := 0 to ComponentCount - 1 do
begin
xComponent := Components[nLoop];
if xComponent is TRzCustomEdit then
begin
(xComponent as TRzCustomEdit).Text := xMainNode.AttributeByName[xComponent.Name];
end;
if xComponent is TRzCheckBox then
begin
(xComponent as TRzCheckBox).Checked := xml_X2Boolean(xMainNode.AttributeByName[xComponent.Name], false);
end;
end;
finally
FreeAndNil(xDocument);
end;
end;
procedure TfrmFTPSetup.SaveToXML(szFileName : string);
var
xComponent : TComponent;
nLoop : Integer;
xMainNode : TXmlNode;
xDocument : TNativeXml;
begin
inherited;
xDocument := TNativeXml.CreateName('ftpcontrol');
try
xMainNode := xml_ChildNodeByNameCreate(xDocument.Root, 'options');
for nLoop := 0 to ComponentCount - 1 do
begin
xComponent := Components[nLoop];
if xComponent is TRzCustomEdit then
begin
xMainNode.AttributeByName[xComponent.Name] := (xComponent as TRzCustomEdit).Text;
end;
if xComponent is TRzCheckBox then
begin
xMainNode.AttributeByName[xComponent.Name] := xml_Boolean2X((xComponent as TRzCheckBox).Checked);
end;
end;
xDocument.XmlFormat := xfReadable;
xDocument.SaveToFile(szFileName);
finally
FreeAndNil(xDocument);
end;
end;
It's not considered good practice to access properties of visual components on a form. It is considered better to have seperate properties. In the example above you would have username and password properties with get and set methods.
For example:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
private
function GetPassword: string;
function GetUsername: string;
procedure SetPassword(const Value: string);
procedure SetUsername(const Value: string);
public
property Password: string read GetPassword write SetPassword;
property Username: string read GetUsername write SetUsername;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.GetPassword: string;
begin
Result := Edit2.Text;
end;
function TForm1.GetUsername: string;
begin
Result := Edit1.Text;
end;
procedure TForm1.SetPassword(const Value: string);
begin
Edit2.Text := Value;
end;
procedure TForm1.SetUsername(const Value: string);
begin
Edit1.Text := Value;
end;
end.
This means you can change the visual components on the form without having it affecting the calling code.
Another option would be to pass the object as a property to the dialog;
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TUserObject = class(TObject)
private
FPassword: string;
FUsername: string;
public
property Password: string read FPassword write FPassword;
property Username: string read FUsername write FUsername;
end;
TForm1 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
btnOK: TButton;
procedure btnOKClick(Sender: TObject);
private
FUserObject: TUserObject;
procedure SetUserObject(const Value: Integer);
public
property UserObject: Integer read FUserObject write SetUserObject;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnOKClick(Sender: TObject);
begin
FUserObject.Username := Edit1.Text;
FUserObject.Password := Edit2.Text;
ModalResult := mrOK;
end;
procedure TForm1.SetUserObject(const Value: Integer);
begin
FUserObject := Value;
Edit1.Text := FUserObject.Username;
Edit2.Text := FUserObject.Password;
end;
end.
Hope that helps.
Delphi at least have 'With', though it doesn't solve the problem completely.
if (Dialog.ShowModal = mrOk)
begin
with MyObject do
begin
Username := Dialog.Edit1.Text;
Password := Dialog.Edit2.Text;
// ... again, many more of the same
end;
end;
And builder AFAIK has nothing alike.
Binding controls to data works well in Delphi, but unfortunately only when that data resides in a TDataSet descendant. You could write a TDataSet descendant that uses an object for data storage, and it turns out that one such thing already exists. See link below... This implementation appears to only work with collections of objects (TCollection or TObjectList), not single objects.
http://www.torry.net/pages.php?id=563 - search the page for for "Snap Object DataSet"
I have no personal experience with this, but it would be very useful if it works and especially if it would also work with single object instances, such as a property on a data module...
Look up "mediator pattern". It's a GoF design pattern, and in their book the GoF in fact motivate this design pattern with a somewhat similar situation to what you're describing here. It aims at solving a different problem -- coupling -- but I think you have this problem too anyhow.
In short, the idea is to create a dialog mediator, an extra object that sits in between all the dialog widgets. No widget knows about any other widget, but each widget does know the mediator. The mediator knows all widgets. When one widget changes it informs the mediator; the mediator then informs the relevant widgets about this. For example, when you click OK the mediator may inform other widgets about this event.
This way each widgets takes care of events and actions related to itself only. The mediator takes care of the interaction between all widgets, so all this "boilerplate" code is split over all widgets, and the "residue" which is global to all widgets is the interaction, and it is the responsibility of the mediator.