使用ODATA Web服务添加/创建/删除记录

已加入
2020年3月12日
留言内容
2
编程经验
10+
我有一个问题,C#无法通过从D365BC运行的ODATA Web服务创建/更新/删除。
PS我正在使用VS 2019,C#winforms。

我们公司将使用Dynamics 365商业中心(D365BC),该中心使用编程语言AL。
AL是一种不错的语言,但是有些事情我们只是喜欢用C#来做。
在D365BC中,您可以轻松创建表和页面(使用这些表的对象)。对于每个页面,您都可以通过单击复选框来创建Web服务。这些Web服务可用于通过SOAP / ODATA v3和ODATA v4进行通信。所有这些都可以同时使用。
因此,创建允许与D365BC中创建的表进行通信的Web服务非常容易。
如果使用SOAP Web服务,则可以执行所有CRUD功能。但是,当我使用ODATA(v3或v4)时,我可以从Web服务中读取信息,但不能创建/更新或删除。我们可能偶尔要使用ODATA的原因是ODATA比SOAP快。
我在D365BC中拥有的代码:
一个简单的表,其中每个字段都是SQL表中的一条记录。
C#:
table 50109 "Workers"
{
    DataClassification = ToBeClassified;

    fields
    {
        field(1; "No."; Code[20])
        {
            DataClassification = ToBeClassified;
        }

        field(10; "First name"; Text[50])
        {
            DataClassification = ToBeClassified;
        }

        field(20; "Last Name"; Text[50])
        {
            DataClassification = ToBeClassified;

        }

        field(40; FunctionName; Text[50])
        {
            DataClassification = ToBeClassified;

        }
    }

    trigger OnInsert()
    var
        myInt: Integer;
    begin

    end;

    trigger OnModify()
    var
        myInt: Integer;
    begin

    end;

    trigger OnDelete()
    var
        myInt: Integer;
    begin

    end;
}

使用该表并能够从已使用表中制作Web服务的卡片页。
C#:
page 50108 "Workers Card"
{
    PageType = Card;
    ApplicationArea = All;
    UsageCategory = Administration;
    SourceTable = Workers;

    layout
    {
        area(Content)
        {
            group(General)
            {
                field("No."; "No.")
                {
                    ApplicationArea = Basic;
                    Importance = Promoted;
                }

                field("First name"; "First name")
                {
                    ApplicationArea = Basic;
                }

                field("Last name"; "Last name")
                {
                    ApplicationArea = Basic;
                }

                field(FunctionName; FunctionName)
                {
                    ApplicationArea = Basic;

                }
            }
        }
    }
}

比有一个列表页面,D365BC中使用它来获取记录列表,而该记录并不是Web服务真正需要的,但我提供它只是为了完整。
C#:
page 50109 "Workers 清单"
{
    PageType = 清单;
    ApplicationArea = All;
    UsageCategory = 清单s;
    SourceTable = Workers;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field("No."; "No.")
                {
                    ApplicationArea = Basic;
                }

                field("First name"; "First name")
                {
                    ApplicationArea = Basic;
                }

                field("Last Name"; "Last Name")
                {
                    ApplicationArea = Basic;
                }

                field(FunctionName; FunctionName)
                {
                    ApplicationArea = Basic;
                }
            }
        }
    }
}

为了能够创建Web服务,我们需要向D365BC提供一些xml文件。我提供这个只是为了完整。
XML:
<?xml version = "1.0" encoding = "utf-8" ?>
<ExportedData>
    <TenantWebServiceCollection>
        <TenanatWebService>
            <ObjectType>Page</ObjectType>
            <ObjectID>50108</ObjectID>
            <ServiceName>WorkersWebService</ServiceName>
            <Published>true</Published>
        </TenanatWebService>
    </TenantWebServiceCollection>
</ExportedData>

使用C#从ODATA Web服务读取是没有问题的:
C#:
listBox1.Items.Clear();
WorkersReadFromAlWebService = new 清单<WorkerClass>();
string _wsURL = "//api.businesscentral.dynamics.com/v2.0/SomeFunfyGuid/Sandbox/WS/CRONUS NL/Page/WorkersWS";
string _userName = "UserName";
string _wsKey = "Password";
//Create an instance of the D365BC SOAP WS
BasicHttpBinding _binding = new BasicHttpBinding();
//Set https usage
_binding.Security.Mode = BasicHttpSecurityMode.Transport;
_binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
using (WorkersWS_PortClient _ws = new WorkersWS_PortClient(_binding, new EndpointAddress(_wsURL)))
{
    _ws.ClientCredentials.UserName.UserName = _userName;
    _ws.ClientCredentials.UserName.Password = _wsKey;
    //Filter
    清单<WorkersWS_Filter> _filters = new 清单<WorkersWS_Filter>();
    WorkersWS_Filter _filter = new WorkersWS_Filter
    {
        Field = WorkersWS_Fields.No,
        Criteria = "*"
    };
    
    _filters.Add(_filter);
    try
    {
        foreach (WorkersWS _workerWS in _ws.ReadMultiple(_filters.ToArray(), "", 0))
        {
            WorkerClass _wc = new WorkerClass();
            _wc.E_Tag = _workerWS.Key;
            _wc.First_Name = _workerWS.First_name;
            _wc.Last_Name = _workerWS.Last_Name;
            _wc.FunctionName = _workerWS.FunctionName;
            WorkersReadFromAlWebService.Add(_wc);
            listBox1.Items.Add(_workerWS.Last_Name + " / " + _workerWS.First_name + " (No: " + _workerWS.No + ")");
        }
    }
    catch (Exception _ex)
    {
        
    }
}

ClearWorker();//Clears some textboxes

我的C#尝试使用ODATA Web服务在表中更新或创建新记录:
C#:
string _url = "//api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV4/Company('CRONUS NL')/WorkersWS";
//string _url = "//api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/ODataV3/Company('CRONUS NL')/WorkersWS";//ODATA V3, makes no difference
HttpWebRequest _request = (HttpWebRequest)WebRequest.Create(_url);//
_request.ContentType = "application/json; charset=utf-8";
_request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes("UserNameassword"));
_request.Accept = "*/*";
_request.KeepAlive = true;
//_request.Method = "PUT";//error 405: Method is not allowed
_request.Method = "PATCH";//error 405: Methode is not allowed
string _etag = rtbE_Tag.Text;
_request.Headers["If-Match"] = String.Format("W/\"{0}\"", _etag);
//_request.Method = "POST";//(when using POST, the 4 lines above are commented out)error 400: invalid method
string body = "{" + Environment.NewLine +
                "\"No\":" + tbNo.Text + "," + Environment.NewLine +
                "\"First_name\":\"" + tbFirstName.Text + "\"," + Environment.NewLine +
                "\"Last_Name\":\"" + tbLastName.Text + "\"," + Environment.NewLine +
                "\"FunctionName\":\"" + tbFunctionName.Text + "\"," + Environment.NewLine +
                "}";
byte[] data = Encoding.ASCII.GetBytes(body);
try
{
    _request.ContentLength = data.Length;
    Stream requestStream = _request.GetRequestStream();
    requestStream.Write(data, 0, data.Length);
    requestStream.Close();
    HttpWebResponse _response = _request.GetResponse() as HttpWebResponse;//Here we get the exception errors 400 or 405
    Console.WriteLine(_response.StatusCode);
}
catch (Exception ex)
{

}

因此,如果我使用Patch,Put或Post(错误号除外),则没有任何区别。
我试图通过D365BC论坛获取答复,但我只是没有得到答复(至少没有得到任何帮助的答复)。
自己找到答案就像口头上的“大海捞针”。 D365BC使用相对新语言AL。而且,如果您发现某些内容,通常是使用C / AL的Navision,这是完全不同的。

我还使用Nuget软件包管理器尝试了一种完全不同的方法,并查看那里有哪些ODATA软件包。
其中许多是为MVC创建的(我正在使用Win Forms)。
但是我尝试过:
-Microsoft.OData.Data(用于ODATA v3,它不只是安装在VS 2019上,而是使用我在网上找到的东西安装在VS 2019上,但是所有这些示例都假设没有身份验证的ODATA Web服务。使用身份验证需要在OdataClient.odata.config文件中设置一些设置,那么不会创建此文件(也许仅在使用MVC时),如果我自己创建它并设置身份验证设置,则它什么都不做。我收到验证错误,未设置密码/用户名)
-Microsoft.OData.Core(与ODATA v4一起使用,其行为与.DATA版本完全相同(同样的问题),再次没有运气)
-Simple.Odata.Client(无法通过身份验证,这是一个显示停止器)
-也尝试了第三个(忘记了名称),但是仅仅使用身份验证也是一个问题。

当我与某些人谈论这个问题时,他们或多或少都声称SOAP和ODATA之间没有太大区别。但是,SOAP有效,而ODATA无效。
我希望有人对此有经验,愿意并且有能力为我提供帮助。

我将以有效的SOAP代码结束,我们可以使用它来更新记录:
C#:
string _wsURL = "//api.businesscentral.dynamics.com/v2.0/SomeFunkyGuid/Sandbox/WS/CRONUS NL/Page/WorkersWS";
string _userName = "Username";
string _wsKey = "Password";
//Create an instance of the D365BC SOAP WS
BasicHttpBinding _binding = new BasicHttpBinding();
//Set https usage
_binding.Security.Mode = BasicHttpSecurityMode.Transport;
_binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
using (WorkersWS_PortClient _ws = new WorkersWS_PortClient(_binding, new EndpointAddress(_wsURL)))
{
    _ws.ClientCredentials.UserName.UserName = _userName;
    _ws.ClientCredentials.UserName.Password = _wsKey;
    try
    {
        WorkersWS _wws = _ws.Read(tbNo.Text);
        if (_wws.No == tbNo.Text)
        {
            _wws.First_name = tbFirstName.Text;
            _wws.Last_Name = tbLastName.Text;
            _wws.FunctionName = tbFunctionName.Text;
            _ws.Update(ref _wws);
            if (_wws.No == tbNo.Text)
            {
                tbResult.Text = "Update success.";
            }
        }
    }
    catch (Exception _ex)
    {
        
    }
}


亲切的问候,


克莱门斯·林德斯

亲切的问候,

克莱门斯·林德斯
 
Last edited:

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,028
地点
挪威
编程经验
10+
insertcode.png
 

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,921
地点
英国
编程经验
10+
其中许多是为MVC创建的(我正在使用Win Forms)。
为什么会有问题呢?

我也同意,上面有很多代码可以破解。也许您可以分解问题并缩小您所提供的代码库,以减少对我们的了解。
 
最佳 底部