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
Related
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';
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!
I Need add custom values to AppSettings in a webservice
i have this code, but nothing happens.
procedure TWebService1.AddStrConn(KeyConn, ValueConn: String);
var
config : System.Configuration.Configuration;
begin
config:=ConfigurationManager.OpenExeConfiguration(System.Reflection.Assembly.GetExecutingAssembly().Location);
config.AppSettings.Settings.Add(KeyConn,ValueConn);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection('appSettings');
end;
also try
procedure TWebService1.AddStrConn(KeyConn, ValueConn: String);
var
config : System.Configuration.Configuration;
begin
config:=ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Add(KeyConn,ValueConn);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection('appSettings');
end;
You are using OpenExeConfiguration, which is intended for *.exe.config. To open web.config, try something like
Configuration cfg = WebConfigurationManager.OpenWebConfiguration("~");
It should allow you to save, provided your service has the privileges to do so.
I'm trying to create a code to allow an existing classic asp program to use an asp.net web service. Updating from the classic asp is not an option, as I'm working in a big company and things are the way they are.
I've been browsing through a chunk of tutorials supposedly helping in this, but I haven't managed to get them to work yet. As a beginner I might've made some real obvious mistakes but I just don't know what.
First, the web service is located on an external server. The method "Greeting" needs a String parameter by which it determines which String is sent back. Inputting "g" to it procudes this xml:
<?xml version="1.0" encoding="utf-8" ?>
<string xmlns="http://server1/Logger_WebService/">Greetings and welcome!</string>
I assume the xpath for getting the contents is either "string/*" or "*"?
Next, my web service itself looks like this:
<WebMethod()> _
Public Function Greeting(ByVal stringel As String) As String
If stringel.ToLower = "g" Then
Return "Greetings and welcome!"
Else
Return "Bye then!"
End If
End Function
The web service works fine from a regular asp.net solution.
Now here's the problem, the classic asp code looks like this (4 different ways I've tried to get this to work, SOAP toolkit is installed on the web service server, all examples taken and modified from tutorials):
'******* USING GET METHOD
Dim wsurl="http://server1/Logger_WebService/service.asmx/Greeting?g"
Dim xmlhttp
Set xmlhttp=Server.CreateObject("MSXML2.ServerXMLHTTP")
xmlhttp.open "GET",wsurl,false
xmlhttp.send
Dim rValue
'rValue=xmlhttp.responseXML.selectSingleNode("string") 'use XPATH as input argument
' or you can get response XML
rValue=xmlhttp.responseXML
Set xmlhttp=nothing
'------------------------------------------------------
'******* USING POST METHOD
Dim wsurl="http://server1/Logger_WebService/service.asmx/Greeting"
Dim xmlhttp
Set xmlhttp=Server.CreateObject("MSXML2.ServerXMLHTTP")
xmlhttp.open "POST",wsurl,false
xmlhttp.send "stringeli=g"
Dim rValue
rValue=xmlhttp.responseXML.selectSingleNode("string")
' or you can get response XML
' rValue=xmlhttp.responseXML
Set xmlhttp=nothing
'------------------------------------------------------
Response.Write consumeWebService()
Function consumeWebService()
Dim webServiceUrl, httpReq, node, myXmlDoc
webServiceUrl = "http://server1/Logger_WebService/service.asmx/Greeting?stringel=g"
Set httpReq = Server.CreateObject("MSXML2.ServerXMLHTTP")
httpReq.Open "GET", webServiceUrl, False
httpReq.Send
Set myXmlDoc =Server.CreateObject("MSXML.DOMDocument")
myXmlDoc.load(httpReq.responseBody)
Set httpReq = Nothing
Set node = myXmlDoc.documentElement.selectSingleNode("string/*")
consumeWebService = " " & node.text
End Function
'------------------------------------------------------
Response.Write(Helou())
Public Function Helou()
SET objSoapClient = Server.CreateObject("MSSOAP.SoapClient")
objSoapClient.ClientProperty("ServerHTTPRequest") = True
' needs to be updated with the url of your Web Service WSDL and is
' followed by the Web Service name
Call objSoapClient.mssoapinit("http://server1/Logger_WebService/service.asmx?WSDL", "Service")
' use the SOAP object to call the Web Method Required
Helou = objSoapClient.Greeting("g")
End Function
I seriously have no idea why nothing works, I've tried them every which way with loads of different settings etc. One possible issue is that the web service is located on a server which in ASP.Net required me to input this "[ServiceVariableName].Credentials = System.Net.CredentialCache.DefaultCredentials". I do this from within company network, and there are some security and authorization issues.
I only need to be able to send information anyhow, not receive, as the actual method I will be using is going to insert information into a database. But for now, just getting the Hello World thingie to work seems to provide enough challenge. :)
Thx for all the help. I'll try to check back on holiday hours to check and reply to the comments, I've undoubtedly left out needed information.
Please, talk as you would to an idiot, I'm new to this so chances are I can understand better that way. :)
You might consider writing a bit of .NET wrapper code to consume the web service. Then expose the .NET code as a COM object that the ASP can call directly. As you've seen, there is no tooling to help you in classic ASP, so consider using as much .NET as possible, for the tooling. Then, use COM to interoperate between the two.
A colleague finally got it working after putting a whole day into it. It was decided that it's easier by far to send information than it is to receive it. Since the eventual purpose of the web service is to write data to the DB and not get any message back, we attempted the thing by simply writing a file in the web service.
The following changes were needed:
First, in order to get it to work through the company networks, anonymous access had to be enabled in IIS.
The web service needed the following change in the web.config:
<webServices>
<protocols>
<add name="HttpGet"/>
</protocols>
</webServices>
And the web service code-behind was changed like so:
<WebMethod()> _
Public Function Greeting(ByVal stringel As String) As String
Dim kirj As StreamWriter
'kirj = File.CreateText("\\server1\MyDir\Logger_WebService\test.txt")
'if run locally, the line above would need to be used, otherwise the one below
kirj = File.CreateText("C:\Inetpub\serverroot\MyDir\Logger_WebService\test.txt")
kirj.WriteLine(stringel)
kirj.Close()
kirj.Dispose()
Return stringel
End Function
As we got the above to work, it was a simple matter of applying the same to the big web method that would parse and check the info and insert it into the database.
The classic asp code itself that needs to be added to the old page, which was the biggest problem, turned out to be relatively simple in the end.
function works()
message = "http://server1/mydir/logger_webservice/service.asmx/Greeting?" & _
"stringel=" & "it works"
Set objRequest = Server.createobject("MSXML2.XMLHTTP")
With objRequest
.open "GET", message, False
.setRequestHeader "Content-Type", "text/xml"
.send
End With
works = objRequest.responseText
end function
works()
Took about a week's worth of work to get this solved. :/ The hardest part was simply not ever knowing what was wrong at any one time.
You might be missing the SOAPAction header. Here's a working example:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class GreetingService : WebService
{
[WebMethod]
public string Greet(string name)
{
return string.Format("Hello {0}", name);
}
}
And the calling VBS script:
Dim SoapRequest
Set SoapRequest = CreateObject("MSXML2.XMLHTTP")
Dim myXML
Set myXML = CreateObject("MSXML.DOMDocument")
myXML.Async=False
SoapRequest.Open "POST", "http://localhost:4625/GreetingService.asmx", False
SoapRequest.setRequestHeader "Content-Type","text/xml;charset=utf-8"
SoapRequest.setRequestHeader "SOAPAction", """http://tempuri.org/Greet"""
Dim DataToSend
DataToSend= _
"<soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" xmlns:tem=""http://tempuri.org/"">" & _
"<soapenv:Header/>" & _
"<soapenv:Body>" & _
"<tem:Greet>" & _
"<tem:name>John</tem:name>" & _
"</tem:Greet>" & _
"</soapenv:Body>" & _
"</soapenv:Envelope>"
SoapRequest.Send DataToSend
If myXML.load(SoapRequest.responseXML) Then
Dim Node
Set Node = myXML.documentElement.selectSingleNode("//GreetResult")
msgbox Node.Text
Set Node = Nothing
End If
Set SoapRequest = Nothing
Set myXML = Nothing
Might want to double-check the version of the MSXML components. Are you using Windows Authentication? I've noticed some odd XML parsing problems with IIS 7, Classic ASP, and MSXML.
It would also help to get a useful error. Check the ** myXML.parseError.errorCode** and if its not 0 write out the error.
Reference Code:
If (myXML.parseError.errorCode <> 0) then
Response.Write "XML error: " & myXML.parseError.reason
Else
'no error, do whatever here
End If
'You get the idea...
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.