Create Timer Azure Function which use CSOM to integrate with SharePoint Online, locally and deployed inside Azure - azure-devops

I have created an Azure Function which runs on schedule bases >> it worked locally and also when i deploy it to Azure >> the function uses PnP core. Here is my startup.cs:-
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PnP.Core.Auth;
using System.Security.Cryptography.X509Certificates;
[assembly: FunctionsStartup(typeof(FunctionApp2.Startup))]
namespace FunctionApp2
{
class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var config = builder.GetContext().Configuration;
var azureFunctionSettings = new AzureFunctionSettings();
config.Bind(azureFunctionSettings);
builder.Services.AddPnPCore(options =>
{
options.DisableTelemetry = true;
var authProvider = new X509CertificateAuthenticationProvider(azureFunctionSettings.ClientId,
azureFunctionSettings.TenantId,
StoreName.My,
StoreLocation.CurrentUser,
azureFunctionSettings.CertificateThumbprint);
options.DefaultAuthenticationProvider = authProvider;
options.Sites.Add("Default", new PnP.Core.Services.Builder.Configuration.PnPCoreSiteOptions
{
SiteUrl = azureFunctionSettings.SiteUrl,
AuthenticationProvider = authProvider
});
});
}
}
}
and the function class:-
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using PnP.Core.Services;
using PnP.Core.Model.SharePoint;
using System.Collections.Generic;
namespace FunctionApp2
{
public class Function1
{
private readonly IPnPContextFactory pnpContextFactory;
public Function1(IPnPContextFactory pnpContextFactory)
{
this.pnpContextFactory = pnpContextFactory;
}
[FunctionName("Function1")]
public void Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
using (var context = pnpContextFactory.Create("Default"))
{
var myList = context.Web.Lists.GetByTitle("Tracking");
Dictionary<string, object> values = new Dictionary<string, object>
{
{ "Title", System.DateTime.Now }
};
//code goes here
}
}
}
}
and finally the localsetting.json :-
{ "IsEncrypted": false,
"Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet",
"SiteUrl": "https://**.sharepoint.com/",
"TenantId": "**",
"ClientId": "**",
"CertificateThumbPrint": "**",
"WEBSITE_LOAD_CERTIFICATES": "**" } }
where i am able to test the function ,locally since i am using the Azure Storage locally + i am able to secure my call to azure active directory App using certificate (self-signed).
now i am trying to do the same project, but instead of using PnP, to use the CSOM library. i search the net but i could not find a complete code, i tried the following from an old post on the net:-
starup.cs code:-
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Identity.Client;
using System.IO;
using System;
using System.Security.Cryptography.X509Certificates;
using Microsoft.SharePoint.Client;
using System.Threading.Tasks;
[assembly: FunctionsStartup(typeof(FunctionApp2.Startup))]
namespace FunctionApp2
{ class Startup :
FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder)
{private static string ClientId = "(Application ID)";
private static string Cert = "(filename).pfx";
private static string CertPassword = "(password)";
private static string Authority = "https://login.windows.net/(tenantName).onmicrosoft.com/";
private static string Resource = "https://(tenantName).sharepoint.com/";
public async static Task<ClientContext> GetClientContext(string siteUrl)
{
var authenticationContext = new AuthenticationContext(Authority, false);
var certPath = Path.Combine(Environment.GetEnvironmentVariable("HOME"), "site\\wwwroot\\<FunctionName>\\", Cert);
var cert = new X509Certificate2(System.IO.File.ReadAllBytes(certPath),
CertPassword,
X509KeyStorageFlags.Exportable |
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet);
var authenticationResult = await authenticationContext.AcquireTokenAsync(Resource, new (ClientId, cert));
var token = authenticationResult.AccessToken;
var ctx = new ClientContext(siteUrl);
ctx.ExecutingWebRequest += (s, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + authenticationResult.AccessToken;
};
return ctx;
}
}}
the function code:-
using System; using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using PnP.Core.Services;
using PnP.Core.Model.SharePoint;
using System.Collections.Generic;
namespace FunctionApp2 { public class Function1{
private readonly IPnPContextFactory pnpContextFactory;
public Function1(IPnPContextFactory pnpContextFactory)
{
this.pnpContextFactory = pnpContextFactory;
}
[FunctionName("Function1")]
public void Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
using (var context = pnpContextFactory.Create("Default"))
{
var myList = context.Web.Lists.GetByTitle("Tracking");
Dictionary<string, object> values = new Dictionary<string, object>
{
//code goes here
};
}
}
}}
but currently i am facing these issue:-
inside my startup class for the CSOM project, i am getting an error that AuthenticationContext can not be found and its reference Microsoft.IdentityModel.client.ActiveDirectory is obsolete now
how i can define to use the local storage to be able to debug my code when using CSOM.
what i need to do inside my Function class to replace PnP code with CSOM ?
Thanks

Related

Replace SQLite to LocalDb for unittesting for a ABP.io project

I am working with ABP and I used the basic setup which is generated by the abp cli with EF Core and SQL Server.
Now, I would like to replace SQLite (the generated one), with a LocalDb database, since I using spatial data for unit- and integration testing.
However, I am not sure how to make the replacement. I have identified the file which initialize the SQLite, and it is reseeded after each unittest.
When switching to LocalDb, I will not flush / seed database, and I think I have solved that out.
This is the generated file ...\ABPProject\aspnet-core\ABPProject.EntityFrameworkCore.Tests\EntityFrameworkCore\ABPProjectEntityFrameworkCoreTestModule.cs that uses SQLite, can you please give me an example / ideas how to use LocalDb instead of SQLite?
[DependsOn(
typeof(ABPProjectEntityFrameworkCoreModule),
typeof(ABPProjectTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ABPProjectEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
ConfigureInMemorySqlite(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
_sqliteConnection.Dispose();
}
private static SqliteConnection CreateDatabaseAndGetConnection()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ABPProjectDbContext>()
.UseSqlite(connection)
.Options;
using (var context = new ABPProjectDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}
This was really simple. Just do this:
namespace ABPProject.EntityFrameworkCore;
[DependsOn(
typeof(ABPProjectEntityFrameworkCoreModule),
typeof(ABPProjectTestBaseModule),
typeof(AbpEntityFrameworkCoreSqliteModule)
)]
public class ABPProjectEntityFrameworkCoreTestModule : AbpModule
{
private SqliteConnection _sqliteConnection;
public override void ConfigureServices(ServiceConfigurationContext context)
{
//ConfigureInMemorySqlite(context.Services);
ConfigureLocalDb(context.Services);
}
private void ConfigureInMemorySqlite(IServiceCollection services)
{
_sqliteConnection = CreateDatabaseAndGetConnection_Sqlite();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(_sqliteConnection);
});
});
}
private void ConfigureLocalDb(IServiceCollection services)
{
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlServer("Server=(LocalDb)\\MSSQLLocalDB;Database=ABPProject_Unittest;Trusted_Connection=True;TrustServerCertificate=True");
});
});
}
public override void OnApplicationShutdown(ApplicationShutdownContext context)
{
if (_sqliteConnection is not null)
{
_sqliteConnection.Dispose();
}
}
private static SqliteConnection CreateDatabaseAndGetConnection_Sqlite()
{
var connection = new SqliteConnection("Data Source=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ABPProjectDbContext>()
.UseSqlite(connection, x => x.UseNetTopologySuite())
.Options;
using (var context = new ABPProjectDbContext(options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}
return connection;
}
}
Then you need to disable re-seeding each time between test in aspnet-core\test\ABPProject.TestBase\ABPProjectTestBaseModule.cs
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
//SeedTestData(context);
}
How to reset the database when starting all tests? Use XUnit collections, which enable setup before all tests are starting and teardown after all tests are executed.
You can read more about it here https://xunit.net/docs/shared-context but basically, you create a new class (NOTE! You need to place this file in each test project, since the class can not be shared between different test projects):
public class DatabaseFixture : ABPProjectTestBase<ABPProjectEntityFrameworkCoreTestModule>, IAsyncLifetime
{
// ... create your shared vars here
public DatabaseFixture()
{
}
public async Task InitializeAsync()
{
IDbContextProvider<ABPProjectDbContext> _dbContextProvider = GetRequiredService<IDbContextProvider<ABPProjectDbContext>>();
IUnitOfWorkManager _unitOfWorkManager = GetRequiredService<IUnitOfWorkManager>();
IDataSeeder _dataSeeder = GetRequiredService<IDataSeeder>();
using (var uow = _unitOfWorkManager.Begin())
{
var context = await _dbContextProvider.GetDbContextAsync();
var tableNames = context.Model.GetEntityTypes()
.Where(t => t.GetTableName().StartsWith("App"))
.Select(t => t.GetTableName())
.Distinct()
.ToList();
// Disable constraints
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {tableName} NOCHECK CONSTRAINT ALL");
}
// Remove rows
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"DELETE FROM {tableName}");
}
// Re-enable constraints
foreach (var tableName in tableNames)
{
await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {tableName} CHECK CONSTRAINT ALL");
}
}
// Re - seed database
await _dataSeeder.SeedAsync();
}
public async Task DisposeAsync()
{
// ... clean up test data from the database with and use async calls here...
}
}
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
The last thing is to decorate each test class with following:
namespace ABPProject.Things
{
[Collection("Database collection")]
public class ThingAppService_Tests : ABPProjectApplicationTestBase
{
public readonly DatabaseFixture _fixture;
private readonly IThingAppService _thingAppService;
public ThingAppService_Tests(DatabaseFixture fixture)
{
_fixture = fixture;
_thingAppService = GetRequiredService<IThingAppService>();
}
[Fact]
public async Task GetListAsync_Should_Get_all_Things()
{
// This is a test
}
}
}

Use different database authentication methods for design time and runtime in EF Core

Runtime:
I am using .NET 6 and EF Core in an Azure Function. To connect with an Azure SQL Database, I want to use AAD-Authentication, so I configured my DbContext as follows:
public class FunctionContext : DbContext {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
SqlConnection connection = new();
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = Environment.GetEnvironmentVariable("userAssignedClientId") });
var token = credential.GetToken(new Azure.Core.TokenRequestContext(new[] { "https://database.windows.net/.default" }));
connection.ConnectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
connection.AccessToken = token.Token;
optionsBuilder.UseSqlServer(connection);
optionsBuilder.LogTo(Console.WriteLine);
optionsBuilder.UseExceptionProcessor();
optionsBuilder.EnableSensitiveDataLogging();
}
}
The connection string "SqlConnectionString" is available as an environment variable and has the following form:
"Server=demo.database.windows.net; Database=testdb";
Migrations:
I want to update the database with every deployment. I am using Azure DevOps pipelines to deploy the application, and I have a service principal that I can use to log in. So I need to use a connection string that looks like this:
"Server=demo.database.windows.net; Authentication=Active Directory Service Principal; Encrypt=True; Database=testdb; User Id=AppId; Password=secret";
Is there a possiblity to use two different connection strings for runtime and migrations?
I tried modifiying the Factory method that Update-Database uses to create the context, but since the OnConfiguring method pasted above is called anyway, I still end up with the same connection string.
The solution I found was not to implement the OnConfiguring method, but pass the configuration directly in the Startup.cs as follows:
Startup.cs (Context at runtime)
internal class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddDbContext<FunctionContext>(options =>
{
SqlConnection connection = new();
var credentialOptions = new DefaultAzureCredentialOptions {
ManagedIdentityClientId = Environment.GetEnvironmentVariable("userAssignedClientId")};
var credential = new DefaultAzureCredential(credentialOptions);
var token = credential.GetToken(new Azure.Core.TokenRequestContext(new[] { "https://database.windows.net/.default" }));
connection.ConnectionString = Environment.GetEnvironmentVariable("SqlConnectionString");
connection.AccessToken = token.Token;
options.UseSqlServer(connection);
options.LogTo(Console.WriteLine);
options.UseExceptionProcessor();
options.EnableSensitiveDataLogging();
});
}
}
DesignTimeFunctionContextFactory.cs (Context at design time)
public class DesignTimeFunctionContextFactory : IDesignTimeDbContextFactory<FunctionContext>
{
public FunctionContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<FunctionContext>();
optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("SqlAdminConnectionString"), options => options.EnableRetryOnFailure());
return new FunctionContext(optionsBuilder.Options);
}
}

CognitoAuthentication Extension for .NET with Unity (StartWithSrpAuthAsync()) issue

After importing the AWS-SDK for .NET dll's including the AWS.Extension.CognitoAuthentication into Unity 2018.2, I am having a problem with the StartWithSrpAuthAsync function taken from AuthenticateWithSrpAsync provided by https://aws.amazon.com/blogs/developer/cognitoauthentication-extension-library-developer-preview/
Code from site:
public async void AuthenticateWithSrpAsync()
{
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
FallbackRegionFactory.GetRegionEndpoint());
CognitoUserPool userPool = new CognitoUserPool("poolID", "clientID", provider);
CognitoUser user = new CognitoUser("username", "clientID", userPool, provider);
string password = "userPassword";
AuthFlowResponse context = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
{
Password = password
}).ConfigureAwait(false);
}
}
I want a button script to take in a username and password from the user and authenticate it with the UserPool I created in Cognito.
Button Script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Amazon;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
public class test : MonoBehaviour {
public string userName;
public string userPassword;
public string clientID;
public string poolID;
public AuthFlowResponse authResponse;
public CognitoUserPool userPool;
public AmazonCognitoIdentityProviderClient provider;
public CognitoUser user;
void Start()
{
}
public void OnClick()
{
try
{
AuthenticateWithSrpAsync();
}
catch(Exception ex)
{
Debug.Log(ex);
}
}
public async void AuthenticateWithSrpAsync()
{
RegionEndpoint CognitoIdentityRegion = RegionEndpoint.USEast1;
provider = new AmazonCognitoIdentityProviderClient(null, CognitoIdentityRegion);
userPool = new CognitoUserPool(poolID, clientID, provider, null);
user = new CognitoUser(userName, clientID, userPool, provider);
string name = user.Username.ToString();
Debug.Log(name);
authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() {
Password = userPassword
}).ConfigureAwait(false);
Debug.Log(user.SessionTokens.IdToken);
Debug.Log("Success");
}
}
The app client does not require a secret key.
App Client
https://imgur.com/a/NUzBghb
The User status is confirmed/enabled and the email is verified.
User
https://imgur.com/lsnG5tT
What ends up happening is the script runs until it gets to:
authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() {
Password = userPassword
}).ConfigureAwait(false);
Debug.Log(user.SessionTokens.IdToken);
Debug.Log("Success");
And does absolutely nothing afterwards. Neither of the debugs show in the console as well as any Error or warning messages.
Unity Console:
https://imgur.com/Hxpcmoj
I have looked through the StackOverflow questions as well as every other resource I could find on google. I have also replicated this in Unity 2017.3
I'm using .NetFramework 4.6
Try checking the checkbox "Enable username-password(non-SRP)...shown in the image in the link https://imgur.com/a/NUzBghb.
I don't think it is related to Unity as the same code is working fine at my end. Can you try following:
_provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), );

Chat Application using SignalR and Ionic

I am developing a chat application.I use SignalR and Ionic for this.And I get an error on the Ionic side.
Startup.cs
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(SignalR.Startup))]
namespace SignalR
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
ChatHub.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
namespace SignalR
{
public class ChatHub : Hub
{
public void Send(string username,string message)
{
Clients.All.sendMessage(username,message);
}
}
}
controller.js
angular.module('starter.controllers', [])
.controller('DashCtrl', function ($scope) {
$scope.name = 'Onur'; // holds the user's name
$scope.message = ''; // holds the new message
$scope.messages = []; // collection of messages coming from server
$scope.chatHub = null; // holds the reference to hub
$scope.chatHub = $.connection.chatHub; // initializes hub
$.connection.hub.start(); // starts hub
// register a client method on hub to be invoked by the server
$scope.chatHub.client.broadcastMessage = function (name, message) {
var newMessage = name + ' says: ' + message;
// push the newly coming message to the collection of messages
$scope.messages.push(newMessage);
$scope.$apply();
};
$scope.newMessage = function () {
// sends a new message to the server
$scope.chatHub.server.sendMessage($scope.name, $scope.message);
$scope.message = '';
};
})
Faield to load resource
hubs(0,0)
TypeError: Cannot read property 'chatHub' of undefined
I'm getting errors.Where am I doing wrong.Help me
You need to start the hub after registering client methods. It should work.

Testing code in a custom NancyFx Bootstrapper

I have a custom Nancy Bootstrapper which uses StructureMapNancyBootstrapper but the issue is the same regardless of container.
public class CustomNancyBootstrapper : StructureMapNancyBootstrapper
{
protected override void RequestStartup(IContainer container, IPipelines pipelines, NancyContext context)
{
var auth = container.GetInstance<ICustomAuth>();
auth.Authenticate(context);
}
}
I want to write a test to assert that Authenticate is called with the context... something like this...
[Test]
public void RequestStartup_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuthentication = new Mock<ICustomAuth>();
var mockContainer = new Mock<IContainer>();
var mockPipelines = new Mock<IPipelines>();
var context = new NancyContext();
mockContainer.Setup(x => x.GetInstance<ICustomAuth>()).Returns(mockAuthentication.Object);
// exercise
_bootstrapper.RequestStartup(_mockContainer.Object, _mockPipelines.Object, context);
// verify
mockAuthentication.Verify(x => x.Authenticate(context), Times.Once);
}
The problem is that I can't call RequestStartup because it's protected as defined in NancyBootstrapperBase.
protected virtual void RequestStartup(TContainer container, IPipelines pipelines, NancyContext context);
Is there a "proper"/"offical" Nancy way to do this without creating another derived class and exposing the methods as that just seems like a hack?
Thanks
I guess you can "fake" the request by using Browser from Nancy.Testing:
var browser = new Browser(new CustomNancyBootstrapper());
var response = browser.Get("/whatever");
There is a good set of articles about testing NancyFx application:
http://www.marcusoft.net/2013/01/NancyTesting1.html
Turns out Nancy offers a IRequetStartup interface so you can take the code out of the custom bootstrapper and do something like this...
public class MyRequestStart : IRequestStartup
{
private readonly ICustomAuth _customAuthentication;
public MyRequestStart(ICustomAuth customAuthentication)
{
if (customAuthentication == null)
{
throw new ArgumentNullException(nameof(customAuthentication));
}
_customAuthentication = customAuthentication;
}
public void Initialize(IPipelines pipelines, NancyContext context)
{
_customAuthentication.Authenticate(context);
}
}
and the test is easy and concise
[Test]
public void When_Initialize_Calls_CustomAuth_Authenticate_WithContext()
{
// set up
var mockAuth = new Mock<ICustomAuth>();
var requestStartup = new MyRequestStart(mockAuth.Object);
var mockPipeline = new Mock<IPipelines>();
var context = new NancyContext();
// exercise
requestStartup.Initialize(mockPipeline.Object, context);
// verify
mockAuth.Verify(x => x.Authenticate(context), Times.Once);
}
https://github.com/NancyFx/Nancy/wiki/The-Application-Before%2C-After-and-OnError-pipelines#implementing-interfaces