diff --git a/Odata-docs/TOC.yml b/Odata-docs/TOC.yml index 5bd50928..207ac58e 100644 --- a/Odata-docs/TOC.yml +++ b/Odata-docs/TOC.yml @@ -334,6 +334,8 @@ href: /odata/odatalib/optional-parameters - name: Set schema for out-of-line annotations href: /odata/odatalib/edm/set-annotations-schema + - name: Enable annotations on target path + href: /odata/odatalib/edm/annotations-on-target-path - name: Batching items: - name: Batching requests diff --git a/Odata-docs/odatalib/v7/edm/annotations-on-target-path.md b/Odata-docs/odatalib/v7/edm/annotations-on-target-path.md new file mode 100644 index 00000000..64458c2d --- /dev/null +++ b/Odata-docs/odatalib/v7/edm/annotations-on-target-path.md @@ -0,0 +1,383 @@ +--- +title: "Enable annotations on target path" +description: "Enable annotations on target path" +author: kenitoinc +ms.author: kemunga +ms.date: 05/2/2023 +ms.topic: article + +--- +# Enable annotations on target path +**Applies To**: [!INCLUDE[appliesto-odataclient](../../../includes/appliesto-odatalib-v7.md)] + +The Edm library lets you [define annotations](/odata/odatalib/edm/define-annotations). When annotations relevant to a particular model element are expressed **out-of-line/externally** in an `Annotations` element, a path expression that uniquely identifies the target element must be specified. [This](/odata/odatalib/edm/set-annotations-schema) page demonstrates how to set the target for **out-of-line** annotations. + +## Introduction + +In the previous versions of `Microsoft.OData.Edm` library, we could annotate model elements, however we couldn't annotate paths to a model element. Consider the follow sample metadata describing a service where a model element Employee defined in the `NS` namespace has annotations defined out-of-line in the Default namespace: + +```xml + + + + + + + + + + + + + + + + + + + + + + +``` + +Take note of the **Target** attribute on the `Annotations` element. It specifies the path expression to the target model element, in this case the fully qualified name of the element comprising of the namespace and the element name. + +There are scenarios where we need to annotate on a path. For example, a customer can annotate different permissions to a path. +Users could have permissions to access path `/Me/Inbox` but could not have permissions to access path `/Admin/Inbox`. + +[Target Path](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_TargetPath) is a path to a model element. The allowed path expressions are: + +- The qualified name of an entity container, followed by a forward slash and the name of a container child element +- The target path of a container child followed by a forward slash and one or more forward-slash separated property, navigation property, or type-cast segments + +Below are examples of paths to a model element. + +``` +MySchema.MyEntityContainer/MyEntitySet/MyProperty +MySchema.MyEntityContainer/MyEntitySet/MyNavigationProperty +MySchema.MyEntityContainer/MyEntitySet/MySchema.MyEntityType/MyProperty +MySchema.MyEntityContainer/MyEntitySet/MySchema.MyEntityType/MyNavProperty +MySchema.MyEntityContainer/MyEntitySet/MyComplexProperty/MyProperty +MySchema.MyEntityContainer/MyEntitySet/MyComplexProperty/MyNavigationProperty +MySchema.MyEntityContainer/MySingleton/MyComplexProperty/MyNavigationProperty +``` + +[IEdmTargetPath](https://github.com/OData/odata.net/blob/57572612f1ff833d395b44e6b2c498d7d0d31890/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmTargetPath.cs#L15) is an interface in the `Microsoft.OData.Edm` that allows us to define a target a path. + +[EdmTargetPath](https://github.com/OData/odata.net/blob/57572612f1ff833d395b44e6b2c498d7d0d31890/src/Microsoft.OData.Edm/Schema/EdmTargetPath.cs#L14) is a class that implements the `IEdmTargetPath` interface. Below is an example of how we use the `EdmTargetPath` class to define a target path. + +```csharp +EdmTargetPath targetPath = new EdmTargetPath(sampleModel.Container, sampleModel.EntitySet, sampleModel.NameProperty); +``` + +Below is a simple Edm model with an out-of-line annotation that targets a path to a model element. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +We will look into implementations of reading and writing out-of-line annotations in sections below. + +## Create a .NET Core console application +Create a .NET Core console application and import the `Microsoft.OData.Edm` nuget package: + +--- + +# [Visual Studio](#tab/visual-studio) + +In the Visual Studio **Package Manager Console**: +```powershell +Install-Package Microsoft.OData.Edm +``` + +# [.NET Core CLI](#tab/netcore-cli) + +```dotnetcli +dotnet add package Microsoft.OData.Edm +``` + +--- + +## Define the Edm model +In the _Program.cs_ file, add the GetEdmModel method. + +```csharp +namespace AnnotationsTarget +{ + internal class Program + { + static void Main(string[] args) + { + } + + private static EdmModel GetEdmModel() + { + EdmModel model = new EdmModel(); + + var customer = new EdmEntityType("NS", "Customer"); + customer.AddKeys(customer.AddStructuralProperty("Id", EdmCoreModel.Instance.GetInt32(false))); + customer.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String, isNullable: false); + model.AddElement(customer); + + var container = new EdmEntityContainer("NS", "Default"); + var entitySet = new EdmEntitySet(container, "Customers", customer); + container.AddElement(entitySet); + + model.AddElement(container); + + return model; + } + } +} + +``` + +The method above creates an `IEdmModel` that we will use in writing annotations. + +## Write out-of-line annotations +Add the `WriteAnnotations` method to the _Program.cs_ file. + +This is what the `WriteAnnotations` method does: +- Call GetEdmModel method to create an `EdmModel` +- Create a target path +- Define a `WritePermission` Edm term +- Create an annotation with the target path +- Write the xml metadata to the console + +We are setting the `Write permission` to the path `NS.Default/Customers/Name` as `false`. + +```csharp +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm; +using System.Reflection; +using System.ComponentModel; +using Microsoft.OData.Edm.Validation; +using System.Xml; +using static System.Runtime.InteropServices.JavaScript.JSType; +using System.Xml.Linq; + +namespace AnnotationsTarget +{ + internal class Program + { + static void Main(string[] args) + { + WriteAnnotations(); + } + + // Other existing method(s) + + internal static void WriteAnnotations() + { + EdmModel model = GetEdmModel(); + var container = model.EntityContainer; + var entitySet = model.FindDeclaredEntitySet("Customers"); + var customer = model.FindDeclaredType("NS.Customer") as IEdmStructuredType; + var nameProperty = customer.DeclaredProperties.Where(x => x.Name == "Name").FirstOrDefault(); + + EdmTargetPath targetPath = new EdmTargetPath(container, entitySet, nameProperty); + EdmTerm term = new EdmTerm("NS", "WritePermission", EdmCoreModel.Instance.GetBoolean(false)); + model.AddElement(term); + EdmVocabularyAnnotation annotation = new EdmVocabularyAnnotation(targetPath, term, new EdmStringConstant("Write Permission on Customer Name.")); + annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.OutOfLine); + model.SetVocabularyAnnotation(annotation); + + using (StringWriter sw = new StringWriter()) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Encoding = System.Text.Encoding.UTF8; + settings.Indent = true; + + using (XmlWriter xw = XmlWriter.Create(sw, settings)) + { + IEnumerable errors; + CsdlWriter.TryWriteCsdl(model, xw, CsdlTarget.OData, out errors); + xw.Flush(); + } + + string output = sw.ToString(); + Console.Write(output); + } + } + } +} +``` + +The output on the console should be as below: + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +## Read out-of-line annotation +Add the `ReadAnnotations` method to the _Program.cs_ file. + +The `ReadAnnotations` method does the following +- Parse the metadata to an edm model +- Use the model's `GetTargetPath` extension method to get the target path +- Use the model's `FindDeclaredTerm` extension method to get the edm term +- Use the model's `FindVocabularyAnnotations(IEdmTargetPath, IEdmTerm)` extension method to get the annotation +- Write the annotation value to console + + +```csharp +namespace AnnotationsTarget +{ + internal class Program + { + static void Main(string[] args) + { + // Other codes + ReadAnnotations(); + } + + // Other methods + + internal static void ReadAnnotations() + { + string csdl = @" + + + + + + + + + + + + + + + + + + + + +"; + + IEdmModel model; + IEnumerable errors; + CsdlReader.TryParse(XElement.Parse(csdl).CreateReader(), out model, out errors); + IEdmTargetPath targetPath = model.GetTargetPath("NS.Default/Customers/Name"); + IEdmTerm deletePermissionTerm = model.FindDeclaredTerm("NS.DeletePermission"); + + // Get annotation for a specific target path and term. + IEdmVocabularyAnnotation annotation = model.FindVocabularyAnnotations(targetPath, deletePermissionTerm).FirstOrDefault(); + if (annotation != null) + { + IEdmBooleanConstantExpression boolConstant = annotation.Value as IEdmBooleanConstantExpression; + if (boolConstant != null) + { + Console.WriteLine(boolConstant.Value); + } + } + } + } +} +``` + +The output on the console should be as below: + +`False` + +## Read all annotations with a specific annotation target +We use the `Model.FindVocabularyAnnotations(IEdmTargetPath)` extension method to get all the annotations that target a specific target path + +Add the following contents to the `ReadAnnotations` method: + +```csharp + +namespace AnnotationsTarget +{ + internal class Program + { + static void Main(string[] args) + { + // Other codes + } + + // Other methods + + internal static void ReadAnnotations() + { + // Other codes + + // Get all annotations for a certain target path. + List annotations = model.FindVocabularyAnnotations(targetPath).ToList(); + IEdmVocabularyAnnotation annotation1 = annotations[0]; + IEdmVocabularyAnnotation annotation2 = annotations[1]; + IEdmBooleanConstantExpression boolConstant1 = annotation1.Value as IEdmBooleanConstantExpression; + IEdmBooleanConstantExpression boolConstant2 = annotation2.Value as IEdmBooleanConstantExpression; + IEdmTargetPath targetPath1 = annotation1.Target as IEdmTargetPath; + IEdmTargetPath targetPath2 = annotation2.Target as IEdmTargetPath; + + Console.WriteLine($"Annotations count: {annotations.Count}"); + Console.WriteLine($"Annotations target 1: {targetPath1.Path}"); + Console.WriteLine($"Annotations target 2: {targetPath2.Path}"); + Console.WriteLine($"Annotations value 1: {boolConstant1.Value}"); + Console.WriteLine($"Annotations value 2: {boolConstant2.Value}"); + } + } +} +``` + +The output on the console should be as below: + +``` +Delete Permission on Customer Name. +Annotations count: 2 +Annotations target 1: NS.Default/Customers/Name +Annotations target 2: NS.Default/Customers/Name +Annotations value 1: False +Annotations value 2: False +```