Azure ArmClient Rename & Copy DB Operations - c#

A bit of background, I am looking to replace existing code in a C# App from the existing Microsoft.Azure.Management.Fluent (now deprecated) to the newer Azure.ResourceManager components.
Existing code to copy a database:
public async Task<bool> CopyDb(string? server, string? fromName, string? toName)
{
_log.LogInformation("Connecting to Azure");
var azure = GetAzureObject();
var servers = await azure.SqlServers.ListAsync();
var fromServer = servers.FirstOrDefault(f => server != null && server.Contains(f.Name));
if (fromServer == null)
{
throw new InvalidOperationException("Unable to find original database server");
}
var toNameBackup = $"{toName}-Old";
var existingDbs = await fromServer.Databases.ListAsync();
var fromDB = existingDbs.FirstOrDefault(f => f.Name.Equals(fromName));
if (fromDB == null)
{
throw new InvalidOperationException("Unable to find original database");
}
if (existingDbs.Any(a => a.Name.Equals(toNameBackup, StringComparison.OrdinalIgnoreCase))
&& existingDbs.Any(a => a.Name.Equals(toName, StringComparison.OrdinalIgnoreCase)))
{
_log.LogInformation("Deleting any existing backup called {0}", toNameBackup);
await fromServer.Databases.DeleteAsync(toNameBackup);
}
if (existingDbs.Any(a => a.Name.Equals(toName, StringComparison.OrdinalIgnoreCase)))
{
_log.LogInformation("Renaming target database from {0} to {1} (if exists)", toName, toNameBackup);
await (await fromServer.Databases.GetAsync(toName)).RenameAsync(toNameBackup);
}
_log.LogInformation("Copying database from from {0} to {1}", fromName, toName);
var result = await fromServer.Databases.
Define(toName).
WithSourceDatabase(fromDB).
WithMode(Microsoft.Azure.Management.Sql.Fluent.Models.CreateMode.Copy).CreateAsync();
return result != null;
}
private Microsoft.Azure.Management.Fluent.IAzure GetAzureObject()
{
var clientId = _configuration["AzureClientId"];
var clientSecret = _configuration["AzureClientSecret"];
var tenantId = _configuration["AzureTenantId"];
var subscriptionId = _configuration["AzureSubscriptionId"];
var credentials = Microsoft.Azure.Management.ResourceManager.Fluent.SdkContext.AzureCredentialsFactory.FromServicePrincipal(
clientId: clientId,
clientSecret: clientSecret,
tenantId: tenantId,
environment: Microsoft.Azure.Management.ResourceManager.Fluent.AzureEnvironment.AzureGlobalCloud);
return Microsoft.Azure.Management.Fluent.Azure.Configure().Authenticate(credentials).WithSubscription(subscriptionId);
}
The newer components all work with resources and I've been struggling how to do a couple operations with the newer Azure.ArmClient. I've been able to query with it finding my SQL server and databases. I can even delete some DBs, but I'm unable to work out how to rename or copy databases like the above code. I know there are alternative ways to do this directly in SQL, but I'd prefer to see how to do it in code.
I have had a look around MS docs, I can only find information on the object definitions but no examples.
I have managed to get down to the point of renaming:-
var backupDb = fromServer.GetSqlDatabase(toName);
if (backupDb != null && backupDb.Value != null)
{
// What do I pass in to the definition?
var moveDefinition = new SqlResourceMoveDefinition()
{
// What to set here?
};
await (await backupDb.Value.GetAsync()).Value.RenameAsync(moveDefinition);
}
I'm not sure on how to define the SqlResourceMoveDefinition. I also can't work out at all how to perform the copy like in the older SDK.
Anyone have any guides on how to achieve these operations in C#?

Managed to work it out after eventually working from https://learn.microsoft.com/en-us/dotnet/azure/sdk/resource-management?tabs=PowerShell. There may be better ways to do this, and I'll edit the answer when I find them if others don't by then!
public async Task<bool> CopyDb(string? server, string? fromName, string? toName)
{
_log.LogInformation("Connecting to Azure");
var azure = GetAzureSubscription();
var servers = azure.GetSqlServers().ToList();
var fromServer = servers.SingleOrDefault(f => server != null && f.Data != null && server.Contains(f.Data.Name));
if (fromServer == null)
{
throw new InvalidOperationException("Unable to find original database server");
}
var oldName = $"{toName}-Old";
var databases = fromServer.GetSqlDatabases();
_log.LogInformation("Check for any existing backup called {0}", oldName);
if (await databases.ExistsAsync(oldName))
{
_log.LogInformation("Deleting for any existing backup called {0}", oldName);
var oldBackup = await databases.GetAsync(oldName);
await oldBackup.Value.DeleteAsync(WaitUntil.Completed);
}
_log.LogInformation("Check target database {0} exists", toName, oldName);
if (await databases.ExistsAsync(toName))
{
_log.LogInformation("Renaming target database from {0} to {1}", toName, oldName);
var toDbBackup = await databases.GetAsync(toName);
var resourceIdString = toDbBackup.Value.Data.Id.Parent?.ToString();
var newResourceId = new ResourceIdentifier($"{resourceIdString}/databases/{oldName}");
var moveDefinition = new SqlResourceMoveDefinition(newResourceId);
var toDb = await toDbBackup.Value.GetAsync();
await toDb.Value.RenameAsync(moveDefinition);
}
_log.LogInformation("Copying database from from {0} to {1}", fromName, toName);
var fromDb = await databases.GetAsync(fromName);
var result = await databases.CreateOrUpdateAsync(WaitUntil.Completed, toName, fromDb.Value.Data);
_log.LogInformation("Operation completed!");
return result.HasValue;
}
private SubscriptionResource GetAzureSubscription()
{
var configValue = _configuration["AzureSubscriptionId"];
var subscriptionId = new ResourceIdentifier($"/subscriptions/{configValue}");
return GetAzureArmClient().GetSubscriptionResource(subscriptionId);
}
private ArmClient GetAzureArmClient()
{
var clientId = _configuration["AzureClientId"];
var clientSecret = _configuration["AzureClientSecret"];
var tenantId = _configuration["AzureTenantId"];
var credentials = new ClientSecretCredential(
clientId: clientId,
clientSecret: clientSecret,
tenantId: tenantId);
return new ArmClient(credentials);
}

Related

In Azure Blob container, How to delete all files by name under a directory?

I use Azure blob client API, and I want to delete all files name "fieldA".
My diretory is as
/all/folder1/fieldA
/all/folder1/fieldB
/all/folder2/fieldA
/all/folder2/fieldC
After delete, I want it become
/all/folder1/fieldB
/all/folder2/fieldC
I do some search it says Azure blob container does not support regular expression search.
So I think the only way is to list all blob -> filter fieldA -> delete.
I wrote some code as following, and it keep give me unknow error. Could I know if there is sth not right?
var ctoken = new BlobContinuationToken();
do
{
var result = await blobContainer.ListBlobsSegmentedAsync('/all', true, BlobListingDetails.None, null, ctoken, null, null);
ctoken = result.ContinuationToken;
var a = result.Results.Where(item => (item as CloudBlob).Name.EndsWith("fieldA"));
await Task.WhenAll(a
.Select(item => (item as CloudBlob)?.DeleteAsync())
.Where(task => task != null)
);
} while (ctoken != null);
If you want to delete all files by name under a directory, please refer to the following code
My container is as below
container: test
blob :
/all/folder1/test.txt
/all/folder1/test1.txt
/all/folder2/test.txt
/all/folder2/test2.txt
Code
static async Task Main(string[] args)
{
string accountName = "andyprivate";
string keyValue = "";
string containerName = "test";
StorageCredentials credentials = new StorageCredentials(accountName, keyValue);
CloudBlobContainer container = new CloudBlobContainer(new Uri($"https://{accountName}.blob.core.windows.net/{containerName}"), credentials);
string prefix = "all/";
await ListBlobsHierarchicalListingAsync(container, prefix);
}
private static async Task ListBlobsHierarchicalListingAsync(CloudBlobContainer container, string prefix)
{
BlobContinuationToken continuationToken = null;
do
{
BlobResultSegment resultSegment = await container.ListBlobsSegmentedAsync(prefix,
false, BlobListingDetails.Metadata, null, continuationToken, null, null);
var a = resultSegment.Results.OfType<CloudBlob>().Where(item => item.Name.EndsWith("test.txt")).ToList();
var b = resultSegment.Results.OfType<CloudBlobDirectory>().ToList();
await Task.WhenAll(a
.Select(item => item?.DeleteAsync())
.Where(task => task != null));
foreach (var r1 in b) {
Console.WriteLine("Virtual directory prefix: {0}", r1.Prefix);
await ListBlobsHierarchicalListingAsync(container, r1.Prefix);
}
continuationToken = resultSegment.ContinuationToken;
} while (continuationToken != null);
}

Paging with CosmosClient in CosmosDB

I'm trying to implement paging using the SDK v3 CosmosClient instead of the old DocumentClient.
Reason for this is it seems DocumentClient doesn't translate LINQ queries that contains spatial functions very well (ie: When using Within() I'll get an error from DocumentClient stating the methods is not implemented).
Paging works well with DocumentClient.CreateDocumentQuery<T> as such:
var query = DocumentClient.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri("master", "features"), feedOptions)
.Where(t => t.Type == typeof(T).Name)
.Where(pred)
.AsDocumentQuery();
string queryContinuationToken = null;
var page = await query.ExecuteNextAsync<T>();
if (query.HasMoreResults)
queryContinuationToken = page.ResponseContinuation;
I'm at a little loss as to where to gather a continuation token using CosmosClient and its Container class:
QueryRequestOptions options = new QueryRequestOptions();
options.MaxItemCount = maxRecords;
FeedIterator<T> feed;
if (continuationToken == "")
feed = Container.GetItemLinqQueryable<T>(true, null, options).Where(x => x.Type == typeof(T).Name).Where(pred).ToFeedIterator();
else
feed = Container.GetItemLinqQueryable<T>(true, continuationToken, options).Where(x => x.Type == typeof(T).Name).Where(pred).ToFeedIterator();
FeedIterator seems to have some of the members IDocumentQuery has (like HasMoreResults) but I can't find a continuation token anywhere.
What am I missing?
Alright, here's a Where method I implemented. Seems to work at first glance.
If you do var f = feed.ReadNextAsync() you won't get an object that's of type FeedResponse, preventing you access to the token. You need to declare f explicitly of type FeedResponse<T>
public async Task<(IEnumerable<T> Results, string ContinuationToken)> Where<T>(Expression<Func<T, bool>> pred, int maxRecords = 0, string partitionKey = "", string continuationToken = "") where T : IDocumentModel
{
QueryRequestOptions options = new QueryRequestOptions();
if (partitionKey != "")
options.PartitionKey = new PartitionKey(partitionKey);
if (maxRecords == 0)
{
return (Container.GetItemLinqQueryable<T>(true, null, options).Where(x => x.Type == typeof(T).Name).Where(pred), "");
}
else
{
options.MaxItemCount = maxRecords;
string token = "";
FeedIterator<T> feed;
List<T> res = new List<T>();
if (continuationToken == "")
feed = Container.GetItemLinqQueryable<T>(true, null, options).Where(x => x.Type == typeof(T).Name).Where(pred).ToFeedIterator();
else
feed = Container.GetItemLinqQueryable<T>(true, continuationToken, options).Where(x => x.Type == typeof(T).Name).Where(pred).ToFeedIterator();
Microsoft.Azure.Cosmos.FeedResponse<T> f = await feed.ReadNextAsync();
token = f.ContinuationToken;
foreach (var item in f)
{
res.Add(item);
}
return (res, token);
}
}
With the version 3.12.0 of the Cosmos SDK the following works as expected as a pretty much in place replacement for the older DocumentQuery.
Original DocumentClient method for comparison:
IDocumentQuery<ToDoItem> query = client.CreateDocumentQuery<ToDoItem>(collectionUri)
.Where(t => t.Description.Contains(searchterm))
.AsDocumentQuery();
while (query.HasMoreResults)
{
foreach (ToDoItem result in await query.ExecuteNextAsync())
{
log.LogInformation(result.Description);
}
}
Using a CosmosClient this becomes:
var database = client.GetDatabase("ToDoItems");
var container = database.GetContainer("Items");
var query = container.GetItemLinqQueryable<ToDoItem>()
.Where(t => t.Description.Contains(searchTerm))
.ToFeedIterator();
while (query.HasMoreResults)
{
foreach (ToDoItem result in await query.ReadNextAsync())
{
log.LogInformation(result.Description);
}
}
So your query is now a FeedIterator, and you can call HasMoreResults and ReadNextAsync on it.
Admittedly this won't get you access to the diagnostics, request charge, etc. that comes on the FeedIterator, but it will page through the results cleanly.
IQueryable<returnVModel> query;
var requestOptions = new QueryRequestOptions
{
MaxItemCount = 20
};
if (Token == "" || Token == null)
{
query = Container.GetItemLinqQueryable<returnVModel>(false, null, requestOptions).Where(x => x.id == id);
}
else
{
query = Container.GetItemLinqQueryable<returnVModel>(false, Token, requestOptions).Where(x => x.id == id);
}
var ct = new CancellationTokenSource();
var totalCount = await query.CountAsync(ct.Token); //Total Count
var feedIterator = query.ToFeedIterator();
var queryResults = new List<returnVModel>();
FeedResponse<returnVModel> feedResults = await feedIterator.ReadNextAsync(ct.Token);
queryResults.AddRange(feedResults); // Output
var PaginationToken = feedResults.ContinuationToken //Token
First time we need to pass token as null, from next page onwards pass the token which we received in previous output.
Pagination was working fine in v3.

How to inject a variable into every class or method in c#

I have the following code.
[HttpGet]
public async Task<List<TenantManagementWebApi.Entities.SiteCollection>> Get()
{
var tenant = await TenantHelper.GetActiveTenant();
var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
string domainUrl = tenant.TestSiteCollectionUrl;
string tenantName = domainUrl.Split('.')[0];
string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
using (var context = new OfficeDevPnP.Core.AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant(tenantAdminUrl, tenant.Email, keyVaultHelper.SecretValue))
{
Tenant tenantOnline = new Tenant(context);
SPOSitePropertiesEnumerable siteProps = tenantOnline.GetSitePropertiesFromSharePoint("0", true);
context.Load(siteProps);
context.ExecuteQuery();
List<TenantManagementWebApi.Entities.SiteCollection> sites = new List<TenantManagementWebApi.Entities.SiteCollection>();
foreach (var site in siteProps)
{
if(site.Template.Contains("SITEPAGEPUBLISHING#0") || site.Template.Contains("GROUP#0"))
{
string strTemplate= default(string);
if(site.Template.Contains("SITEPAGEPUBLISHING#0"))
{
strTemplate = "CommunicationSite";
};
if (site.Template.Contains("GROUP#0"))
{
strTemplate = "Modern Team Site";
};
try
{
Guid id = Guid.NewGuid();
Entities.SiteCollection sc = new Entities.SiteCollection()
{
Id = id.ToString(),
Owner = site.Owner,
Template = strTemplate,
Title = site.Title,
Active = false,
Url = site.Url
};
var added = await siteCollectionStore.AddAsync(sc);
sites.Add(sc);
}
catch (System.Exception ex)
{
throw ex;
}
}
}
return sites;
};
}
However the following lines, I am repeating them on every method:
var tenant = await TenantHelper.GetActiveTenant();
var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
string domainUrl = tenant.TestSiteCollectionUrl;
string tenantName = domainUrl.Split('.')[0];
string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
I will have lots of API controllers on my project
Is there an easy way (not refactor as a method), to make my code cleaner and inject the variables I need without copying and pasting every single time?

Test connection with TLSharp

I'm trying to send a message with TLSharp but cant,i dont get errors,it just execute the code and do nothing;
This is my method.
public virtual async Task SendMessageTest()
{
string NumberToSendMessage = "+55199999999";
if (string.IsNullOrWhiteSpace(NumberToSendMessage))
throw new Exception("TESTE");
// this is because the contacts in the address come without the "+" prefix
var normalizedNumber = NumberToSendMessage.StartsWith("+") ?
NumberToSendMessage.Substring(1, NumberToSendMessage.Length - 1) :
NumberToSendMessage;
var client = NewClient();
var tsk = client.ConnectAsync();
await client.ConnectAsync();
var result = await client.GetContactsAsync();
var user = result.users.lists
.OfType<TLUser>()
.FirstOrDefault(x => x.phone == normalizedNumber);
if (user == null)
{
throw new System.Exception("Number was not found in Contacts List of user: " + NumberToSendMessage);
}
await client.SendTypingAsync(new TLInputPeerUser() { user_id = user.id });
Thread.Sleep(3000);
await client.SendMessageAsync(new TLInputPeerUser() { user_id = user.id }, "TEST");
}
This is my code,is says is wait for activation,what should i do?
I'm trying to use this method also,but it doenst return nothing too.
I'm new to TelegramApi,what i'm doing wrong?
await client.ConnectAsync();
You should authorize first! And only after that you can call other methods. Look at the examples here.
In general you should write something like this:
var hash = await client.SendCodeRequestAsync(NotRegisteredNumberToSignUp);
var code = Console.ReadLine(); //Input the code, that was sent to your phone
var loggedInUser = await client.MakeAuthAsync(NotRegisteredNumberToSignUp, hash, code);

C# Google drive sdk. How to get a list of google drive folders?

I'm writing a program to allow a user to upload files to their Google Drive account. I have the upload part working and am using OAuth2. The issue I'm currently having is getting a list of folders from the users Drive account.
I found some code that is supposed to do this using the .setUserCredentials method, but it doesn't work:
DocumentsService service1 = new DocumentsService("project");
service1.setUserCredentials("user","pass");
FolderQuery query1 = new FolderQuery();
// Make a request to the API and get all documents.
DocumentsFeed feed = service1.Query(query1);
// Iterate through all of the documents returned
foreach (DocumentEntry entry in feed.Entries)
{
var blech = entry.Title.Text;
}
Nothing is returned. Ideally, I want to use OAuth2 to do this. I've been trying with the following code, trying to set the authentication token, but I always get denied access:
String CLIENT_ID = "clientid";
String CLIENT_SECRET = "secretid";
var docprovider = new NativeApplicationClient(GoogleAuthenticationServer.Description, CLIENT_ID, CLIENT_SECRET);
var docstate = GetDocAuthentication(docprovider);
DocumentsService service1 = new DocumentsService("project");
service1.SetAuthenticationToken(docstate.RefreshToken);
FolderQuery query1 = new FolderQuery();
DocumentsFeed feed = service1.Query(query1); //get error here
// Iterate through all of the documents returned
foreach (DocumentEntry entry in feed.Entries)
{
// Print the title of this document to the screen
var blech = entry.Title.Text;
}
..
private static IAuthorizationState GetDocAuthentication(NativeApplicationClient client)
{
const string STORAGE = "storagestring";
const string KEY = "keystring";
string scope = "https://docs.google.com/feeds/default/private/full/-/folder";
// Check if there is a cached refresh token available.
IAuthorizationState state = AuthorizationMgr.GetCachedRefreshToken(STORAGE, KEY);
if (state != null)
{
try
{
client.RefreshToken(state);
return state; // Yes - we are done.
}
catch (DotNetOpenAuth.Messaging.ProtocolException ex)
{
}
}
// Retrieve the authorization from the user.
state = AuthorizationMgr.RequestNativeAuthorization(client, scope);
AuthorizationMgr.SetCachedRefreshToken(STORAGE, KEY, state);
return state;
}
Specifically, I get "Execution of request failed: https://docs.google.com/feeds/default/private/full/-/folder - The remote server returned an error: (401) Unauthorized".
I've also tried:
var docauth = new OAuth2Authenticator<NativeApplicationClient>(docprovider, GetDocAuthentication);
DocumentsService service1 = new DocumentsService("project");
service1.SetAuthenticationToken(docauth.State.AccessToken);
but "State" is always null, so I get a null object error. What am I doing wrong and how is this done?
You should use the Drive SDK, not the Documents List API, which allows you to list folders. You can use "root" as a folderId if you want to list the root directory.
I actually implemented the v3 version of the GDrive SDK for .NET and needed to search for folders as well.
I prefer requesting uniquely all folders instead of getting all files and then performing a LinQ query to keep just the folders.
This is my implementation:
private async Task<bool> FolderExistsAsync(string folderName)
{
var response = await GetAllFoldersAsync();
return response.Files
.Where(x => x.Name.ToLower() == folderName.ToLower())
.Any();
}
private async Task<Google.Apis.Drive.v3.Data.FileList> GetAllFoldersAsync()
{
var request = _service.Files.List();
request.Q = "mimeType = 'application/vnd.google-apps.folder'";
var response = await request.ExecuteAsync();
return response;
}
You could request the name on the Q this way as well:
request.Q = $"mimeType = 'application/vnd.google-apps.folder' and name = '{folderName}'";
Which would lead and simplify things to (obviating null checking):
private async Task<bool> FolderExistsAsync(string folderName)
{
var response = await GetDesiredFolder(folderName);
return response.Files.Any();
}
private async Task<FileList> GetDesiredFolder(string folderName)
{
var request = _service.Files.List();
request.Q = $"mimeType = 'application/vnd.google-apps.folder' and name = '{folderName}'";
var response = await request.ExecuteAsync();
return response;
}
private IEnumerable<DocumentEntry> GetFolders(string id) {
if (IsLogged) {
var query = new FolderQuery(id)
{
ShowFolders = true
};
var feed = GoogleDocumentsService.Query(query);
return feed.Entries.Cast<DocumentEntry>().Where(x => x.IsFolder).OrderBy(x => x.Title.Text);
}
return null;
}
...
var rootFolders = GetFolders("root");
if (rootFolders != null){
foreach(var folder in rootFolders){
var subFolders = GetFolders(folder.ResourceId);
...
}
}
where GoogleDocumentsService is a instance of DocumentsService and IsLogged is a success logged flag.
I got this way to get list of folders from google drive
FilesResource.ListRequest filelist= service.Files.List();
filelist.Execute().Items.ToList().Where(x => x.MimeType == "application/vnd.google-apps.folder").ToList()

Categories

Resources