I know how to send normal Text via WinHTTP 5.1 Automation and how to turn the reponse stream into an BigText object.
Now I want to send the content of a BigText via POST/PUT basicly this:
CREATE(bigText);
bigText.ADDTEXT('...');
...
CREATE(HTTP, TRUE, TRUE);
HTTP.OPEN('PUT', 'https://...', FALSE);
HTTP.SetCredentials('...', '...', 0);
HTTP.SEND(bigText);
The codeunit actually compiles and the automation object does send the request to the server, however with empty request body.
I tried to use OutStream but than the codeunit does not compile (Automation := OutStream).
I am using Dynamics NAV 2009 SP1, so no DotNet DataType available as well.
I got it to work by a stream juggle
// Variable Declaration:
// HTTP = Automation WinHTTP Services 5.1
// TempBlob = Record <TEMPORARY=YES> TempBlob
// blobOutStream = OutStream
// RequestBodyBigText = BigText
// ResponseBodyBigText = BigText
// RequestInStream = InStream
// ReponseInStream = InStream
// create WinHTTP client, force new instance, don't run locally
CREATE(HTTP, TRUE, FALSE);
HTTP.Open('PUT', '...', FALSE);
HTTP.SetCredentials('...', '....', 0);
// ...
// create InStream from BigText by the help of Temporary=YES TempBlob Record
TempBlob.INIT;
TempBlob."Blob".CREATEOUTSTREAM(blobOutStream);
// write the content of the reuquest body to the temp blob
RequestBodyBigText.WRITE(blobOutStream);
// important, calcfield the temp blob so that we can use the content
// in a new stream
TempBlob.CALCFIELDS("Blob");
TempBlob."Blob".CREATEINSTREAM(RequestInStream);
// send the stream
HTTP.Send(RequestInStream);
// timeout is in seconds
IF HTTP.WaitForResponse(30) THEN BEGIN
ResponseInStream := HTTP.ResponseStream;
CLEAR(ResponseBodyBigText);
ReponseBodyBigText.READ(ResponseInStream);
END;
// now we have a big text (ResponseBodyBigText) filled with the body of the response
If you encounter encoding problems you replace the ResponsBodyBigText.READ with a convertion function and a EOS loop. If you can't use DotNet Interop DataTypes (like me) you can use the ADOStream automation with charset set to UTF-8 or you use an own written COM Object (like I did)
Related
I have a little problem with my outlook interface.
I'm using <outlook\msoutl.tlh> to create and fill an e-mail item automatically.
But now I want to display my signature too.
I noticed that if I don't set the mail body, the signature is added automatically. But as soon as I want to set the mail body, the signature is not shown. It seems that it will be overwritten. So I tried the following :
string standardText = "Hello Outlook"
string signature = IMailItemPtr->Body;
IMailItemPtr->Body = standardText + signature
So first the signature has to be cached and then appended to the standard text for the body.
But here I noticed that the body is empty until set (which is logical).
But now I wonder where my signature is stored and how I can get it?
Here is the important part of my code:
string standardText = "Hello Outlook"
Outlook::_ApplicationPtr spOutlook(__uuidof(Outlook::Application));
// Get the MAPI namespace
Outlook::_NameSpacePtr pMAPI = spOutlook->GetNamespace("MAPI");
// Initiate a new Outlook-session
pMAPI->Logon("", "", false, true); // Log on by using the default profile or existing session (no dialog box).
// Query the MailItem interface
Outlook::_MailItemPtr IMailItemPtr = spOutlook->CreateItem(Outlook::olMailItem);
IMailItemPtr->Subject = mailSubject;
IMailItemPtr->Body = standardText.c_str();
I've been using Boost.Beast for awhile now to send HTTP requests and I think it's great and works perfectly. But does anyone know if it's possible to construct an HTTP batch request using Beast? I'm thinking something like creating several subrequests:
boost::beast::http::request<boost::beast::http::string_body> subrequest_1;
subrequest_1.set(boost::beast::http::field::content_type, "application/http");
...
boost::beast::http::request<boost::beast::http::string_body> subrequest_2;
subrequest_2.set(boost::beast::http::field::content_type, "application/http");
...
and then somehow group them together and send them all in one request.
I've been trying to create a vector, add the subrequests to it and then assign the vector to the body of the request I'd like to send, but that hasn't been successful.
/*
std::vector<boost::beast::http::request<boost::beast::http::string_body>> v;
v.push_back(subrequest_1);
v.push_back(subrequest_2);
boost::beast::http::request<boost::beast::http::string_body> request;
request.method(boost::beast::http::verb::post);
...
request.body() = v;
*/
Thanks in advance!
Well this one turned out to be very simple. I'll just post my solution here in case anyone else feels as lost as I did a couple of days ago.
// Set up the request with a new content type
boost::beast::http::request<boost::beast::http::string_body> request;
request.set(field::content_type, "multipart/mixed; boundary='subrequest_boundary'");
// A string to hold the entire batch request
std::string body;
// Add all messages in e.g. a queue to the string
while ( messages.size() )
{
auto message = messages.front();
// Add a boundary to separate the messages
body.append("--subrequest_boundary");
body.append("Content-Type: application/http\n");
/*
And all more headers you need
*/
// I used the nlohmann library to add json docs
nlohmann::json payload = {
{"message",
}
};
body.append( payload.dump() );
messages.pop();
}
// Add a boundary as wrap up
body.append("--subrequest_boundary--");
// Add the batch request to the request
request.body() = body;
request.prepare_payload();
Epicor ERP 10.2.500 has been recently released with the addition of Epicor Functions. They can be called from within Method and Data Directives.
Do anybody has been able to do so with a Form Customization within Epicor?
This is possible via a REST call to your function API. In this case, I had a function that sent an email from some inputs.
private void epiButtonC1_Click(object sender, System.EventArgs args)
{
//API Key is included in the query param in this example.
var request = (HttpWebRequest)WebRequest.Create("https://{appserver}/{EpicorInstance}/api/v2/efx/{CompanyID}/{LibraryID}/{functionName}/?api-key={yourAPIKey}");
request.Method = "POST";
//All REST v2 requests also sent with authentication method (Token, Basic)
//This should be Base64 encoded
string username = "userName";
string password = "passWord";
string encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
request.Headers.Add("Authorization", "Basic " + encoded);
//Add body to correspond to request signature
request.ContentType = "application/json";
using(var writer = new StreamWriter(request.GetRequestStream()))
{
var values = new Dictionary<string, string>;
{
{"toEmailAddress", "someEmail#email.com"},
{"fromEmailAddress","someOtherEmail#email.com"},
{"body","This is the body"},
{"subject","Hello from Client Code!"}
};
string json = JsonConvert.SerializeObject(values);
writer.Write(json);
}
using (var response = request.GetResponse())
using (var reader = new StreamReader(response.GetResponseStream()))
{
var result = reader.ReadToEnd();
epiTextBoxC1.Text = result.ToString();
}
}
Haven't done it myself personally, but looking into the first post release notes about it here leads me to believe there is no out of the box solution, yet in this version/initial release.
However I'm sure you could do a HTTP request from within the Epicor customization if you have the REST API enabled in your environment.
If you create your own dll that calls the EpicorFunction you can use it within the customization. Still looking for a way to call them directly.
REST endpoint is the recommended way to perform the function call as pointed out by a-moreng.
If for some reason you cannot use this, you can use a passthrough method to any server-side BO via a customization Adapter. For instance, create an updatable BAQ which you can call from a customization using the DynamicQueryAdapter.
Pick an arbitrary table and field to save the BAQ.
Create three string parameters to store the Function library name, the function name, and a delimited list of parameters.
On the GetList method, create a Base Processing Directive.
Split your delimited parameter list and convert them to the appropriate datatypes.
Use the resulting variables to call your function.
If desired, you can pass return variables into the ttResults of the BAQ
I'm working on an app that requires extracting data from an xml web service, then I want to store that data (images+titles+datetime ...) to display it on my app then select an item and navigate to another page that displays more info about this item.
Is there a detailed tutorial that explains the parsing and storing process clearly (with the threads) because I'm gonna need it a lot for my app.Thanks!
I usually use this method, but didn't always get me what i want:
var doc = XDocument.Load(new StringReader(e.Result));
var items = from c in doc.Descendants("item")
select new RSSitem()
{
Title = c.Element("title").Value,
Photo = c.Element("img").Attribute("src").Value,
Description = c.Element("description").Value,
Link = c.Element("link").Value,
};
ListBoxNews.ItemsSource = items;
Sounds like you are in over your head (based on the vague nature of your question). So I'm offering my advise to get up to speed, so you can get started and ask a question that we can help give a definitive answer to.
With WP7 and .NET you shouldn't really have to do much manual parsing of Web Services. You should be able to add a Service Reference and generate a proxy which will handle this for you. This will also generate business objects for the data returned by your service.
Once you have that done, you can look into Windows Phone Navigation which should help you transition between pages in your application.
To consume web services:
String baseUri = “your service URI";
WebClient wc = new WebClient();
public MainPage()
{
InitializeComponent();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_downloadstringcompleted);
// event handler that will handle the ‘downloadstringsompleted’ event
wc.DownloadStringAsync(new Uri(baseUri));
// this method will download your string URI asynchronously
}
void wc_downloadstringcompleted(Object sender, DownloadStringCompletedEventArgs e)
{
// method will get fired after URI download completes
// writes your every code here
}
To parse the data:
using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
break;
case XmlNodeType.Text:
break;
case XmlNodeType.EndElement:
break;
}
}
}
}
To store in isolated storage: http://msdn.microsoft.com/en-us/library/system.io.isolatedstorage.isolatedstoragesettings%28v=vs.95%29.aspx
For navigation:
NavigationService.Navigate(new Uri("/SecondPage.xaml?msg=" + navigationstring, UriKind.Relative));
I'm researching a way to build an n-tierd sync solution. From the WebSharingAppDemo-CEProviderEndToEnd sample it seems almost feasable however for some reason, the app will only sync if the client has a live SQL db connection. Can some one explain what I'm missing and how to sync without exposing SQL to the internet?
The problem I'm experiencing is that when I provide a Relational sync provider that has an open SQL connection from the client, then it works fine but when I provide a Relational sync provider that has a closed but configured connection string, as in the example, I get an error from the WCF stating that the server did not receive the batch file. So what am I doing wrong?
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = hostName;
builder.IntegratedSecurity = true;
builder.InitialCatalog = "mydbname";
builder.ConnectTimeout = 1;
provider.Connection = new SqlConnection(builder.ToString());
// provider.Connection.Open(); **** un-commenting this causes the code to work**
//create anew scope description and add the appropriate tables to this scope
DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription(SyncUtils.ScopeName);
//class to be used to provision the scope defined above
SqlSyncScopeProvisioning serverConfig = new SqlSyncScopeProvisioning();
....
The error I get occurs in this part of the WCF code:
public SyncSessionStatistics ApplyChanges(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeData)
{
Log("ProcessChangeBatch: {0}", this.peerProvider.Connection.ConnectionString);
DbSyncContext dataRetriever = changeData as DbSyncContext;
if (dataRetriever != null && dataRetriever.IsDataBatched)
{
string remotePeerId = dataRetriever.MadeWithKnowledge.ReplicaId.ToString();
//Data is batched. The client should have uploaded this file to us prior to calling ApplyChanges.
//So look for it.
//The Id would be the DbSyncContext.BatchFileName which is just the batch file name without the complete path
string localBatchFileName = null;
if (!this.batchIdToFileMapper.TryGetValue(dataRetriever.BatchFileName, out localBatchFileName))
{
//Service has not received this file. Throw exception
throw new FaultException<WebSyncFaultException>(new WebSyncFaultException("No batch file uploaded for id " + dataRetriever.BatchFileName, null));
}
dataRetriever.BatchFileName = localBatchFileName;
}
Any ideas?
For the Batch file not available issue, remove the IsOneWay=true setting from IRelationalSyncContract.UploadBatchFile. When the Batch file size is big, ApplyChanges will be called even before fully completing the previous UploadBatchfile.
// Replace
[OperationContract(IsOneWay = true)]
// with
[OperationContract]
void UploadBatchFile(string batchFileid, byte[] batchFile, string remotePeer1
I suppose it's simply a stupid example. It exposes "some" technique but assumes you have to arrange it in proper order by yourself.
http://msdn.microsoft.com/en-us/library/cc807255.aspx