Does anyone know how to get IDs of devices connected to DataSnap Server?
I made an application which uses DataSnap Server and I want to limit the connection and identify the connected devices.
This might help
procedure TServerContainer1.DSServer1Connect(
DSConnectEventObject: TDSConnectEventObject);
var
UserName: String;
ClientInfo: TDBXClientInfo;
logmsg: String;
i: Integer;
begin
// Note: this event handler gets called directly AFTER authentication
UserName := TDSSessionManager.GetThreadSession.GetData('UserName');
ClientInfo := DSConnectEventObject.ChannelInfo.ClientInfo;
logmsg := Format('User %s connected via %s from IP address %s',
[UserName, ClientInfo.Protocol, ClientInfo.IpAddress]);
// ...
end;
On the TDSServer component, you can specify an OnConnect event. There is a record type called ‘TDBXClientInfo’ which you can get from the ‘TDBXChannelInfo’ stored in the TDSConnectEventObject of the OnConnect event. This record contains the IP Address.
Mat DeLong has lot of useful information about DataSnap:
https://mathewdelong.wordpress.com/2011/09/15/delphilive-2011-recap/
Related
when my app was using Android5, I can monitor if the SMS was sent or failed by checking the content://sms/sent and content://sms/failed. Aside from via my app, I can also see the message and its status directly from the SMS app of the phone.
Now that the good old Android5 smartphone is dead, I am upgrading to a newer smartphone base on Android8 (Oreo). However I am facing a problem of detecting the status of the sent sms.
First... I can no longer "see" the sms using the phone's default SMS app.
Second... I cant also find the sms using my app even if I scroll through all messages one by one.
This is the code I use to send the SMS:
smsManager := TJSmsManager.JavaClass.getDefault;
smsTo := StringToJString(targetstr);
smsarray := smsmanager.divideMessage(stringtojstring(bodystr));
smsManager.sendmultiparttextMessage(smsTo, nil,smsarray, nil, nil);
While this is the code I use to scroll through all my messages:
uri := StrToJURI('content://sms');
smscursor := TAndroidHelper.Activity.getContentResolver.query(uri, nil, nil,nil,nil);
id_id := smscursor.getColumnIndex(StringToJstring('_id'));
idaddress := smscursor.getColumnIndex(StringToJstring('address'));
idbody := smscursor.getColumnIndex(StringToJstring('body'));
idstatus := smscursor.getColumnIndex(StringToJstring('status'));
idthread_id := smscursor.getColumnIndex(StringToJstring('thread_id'));
idtype := smscursor.getColumnIndex(StringToJstring('type'));
smscursor.moveToFirst;
** using the repeat until loop to read each and every sms until "islast" is true **
But I cant find the sent SMS irregardless if the SMS was successfully sent or not.
I need to do this detection because the signal strength in my area is very low and around 20% of the time the sms failed, and my app should resend the intended message.
PS: I also tried searching for the SMS from the content://sms/sent and content://sms/failed but to no avail.
Anyway, my questions are:
How to make the send out sms show up inside the phone's default sms app?
How to correctly determine if the send out sms was successful or not?
I read somewhere that the method above is using the API and there is a way to use the phone's sms app instead... But I dont have any idea how to do it.
Thank you.
For the benefit of others who may be in similar situation that requires to:
(A) Able to send an SMS directly from the app without using the Phone's built-in SMS default app
(B) The SMS length can be more than the default 160 characters limit (using sendmultiparttextMessage)
(C) Able to know if the SMS was process or not (base on aResultCode of the onReceive from the PendingIntent)
Known Limitation:
Under multi-sim phone, sms is sent via the default sim
if sending to multiple phone recipients, there is no way to know which ones failed, if any. So I suggest send 1 sms to 1 recipient at a time
Doesnt detect if the intended recipient receive the sms or not as the DeliveryIntent is nil
PS: tested under Delphi Community Edition 10.4.2 and Android Oreo 8.0 and 8.1
What I did:
Create and save a unit for the BroadcastReceiver (I call it BCast.pas) from the 2nd answer from this link How read JPendingIntent in Delphi?
In your main.pas (assuming it is save as main.pas), add the following:
ADD TO PROJECT the unit created in step 1 (in my case bcast.pas)
add BCAST in uses clause
add variable for the TBroadCastReceiver (ie fbroadcast : Tbroadcastreceiver;)
make an OnReceiveBroadcast procedure
in my case:
private
{ Private declarations }
procedure OnReceiveBroadcast(aContext: JContext; aIntent: JIntent; aResultCode: integer);
then under implementation, create the code for the OnReceiveBroadcast:
procedure Tmain.OnReceiveBroadcast(aContext: JContext; aIntent: JIntent; aResultCode: integer);
begin
// ... put in the codes here for the result of the last sms send. //
// ... use the the value of AResultcode to do the checking //
// ... AResultcode=1 means ok, other number means possible error //
end;
initialize the fbroadcast as follows: (i put them under my main's onshow):
fbroadcast := TBroadcastReceiver.Create(OnReceiveBroadcast);
fbroadcast.addactions([StringToJString('xxx')]);
where xxx is any string that will be use to match the intent's action
Add in a procedure to sendsms:
procedure tmain.SendSMSAPI(target,messagestr:string);
var
smsManager: JSmsManager; // Androidapi.JNI.Telephony //
smsTo : JString; // Androidapi.JNI.JavaTypes //
PIntent : JPendingIntent; // Androidapi.JNI.App //
smsarray : jarraylist;
APIntent : JArraylist;
intent : JIntent;
begin
Intent := TJIntent.Create;
Intent.setAction(StringToJString('xxx'));
PIntent := TJPendingIntent.JavaClass.getBroadcast(TAndroidHelper.Context, 0, Intent, 0);
APIntent := Tjarraylist.create;
APIntent.add(PIntent);
smsManager:= TJSmsManager.JavaClass.getDefault;
smsTo := StringToJString(target); // Androidapi.Helpers //
smsarray := smsmanager.divideMessage(stringtojstring(messagestr));
smsManager.sendmultiparttextMessage(smsTo, nil,smsarray, APIntent, nil);
end;
I have an Indy TCP server, the client connects to it, send an information, the server receives, create a huge TStringList and send back to the client. This happens thousands of times daily so I decided to put six servers on different .exe doing the same thing on different ports and put the client app to connect each time on a random port.
What is happening:
1) The moment from the client first attempt to connect to the moment he receive all the information he needs is pretty high, like he was doing the work twice.
2) Around 10% of the times the client tries to connect and the server seems to ignore the attempt and the client doesn't fire the except to try again, and stuck.
The server:
MaxConnections is set to 1
ListenQueue to 0
TerminateWaitTime to 5000
ReuseSocket to False on client and server
UsaNagle := True on client and server
The client 6 second Timer that tries to connect to the server:
IdDownloadClient.IOHandler.MaxLineLength := MaxInt;
IdDownloadClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
// Choose a random port
IdDownloadClient.Connect;
IdDownloadClient.IOHandler.WriteLn(lat+','+long);
Timer6.Enabled := True;
The client Timer6 (25 ms):
with IdDownloadClient do
begin
try
if IOHandler.InputBufferIsEmpty then
begin
IOHandler.CheckForDataOnSource(0);
IOHandler.CheckForDisconnect;
if IOHandler.InputBufferIsEmpty then Exit;
end;
end;
receivedtext := IOHandler.ReadLn;
except
Timer6.Enabled := False;
Exit;
end;
if receivedtext = '#' then begin // The server send an '#' to say the complete TStringList has been sent
IdDownloadClient.IOHandler.InputBuffer.Clear;
IdDownloadClient.IOHandler.CloseGracefully;
IdDownloadClient.Disconnect;
The server OnExecute event:
begin
try
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
LatLong := AContext.Connection.IOHandler.ReadLn;
if LatLong <> '' then begin
latF := StrToFloat(StringReplace(Copy(LatLong,0,ansipos(',',LatLong)-1),'.',',',[rfIgnoreCase, rfReplaceAll]));
lonF := StrToFloat(StringReplace(Copy(LatLong,ansipos(',',LatLong)+1,11),'.',',',[rfIgnoreCase, rfReplaceAll]));
// Creates a TStringList from a Memo to send (around half a sec of proccessing)
bufferlist := TStringList.Create;
bufferlist.Add('h-023.64086400000,-046.57425900000 99999999 0300 0301 0001 test|123 test');
for J := 0 to Memo1.Lines.Count-2 do
begin
if ((abs(latF-StrToFloat(StringReplace(Copy(Memo1.Lines[J],2,16),'.',',',[rfIgnoreCase, rfReplaceAll]))) < 0.1) and (abs(lonF-StrToFloat(StringReplace(Copy(Memo1.Lines[J],19,16),'.',',',[rfIgnoreCase, rfReplaceAll]))) < 0.1)) then
bufferlist.Add(Memo1.Lines[J]);
end;
///////// Start to send
for i := 0 to bufferlist.Count-1 do
begin
AContext.Connection.IOHandler.WriteLn(bufferlist[i]);
end;
AContext.Connection.IOHandler.WriteLn('#'); // Send '#' to the client to say the list is over
bufferlist.Free;
end;
except
if Assigned(bufferlist) then bufferlist.Free;
Exit;
end;
end;
Since all the connections comes from 3G/4G phones I assume that some of them end bad and this is causing the problem, so what am I doing wrong in this code?
There is something I can do for solving this problem or at least improve it?
Your server's OnExecute is swallowing all raised exceptions. When the client disconnects, the next socket I/O performed on that client's socket will raise an exception, which you are catching and discarding. So the server will not know the client has disconnected, and will continue to fire the OnExecute event. And since your server is set to MaxConnections=1, a new client cannot connect to the server until the previous client has been fully released. Your OnExecute code would have to call AContext.Connection.Disconnect() directly, or raise an uncaught exception, to release the client thread.
Simple rule of thumb - don't swallow exceptions! If you catch an exception you don't know how to handle, you should re-raise it, as it may be handled higher up in the call stack. In terms of TIdTCPServer, never swallow an Indy exception that is derived from EIdException, let the server handle it. Your use of a try/except just to free bufferlist should be replaced with a try/finally instead.
In fact, I would suggest getting rid of the TStringList for gathering the response data, as it is not actually helping you, it is hindering your server's performance. The client will not receive any response from the server until the entire TStringList has been built in full, and that may cause timeouts on the client side. It would be better to send each line of text to the client as it is being created, so the client knows the server is actually doing something and is not dead/frozen.
Another problem I see with your OnExecute code is that it is directly accessing a UI control (a TMemo) without synchronizing with the UI thread. TIdTCPServer is a multi-threaded component, its events are fired in worker threads. You must synchronize with the UI thread when accessing UI controls in a worker thread.
And lastly, your overuse of Copy() and StringReplace() is a major eye-sore and makes the code hard to maintain in general.
Try something more like this instead:
procedure IdTCPServer1Connect(AContext: TIdContext);
begin
// do this assignment one time, not on every OnExecute loop iteration
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure IdTCPServer1Execute(AContext: TIdContext);
var
s: string;
latF, lonF: Double;
fmt: TFormatSettings;
lines: TStringList;
j: Integer;
begin
s := AContext.Connection.IOHandler.ReadLn;
if s = '' then Exit;
fmt := TFormatSettings.Create;
fmt.DecimalSeparator := '.';
latF := StrToFloat(Fetch(s, ','), fmt);
lonF := StrToFloat(s, fmt);
AContext.Connection.IOHandler.WriteLn('h-023.64086400000,-046.57425900000 99999999 0300 0301 0001 test|123 test');
lines := TStringList.Create;
try
TThread.Synchronize(nil,
procedure
begin
lines.Assign(Memo1.Lines);
end
);
for J := 0 to lines.Count-2 do
begin
s := lines[J];
if (abs(latF-StrToFloat(Copy(s, 2, 16), fmt)) < 0.1) and (abs(lonF-StrToFloat(Copy(s, 19, 16), fmt)) < 0.1) then
AContext.Connection.IOHandler.WriteLn(s);
end;
finally
lines.Free;
end;
AContext.Connection.IOHandler.WriteLn('#'); // Send '#' to the client to say the list is over
end;
Now, on the client side, what you are doing is generally fine (though I would suggest using a worker thread instead of a timer in the main UI thread), however I will say that you do not need to call InputBuffer.Clear() for a graceful disconnect, only an abnormal disconnect (ie, move it into your except handler), and you should not be calling CloseGracefully() at all.
Also, since you are terminating the response with a unique delimiter, I would suggest that once the client detects the server's response starting to arrive, it should use TIdIOHandler.Capture() to read the entire response in one go, instead of calling ReadLn() to read each individual line on each timer event. That will greatly simplify and speed up how fast the client receives the complete response.
procedure Timer6Timer(Sender: TObject);
var
response: TStringList;
begin
with IdDownloadClient do
begin
try
if IOHandler.InputBufferIsEmpty then
begin
IOHandler.CheckForDataOnSource(0);
IOHandler.CheckForDisconnect;
if IOHandler.InputBufferIsEmpty then Exit;
end;
response := TStringList.Create;
try
// The server send an '#' to say the complete response has been sent
IOHandler.Capture(response, '#', False);
// use response as needed...
finally
response.Free;
end;
except
IOHandler.InputBuffer.Clear;
end;
Disconnect;
end;
Timer6.Enabled := False;
end;
I want to work with Google Api using Rest (Delphi XE7), do it this way:
uses
...
{$IF DEFINED(ANDROID)}
REST.Authenticator.OAuth.WebForm.FMX;
{$ENDIF}
{$IF DEFINED(MsWindows)}
REST.Authenticator.OAuth.WebForm.Win;
{$ENDIF}
That procedure is working on Windows, after that programm changes AuchCode to access token and everything working.
procedure TForm2.OAuth2_GoogleTasks_BrowserTitleChanged(const ATitle: string; var DoCloseWebView: boolean);
begin
if Pos('Success code', ATitle) > 0 then
begin
AuthCode := Copy(ATitle, 14, Length(ATitle));
if (AuthCode <> '') then
begin
editactoken.Text:= AuthCode;
DoCloseWebView := true;
webform.Release;
end;
end;
end;
procedure TForm2.Button59Click(Sender: TObject);
begin
WebForm:=Tfrm_OAuthWebForm.Create(nil);
WebForm.OnTitleChanged := self.OAuth2_GoogleTasks_BrowserTitleChanged;
WebForm.ShowWithURL(OAuth2Authenticator1.AuthorizationRequestURI);
end;
But I've met a trouble that I cant copy auth code on android:
Form2.OAuth2_GoogleTasks_BrowserTitleChanged doesn't work because title doesn't change;
There is no title at all in REST.Authenticator.OAuth.WebForm.FMX :
private
{ Private declarations }
FOnBeforeRedirect: TOAuth2WebFormRedirectEvent;
FOnAfterRedirect: TOAuth2WebFormRedirectEvent;
FOnBrowserTitleChanged : TOAuth2WebFormTitleChangedEvent;
FLastURL: string;
public
while in REST.Authenticator.OAuth.WebForm.Win it looks like this:
private
{ Private declarations }
FOnBeforeRedirect: TOAuth2WebFormRedirectEvent;
FOnAfterRedirect: TOAuth2WebFormRedirectEvent;
FOnBrowserTitleChanged : TOAuth2WebFormTitleChangedEvent;
FLastTitle: string; // <-------
FLastURL: string
there is no property webform.lasttitle in fmx variant (in win i could get it)
LastUrl property looks like that after redirecting : "......" but I can't understand how to use it to get auth code.
Try to use IFMXClipboardService but could not get this text from Twebbrowser.
Google not allowed to use Http get method for lastUrl adress to get response and parse this code (error 405).
I've read some article that there is no way to get html code from webbrowser fmx, is it so?
Any ideas how can I get this code in my programm?
Android (with Google Play Services installed) provides a native solution for authentication with other Google APIs, where no web browser is required:
See https://developers.google.com/android/guides/http-auth:
When you want your Android app to access Google APIs using the user's
Google account over HTTP, the GoogleAuthUtil class and related APIs
provide your users a secure and consistent experience for picking an
account and retrieving an OAuth 2.0 token for your app.
You can then use that token in your HTTP-based communications with
Google API services that are not included in the Google Play services
library, such as the Blogger or Translate APIs.
I suggest to do some research in this direction. The page linked above also mentions the GoogleApiClient for some (non HTTP) services.
I have a TIdUDPServer on a form setup with a default port and broadcast mode enabled and cannot get it to receive anything under Android.
The same code is working fine under iOS but if I retarget Android I do not receive anything.
Is there a trick I am missing. I have checked the "User Permissions" and turn on everything I think might influence this but have had no luck with getting it working.
Thanks in advance, Martin
Is your device connected to WiFi or a mobile network? UDP does not work over mobile unless you initiate an outgoing UDP connection first to open up the mobile's carrier's firewall.
Are the UDP packets being sent directly to the device's IP or to a broadcast IP? By default, Android discards UDP packets that are not addressed directly to the device's IP. For multicast packets, WifiManager.createMulticastLock() must been called beforehand to allow such packets, and you need to include the CHANGE_WIFI_MULTICAST_STATE permission in the app's manifest. That might apply to UDP broadcasts as well, I am not sure.
Thank you Remy for putting me on the right track.
Here is the complete code to receive broadcast UDP packets...
unit Androidapi.JNI.WiFiManager;
interface
uses
Androidapi.JNIBridge, Androidapi.Jni, androidapi.JNI.JavaTypes, androidapi.JNI.Net,
androidapi.JNI.Os, FMX.Helpers.Android, Androidapi.JNI.GraphicsContentViewText, SysUtils;
Type
JWiFiManager = interface; // android/net/wifi/WifiManager
JMulticastLock = interface; // android/net/wifi/WifiManager$MulticastLock
JWiFiManagerClass = interface(JObjectClass)
['{F69F53AE-BC63-436A-8F69-57389B30CAA8}']
function getSystemService(Contex: JString): JWiFiManager; cdecl;
end;
[JavaSignature('android/net/wifi/WifiManager')]
JWiFiManager = interface(JObject)
['{382E85F2-6BF8-4255-BA3C-03C696AA6450}']
function createMulticastLock(tag: JString): JMulticastLock;
end;
TJWiFiManager = class(TJavaGenericImport<JWiFiManagerClass, JWiFiManager>) end;
JMulticastLockClass = interface(JObjectClass)
['{C0546633-3DF2-46B0-8E2C-C14411674A6F}']
end;
[JavaSignature('android/net/wifi/WifiManager$MulticastLock')]
JMulticastLock = interface(JObject)
['{CFA00D0C-097C-45E3-8B33-0E5A6C9FB9F1}']
procedure acquire();
function isHeld(): Boolean;
procedure release();
procedure setReferenceCounted(refCounted: boolean);
end;
TJMulticastLock = class(TJavaGenericImport<JMulticastLockClass, JMulticastLock>) end;
function GetWiFiManager: JWiFiManager;
implementation
function GetWiFiManager: JWiFiManager;
var
Obj: JObject;
begin
Obj := SharedActivityContext.getSystemService(TJContext.JavaClass.WIFI_SERVICE);
if not Assigned(Obj) then
raise Exception.Create('Could not locate Wifi Service');
Result := TJWiFiManager.Wrap((Obj as ILocalObject).GetObjectID);
if not Assigned(Result) then
raise Exception.Create('Could not access Wifi Manager');
end;
And then to acquire the necessary lock...
wifi_manager := GetWiFiManager;
multiCastLock := wifi_manager.createMulticastLock(StringToJString('LightFactory Remote'));
multiCastLock.setReferenceCounted(true);
multiCastLock.acquire;
Finally dont forget the permissions mentioned in Remy's post.
I have an Android application communicating with a Delphi 2006 web service application using Indy 10 TIdHttpServer (coming with Delphi 2006). The Delphi application generates a big XML file and serves this. The XML generation may last more than 5 minutes.
If the duration of GenerateXml() is more than about 5 minutes (*), I detect an error 10053 in TIdHTTPResponseInfo.WriteContent if running in the Delphi IDE:
Socket Error # 10053 Software caused connection abort.
However, on the android side nothing is detected and the HttpGet-call lasts forever.
My questions are:
1.) Why do I get the error 10053 and how can I avoid it? It seems like android times out the connection, but http.socket.timeout is set to infinite.
and
2.) What can I do to detect such an error on the client side (other than setting timeout, which would have to be too big to be useful)? Can I do something in TIdHttpServer.OnException?
Here is my code.
Android - download function, which is run inside an AsyncTask:
protected static HttpEntity downloadEntity(String url) throws IOException {
HttpClient client = new DefaultHttpClient();
//Check because of Error 10053: but timeout is null -> infinite
Log.d("TAG", "http.socket.timeout: " + client.getParams().getParameter("http.socket.timeout"));
HttpGet get = new HttpGet(url);
HttpResponse response;
try {
//in case of Error 10053 the following call seems to last forever (in PlainSocketImpl.read)
response = client.execute(get);
} catch (ClientProtocolException e) {
//...
}
//...
return response.getEntity();
}
Delphi implementation of TIdHttpServer.OnCommandGet:
procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
TempStream: TMemoryStream;
begin
ResponseInfo.ContentType := 'text/xml';
TempStream := TMemoryStream.Create;
XMLDoc.SaveToStream(TempStream);
ResponseInfo.FreeContentStream := True;
ResponseInfo.ContentStream := TempStream;
end;
procedure TMyService.HTTPServerCommandGet(AContext: TIdContext; RequestInfo: TIdHTTPRequestInfo;
ResponseInfo: TIdHTTPResponseInfo);
begin
Coinitialize(nil);
try
//...
ServeXmlDoc(GenerateXml(), ResponseInfo);
finally
CoUninitialize;
end;
end;
Edit: (*) I have done further testing and experienced the error even in cases where the whole process had a duration of under 2 minutes.
Something between Android and your server, such as a firewall/router, is likely cutting the connection after it is idle for too long. You should try enabling TCP keep-alives to avoid that.
On the other hand, this is the kind of situation that HTTP 1.1's chunked transfer encoding was designed to handle (assuming you are using HTTP 1.1 to begin with). Instead of waiting 5 minutes for the entire XML to be generated in full before then sending it to the client, you should send the XML in pieces as they are being generated. Not only does that keep the connection active, but it also reduces the server's memory footprint since it doesn't have to store the entire XML in memory at one time.
TIdHTTPServer does not (yet) natively support sending chunked responses (but TIdHTTP does support receiving chunked responses), however it would not be very difficult to implement manually. Write a custom TStream derived class and overwrite its virtual Write() method (or use Indy's TIdEventStream class) to write data to the HTTP client using the format outlined in RFC 2616 Section 3.6.1. With that, you can have ServeXmlDoc() set the ResponseInfo.TransferEncoding property to 'chunked' and call the ResponseInfo.WriteHeader() method without setting either the ResponseInfo.ContentText or ResponseInfo.ContentStream properties, then pass your custom stream to IXMLDocument.SaveToStream() so it will finish writing the response data after the headers. For example:
type
TMyChunkedStream = class(TStream)
private
fIO: TIdIOHandler;
public
constructor Create(AIO: TIdIOHandler);
function Write(const Buffer; Count: Longint): Longint; override;
procedure Finished;
...
end;
constructor TMyChunkedStream.Create(AIO: TIdIOHandler);
begin
inherited Create;
fIO := AIO;
end;
function TMyChunkedStream.Write(const Buffer; Count: Longint): Longint; override;
begin
if Count > 0 then
begin
fIO.WriteLn(IntToHex(Count, 1));
fIO.Write(RawToBytes(Buffer, Count));
fIO.WriteLn;
end;
Result := Count;
end;
procedure TMyChunkedStream.Finished;
begin
fIO.WriteLn('0');
fIO.WriteLn;
end;
procedure ServeXmlDoc(XmlDoc: IXMLDocument; ResponseInfo: TIdHTTPResponseInfo);
var
TempStream: TMyChunkedStream;
begin
ResponseInfo.ContentType := 'text/xml';
ResponseInfo.TransferEncoding := 'chunked';
ResponseInfo.WriteHeader;
TempStream := TMyChunkedStream.Create(ResponseInfo.Connection.IOHandler);
try
XMLDoc.SaveToStream(TempStream);
TempStream.Finished;
finally
TempStream.Free;
end;
end;
If, on the other hand, the bulk of your waiting is inside of GenerateXml() and not in XmlDoc.SaveToStream(), then you need to rethink your server design, and figure out a way to speed up GenerateXml(), or just get rid of IXMLDocument and create the XML manually so you can send it using the ResponseInfo.Connection.IOHandler as you are creating the XML content.