diff --git a/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK.csproj b/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK.csproj index 7ba7640..b13e9f5 100644 --- a/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK.csproj +++ b/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK.csproj @@ -6,8 +6,9 @@ - + + diff --git a/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Hotel.cs b/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Hotel.cs index aea181d..cc9f6c0 100644 --- a/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Hotel.cs +++ b/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Hotel.cs @@ -1,56 +1,52 @@ -namespace AzureSearch.SDKHowTo +using System; +using Microsoft.Spatial; +using Azure.Search.Documents.Indexes; +using Azure.Search.Documents.Indexes.Models; +using System.Text.Json.Serialization; + +namespace AzureSearch.SDKHowTo { - using System; - using Microsoft.Azure.Search; - using Microsoft.Azure.Search.Models; - using Microsoft.Spatial; - using Newtonsoft.Json; - - // The SerializePropertyNamesAsCamelCase attribute is defined in the Azure Search .NET SDK. - // It ensures that Pascal-case property names in the model class are mapped to camel-case + + + // The JsonPropertyName attribute is defined in the Azure Search .NET SDK. + // Here it used to ensure that Pascal-case property names in the model class are mapped to camel-case // field names in the index. - [SerializePropertyNamesAsCamelCase] public partial class Hotel { - [System.ComponentModel.DataAnnotations.Key] - [IsFilterable] + [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } - [IsFilterable, IsSortable, IsFacetable] + [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? BaseRate { get; set; } - [IsSearchable] + [SearchableField] public string Description { get; set; } - [IsSearchable] - [Analyzer(AnalyzerName.AsString.FrLucene)] - [JsonProperty("description_fr")] + [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)] public string DescriptionFr { get; set; } - [IsSearchable, IsFilterable, IsSortable] + [SearchableField(IsFilterable = true, IsSortable = true)] public string HotelName { get; set; } - [IsSearchable, IsFilterable, IsSortable, IsFacetable] - [SynonymMaps("desc-synonymmap")] + [SearchableField(IsFilterable = true, IsFacetable = true, IsSortable = true)] public string Category { get; set; } - [IsSearchable, IsFilterable, IsFacetable] - [SynonymMaps("desc-synonymmap")] + [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } - [IsFilterable, IsFacetable] + [SimpleField(IsFilterable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } - [IsFilterable, IsFacetable] + [SimpleField(IsFilterable = true, IsFacetable = true)] public bool? SmokingAllowed { get; set; } - [IsFilterable, IsSortable, IsFacetable] + [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } - [IsFilterable, IsSortable, IsFacetable] + [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public int? Rating { get; set; } - [IsFilterable, IsSortable] + [SimpleField(IsFilterable = true, IsSortable = true)] public GeographyPoint Location { get; set; } } } diff --git a/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Program.cs b/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Program.cs index 7c6204c..7ac537f 100644 --- a/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Program.cs +++ b/DotNetHowToEncryptionUsingCMK/DotNetHowToEncryptionUsingCMK/Program.cs @@ -1,15 +1,18 @@ #define HowToExample +using System; +using System.Linq; +using System.Threading; +using Azure; +using Azure.Search.Documents; +using Azure.Search.Documents.Indexes; +using Azure.Search.Documents.Indexes.Models; +using Azure.Search.Documents.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Spatial; + namespace AzureSearch.SDKHowTo { - using System; - using System.Linq; - using System.Threading; - using Microsoft.Azure.Search; - using Microsoft.Azure.Search.Models; - using Microsoft.Extensions.Configuration; - using Microsoft.Spatial; - class Program { // This sample shows how to create a synonym-map and an index that are encrypted with customer-managed key in Azure Key Vault @@ -18,206 +21,207 @@ static void Main(string[] args) IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); IConfigurationRoot configuration = builder.Build(); - SearchServiceClient serviceClient = CreateSearchServiceClient(configuration); + SearchIndexClient indexClient = CreateSearchIndexClient(configuration); Console.WriteLine("Cleaning up resources...\n"); - CleanupResources(serviceClient); + CleanupResources(indexClient); Console.WriteLine("Creating synonym-map encrypted with customer managed key...\n"); - CreateSynonymsEncryptedUsingCustomerManagedKey(serviceClient, configuration); + CreateSynonymsEncryptedUsingCustomerManagedKey(indexClient, configuration); Console.WriteLine("Creating index encrypted with customer managed key...\n"); - CreateHotelsIndexEncryptedUsingCustomerManagedKey(serviceClient, configuration); + CreateHotelsIndexEncryptedUsingCustomerManagedKey(indexClient, configuration); - ISearchIndexClient indexClient = serviceClient.Indexes.GetClient("hotels"); + SearchIndex index = indexClient.GetIndex("hotels"); + index = AddSynonymMapsToFields(index); + indexClient.CreateOrUpdateIndex(index); + + SearchClient searchClient = indexClient.GetSearchClient("hotels"); Console.WriteLine("Uploading documents...\n"); - UploadDocuments(indexClient); + UploadDocuments(searchClient); - ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(configuration); + SearchClient searchClientForQueries = CreateSearchClient(configuration); - RunQueries(indexClientForQueries); + RunQueries(searchClientForQueries); Console.WriteLine("Complete. Press any key to end application...\n"); Console.ReadKey(); } - private static SearchServiceClient CreateSearchServiceClient(IConfigurationRoot configuration) + private static SearchIndexClient CreateSearchIndexClient(IConfigurationRoot configuration) { - string searchServiceName = configuration["SearchServiceName"]; + string searchServiceEndPoint = configuration["SearchServiceEndPoint"]; string adminApiKey = configuration["SearchServiceAdminApiKey"]; - SearchServiceClient serviceClient = new SearchServiceClient(searchServiceName, new SearchCredentials(adminApiKey)); - return serviceClient; + SearchIndexClient indexClient = new SearchIndexClient(new Uri(searchServiceEndPoint), new AzureKeyCredential(adminApiKey)); + return indexClient; } - private static SearchIndexClient CreateSearchIndexClient(IConfigurationRoot configuration) + private static SearchClient CreateSearchClient(IConfigurationRoot configuration) { - string searchServiceName = configuration["SearchServiceName"]; + string searchServiceEndPoint = configuration["SearchServiceEndPoint"]; string queryApiKey = configuration["SearchServiceQueryApiKey"]; - SearchIndexClient indexClient = new SearchIndexClient(searchServiceName, "hotels", new SearchCredentials(queryApiKey)); - return indexClient; + SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), "hotels", new AzureKeyCredential(queryApiKey)); + return searchClient; } - private static void CleanupResources(SearchServiceClient serviceClient) + private static void CleanupResources(SearchIndexClient indexClient) { - if (serviceClient.Indexes.Exists("hotels")) + try { - serviceClient.Indexes.Delete("hotels"); + if (indexClient.GetIndex("hotels") != null) + { + indexClient.DeleteIndex("hotels"); + } + if (indexClient.GetSynonymMapNames().Value.Contains("desc-synonymmap")) + { + indexClient.DeleteSynonymMap("desc-synonymmap"); + } } - - if (serviceClient.SynonymMaps.Exists("desc-synonymmap")) + catch (RequestFailedException e) when (e.Status == 404) { - serviceClient.SynonymMaps.Delete("desc-synonymmap"); + //if exception occurred and status is "Not Found", this is work as expect + Console.WriteLine("Failed to find index and this is because it's not there."); } } - private static void CreateSynonymsEncryptedUsingCustomerManagedKey(SearchServiceClient serviceClient, IConfigurationRoot configuration) + private static void CreateSynonymsEncryptedUsingCustomerManagedKey(SearchIndexClient indexClient, IConfigurationRoot configuration) { - var synonymMap = new SynonymMap() + var synonymMap = new SynonymMap("desc-synonymmap", "hotel, motel\ninternet,wifi\nfive star=>luxury\neconomy,inexpensive=>budget") { - Name = "desc-synonymmap", - Synonyms = "hotel, motel\ninternet,wifi\nfive star=>luxury\neconomy,inexpensive=>budget", EncryptionKey = GetEncryptionKeyFromConfiguration(configuration) }; - serviceClient.SynonymMaps.CreateOrUpdate(synonymMap); + indexClient.CreateOrUpdateSynonymMap(synonymMap); + } + + private static SearchIndex AddSynonymMapsToFields(SearchIndex index) + { + //remove SynonymMaps attribute in class hotel, need Add maps manually. + index.Fields.First(f => f.Name == "Category").SynonymMapNames.Add("desc-synonymmap"); + index.Fields.First(f => f.Name == "Tags").SynonymMapNames.Add("desc-synonymmap"); + return index; } - private static void CreateHotelsIndexEncryptedUsingCustomerManagedKey(SearchServiceClient serviceClient, IConfigurationRoot configuration) + private static void CreateHotelsIndexEncryptedUsingCustomerManagedKey(SearchIndexClient indexClient, IConfigurationRoot configuration) { - var definition = new Index() + FieldBuilder fieldBuilder = new FieldBuilder(); + var searchFields = fieldBuilder.Build(typeof(Hotel)); + var definition = new SearchIndex("hotels", searchFields) { - Name = "hotels", - Fields = FieldBuilder.BuildForType(), EncryptionKey = GetEncryptionKeyFromConfiguration(configuration) }; - serviceClient.Indexes.Create(definition); + indexClient.CreateOrUpdateIndex(definition); } - private static EncryptionKey GetEncryptionKeyFromConfiguration(IConfigurationRoot configuration) + private static SearchResourceEncryptionKey GetEncryptionKeyFromConfiguration(IConfigurationRoot configuration) { Uri keyVaultKeyUri = new Uri(configuration["AzureKeyVaultKeyIdentifier"]); + if (!keyVaultKeyUri.Host.Contains("vault.azure.net") || keyVaultKeyUri.Segments.Length != 4 || keyVaultKeyUri.Segments[1] != "keys/") { throw new ArgumentException("Invalid 'AzureKeyVaultKeyIdentifier' - Expected format: 'https://.vault.azure.net/keys//'", "AzureKeyVaultKeyIdentifier"); } - var encryptionKey = new EncryptionKey - { - KeyVaultUri = $"{keyVaultKeyUri.Scheme}://{keyVaultKeyUri.Host}", - KeyVaultKeyName = keyVaultKeyUri.Segments[2].Trim('/'), - KeyVaultKeyVersion = keyVaultKeyUri.Segments[3].Trim('/') - }; - + SearchResourceEncryptionKey encryptionKey = null; string applicationId = configuration["AzureActiveDirectoryApplicationId"]; if (!string.IsNullOrWhiteSpace(applicationId)) { - encryptionKey.AccessCredentials = new AzureActiveDirectoryApplicationCredentials + encryptionKey = new SearchResourceEncryptionKey(new Uri($"{keyVaultKeyUri.Scheme}://{keyVaultKeyUri.Host}"), keyVaultKeyUri.Segments[2].Trim('/'), keyVaultKeyUri.Segments[3].Trim('/')) { ApplicationId = applicationId, ApplicationSecret = configuration["AzureActiveDirectoryApplicationSecret"] }; } - encryptionKey.Validate(); + return encryptionKey; } - private static void UploadDocuments(ISearchIndexClient indexClient) + private static void UploadDocuments(SearchClient searchClient) { - var hotels = new Hotel[] - { - new Hotel() - { - HotelId = "1", - BaseRate = 199.0, - Description = "Best hotel in town", - DescriptionFr = "Meilleur hôtel en ville", - HotelName = "Fancy Stay", - Category = "Luxury", - Tags = new[] { "pool", "view", "wifi", "concierge" }, - ParkingIncluded = false, - SmokingAllowed = false, - LastRenovationDate = new DateTimeOffset(2010, 6, 27, 0, 0, 0, TimeSpan.Zero), - Rating = 5, - Location = GeographyPoint.Create(47.678581, -122.131577) - }, - new Hotel() - { - HotelId = "2", - BaseRate = 79.99, - Description = "Cheapest hotel in town", - DescriptionFr = "Hôtel le moins cher en ville", - HotelName = "Roach Motel", - Category = "Budget", - Tags = new[] { "motel", "budget" }, - ParkingIncluded = true, - SmokingAllowed = true, - LastRenovationDate = new DateTimeOffset(1982, 4, 28, 0, 0, 0, TimeSpan.Zero), - Rating = 1, - Location = GeographyPoint.Create(49.678581, -122.131577) - }, - new Hotel() - { - HotelId = "3", - BaseRate = 129.99, - Description = "Close to town hall and the river" - } - }; - - var batch = IndexBatch.Upload(hotels); - + IndexDocumentsBatch batch = IndexDocumentsBatch.Create( + IndexDocumentsAction.Upload( + new Hotel() + { + HotelId = "1", + BaseRate = 199.0, + Description = "Best hotel in town", + DescriptionFr = "Meilleur hôtel en ville", + HotelName = "Fancy Stay", + Category = "Luxury", + Tags = new[] { "pool", "view", "wifi", "concierge" }, + ParkingIncluded = false, + SmokingAllowed = false, + LastRenovationDate = new DateTimeOffset(2010, 6, 27, 0, 0, 0, TimeSpan.Zero), + Rating = 5, + Location = GeographyPoint.Create(47.678581, -122.131577) + }), + IndexDocumentsAction.Upload( + new Hotel() + { + HotelId = "2", + BaseRate = 79.99, + Description = "Cheapest hotel in town", + DescriptionFr = "Hôtel le moins cher en ville", + HotelName = "Roach Motel", + Category = "Budget", + Tags = new[] { "motel", "budget" }, + ParkingIncluded = true, + SmokingAllowed = true, + LastRenovationDate = new DateTimeOffset(1982, 4, 28, 0, 0, 0, TimeSpan.Zero), + Rating = 1, + Location = GeographyPoint.Create(49.678581, -122.131577) + })); try { - indexClient.Documents.Index(batch); + IndexDocumentsResult result = searchClient.IndexDocuments(batch); } - catch (IndexBatchException e) + catch (Exception) { // Sometimes when your Search service is under load, indexing will fail for some of the documents in // the batch. Depending on your application, you can take compensating actions like delaying and // retrying. For this simple demo, we just log the failed document keys and continue. - Console.WriteLine( - "Failed to index some of the documents: {0}", - String.Join(", ", e.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key))); + Console.WriteLine("Failed to index some of the documents: {0}"); } Console.WriteLine("Waiting for documents to be indexed...\n"); Thread.Sleep(2000); } - private static void RunQueries(ISearchIndexClient indexClient) + private static void RunQueries(SearchClient searchClient) { - SearchParameters parameters; - DocumentSearchResult results; + SearchOptions searchOptions; + SearchResults results; Console.WriteLine("Search with terms nonexistent in the index:\n"); - parameters = - new SearchParameters() - { - SearchFields = new[] { "category", "tags" }, - Select = new[] { "hotelName", "category", "tags" }, - }; + searchOptions = new SearchOptions(); + searchOptions.SearchFields.Add("Category"); + searchOptions.SearchFields.Add("Tags"); + searchOptions.Select.Add("HotelName"); + searchOptions.Select.Add("Category"); + searchOptions.Select.Add("Tags"); Console.WriteLine("Search the entire index for the phrase \"five star\":\n"); - results = indexClient.Documents.Search("\"five star\"", parameters); + results = searchClient.Search("\"five star\"", searchOptions); WriteDocuments(results); Console.WriteLine("Search the entire index for the term 'internet':\n"); - results = indexClient.Documents.Search("internet", parameters); + results = searchClient.Search("internet", searchOptions); WriteDocuments(results); Console.WriteLine("Search the entire index for the terms 'economy' AND 'hotel':\n"); - results = indexClient.Documents.Search("economy AND hotel", parameters); + results = searchClient.Search("economy AND hotel", searchOptions); WriteDocuments(results); } - private static void WriteDocuments(DocumentSearchResult searchResults) + private static void WriteDocuments(SearchResults searchResults) { - foreach (SearchResult result in searchResults.Results) + foreach (SearchResult result in searchResults.GetResults()) { Console.WriteLine(result.Document); }