簡介
交易者可能希望安排一次郵寄活動,以維持與其他交易者、訂戶、客戶或朋友的業(yè)務(wù)關(guān)系。此外,可能需要發(fā)送截圖、日志或報告。這些任務(wù)可能不是最經(jīng)常出現(xiàn)的任務(wù),但是擁有這樣的特性顯然是一個優(yōu)勢。在這里使用方便的MQL工具絕對是困難的,甚至是完全不可能的。在本文的最后,我們將回到使用專門的MQL工具來解決此任務(wù)的問題。在此之前,我們將使用MQL和C#的組合。這將使我們相對容易地編寫必要的代碼并將其連接到終端,此外,它還將設(shè)置一個與此連接相關(guān)的非常有趣的挑戰(zhàn)。
本文面向初學(xué)者和中層開發(fā)人員,他們希望加深他們對編寫庫的知識,并將它們與終端集成,同時更加熟悉Google服務(wù)。
設(shè)置任務(wù)
現(xiàn)在,讓我們更準(zhǔn)確地定義我們將要做什么,有一個可更新的聯(lián)系人列表,允許用戶向列表中的任何聯(lián)系人發(fā)送一次或多次帶有附件的電子郵件。需要考慮的事項:
- 列表中的某些聯(lián)系人可能沒有地址,或者地址不正確。此外,可能有多個地址。
- 可以更改列表-可以添加或刪除聯(lián)系人。
- 聯(lián)系人可以重復(fù)。
- 它們也可能被排除在郵寄活動之外,而留在列表中。換句話說,聯(lián)系人的活動應(yīng)該是可調(diào)整的。
- 此外,列表中肯定會包含與相關(guān)任務(wù)無關(guān)的聯(lián)系人。
實施列表管理是最明顯的任務(wù)。我們這里有什么選擇?
- HDD 數(shù)據(jù)庫或 CSV 文件不方便且不夠可靠。它并不總是可用的,可能需要一個額外的軟件來管理這樣的存儲。
- 一個特殊網(wǎng)站的數(shù)據(jù)庫,有一個Joomla類型的CMS,這是一個很好的解決方案。數(shù)據(jù)受到保護(hù),可以從任何地方訪問。此外,電子郵件可以很容易地從網(wǎng)站發(fā)送。然而,也有一個顯著的缺點。與此類網(wǎng)站交互需要一個特殊的附加組件,這樣的附加組件可能非常大,并且充滿了安全漏洞。換句話說,這里必須有可靠的基礎(chǔ)設(shè)施。
- 使用現(xiàn)成的 Google 服務(wù)。在那里,您可以安全地存儲和管理聯(lián)系人,以及從不同的設(shè)備訪問他們。尤其是,您可以形成各種列表(組)并發(fā)送電子郵件。這就是我們舒適工作所需要的一切,所以我們堅持這個選擇。
與 Google 交互有大量的文檔記錄,例如這里。要開始使用Google,請注冊一個帳戶并在那里創(chuàng)建聯(lián)系人列表,該列表應(yīng)包含我們要向其發(fā)送電子郵件的聯(lián)系人。在“聯(lián)系人”中,創(chuàng)建具有特定名稱的組,例如“Forex”,并將所選聯(lián)系人添加到該組中。每個聯(lián)系人都可以保存多個數(shù)據(jù)以備以后使用。不幸的是,如果用戶仍然需要一個額外的數(shù)據(jù)字段,則無法創(chuàng)建它,這不會造成不便,因為有很多數(shù)據(jù)字段可用,稍后我將演示如何使用它們,
現(xiàn)在是時候開始主要任務(wù)了。
谷歌方面的準(zhǔn)備工作
假設(shè)我們已經(jīng)有一個谷歌帳戶,使用谷歌“開發(fā)控制臺”恢復(fù)項目開發(fā)。在這里您可以詳細(xì)了解如何使用控制臺和開發(fā)項目,當(dāng)然,上面的鏈接將介紹另一個項目。我們的項目需要一個名字,讓它名為 ' WorkWithPeople'。我們還需要其他服務(wù),在此階段,啟用以下選項:
第一個提供了對聯(lián)系人列表的訪問(實際上,它也提供了對其他事物的訪問,但我們只需要列表)。有另一個用于訪問聯(lián)系人列表的服務(wù)-Contacts API,但目前不建議使用,因此我們不注意它。
顧名思義,第二個服務(wù)提供對郵件的訪問。
啟用服務(wù)并獲取授予應(yīng)用程序訪問它們的密鑰。不需要寫下來或者記住,以 json 格式下載附件,其中包含訪問 Google 資源所需的所有數(shù)據(jù),包括這些密鑰。將文件保存在磁盤上,也許可以給它一個更有意義的名稱。在我的例子中,它被稱為“WorkwithPeople_gmail.json”。這就完成了與谷歌的直接操作,我們已經(jīng)創(chuàng)建了帳戶、聯(lián)系人列表和項目,并獲得了訪問文件。
現(xiàn)在,讓我們繼續(xù)在 VS 2017 工作。
項目和開發(fā)包
打開 VS 2017并創(chuàng)建一個標(biāo)準(zhǔn)Class Library(.NET Framework)項目。以任何可記憶的方式命名它(在我的例子中,它與Google項目名稱“WorkwithPeople”一致,盡管這不是必須的)。使用NuGet立即安裝其他軟件包:
- Google.Apis
- Google.Apis.People.v1
- Google.Apis.PeopleService.v1
- Google.Apis.Gmail.v1
- MimeKit
在安裝過程中,Nuget提供安裝相關(guān)軟件包的服務(wù),要對此表示同意。在我們的例子中,該項目接收谷歌的軟件包,用于處理聯(lián)系人和管理電子郵件。現(xiàn)在我們準(zhǔn)備好開發(fā)代碼了。
訪問聯(lián)系人
讓我們從輔助類開始,如果我們考慮到某個谷歌聯(lián)系人所包含的數(shù)據(jù)量,那么很明顯,我們的任務(wù)不需要它的主要部分,我們需要一個聯(lián)系人姓名和地址發(fā)送電子郵件。事實上,我們還需要來自另一個欄位的數(shù)據(jù),但稍后會討論更多。
相應(yīng)的類可以如下所示:
namespace WorkWithPeople
{
internal sealed class OneContact
{
public OneContact(string n, string e)
{
this.Name = n;
this.Email = e;
}
public string Name { get; set; }
public string Email { get; set; }
}
}
有兩個“字符串”類型的屬性存儲聯(lián)系人姓名和地址,以及一個簡單的構(gòu)造函數(shù),其中包含兩個參數(shù)來初始化它們,沒有實現(xiàn)另外的檢查,它們將在其它地方進(jìn)行。
讀取聯(lián)系人列表時會創(chuàng)建簡單元素列表。這允許根據(jù)這個新構(gòu)建的列表的數(shù)據(jù)進(jìn)行郵件活動。如果要更新列表,請刪除所有列表元素,然后重復(fù)從Google帳戶讀取和選擇數(shù)據(jù)的操作。
還有另一個輔助類,聯(lián)系人列表可能包含無效的電子郵件地址,或者根本沒有地址,在發(fā)送電子郵件之前,我們需要確保地址正確無誤,讓我們開發(fā)新的輔助類來實現(xiàn)這一點:
namespace WorkWithPeople
{
internal static class ValidEmail
{
public stati cbool IsValidEmail(this string source) => !string.IsNullOrEmpty(source) && new System.ComponentModel.DataAnnotations.EmailAddressAttribute().IsValid(source);
}
}
為了執(zhí)行檢查,我們使用可用的工具,盡管我們也可以使用正則表達(dá)式。為了便于進(jìn)一步使用,我們開發(fā)了代碼作為擴展方法。由于不難猜測,如果包含郵件地址的字符串通過檢查,則該方法返回true,否則返回false?,F(xiàn)在是時候轉(zhuǎn)到代碼的主要部分了。
訪問和使用服務(wù)
我們已經(jīng)創(chuàng)建了項目,獲取了密鑰,并下載了用于授權(quán)應(yīng)用程序的JSON文件。因此,讓我們創(chuàng)建一個新的類ContactsPeople并將適當(dāng)?shù)某绦蚣砑拥轿募校?/p>
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Net.Mail;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.People.v1;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Google.Apis.Http;
using Google.Apis.PeopleService.v1;
using Google.Apis.PeopleService.v1.Data;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
namespace WorkWithPeople
{
internal sealed class ContactsPeople
{
public static string Applicationname { get; } = 'WorkWithPeople';
.....
添加包含Google項目名稱的靜態(tài)屬性,此靜態(tài)屬性為只讀。
將封閉字段和枚舉添加到類:
private enum PersonStatus
{
Active,
Passive
};
private string _groupsresourcename;
private List<OneContact> _list = new List<OneContact>();
private UserCredential _credential;
private PeopleService _pservice;
private GmailService _gservice;
枚舉用于將聯(lián)系人標(biāo)記為“主動”(接收電子郵件)和“被動”(不接收電子郵件)。其他封閉字段:
- _groupsresourcename. 與“聯(lián)系人”中創(chuàng)建的組相對應(yīng)的Google資源名稱,(在我們的例子中,所選的組名是“Forex”)。
- _list. 郵寄活動要應(yīng)用到的聯(lián)系人列表。
- _credential. 應(yīng)用 'powers'.
- _pservice, _gservice. 用于處理聯(lián)系人和郵件的服務(wù)。
讓我們編寫主要工作函數(shù)的代碼:
publicint WorkWithGoogle(string credentialfile,
string user,
string filedatastore,
string groupname,
string subject,
string body,
bool isHtml,
List<string> attach = null)
{
...
其參數(shù)是:
- credentialfile. 訪問包含用于訪問服務(wù)的所有數(shù)據(jù)的JSON文件的名稱和路徑,它以前是從谷歌賬戶下載的。
- user. 谷歌帳戶名 - xxxxx@gmail.com地址。
- filedatastore. 輔助文件夾的名稱-用戶PC上的存儲(可以是任意的)。該文件夾是在 AppData(%APPDATA%)中創(chuàng)建的,其中包含包含附加訪問數(shù)據(jù)的文件。
- groupname. 我們創(chuàng)建的郵寄活動的聯(lián)系人組的名稱,在我們的例子中,它是“Forex”。
- subject, body, isHtml. 郵件主題和文本,以及是否以HTML格式編寫。
- attach. 附件列表。
返回值 - 已發(fā)送電子郵件的數(shù)量。開始編寫函數(shù)代碼:
if (!File.Exists(credentialfile))
throw (new FileNotFoundException('Not found: ' credentialfile));
using (var stream = new FileStream(credentialfile, FileMode.Open, FileAccess.Read))
{
if (_credential == null) {
_credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[]
{
GmailService.Scope.GmailSend,
PeopleService.Scope.ContactsReadonly
},
user,
CancellationToken.None,
new FileDataStore(filedatastore)).Result;
CreateServicies();
}
else if (_credential.Token.IsExpired(Google.Apis.Util.SystemClock.Default)) {
bool refreshResult = _credential.RefreshTokenAsync(CancellationToken.None).Result;
_list.Clear();
if (!refreshResult) return 0;
CreateServicies();
}
}// using (var stream = new FileStream(credentialfile, FileMode.Open, FileAccess.Read))
請注意定義應(yīng)請求服務(wù)的訪問權(quán)限的字符串?dāng)?shù)組:
- GmailService.Scope.GmailSend. 這是發(fā)送電子郵件的權(quán)限。
- PeopleService.Scope.ContactsReadonly. 以只讀模式訪問聯(lián)系人。
另外,注意調(diào)用 GoogleWebAuthorizationBroker.AuthorizeAsync. 它的名稱表明調(diào)用是異步執(zhí)行的。
請注意,如果以前收到的令牌已過期,代碼將更新它并從以前形成的 _list 中刪除所有對象。
輔助的 CreateServicies() 函數(shù)創(chuàng)建并初始化必要的對象:
private void CreateServicies()
{
_pservice = new PeopleService(new BaseClientService.Initializer()
{
HttpClientInitializer = _credential,
ApplicationName = Applicationname
});
_gservice = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = _credential,
ApplicationName = Applicationname
});
}
如我們所見,在執(zhí)行上面顯示的代碼段后,我們可以訪問必要的服務(wù):
- 使用JSON數(shù)據(jù)文件,我們首先請求“powers”,并將它們保存在_credential字段中。然后我們調(diào)用將“power”和項目名稱字段傳遞給它們的服務(wù)構(gòu)造函數(shù)作為初始化列表。
現(xiàn)在是獲取為郵寄活動選擇的組的聯(lián)系人列表的時間:
try {
if (_list.Count == 0)
GetPeople(_pservice, null, groupname);
}
catch (Exception ex) {
ex.Data.Add('call GetPeople: ', ex.Message);
throw;
}
#if DEBUG
int i = 1;
foreach (var nm in _list) {
Console.WriteLine('{0} {1} {2}', i , nm.Name, nm.Email);
}
#endif
if (_list.Count == 0) {
Console.WriteLine('Sorry, List is empty...');
return 0;
}
GetPeople(…)函數(shù)(稍后描述)是要填充存儲聯(lián)系人的 _list。此函數(shù)用作異常源,因此其塊包裝在try代碼塊中。在已連接的程序集中未檢測到異常類型,因此catch塊以最一般的形式寫入。換句話說,我們不必在這里包括所有可能發(fā)生的事件,這樣就不會丟失用于調(diào)試的有價值的數(shù)據(jù)。因此,向異常添加您認(rèn)為必要的數(shù)據(jù)并重新激活它。
請記住,只有當(dāng)它為空時(即當(dāng)它接收到新令牌或更新舊令牌時),才會更新_ list。
下一個塊僅對調(diào)試版本執(zhí)行. 整個形成的列表只顯示在控制臺中。
最后一塊很明顯,如果名單仍然空白,則進(jìn)一步的工作沒有點,并被相應(yīng)的信息所阻止。
該函數(shù)以形成一個傳出電子郵件并進(jìn)行郵件活動而結(jié)束:
using (MailMessage mail = new MailMessage
{
Subject = subject,
Body = body,
IsBodyHtml = isHtml
}) // MailMessage mail = new MailMessage
{
if (attach != null)
{
foreach (var path in attach)
mail.Attachments.Add(new Attachment(path));
} // if (attach != null)
foreach (var nm in _list)
mail.To.Add(new MailAddress(nm.Email, nm.Name));
try
{
SendOneEmail(_gservice, mail);
}
catch (Exception ex)
{
ex.Data.Add('call SendOneEmail: ', ex.Message);
throw;
}
}// using (MailMessage mail = new MailMessage
此處創(chuàng)建了MailMessage庫類的實例。接下來是它隨后的初始化和填充字段。如果有附件,則添加附件列表。最后,形成前一階段獲得的郵件列表。
郵寄由SendOneEmail(…)稍后描述的函數(shù)執(zhí)行。就像GetPeople(…)函數(shù)一樣,它也可能成為異常源。因此,它的調(diào)用也被包裝在try代碼中,而在catch中的處理也是類似的。
此時,WorkWithGoogle(…) 主函數(shù)的工作被認(rèn)為是完成的,它返回_list.Count值,假設(shè)電子郵件消息從列表發(fā)送到每個聯(lián)系人。
填寫聯(lián)系人列表
獲取訪問權(quán)限后,準(zhǔn)備好填寫_list,這是通過以下函數(shù)完成的:
private void GetPeople(PeopleService service, string pageToken, string groupName)
{
...
其參數(shù)是:
- service. 指向先前創(chuàng)建的Google聯(lián)系人訪問類的鏈接。
- pageToken. 可能有多個聯(lián)系人。此參數(shù)告訴開發(fā)人員聯(lián)系人列表占用多個頁面。
- groupName. 我們感興趣的聯(lián)系人組的名稱,
第一次使用 pageToken = NULL 調(diào)用函數(shù),如果對 Google 的請求隨后返回值不同于NULL的令牌,則遞歸調(diào)用該函數(shù)。
if (string.IsNullOrEmpty(_groupsresourcename))
{
ContactGroupsResource groupsResource = new ContactGroupsResource(service);
ContactGroupsResource.ListRequest listRequest = groupsResource.List();
ListContactGroupsResponse response = listRequest.Execute();
_groupsresourcename = (from gr in response.ContactGroups
where string.Equals(groupName.ToUpperInvariant(), gr.FormattedName.ToUpperInvariant())
select gr.ResourceName).Single();
if (string.IsNullOrEmpty(_groupsresourcename))
throw (new MissingFieldException($'Can't find GroupName: {groupName}'));
}// if (string.IsNullOrEmpty(_groupsresourcename))
我們需要通過組名找到一個資源名,為此,請求所有資源的列表,并在簡單的 lambda 表達(dá)式中找到所需的資源。請注意,應(yīng)該只有一個具有所需名稱的資源,如果在工作期間找不到資源,則啟用異常。
Google.Apis.PeopleService.v1.PeopleResource.ConnectionsResource.ListRequest peopleRequest =
new Google.Apis.PeopleService.v1.PeopleResource.ConnectionsResource.ListRequest(service, 'people/me')
{
PersonFields = 'names,emailAddresses,memberships,biographies'
};
if (pageToken != null) {
peopleRequest.PageToken = pageToken;
}
讓我們構(gòu)造對谷歌的請求,以獲得必要的列表,為此,請指定我們感興趣的谷歌聯(lián)系人數(shù)據(jù)中的字段:
- names, emailAddresses. 用于創(chuàng)建 OneContact 類的實例.
- memberships. 檢查聯(lián)系人是否屬于我們組。
- biographies. 選擇此字段是為了管理聯(lián)系人活動,盡管它是為了存儲聯(lián)系人的個人簡歷而設(shè)計的。為了使聯(lián)系人被識別為活動聯(lián)系人并向其地址發(fā)送電子郵件,字段必須以一開頭。在任何其他情況下,即使聯(lián)系人位于必要的組中,也被認(rèn)為是被動的和忽略的。沒有必要為此使用這個特定的字段,在我們的例子中,它的選擇可能是因為它的使用相對較少。這對于用戶管理郵件活動非常方便,因為它允許“啟用/禁用”某些聯(lián)系人。
最后,提出請求:
var request = peopleRequest.Execute();
var list1 = from person in request.Connections
where person.Biographies != null
from mem in person.Memberships
where string.Equals(_groupsresourcename, mem.ContactGroupMembership.ContactGroupResourceName) &&
PersonActive(person.Biographies.FirstOrDefault()?.Value) == PersonStatus.Active
let name = person.Names.First().DisplayName
orderby name
let email = person.EmailAddresses?.FirstOrDefault(p => p.Value.IsValidEmail())?.Value
where !string.IsNullOrEmpty(email)
select new OneContact(name, email);
_list.AddRange(list1);
if (request.NextPageToken != null) {
GetPeople(service, request.NextPageToken, groupName);
}
}//void GetPeople(PeopleService service, string pageToken, string groupName)
發(fā)出請求并對 lambda 表達(dá)式中的必要數(shù)據(jù)進(jìn)行排序,它看起來相當(dāng)笨重,但實際上相當(dāng)簡單。一個聯(lián)系人應(yīng)該有一個非零的個人簡歷,在正確的組中,是一個活躍的聯(lián)系人,并有一個正確的地址。讓我們在這里展示通過“biographies”字段內(nèi)容定義單個聯(lián)系人的“active/passive”狀態(tài)的功能:
private PersonStatus PersonActive(string value)
{
try {
switch (Int32.Parse(value))
{
case 1:
return PersonStatus.Active;
default:
return PersonStatus.Passive;
}
}
catch (FormatException) { return PersonStatus.Passive; }
catch (OverflowException) { return PersonStatus.Passive; }
}//PersonStatus PersonActive(string value)
這是項目中唯一一個不尋求重新啟用異常(嘗試在現(xiàn)場處理其中一些異常)的函數(shù)。
我們快完成了!將獲得的列表添加到_list中,如果沒有讀取所有聯(lián)系人,則使用新的標(biāo)記值遞歸調(diào)用函數(shù)。
發(fā)送電子郵件
這由以下輔助函數(shù)執(zhí)行:
private void SendOneEmail(GmailService service, MailMessage mail)
{
MimeKit.MimeMessage mimeMessage = MimeKit.MimeMessage.CreateFromMailMessage(mail);
var encodedText = Base64UrlEncode(mimeMessage.ToString());
var message = new Message { Raw = encodedText };
var request = service.Users.Messages.Send(message, 'me').Execute();
}// bool SendOneEmail(GmailService service, MailMessage mail)
它的調(diào)用如上所述,這個簡單函數(shù)的目的是為發(fā)送和執(zhí)行郵件活動準(zhǔn)備電子郵件,此外,該函數(shù)還具有所有“繁重”的準(zhǔn)備工作。不幸的是,Google不接受MailMessage類形式的數(shù)據(jù),因此,以可接受的形式準(zhǔn)備數(shù)據(jù)并對其進(jìn)行編碼。MimeKit程序集包括執(zhí)行編碼的工具。但是,我相信使用一個我們可以使用的簡單函數(shù)要容易得多,我不會在這里展示它,因為它很簡單。請注意在Service.Users.Messages.Send調(diào)用中的string類型的userId,它等于“me”的特殊值,允許Google訪問您的帳戶以獲取發(fā)送者數(shù)據(jù)。
這就結(jié)束了對ContactsPeople類的分析,其余的函數(shù)是輔助的,因此沒有必要停留在它們上面。
終端連接器
唯一剩下的問題是將(未完成的)組件連接到終端。乍一看,任務(wù)很簡單,定義幾個靜態(tài)方法,編譯項目并將其復(fù)制到終端的庫文件夾中。從MQL代碼調(diào)用程序集的靜態(tài)方法。但我們究竟應(yīng)該復(fù)制什么呢?有一個dll庫形式的程序集。在我們的工作中,NuGet還下載了十幾個程序集。有一個JSON文件存儲用于訪問Google的數(shù)據(jù)。讓我們嘗試將整個集合復(fù)制到“Libraries”文件夾。創(chuàng)建一個基本的MQL腳本(這里沒有附加代碼的意義),然后嘗試從程序集調(diào)用靜態(tài)方法。異常!沒有找到 Google.Apis.dll,這是一個非常令人不快的驚喜,這意味著CLR無法找到所需的程序集,盡管它與我們的主程序集位于同一文件夾中。為什么會這樣?不值得詳細(xì)研究這里的情況。所有對細(xì)節(jié)感興趣的人都可以在 Richter 的名著中找到它們(在關(guān)于尋找私有程序庫的章節(jié)中)。
已經(jīng)有許多與 MetaTrader 一起工作的功能完備的.NET應(yīng)用程序的例子,在那里也發(fā)生了這樣的問題。它們是怎么解決的呢?在這里通過在.NET應(yīng)用程序和MQL程序之間創(chuàng)建通道解決了這個問題,而這里使用了一種基于事件的模式。我可以建議使用類似的方法,使用命令行將所需數(shù)據(jù)從MQL程序傳遞到.NET應(yīng)用程序。
但值得考慮的是更“優(yōu)雅”,簡單和普遍的解決方案。我的意思是使用 AppDomain.AssemblyResolve事件管理程序集下載。當(dāng)執(zhí)行要求無法按名稱綁定程序集時,會發(fā)生此事件。在這種情況下,事件處理程序可以從另一個文件夾(擁有處理程序知道的地址)加載和返回程序集。因此,在這里提出一個相當(dāng)漂亮的解決方案:
- 在 “Libraries” 文件夾中創(chuàng)建一個具有不同名稱的文件夾(在我的例子中,它是“WorkWithPeople”),
- 將其方法導(dǎo)入到帶有MQL的文件中的程序集復(fù)制到 “Libraries”文件夾。
- 所有其他項目程序集,包括包含訪問Google服務(wù)數(shù)據(jù)的JSON文件,都被復(fù)制到“WorkWithPeople”文件夾中。
- 讓libraries文件夾中的主程序集知道它應(yīng)該在哪里查找其他程序集 - “WorkWithPeople”文件夾的完整路徑。
這樣,我們就得到了一個可行的解決方案,而不會把“Libraries”文件夾弄亂。剩下的就是在代碼中實現(xiàn)決策了。
控制類
讓我們創(chuàng)建一個靜態(tài)類
public static class Run
{
static Run() {
AppDomain.CurrentDomain.AssemblyResolve = ResolveAssembly;
}// Run()
并向其中添加事件處理程序,以便它盡快出現(xiàn)在處理程序鏈中。讓我們定義處理程序本身:
static Assembly ResolveAssembly(object sender, ResolveEventArgs args) {
String dllName = new AssemblyName(args.Name).Name '.dll';
return Assembly.LoadFile(Path.Combine(_path, dllName) );
}// static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
現(xiàn)在,每當(dāng)檢測到程序集時,都會調(diào)用此處理程序。其目標(biāo)是下載并返回程序集,該程序集結(jié)合了_path變量和計算名稱之間的路徑?,F(xiàn)在,只有處理程序找不到程序集時才會出現(xiàn)異常。
初始化函數(shù)如下:
WorkwithPeoplepublic static void Initialize(string Path, string GoogleGroup, string AdminEmail, string Storage)
{
if (string.IsNullOrEmpty(Path) ||
string.IsNullOrEmpty(GoogleGroup) ||
string.IsNullOrEmpty(AdminEmail) ||
string.IsNullOrEmpty(Storage)) throw (new MissingFieldException('Initialize: bad parameters'));
_group = GoogleGroup;
_user = AdminEmail;
_storage = Storage;
_path = Path;
}// Initialize(string Path, string GoogleGroup, string AdminEmail, string Storage)
在嘗試發(fā)送電子郵件之前,應(yīng)首先調(diào)用此函數(shù),其參數(shù)是:
- Path. 處理程序查找程序集以及用于訪問Google的數(shù)據(jù)文件所在的路徑。
- GoogleGroup. 用于郵寄的聯(lián)系人中的組的名稱。
- AdminEmail. 帳戶名/郵件地址(xxx@google.com),代表該帳戶進(jìn)行郵件發(fā)送。
- Storage. 存儲一些附加數(shù)據(jù)的輔助文件的名稱。
所有描述的參數(shù)不應(yīng)為空字符串,否則將激活異常。
為包含的文件創(chuàng)建一個列表和一個簡單的添加函數(shù):
public static void AddAttachment (string attach) { _attachList.Add(attach);}
該函數(shù)沒有錯誤檢查工具,因為它處理的是在 MetaTrader 環(huán)境中初步創(chuàng)建的屏幕截圖和其他文件。假設(shè)這是由終端中工作的控制工具完成的。
讓我們馬上創(chuàng)建一個郵寄對象
static ContactsPeople _cContactsPeople = new ContactsPeople();
并通過調(diào)用函數(shù)來執(zhí)行:
public static int DoWork(string subject, string body, bool isHtml = false) {
if (string.IsNullOrEmpty(body))
throw (new MissingFieldException('Email body null or empty'));
int res = 0;
if (_attachList.Count > 0) {
res = _cContactsPeople.WorkWithGoogle(Path.Combine(_path, 'WorkWithPeople_gmail.json'),
_user,
_storage,
_group,
subject,
body,
isHtml,
_attachList);
_attachList.Clear();
} else {
res = _cContactsPeople.WorkWithGoogle(Path.Combine(_path, 'WorkWithPeople_gmail.json'),
_user,
_storage,
_group,
subject,
body,
isHtml);
}// if (_attachList.Count > 0) ... else ...
return res;
}// static int DoWork(string subject, string body, bool isHtml = false)
輸入?yún)?shù)如下:
- subject. 電子郵件主題。
- body. 電子郵件文本。
- isHtml. 電子郵件是否具有HTML格式。
有兩個選項可以調(diào)用_cContactsPeople.WorkWithGoogle,具體取決于電子郵件功能附件。調(diào)用的第一個參數(shù)特別有趣:
Path.Combine(_path, 'WorkWithPeople_gmail.json')
這是包含用于訪問Google服務(wù)的數(shù)據(jù)的文件的完整路徑。
DoWork(…)函數(shù)返回已發(fā)送電子郵件的數(shù)量。
除用于訪問Google的數(shù)據(jù)文件外,VS 2017的整個項目位于所附的google.zip檔案中。
MetaTrader 方面的準(zhǔn)備工作
程序集代碼已就緒,讓我們繼續(xù)到終端,在那里創(chuàng)建一個簡單的腳本??梢赃@樣編寫(跳過開頭部分代碼):
#import 'WorkWithPeople.dll'
void OnStart()
{
string scr = 'scr.gif';
string fl = TerminalInfoString(TERMINAL_DATA_PATH) '\\MQL5\\Files\\';
ChartScreenShot(0, scr, 800, 600);
Run::Initialize('e:\\Forex\\RoboForex MT5 Demo\\MQL5\\Libraries\\WorkWithPeople\\' ,'Forex' ,'ХХХХХХ@gmail.com' ,'WorkWithPeople' );
Run::AddAttachment(fl scr);
int res = Run::DoWork('some subj' ,
'Very big body' ,
false );
Print('result: ', res);
}
代碼非常清楚。導(dǎo)入程序集。我們要做的第一件事是初始化它,添加先前制作的屏幕截圖并執(zhí)行郵件發(fā)送。完整的代碼可以在附加文件 google_test1.mq5中找到。
另一個例子是在M5上工作的指標(biāo),每次檢測到新燭形時都會發(fā)送帶有屏幕截圖的電子郵件:
#import 'WorkWithPeople.dll'
input string scr='scr.gif';
string fp;
int OnInit()
{
fp=TerminalInfoString(TERMINAL_DATA_PATH) '\\MQL5\\Files\\';
Run::Initialize('e:\\Forex\\RoboForex MT5 Demo\\MQL5\\Libraries\\WorkWithPeople\\','Forex','0ndrei1960@gmail.com','WorkWithPeople');
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(IsNewCandle())
{
ChartScreenShot(0,scr,800,600);
Run::AddAttachment(fp scr);
string body='Time: ' TimeToString(TimeLocal());
int res=Run::DoWork('some subj',body,false);
Print(body);
}
return(rates_total);
}
指標(biāo)的完整代碼可以在所附 google_test2.mq5 文件中找到,它非常簡單,因此不需要進(jìn)一步的討論。
結(jié)論
讓我們看看結(jié)果,我們分析了使用谷歌聯(lián)系人與合作伙伴進(jìn)行交互,以及將程序集與終端集成的方法,使用戶可以避免將文件夾與不必要的文件混淆。匯編代碼的效率也值得一提,我們對這一問題的關(guān)注不夠,但可以提供一系列活動來解決這一問題:
- 在谷歌中劃分授權(quán)和發(fā)送電子郵件的目標(biāo)。由計時器在單獨的線程中進(jìn)行授權(quán)。
- 嘗試使用線程池發(fā)送電子郵件。
- 使用異步工具對電子郵件附件進(jìn)行“大量”編碼。
這并不意味著您應(yīng)該使用所有這些方法,但它們的使用可能會提高性能,并允許將生成的程序集作為單獨流程的一部分與MetaTrader一起單獨應(yīng)用。
總之,讓我們回到使用MQL工具解決此任務(wù)的問題,有可能嗎?根據(jù)谷歌文檔,答案是肯定的,使用GET/POSTt請求可以獲得相同的結(jié)果,并提供適當(dāng)?shù)氖纠?。因此,可以使用常?guī)的WebRequest。這種方法的可行性仍然是一個爭論的問題,由于請求的數(shù)量非常多,因此很難編寫、調(diào)試和維護(hù)這樣的代碼。
文章中使用的程序
# |
名稱
|
類型
|
描述 |
1 |
google_test1.mq5
|
腳本
|
制作屏幕截圖并發(fā)送到多個地址的腳本。
|
2
|
google_test1.mq5 |
指標(biāo)
|
在每個新燭形上發(fā)送電子郵件的示例指標(biāo)
|
3 |
google.zip |
檔案 |
程序集和測試控制臺應(yīng)用程序項目。 |
|