Tampilkan postingan dengan label Integration. Tampilkan semua postingan
Tampilkan postingan dengan label Integration. Tampilkan semua postingan

Rabu, 29 Oktober 2014

Microsoft Dynamics GP 2015 Developer's Preview: .NET Framework Interoperability - Part 3

In part 2 of the series, I delivered a brief primer on the service architecture in Microsoft Dynamics GP 2015 and how you are able to consume services natively created with Dexterity. There are two types of services that can be created: services that wrap existing windows and forms functionality, i.e., creating a customer; and services that can wrap around existing sanScript procedures, i.e, retrieving customer information. The truth is, none of that stuff would be possible without the ability to interoperate with the Microsoft .NET Framework.

Today, I'm going to show you some of the .NET interop capabilities built into Dexterity 14.0 and how these can enhance the user experience and your application integration capabilities. The example used in this post is based on my previous article, Building a COM Interop Assembly to use with Microsoft Dexterity, which showed how we had to leverage .NET's ability to expose an assembly via COM interfaces, so we could reference its methods using Dexterity. In that particular example, I built some standard methods to expose the sine, cosine, and tangent trigonometric functions to a Dexterity application. This time around, I will show how to build the same functionality taking full advantage of Dexterity's new .NET interop capabilities.

The user experience will be slightly different this time. In addition to a window that will allow you to calculate any of the trigonometric functions on a specific angle (entered in degrees), we will display a .NET form showing a visual representation of that angle. The .NET form will be dynamically built and displayed from Dexterity using the methods and properties provided by the Form class (System.Windows.Forms) and the visual drawings will be delivered using the classes exposed in the System.Drawing namespace.

1. The first things we must do is incorporate references to the corresponding assemblies for the Forms class and Drawing namespaces. As developer, the first thing you will notice is the enhancements to Dexterity's Library Definition window, which now allows for the selection of a .NET Assembly library type.

Library Definition
Once the type has been selected, you can browse out to the different .NET assemblies loaded on your operating system.

.NET Assemblies
In particular, what I like is the ability to select the .NET Framework assembly that I want to work with directly, as opposed to having to compile my application for a specific Framework version as you normally would do with Visual Studio.

Resource Explorer will now reflect the different .NET namespaces selected for you application.

Resource Explorer

2. Since this article assumes some familiarity with Dexterity, I'm not going to dive into the process of building the form and window, but rather point out that it follows the same layout and properties I used in my previous article.

Form and Window Definition
The only "rarity" here is I added a local field called '(L) GraphicAngleForm' that will serve as a generic reference to the .NET form we will dynamically create. The '(L) Conversion' and '(L) Angle' fields are currency fields and are formatted with two decimals and unsigned (DLR11_U2).

Generic Reference field

3. The code for our '(L) Sine' button will look something like this.

MGB_Trigonometric_Test l_Sine_CHG
// Created by Mariano Gomez, MVP
// This code is licensed under the Creative Commons
// Attribution-NonCommercial-ShareAlike 3.5 Generic license.
using System;
using System.Windows.Forms;
using System.Drawing;

local currency l_angle;
local Form f;

'(L) Prompt' = "The Sin(%1°) value is ";
substitute '(L) Prompt', str('(L) Angle');

// calculate the sine value of the angle after converting it to radians
'(L) Conversion' = Math.Sin(Math.PI * '(L) Angle' / 180.0);

if empty('(L) GraphicAngleForm') then
f = new Form();
'(L) GraphicAngleForm' = f;

f.Text = "Graphical Representation of the Angle entered";
f.BackColor = Color.White;

// Set the size of the form
f.ClientSize = new Size(640, 480);

// Make the form a fixed size
f.MaximumSize = f.ClientSize;
f.MinimumSize = f.ClientSize;

// event handlers
f.Paint += PaintAngle of form MGB_Trigonometric_Test;
f.FormClosed += CloseDrawingForm of form MGB_Trigonometric_Test;

end if;

// show and activate our .NET form
'(L) GraphicAngleForm'.Show();
'(L) GraphicAngleForm'.Activate();

When you really look at the code, the first thing you will notice is the first 3 lines. sanScript now implements the using statement just like C#. sanScript has also been extended to use the implement statement for dynamically referencing an assembly.

using System;
using System.Windows.Forms;
using System.Drawing;


Next we can then calculate the sine of the angle in radians by simply calling the Math.Sin() method in the System namespace (the System namespace is part of the Microsoft Core Library, mscorlib.dll). We also reference the Math.PI constant to perform the conversion of the angle from degrees to radians.

// calculate the sine value of the angle after converting it to radians
'(L) Conversion' = Math.Sin(Math.PI * '(L) Angle' / 180.0);


Once we have the angle converted and displayed in the '(L) Conversion' field, we can proceed to create the form by instantiating the Form class - in reality, this part is no different than what you would normally do for COM classes - and setting some properties for the .NET form we want to display. Once we have set the size of the window, we need to create some event handlers for when the .NET form open and closes. As you can see in the code, Dexterity now implements event handlers through the use of the += operator.

// event handlers
f.Paint += PaintAngle of form MGB_Trigonometric_Test;
f.FormClosed += CloseDrawingForm of form MGB_Trigonometric_Test;


Our event handlers in this case will be the PaintAngle (on .NET form open) and CloseDrawingForm (on .NET form close). The PaintAngle event handler will display the actual graphical representation of the angle, and the CloseDrawingForm event handler will do some cleanup for us. Now, as you may suspect, these event handlers are implemented as procedure scripts to the MGB_Trigonometric_Test form (form procedures). So let's take a look at the PaintAngle form procedure:

PrintAngle
// Created by Mariano Gomez, MVP
// This code is licensed under the Creative Commons
// Attribution-NonCommercial-ShareAlike 3.5 Generic license.
using System;
using System.Windows.Forms;
using System.Drawing;

inout System.Object sender;
inout PaintEventArgs e;

local Pen bp, rp;
local Color c;

// Get the color
c = Color.Blue;

// Create the blue pen
bp = new Pen(c, 2);

e.Graphics.DrawLine(bp, 320, 240, 520, 240);
e.Graphics.DrawLine(bp, 320, 240, 320 + Math.Cos('(L) Conversion' of window MGB_Trigonometric_Test) * 200, 240
- Math.Sin('(L) Conversion' of window MGB_Trigonometric_Test) * 200);

c = Color.Red;
rp = new Pen(c, 2);

e.Graphics.DrawEllipse(rp, 300, 220, 40, 40);

Again, the mechanics here is not so much the important part, but rather to highlight the clever way in which the event handler parameters are declared for the sender of the event and the event arguments being passed by the sender of the event. For this the Dexterity team chose to implement inout parameters.

Overall the PrintAngle procedure simply draws two lines and a circle. The first line is the base line of the angle and will simply be a horizontal line. The second line actually shows the displacement in reference to the base line to give the actual angle representation. This all looks something like this when the code is executed:

Angle Calculator
Having the ability to leverage .NET capabilities directly from Dexterity has now opened up a new realm of possibilities for Microsoft Dynamics GP business application developers. Applications that were once thought to be out of reach or required complex workarounds are now a thing of the past. Code portability is now much more compact and robust than before. The good part is, Dexterity developers do not need relearn any development patterns and/or be exposed to steep learning curves. All the .NET Framework documentation is available online with tons of sample code to go along.



This article is part of the Microsoft Dynamics GP 2015 Developer's Preview series.

Microsoft Dynamics GP 2015 Developer's Preview: Loading the VHD image - Part 1
Microsoft Dynamics GP 2015 Developer's Preview: Working with Sample URIs - Part 2

Until next post!

MG.-
Mariano Gomez, MVP
Intelligent Partnerships, LLC
http://www.intelligentpartnerships.com/

Selasa, 23 September 2014

Microsoft Dynamics GP 2015 Developer's Preview: Working with Sample URIs - Part 2

Microsoft Dynamics GP 2015 Developer's Preview: Working with Sample URIs - Part 2

In the part 1 video, I explained how to mount the Microsoft Dynamics GP 2015 Developer's Preview virtual hard disk using Hyper-V. My intent was to provide a part two showing how to mount the VHD file on Windows Azure, but realized it would take more time than I wanted to invest in really getting the point across on many of the aspects around the new service architecture components, so I have decided to forgo the Azure portion until some other day.

Today, I will focus on some of the sample service requests provided on the Developer's preview image, which can be found in the Example Service Requests.txt file available on the desktop of the image.

Before however, I wanted to touch base on REpresentational State Transfer (REST) services. REST, a term first coined by Roy Fielding (a principle author of the HTTP specification) in his doctoral dissertation, is an architectural style that treats networked application states and functionality as resources, which share a uniform interface. This architectural style differs in many ways from that of the Remote Procedure Call (RPC) architecture where services reside on the network and are invoked using request parameters and control data contained within messages.

Some of the basic principles governing REST services are:

  • Actors interact with resources, and resources are anything that can be named and represented. Each resource can be addressed via a unique Uniform Resource Identifier (URI).
  • Interaction with resources (located through their unique URIs) is accomplished using a uniform interface of the HTTP standard verbs (GET, POST, PUT, and DELETE). Also important in the interaction is the declaration of the resource's media type, which is designated using the HTTP Content-Type header. (XHTML, XML, JPG, PNG, and JSON are some well-known media types.)
  • Resources are self-descriptive. All the information necessary to process a request on a resource is contained inside the request itself (which allows services to be stateless).
  • Resources contain links to other resources (hyper-media).

While REST is defined by its author using strict architectural principles, the term is often used loosely to describe any simple URI-based request to a specific domain over HTTP without the use of an additional messaging layer such as Simple Object Access Protocol (SOAP). Implementations adhering to the strict principles of REST are often referred to as being “RESTful,” while those which follow a loose adherence are called “REST-Like”. Microsoft Dynamics GP Services can be considered REST-like (See Chapter 1: Microsoft Dynamics GP Service, page 3 of the Microsoft Dynamics GP Service Based Architecture Preview documentation).

A quick sample

As an example, imagine you need to build a service that interacts with the Microsoft Dynamics GP item master list: basically, a service that could produce the list of items and/or information about a specific item in the list, from a specific company database - in this case Fabrikam - and to be more precise, that company database resides within a specific tenant. Technically speaking, this service could also add or retrieve data for an item to and from the item master in Fabrikam, on the current tenant.

When building a REST-like service, you can must answer 3 basic questions:


  • What resources you are trying to define or expose
  • How are you going to represent the resources (URIs)
  • What actions are you going to support for each URI (HTTP verbs).


  • For our example, the resources will be defined by the hierarchy Tenants(Name:tenant_name)/Companies(company_name)/Items(item_number). The URIs are really dependent on where the service is going to be hosted, so for example, this could be in the form of http://somedomain.com:port_number/gpservice/ followed by the above hierarchy. The next thing in line is then to understand what HTTP verbs or actions are supported with each URI.

    Next, we need to determine the URIs for each resource. Right now we only need to determine the relative URIs since the absolute URI will be determined by where we host the service. The item master will be the root URI of the service (/). Using this syntax, /Items() will return all of items contained in the item master; /Items({ItemNumber}) will be the URI for each item within the item master.

    Under the current Developer's preview implementation, if you wanted to retrieve information about an item (HTTP GET), you would then use the following URI notation from your browser:

    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Items(2GPROC)

    By copy and pasting the above URL in the browser, the service call will generate a JavaScript Object Notation file (.json), as shown below:

    Items(2GPROC).json
    {
    "Status": {
    "CorrelationId": "d3056b1bb9d84775ad269abfa09cfa77",
    "Code": 200
    },
    "Payload": {
    "Trace": [],
    "ItemNumber": "2GPROC",
    "ItemDescription": "2 Ghz Processor",
    "NoteIndex": 333.0,
    "ItemShortName": "",
    "ItemType": "SalesInventory",
    "ItemGenericDescription": "",
    "StandardCost": 0.0,
    "CurrentCost": 250.0,
    "ItemShippingWeight": 0.0,
    "DecimalPlacesQTYS": "NotUsed",
    "DecimalPlacesCurrency": "One",
    "ItemTaxScheduleID": "",
    "TaxOptions": "Nontaxable",
    "IVIVIndex": 18,
    "IVIVOffsetIndex": 18,
    "IVCOGSIndex": 137,
    "IVSalesIndex": 112,
    "IVSalesDiscountsIndex": 128,
    "IVSalesReturnsIndex": 134,
    "IVInUseIndex": 0,
    "IVInServiceIndex": 141,
    "IVDamagedIndex": 141,
    "IVVariancesIndex": 783,
    "DropShipIndex": 445,
    "PurchasePriceVarianceIndex": 446,
    "UnrealizedPurchasePriceVarianceIndex": 446,
    "InventoryReturnsIndex": 450,
    "AssemblyVarianceIndex": 0,
    "ItemClassCode": "RM-ACT",
    "ItemTrackingOption": 1,
    "LotType": "",
    "KeepPeriodHistory": true,
    "KeepTrxHistory": true,
    "KeepCalendarHistory": true,
    "KeepDistributionHistory": true,
    "AllowBackOrders": true,
    "ValuationMethod": "FIFOPerpetual",
    "UOfMSchedule": "EACH",
    "AlternateItem1": "",
    "AlternateItem2": "",
    "MasterRecordType": 1,
    "ModifiedDate": "2017-05-21T00:00:00",
    "CreatedDate": "2017-05-21T00:00:00",
    "WarrantyDays": 0,
    "PriceLevel": "",
    "LocationCode": "",
    "PurchInflationIndex": 0,
    "PurchMonetaryCorrectionIndex": 0,
    "InventoryInflationIndex": 0,
    "InventoryMonetaryCorrectionIndex": 0,
    "COGSInflationIndex": 0,
    "COGSMonetaryCorrectionIndex": 0,
    "ItemCode": "",
    "TaxCommodityCode": "",
    "PriceGroup": "BUY",
    "PriceMethod": "CurrencyAmount",
    "PurchasingUOfM": "",
    "SellingUOfM": "",
    "KitCOGSAccountSource": "FromComponentItem",
    "LastGeneratedSerialNumber": "",
    "ABCCode": "B",
    "RevalueInventory": true,
    "TolerancePercentage": 0.0,
    "PurchaseItemTaxScheduleID": "",
    "PurchaseTaxOptions": "NonTaxable",
    "ItemPlanningType": "Normal",
    "StatisticalValuePercentage": 0.0,
    "CountryOrigin": "",
    "Inactive": false,
    "MinShelfLife1": 0,
    "MinShelfLife2": 0,
    "IncludeinDemandPlanning": false,
    "LotExpireWarning": true,
    "LotExpireWarningDays": 0,
    "LastGeneratedLotNumber": "",
    "LotSplitQuantity": 0.0,
    "UseQtyOverageTolerance": false,
    "UseQtyShortageTolerance": false,
    "QtyOverageTolerancePercentage": 0.0,
    "QtyShortageTolerancePercentage": 0.0,
    "IVSTDCostRevaluationIndex": 0,
    "UserCategoryValues1": "",
    "UserCategoryValues2": "",
    "UserCategoryValues3": "",
    "UserCategoryValues4": "",
    "UserCategoryValues5": "",
    "UserCategoryValues6": ""
    }
    }

    You can also retrieve an XML payload by specifying the extension in the URI, as follows:

    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Items(2GPROC).xml

    Here are other examples of URI notations to perform various service calls to retrieve data from Microsoft Dynamics GP, as provided in the Developer's preview:

    Checking the status of the GP Service.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Utility/Ping

    Obtaining help on supported HTTP verbs.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Utility/Help

    Retrieve information on customer AARONFIT0001 (Aaron Fitz Electrical).
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Customers(AARONFIT0001)

    Retrieve information on customer COMPUTER0001(Computer World).
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Customers(COMPUTER0001)

    Retrieve information on item number 100XLG.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Items(100XLG)

    Retrieve information on site 101G.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Sites(101G)

    Retrieve information on site 104G.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Sites(104G)

    Retrieve information on all companies under the current tenant.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Companies()

    Retrieve information about Fabrikam, Inc. under the current tenant.
    http://localhost:8084/GPService/Tenants(Name=DefaultTenant)/Companies(Fabrikam,%20Inc.)/Companies(TWO)

    I want to mention that there 2 HTML files provided with the preview, which contain JavaScript sample code showing how to access the Dynamics GP Service. These can be found under the Samples folder. The scripts show how to make use of the HTTP POST, HTTP PATCH, and HTTP DELETE actions to create a new, and update and delete an existing record in Microsoft Dynamics GP respectively.

    There's also a .NET sample application that show how to consume a GP Service as well. This sample can be loaded with Visual Studio in the Developer's Preview image.

    While this is all good, In my next article I will show how to build a Microsoft Dexterity-based service that can be consumed by other applications.

    Until next post!

    MG.-
    Mariano Gomez, MVP
    IntelligentPartnerships, LLC
    http://www.IntelligentPartnerships.com

    Selasa, 26 Agustus 2014

    Customizating Integration Manager Logs - Part 2

    Customizating Integration Manager Logs - Part 2

    In my previous post I talked about all the out of the box options for setting up Integration Manager ("IM") logs and frankly, the Trace level log is good for most users of IM users. However, when "good" is not good enough, it's necessary to resort to some of the objects and functions available as part of IM's scripting library.

    Errors Collection object, Error object, and functions

    Integration Manager provides the Errors Collection object which is nothing more than a collection or list of all the errors generated during an integration. The Errors Collection must be explicitly retrieved in order to work with the properties within the collection. To navigate the collection we need the Error object to get information about the specific error within the Errors Collection, for example, time of the error, the specific error text, and the type of severity (error or warning).

    IM also provides a number of functions that allow a developer to write into the log file directly. These functions are: LogDetail, LogDocDetail, LogWarning, and LogDocWarning. Each of these functions is discussed in greater detail in Part 5 - Using VBScript, Chapter 22 - Functions of the Integration Manager User's Guide. The following example puts all these together:

    After Document script
    '
    ' Created by Mariano Gomez, MVP
    ' This code is licensed under the Creative Commons
    ' Attribution-NonCommercial-ShareAlike 3.0 Generic license.
    Const SEVERITY_MEDIUM 1000
    Const SEVERITY_CRITICAL 2000

    Dim imErrors ' reference the Errors Collection
    Dim imError ' reference a specific error within the collection

    Set imErrors = GetVariable("Errors") 
    If imErrors.Count > 0 Then
     For i = 1 to imErrors.Count
      Set imError = imErrors.Item(i) ' get the error represented by the index
     
      'Check the severity level of the error
      if imError.Severity = GetVariable("SeverityWarning") then
       'We have hit a warning
       LogDocWarning imError.MessageText, "", SEVERITY_MEDIUM, "Customer Name", SourceFields(somequery.CustomerName)
      Else
      ' We hit a major issue, so now we really want to log all details details
       LogDocDetail imError.MessageText, "", SEVERITY_CRITICAL, "Customer Name", SourceFields(somequery.CustomerName)
      End If
     Next 'Continue if there's more than 1 error
    End If
     

    Note that you can add event logs from anywhere where scripting is allowed in IM. The above sample code is just a small example of how you could customize the logs further, with information that's meaningful to you and your users.

    Hope you found this information useful.

    Until next post!

    MG.-
    Mariano Gomez, MVP
    Intelligent Partnerships, LLC
    http://www.IntelligentPartnerships.com