When you create a Microsoft Dataverse message using a custom API, you must specify the name and data type of each of the request parameters and response properties. The data types can be open or closed. With closed types, every property name and type value is known. All types that are defined in Dataverse are closed. The system knows about closed types and can validate them for you. If you use the wrong name or set the value to the wrong type, you get an error. But closed types aren't dynamic. They don't allow for complex and nested properties. Normally, a specific structure is a good thing, but sometimes your business logic requires a more flexible approach.
Unlike closed types, open types can have dynamic properties. Using open types with custom APIs makes sense when:
You need to use structured dynamic data. This data can't be described by a class, so there's no need to serialize or deserialize it.
You want a custom API to accept a request parameter or return a response property that has a complex type that can't be expressed using the options available. In that case, you need to use a custom closed type.
It's important to understand the scenario that applies to your custom API to use open types correctly. Let's understand how to use open types first.
How to use open types
To use open types, you need a message that's configured for them. With a custom API, you specify that a request parameter or response property is open by setting the Type to Entity (3) or EntityCollection (4) without specifying a LogicalEntityName.
When you don't specify the LogicalEntityName, you're telling Dataverse that Entity is a collection of key/value pairs that can't be validated against any table definition, so it doesn't try. An EntityCollection without a LogicalEntityName is just an array of Entity.
Piezīme
A custom API defined as functions can't use open types as request parameters, but may use them as response properties.
Use Dataverse entities
While not actually an open type, it's worth mentioning that you can have a custom API with parameters or response properties that represent more than one closed entity type. For example, you can create a custom API with a Customer parameter that expects either Account or Contact entity instances. Dataverse allows any entity type. Your plug-in code must check the Entity.LogicalName value to determine whether it's a type you expect.
Use Entity as a dictionary
The more common case is to use Entity as a dictionary. Use the Entity.Attributes collection to specify a set of keys and values. The values can be any .NET type and can be nested. Don't use any other Entity class properties.
Let's say that your application uses data that comes from or is sent to Microsoft Graph and represents the educationSchool resource type. You might use an open type as in the following examples.
To use an open type with the SDK, use the Entity class without specifying the entity name, then set the Entity.Attributes collection with the keys and their values.
Notice that the id value has an id@odata.type annotation. This annotation is necessary to specify that the value represents a Guid rather than a String. Unless specified, OData expects that the simplest JSON type is intended.
If the value is a number, it's Int32. If the value is Decimal, Double, Int16, or Int64, you must specify the type using the <property name>@odata.type annotation.
Notice also that the string values have an annotation to specify that they're strings. These annotations are a mitigation against a known issue in OData. If the string value contains an open bracket ([) or single quote ('), the OData library returns a Microsoft.OData.ODataException. Unless you're certain that the values sent will never include these characters, you must include the "<property name>@odata.type": "String" annotation to prevent this error.
If the value is an array, you must always include an annotation using this pattern: "<property name>@odata.type": "Collection(<type>)"
In addition to basic .NET types, you can also use types known to Dataverse. The SDK for .NET contains definitions of many classes that Dataverse knows about, and in the Web API these types are listed in the Web API Complex Type Reference and Enum Type Reference.
When using the SDK, you can simply set the values.
When using the Web API, you must specify the type using the Web API namespace: Microsoft.Dynamics.CRM. The following example uses these Dataverse Web API types:
Structured dynamic data can be defined using various formats that can be set as a string, like JSON, XML, YAML, and HTML. This kind of data can be easily set using a string parameter or response property. So why use open types?
Use a string when the structured data is passed through your custom API to another service or is consumed as a string by another application that calls your custom API.
Use an open type when the plug-in that supports your custom API, or any plug-ins that extend your custom API, must read or change the structured data.
Parsing a string value into an object such as XDocument or JObject so that you can manipulate it in your plug-in is a relatively expensive operation. When your plug-in, or any other plug-in that might extend the logic in your custom API, changes the data, it must convert the object back into a string. You can avoid these expensive operations by using open types.
With open types, callers of your custom API can use the familiar dictionary structure that the Entity class provides. Your plug-in can interact with it in the same way you work with other Dataverse records.
If you're serializing or deserializing the string data to a class, your data isn't dynamic. You should review the next section.
Custom closed types
Open types allow for dynamic and unstructured data. But you should consider whether your API has truly dynamic parameters or whether you actually want to have a custom type.
Currently, you can't define a custom type that Dataverse knows about. But using open types, you can define a closed-type class that Dataverse can process as an open type. Developers using your custom API can use your classes to have a better, more productive experience with fewer opportunities for errors.
For example, let's say that your custom API requires a parameter that tracks a course using an array of latitude and longitude coordinates. You need a Location class.
You can create a Location class that inherits from the Entity class and contains the properties you need. For example:
using Microsoft.Xrm.Sdk;
namespace MyCompany
{
/// <summary>
/// Specifies a location for use with my_CustomAPI
/// </summary>
public class Location : Entity
{
// Gets or sets the latitude of the Location.
[AttributeLogicalName("Latitude")]
public double Latitude
{
get
{
return GetAttributeValue<double>("Latitude");
}
set
{
SetAttributeValue("Latitude", value);
}
}
// Gets or sets the longitude of the Location.
[AttributeLogicalName("Longitude")]
public double Longitude
{
get
{
return GetAttributeValue<double>("Longitude");
}
set
{
SetAttributeValue("Longitude", value);
}
}
}
}
Because this type inherits from the Entity class, it can use the EntityGetAttributeValue and SetAttributeValue methods to access the values in the Attributes collection. You can use this class in your plug-in code and share it with consumers in a library or in sample code in your documentation. The result is code that's easier to use and read.
Before:
var location = new Entity() {
Attributes =
{
{ "Latitude", 47.66132951804776 },
{ "Longitude", -122.11446844957624},
}
};
// OR
var location = new Entity();
location["Latitude"] = 47.66132951804776;
location["Longitude"] = -122.11446844957624;
After:
var location = new Location {
Latitude = 47.66132951804776,
Longitude = -122.11446844957624
};
// OR
var location = new Location()
location.Latitude = 47.66132951804776;
location.Longitude = -122.11446844957624;
Because the Web API can be used with .NET and other programming languages, the specific method depends on the language and the technologies you use to work with JSON.
When using Json.NET with C#, you can define a Location class like this:
using Newtonsoft.Json;
namespace MyCompany
{
public class Location
{
[JsonProperty("@odata.type")]
public string ODataType => "Microsoft.Dynamics.CRM.expando";
// Gets or sets the latitude of the Location.
public double? Latitude { get; set; }
[JsonProperty("Latitude@odata.type")]
public static string LatitudeType => "Double";
// Gets or sets the longitude of the Location.
public double? Longitude { get; set; }
[JsonProperty("Longitude@odata.type")]
public static string LongitudeType => "Double";
}
}
You can share this class with consumers in a library or in sample code in your documentation.
When a developer uses this class, they don't need to worry about setting the @odata.type annotations.
Location location = new() {
Latitude = 47.66132951804776,
Longitude = -122.11446844957624
};
// OR
var location = new()
location.Latitude = 47.66132951804776;
location.Longitude = -122.11446844957624;
The @odata.type annotations are always included when the class instance is serialized.
When you use the Web API to send data that contains an array, the following error occurs when your custom API has a plug-in:
{
"error": {
"code": "0x80040224",
"message": "Element 'http://schemas.datacontract.org/2004/07/System.Collections.Generic:value' contains
data from a type that maps to the name 'System.Collections.Generic:List`1'. The deserializer has no
knowledge of any type that maps to this name. Consider changing the implementation of the ResolveName
method on your DataContractResolver to return a non-null value for name 'List`1' and namespace
'System.Collections.Generic'.",
}
}
This error doesn't occur when the client application is using the SDK for .NET or when no plug-in is set for the custom API.
Use the Feedback for This page button below to submit questions you have about open types.