If you’ve read my earlier post on authentication of actions invoked in a Microsoft Teams MessageCard, you’ve probably seen the only useful information we get in the user’s token is the Object Id (`oid`).

{
  "iat": 1560799130,
  "ver": "STI.ExternalAccessToken.V1",
  "appid": "48afc8dc-f6d2-4c5f-bca7-069acd9cc086",
  "sub": "bc6c3ca0-5acd-4cd4-b54c-f9c83925e7e3",
  "appidacr": "2",
  "acr": "0",
  "tid": "4b1fa0f3-862b-4951-a3a8-df1c72935c79",
  "oid": "b26c3c10-5fad-4cd3-b54c-f9283922e7e2",
  "iss": "https://substrate.office.com/sts/",
  "aud": "https://serverlessdevops.azurewebsites.net",
  "exp": 1560800030,
  "nbf": 1560799130
}

While this is nice, it doesn’t really tell us much.

However, because we have the object id, we can use this to query the Azure Active Directory to check up on who this user actually is and implement some authorization logic with it.

However, when I was searching for a workable piece of code describing how to access AAD and retrieve users from it, the information was…not very useful. In the end, I have found something workable and I’ll be sharing my solution in this post.

How to set up my application in AAD

In order to do something inside AAD, you need to have an identity over there. Since we’re creating an application (console or API), we need to create an Application Registration.

To do this, navigate to your Azure Active Directory blade inside the Azure Portal and create a new `App registration`. I’ve called mine `ConsoleGraph` because I’m creating a console application to query my AAD.

overview of app registration

On the overview page, you can see both the `Directory (tenant) ID` and `Application (client) ID`. You’ll be needing these later on.

Next thing you need to do is to add a new secret to this application. It doesn’t matter much how you call this secret, just be sure to remember/copy the value as it’s very important and you won’t be seeing it again inside the Azure Portal.

application secret blade

Now that you have all of the details for this application, you still need to make sure this application has the appropriate permissions to query the Azure Active Directory.

Navigate to the `API permissions` blade. Over there, you should be able to add the permission to `Read directory data` from the AAD.

api permissions overview

Your application, `ConsoleGraph` in this sample, will now be able to read all data from the AAD. If you’re very keen on security, you might want to strip down the permissions a bit, but this is good enough for me.

How to connect my application to AAD

Making a connection to the AAD was the trickiest part for me. You have to create an `ActiveDirectoryClient` and create an `AuthenticationContext` which is able to acquire tokens from the AAD.

While this all works, you HAVE to know which settings to use on each specific property. Making a typo or messing up a setting will result in you not receiving a valid token and the error messages aren’t very useful (to me).

Put your application settings in a configuration file

The details of the `Application registration` should be put inside some configuration file, like an app.config file. In a production system, you might want to store the secret values in a more secure system, like Azure KeyVault. For this sample project the app.config is good enough.

Having added the details to your app.config file, your `appSettings` will probably look something like this.

<appSettings>
	<add key="TenantId" value="b1f9cb25-7c7a-4ecd-96c1-513c2b42c350"/>
	<add key="TenantName" value="myTentantName.onmicrosoft.com"/>
	<add key="ClientId" value="d82c0c6a-8c14-4c42-8aca-60c79fcfc9b4"/>
	<add key="ClientSecret" value="27?_MOh_qM633Hcccct;cw:@*$9ojcsNxve)rYI"/>
</appSettings>

I’ve created a small `Settings` class where all of these values are loaded in the appropriate format.

internal class Settings
{
    public const string ResourceUrl = "https://graph.windows.net";
    public static string TenantId => ConfigurationManager.AppSettings["TenantId"];
    public static string TenantName => ConfigurationManager.AppSettings["TenantName"];
    public static string ClientId => ConfigurationManager.AppSettings["ClientId"];
    public static string ClientSecret => ConfigurationManager.AppSettings["ClientSecret"];
    public static string AuthString => "https://login.microsoftonline.com/" + TenantName; 
}

Getting the right values for the `AuthString` and the `ResourceUrl` was the hardest part. The posts I found on the internet weren’t very helpful as each post used some other value and it wasn’t very clear to me what they are for. Eventually, I found these values to work for me.

Connect to AAD

Connecting to AAD is fairly straightforward.
As I mentioned, you need to create an `ActiveDirectoryClient` and use an `AuthenticationContext` in order to retrieve valid tokens.

I’ve used the following block of code to connect to Azure Active Directory and retrieve data from it.

public static ActiveDirectoryClient GetActiveDirectoryClientAsApplication()
{
    Uri servicePointUri = new Uri(Settings.ResourceUrl);
    Uri serviceRoot = new Uri(servicePointUri, Settings.TenantId);
    ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(
        serviceRoot,
        async () => await AcquireTokenAsyncForApplication());
    return activeDirectoryClient;
}

private static async Task<string> AcquireTokenAsyncForApplication()
{
    AuthenticationContext authenticationContext = new AuthenticationContext(Settings.AuthString, false);

    ClientCredential clientCred = new ClientCredential(Settings.ClientId, Settings.ClientSecret);
    AuthenticationResult authenticationResult =
        await authenticationContext.AcquireTokenAsync(
            Settings.ResourceUrl,
            clientCred);
    string token = authenticationResult.AccessToken;
    return token;
}

How to retrieve data from AAD

By using the Active Directory helper method from the code block above you’re able to query everything inside your AAD.

The `ActiveDirectoryClient` can query all of AAD, including the users.
I’ve used it myself to iterate through all of the users and print them per line. You can also use the client to retrieve a specific user by querying on the `ObjectId`. This will result in retrieving a single user.

var client = AuthenticationHelper.GetActiveDirectoryClientAsApplication();

try
{
	var users = await client.Users.OrderBy(user => user.DisplayName).ExecuteAsync();
	var foundUser = await client.Users.Where(user => user.ObjectId == "d62d8c6a-dc69-46c1-99c4-36cd672f0c12").ExecuteAsync();
	foreach (var user in users.CurrentPage)
	{
		Console.WriteLine(user.DisplayName + " " + user.ObjectId);
	}
}
catch (Exception exception)
{
	Console.WriteLine(exception);

By using this `ActiveDirectoryClient`, you can now start to authorize users based on their details like for example a role, group or e-mail address.

If you’re interested in a ready-to-go sample, you can check out my GitHub repository containing all the details and a working console application.

I hope this post will help others because it has surely taken up too much of my time to find out what I needed to do exactly in order to retrieve user data from AAD.

Being able to create Message Cards or Actionable Messages in Microsoft Teams via a Logic App or an Azure Function is great. Especially if you can use this to invoke logic on your API and update the message in the Teams channel.

However, you don’t want everyone to invoke a management API endpoint you’ve exposed to ‘do stuff’ in your cloud environment. Normally, you’d want to authenticate if the user pressing the button (read: invoking the endpoint).

Lucky for us, this is very doable when invoking the endpoint via a Teams MessageCard/Actionable Message.

The token

Because Microsoft Teams is part of the Office 365 suite, you will be logged in as a user on the tenant. Therefore, the software has a user context and is able to pass this along to your API via a JWT Bearer token.

If you log in to the web client of Microsoft Teams (https://teams.microsoft.com) with your favorite browser you’ll be able to find the token which belongs to you.

In order to test this, I’ve created a new MessageCard in my Teams channel with 1 `potentialAction` which will invoke an Azure Function.

messagecard with AuthorizationTest button

If you open up the network tab of your browser’s Developer Tools and press the AuthorizationTest button you’ll see the request is made to a Teams endpoint called `executeAction` with a bearer token in the `Authorization` header.

request in network tab to executeAction

When decoding this token at https://jwt.io/ you’ll see a lot of details which match with your Office 365 user.

{
  "aud": "https://api.spaces.skype.com",
  "iss": "https://sts.windows.net/4b1fa0f3-862b-4951-a3a8-df1c72935c79/",
  "iat": 1560882424,
  "nbf": 1560882424,
  "exp": 1560886324,
  "acct": 0,
  "acr": "1",
  "aio": "AVQAq/8LBACA8+mMRGmy37A7sPouo42hawcsCtG7iqUz//lmEAUCmK67lc2GmhtZIA2LM+1nw18wtIeREMejFpXpmH7uUsKbZGQYV3vyRRmlH7guw3JTBuk=",
  "amr": [
    "pwd",
    "mfa"
  ],
  "appid": "5e3ce6f0-2b1f-4285-8a4b-75ec7a757346",
  "appidacr": "0",
  "family_name": "de Vries",
  "given_name": "Jan",
  "ipaddr": "211.107.84.235",
  "name": "Jan de Vries",
  "oid": "b26c3c10-5fad-4cd3-b54c-f9283922e7e2",
  "puid": "10037FFF9443BDEA",
  "scp": "user_impersonation",
  "sub": "U02i9QRWudZWzrZeQzhaPLpgsGo0go4qjBk5A8Qv1-g",
  "tid": "4c1fc0f3-8c2b-4c51-c3a8-df3c72936c79",
  "unique_name": "jan@jan-v.nl",
  "upn": "jan@jan-v.nl",
  "uti": "avUcwdSBc0SXZfbcANocAA",
  "ver": "1.0"
}

My original assumption was this would be the token which is also sent to your backend API. I was ready to use this information in order to authenticate & authorize if a user was allowed to access the specific endpoint.

This assumption, however, is incorrect!

The token you’ll receive in your API has the following content.

{
  "iat": 1560799130,
  "ver": "STI.ExternalAccessToken.V1",
  "appid": "48afc8dc-f6d2-4c5f-bca7-069acd9cc086",
  "sub": "bc6c3ca0-5acd-4cd4-b54c-f9c83925e7e3",
  "appidacr": "2",
  "acr": "0",
  "tid": "4b1fa0f3-862b-4951-a3a8-df1c72935c79",
  "oid": "b26c3c10-5fad-4cd3-b54c-f9283922e7e2",
  "iss": "https://substrate.office.com/sts/",
  "aud": "https://serverlessdevops.azurewebsites.net",
  "exp": 1560800030,
  "nbf": 1560799130
}

As you can see, the content is very different. This (much) smaller token is still useful as it has the `tid` specified, which is your tenant identifier and the `oid`, which is the object identifier of the user who pressed the button.

How to validate

On GitHub, you can find an (old) repository containing some sample code which you can use to validate the incoming bearer token from Teams. This repository can be found over here: https://github.com/OfficeDev/o365-actionable-messages-utilities-for-dotnet

The validation logic can be found in the `ActionableMessageTokenValidator`.

var o365OpenIdConfig = await _configurationManager.GetConfigurationAsync(new CancellationToken());
var result = new ActionableMessageTokenValidationResult();

var parameters = new TokenValidationParameters
{
  ValidateIssuer = true,
  ValidIssuers = new[] { O365OpenIdConfiguration.TokenIssuer },
  ValidateAudience = true,
  ValidAudiences = new[] { targetServiceBaseUrl },
  ValidateLifetime = true,
  ClockSkew = TimeSpan.FromMinutes(TokenTimeValidationClockSkewBufferInMinutes),
  RequireSignedTokens = true,
  IssuerSigningKeys = o365OpenIdConfig.SigningKeys
};

ClaimsPrincipal claimsPrincipal;
var tokenHandler = new JwtSecurityTokenHandler();

try
{
  // This will validate the token's lifetime and the following claims:
  // 
  // iss
  // aud
  //
  SecurityToken validatedToken;
  claimsPrincipal = tokenHandler.ValidateToken(token, parameters, out validatedToken);
}

What we’re doing over here is creating the validation parameters and the actual validation. The `O365OpenIdConfiguration` contains some constants which are true for every MessageCard action.

If using an Azure Function for your API endpoint, your token validation code might look similar to the following piece of code.

[FunctionName("AuthorizationTest")]
public static async Task Run(
	[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] 
	HttpRequest request,
	ILogger log)
{
	log.LogInformation($"Excuting {nameof(AuthorizationTest)}.");
			
	var bearerToken = request.Headers["Authorization"].ToString();
	var baseUrl = $"{request.Scheme}{Uri.SchemeDelimiter}{request.Host.Value}";

	var validationResult = new ActionableMessageTokenValidationResult();
	try
	{
		var tokenValidator = new ActionableMessageTokenValidator();
		validationResult = await tokenValidator.ValidateTokenAsync(bearerToken.Replace("Bearer ", ""), baseUrl);
	}
	catch(Exception ex)
	{
		log.LogError(ex, "Validation failed");
	}
	// the rest of your logic...

}

After the bearer token has been validated you might want to add some additional logic to check if the request is made from a tenant you trust and/or if the user (Object Id) matches someone who is allowed to invoke the endpoint.
If you want to do this, you’ll have to create some authorization logic for this yourself.

The full code sample which I’ve used in my Azure Function can be found in my ServerlessDevOps GitHub repository.

It has taken us some time to figure all of this out, but it’s working like a charm now!

In my latest post, I’ve shown you how you can use Azure Functions in your Microsoft Teams flow to handle errors in your environment. This stuff works great in a couple of projects I’ve worked on, but what would be even more awesome is to reply to a message in Teams when an action has completed after a button is pressed.

Well, replying & modifying the original message with a status update is quite possible and I’ll show you how in this post.

How do I send a reply to Microsoft Teams?

In the image below you can see a message having posted on my Teams channel and a reply is posted.

reply on teams message

This reply has been sent from my Azure Function. If you want to do this, you need to send a `HttpResponseMessage` with a status code 200 and a specific header value. This header value is `CARD-ACTION-STATUS` and the value will be the message which you will see in the reply.

The code for this will look something similar to the following.

public static async Task<HttpResponseMessage> Run(
	[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
	ILogger log)
{
	// Do your stuff...

	var result = new HttpResponseMessage
	{
		Headers =
		{
			{ "CARD-ACTION-STATUS", $"Timeout of `{request.Timeout}` miliseconds has expired."},
		},
		StatusCode = HttpStatusCode.OK
	};


	return result;
}

That’s all there is to it in order to send a single reply to your message.

So you also mentioned updating the original message?

Yeah, I did!

From your Azure Function (or any other API) it’s possible to change the original message. Updating the message might make sense in a couple of scenarios. The one scenario where we’re using it for is to remove the button(s) in the message, therefore limiting the ‘action’ only to a single use.

While our services are set up to be idempotent, we don’t want to spam the API with useless requests, so removing the button makes sense in our case.

In order to do this, you need to add another header to your response message, named `CARD-UPDATE-IN-BODY` and set the value to `true`. This tells the client (Teams) there’s an update for the card in the body of the response message.

If you want to use this, it makes sense to create a new card with data that’s useful after an action has been executed. The eventual code will look pretty similar to the following snippet.

public static async Task<HttpResponseMessage> Run(
	[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
	ILogger log)
{
	// Do all of your stuff...

	var result = new HttpResponseMessage
	{
		Content = new StringContent(GetContent(request.Timeout)),
		Headers =
		{
			{ "CARD-ACTION-STATUS", $"Timeout of `{request.Timeout}` miliseconds has expired."},
			{ "CARD-UPDATE-IN-BODY", "true" }
		},
		StatusCode = HttpStatusCode.OK
	};
	result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");

	return result;
}

Over here I’m creating a new `Content` property with the update of the card. I do have to make clear it’s a full replacement of the original message. Therefore, you have to create a completely new MessageCard. For me, the content of the new MessageCard looks pretty much like the following piece of JSON.

{
	"@type": "MessageCard",
	"@context": "https://schema.org/extensions",
	"summary": "Testing the timeout",
	"themeColor": "0078D7",
	"sections": [
		{
			"activityImage": "https://jan-v.nl/Media/logo.png",
			"activityTitle": "Timeout test",
			"activitySubtitle":"Testing, testing...",
			"facts": [
				{
					"name": "Timeout (miliseconds):",
					"value": "1000"
				}
			],
			"text": "The response has returned with a timeout of 1000 miliseconds.",
		}
	]
}

In Microsoft Teams this will appear like the following screenshot.

updated message and response

The message gets an `Updated` status, which makes it clear for all users this isn’t the original message.

Erroneous statements on other sites / posts

So if you stumbled on this post while searching for this functionality in a search machine, you probably know it’s hard to find anything useful on the matter. While doing research on this I also saw a post stating the response message has to be returned within 5 seconds in order for Microsoft Teams to process it and show the reply and/or updated message in the channel.
From my experience, I can tell you this isn’t true (at the moment). I’ve tested this timeout function with delays up to 30 seconds and the functionality still works properly as you can see on the image below.

response with 30 seconds

Closing up

If you want to evaluate the complete code there’s a GitHub repository called ServerlessDevOps where I’m doing all of the code updates and trying out new stuff & integrations with Microsoft Teams and Azure Functions.

So, is this something you might consider using in your own projects and keeping your DevOps workplace happy? I’d love to hear it and if you’re missing something which you want to be highlighted in future posts.

So, a couple of weeks back I wrote about leveraging the power of Logic Apps to retrieve Alerts from within your Azure ecosystem and send them to Microsoft Teams. This works great and a fellow Azure MVP, Tom Kerkhove, has enhanced the Logic Apps Template when handling Azure Monitor events.

I'm starting to become a pretty big van of Logic Apps, but there are some (obvious) downsides to it.
First, they live inside your Azure Portal. You can create, modify and export them from within the Portal, which is great, unless you want to integrate them in your ‘regular’ development cycle.

The export feature enables you to copy/paste the Logic Apps to your ARM templates, but this is suboptimal in my opinion. There’s also the Azure Logic Apps Tools for Visual Studio extension, which makes the integration a bit better, but it still feels a bit quirky.

Another downside is the 'language'. When exporting a Logic App you'll be seeing a lot of JSON. While there might be a good reason for this, it's not something I like working in and create (complex?) workflows.

If you can overcome, or accept, these downsides I'd really advice you to look into Logic Apps. If not, well read on!

Azure Functions to the rescue

If your IT organization consists of mostly developers it might make more sense to use Azure Functions to glue different systems with each other instead of Logic Apps. The biggest downside of Azure Functions in this scenario is, you don't have all of the building blocks from a Logic App to your availability. You have to create your own logic for this.

However, the major benefit of using Azure Functions as the glue to your solution is they are written in the language of your choice and can be deployed via your 'normal' CI/CD process.

The only thing the Logic App in the previous post did was receive a HTTP POST message, parsing it and send a message to Teams. All of this can also be done via a standard HTTP triggered Azure Function. And because I prefer writing C# code instead of dragging-dropping building blocks (or write JSON if you’re really hardcore), the Azure Functions approach works best for me.

How to start with this?

The first thing you need to do, besides creating an HTTP triggered Azure Function, is to deserialize the incoming message from Azure Monitor.
The easiest way to get the complete Alert object is by copying the complete JSON message and use the `Paste JSON As Classes` option in Visual Studio.

image

This will create a model with all of the properties and complex types which are available in alert. At the moment it will look very similar to the following model.

/// <summary>
/// Generated via `Paste JSON as Classes`
/// </summary>
public class IncomingAzureMonitorCommonAlertSchema
{
    public string schemaId { get; set; }
    public Data data { get; set; }
}

public class Data
{
    public Essentials essentials { get; set; }
    public Alertcontext alertContext { get; set; }
}

public class Essentials
{
    public string alertId { get; set; }
    public string alertRule { get; set; }
    public string severity { get; set; }
    public string signalType { get; set; }
    public string monitorCondition { get; set; }
    public string monitoringService { get; set; }
    public string[] alertTargetIDs { get; set; }
    public string originAlertId { get; set; }
    public DateTime firedDateTime { get; set; }
    public string description { get; set; }
    public string essentialsVersion { get; set; }
    public string alertContextVersion { get; set; }
}

public class Alertcontext
{
    public object properties { get; set; }
    public string conditionType { get; set; }
    public Condition condition { get; set; }
}

public class Condition
{
    public string windowSize { get; set; }
    public Allof[] allOf { get; set; }
    public DateTime windowStartTime { get; set; }
    public DateTime windowEndTime { get; set; }
}

public class Allof
{
    public string metricName { get; set; }
    public string metricNamespace { get; set; }
    public string _operator { get; set; }
    public string threshold { get; set; }
    public string timeAggregation { get; set; }
    public Dimension[] dimensions { get; set; }
    public float metricValue { get; set; }
}

public class Dimension
{
    public string name { get; set; }
    public string value { get; set; }
}

Once you have this model, you can deserialize the incoming alert message and start creating a message for Teams.

So, what do I send?

You’re quite restricted in what you can send to a Microsoft Teams channel via a webhook. When searching for this you’ll quickly find the different Adaptive Cards. These look nice and possibilities are also great. However, you can’t use them via a webhook. Adaptive Cards only work when using a Bot, something I really don’t want to do/configure at the moment.

The only cards which are supported in Teams, which you can send directly via a webhook, are the legacy Message Cards. While these work fine, I do hope the support for Adaptive Cards will be added soon.

What I did in the previous post was sending out a message with only a `title` and a `text` property in a JSON object. This works and might be useful in a couple scenario’s, but most of the time you want to do more as only informing the users. When an alert pops up, someone probably has to do something with the failing (?) resource.
If this ‘action’ can be automated some way, you can add a button to your message which is able to invoke some HTTP endpoint. This is great, because now we can configure an Azure Functions, Logic App, Automation Job, App Service, etc. to be the endpoint which fixes the root cause of the alert. You just have to remember Microsoft Teams has to be able to invoke the endpoint, which means it has to be a public available endpoint.
In order to add buttons to your Message Card, you have to add Actions to your message. What I came up with is the following type of message.

{
    "@type": "MessageCard",
    "@context": "https://schema.org/extensions",
    "summary": "More as 100 messages on queues",
    "themeColor": "0078D7",
    "sections": [
        {
            "activityImage": "https://jan-v.nl/Media/logo.png",
            "activityTitle": "More as 100 messages on queues",
            "activitySubtitle": "05/02/2019 19:32:20",
            "facts": [
                {
                    "name": "Severity:",
                    "value": "Sev3"
                },
                {
                    "name": "Resource Id:",
                    "value": "3b3729b4-022a-48b5-a2eb-48be0c7e7f44:functionbindings"
                },
                {
                    "name": "Entity:",
                    "value": "correct-implementation-netframework"
                },
                {
                    "name": "Metric value:",
                    "value": "10000"
                }
            ],
            "text": "There are a lot of messages waiting on the queue, please check this ASAP!",
            "potentialAction": [
                {
                    "@type": "HttpPOST",
                    "name": "Fix the stuck Service Bus",
                    "target": "https://serverlessdevops.azurewebsites.net/api/FixFailingServicebus?code=WVq4Ta3ba0i53a3qzHbLWHLnCiRNA8UnhHICIl1UfURskh/Cx0J8IQ==",
                    "body": "{\"ResourceId\": \"3b3729b4-022a-48b5-a2eb-48be0c7e7f44:functionbindings\",\"Entity\": \"correct-implementation-netframework\" }"
                }
            ]
        }
    ]
}

This defines a Message Card which looks like this inside Microsoft Teams.

image

As you can see there’s a big button in the card which enables me to do something. You can add multiple buttons over here. Aside from a fix-button I also add a button with a deeplink to the resource in the Azure Portal most of the time.

You have to keep in mind though, the only type of HTTP methods you can do are GET and POST. When making a POST request a body can be added by adding the optional `body` property to the message.

The JSON sent over here looks a bit more advanced, but as you can see, the message is also a lot more useful.

Looking great so far, can we do more?

Yes we can!

I’ll be writing some more on what you can do with Azure Functions and Microsoft Teams in a couple of my next posts. I think this integration can really help a lot of DevOps teams in keeping their environments in a healthy state, so I’m keen on sharing my experiences with it. If you can’t wait for the blogposts to appear, you can also follow along the progress in my Serverless DevOps repository on GitHub. If you take a look over there, you can see what I’m doing in order to send and receive messages in Teams & Azure Functions.

Stuff I like from this year’s Build conference

.NET Core 3 Preview 4 can be downloaded right now. This new preview brings the Chart control to .NET Core & some WPF improvements. What I like most is the versioning change and the Tiered Compilation. I’m still reading up on this second thing, but it sounds awesome a major reason to upgrade to the latest & greatest! Some more information on TC can be found on a Microsoft blog post dedicated to it: https://devblogs.microsoft.com/dotnet/tiered-compilation-preview-in-net-core-2-1/
The Docker improvements are nice also, but I’m not using it a lot these days so don’t have much of an opinion on these things.

Something which struck me as odd is the announcement of .NET 5, which is .NET Core vNext. The explanation of this version number clears things up a bit though.

We’re skipping the version 4 because it would confuse users that are familiar with the .NET Framework, which has been using the 4.x series for a long time. Additionally, we wanted to clearly communicate that .NET 5 is the future for the .NET platform.

From what I gather from this we can all (.NET Framework and .NET Core) to .NET 5 without much hassle. I don’t know if this is true, but it would make sense from my perspective.

Another announcement was the integration of GitHub and Azure DevOps, which is great! Where Azure DevOps shines at being a great enterprise/company working environment, GitHub shines in collaboration with external people. With this integration, we’ll be able to leverage the power of both platforms where needed more easily. Also, signing in with your GitHub account on Azure DevOps or the Azure Portal is big! You can now add your GitHub identity to the Azure Active Directory and be done with it. Awesome!

The same post mentions being able to do deployment pipelines via YAML now. It’s called Unified Pipelines, which is a great improvement!
By chance we were also looking to Azure Artifacts at the project I’m working on now and with the announcement of yesterday this seems appears like a no-brainer now as the service is now included in the Basic license!

Then there’s the Kubernetes news, which. is. the. best!
I’ve never felt much for the container ecosystem. I do understand what it does and how it’s an improvement to IaaS, but I live mostly in a PaaS, FaaS and SaaS environment, so moving back to ‘the virtual metal’ feels like a step back.
But, the serverless Kubernetes and Kubernetes-based Event-driven Autoscaling (KEDA) is amazing! While it might not be fully serverless, it’s still something which can bring a lot of value to our customers and if we’re honest, that’s what matters the most. I do wonder if this will also become available on theOpenShift platformas KEDA is a partnering between Microsoft & Red Hat. KEDA will also be available on the OpenShift platform!
It’s also open source and can be found on GitHub: https://github.com/kedacore/keda

Are you using Windows Subsystem for Linux? Well, version 2 is announced also, which is even better! It’s faster, has more system calls available and Docker images run right away!
Something which goes hand in hand with the WSL is the command-line terminal and it has had some major improvement! Scott Hanselman has written a nice post on it and how you can tune the terminal. While I was just getting used to ConEmu, I think I’ll just use the new Terminal experience right from the start.

There were also a lot of announcements on machine learning, blockchain, artificial intelligence, mixed reality, etc. While all of this sounds great, it’s not stuff I work with on a daily basis. If you’re interested in this stuff, I’d advise you to check out all of the news posts on these other subjects.
I’ll be sure to check out KEDA once I get around to it, it’s just too awesome not to play with it.

Update

While scrolling through my Twitter feed today I saw a lot of other stuff which peeked my interest.

Apparently dependency injection in Azure Functions is a thing now, for quite some while. I probably missed this announcement in March, as the documentation dates from a couple of months ago. Still, very useful!

The Durable Functions also have gotten quite a bit of love for stateful actor-like capabilities. While I haven’t worked with the Actor model (yet), it is something I would love to dig into. With this announcement working with the Actor model will be even more awesome!
And of course, the API Management integration of Azure Functions is great! There already was some basic Proxy functionality in Azure Functions, but with the full integration of APIM this will work even better!

For App Services we now have a Free Linux tier. Even though I don’t do a lot of development which will be published to a Linux App Service, I do recognize this is a great improvement for a lot of projects. Connecting to a vNet is now also supported for Linux App Services, which is great if you’re working in a hybrid scenario, or have some ‘secured’ resources somewhere.
From the UX improvements it also looks like Docker integration can be found everywhere nowadays. So if you haven’t looked into containers & Docker yet, now is a time to do so. It’ll become very important for us developers to at least know the basics off of it.

Let’s not forget one major improvement in the security space. We now have support for passwords with a length of 256 characters! Finally!

Update 2

It appears the full Entity Framework will become available to .NET Core 3.0! This is major, because the full Entity Framework has a lot more capabilities compared to the Entity Framework Core version. People still stuck on the .NET Framework because of EF should now be able to upgrade. Still, the packages are in preview, so don’t migrate just yet.

Something I like very much is the enhanced syntax highlighting for ARM templates in VS Code. Also, the enhanced schema ‘intellisense’ is very useful when working with these large JSON files.

Have you also noticed the improved Azure Portal? Lots of small improvements which makes the navigation in the portal a lot more slick. Especially the improved resource graphs are useful. Now I don’t have to create my own dashboards in PowerBI or something like it anymore.

With the announced GitHub integration, we also get Azure Boards integration to GitHub. This will make managing your code & project a lot easier when working with both systems on a single project.

And last, but certainly not least, the React Native for Windows repository. Not sure if this was announced at //build/, but I saw it appear in my timeline and it’s awesome enough to mention over here. Now you can create a React Native application and deploy it as a Windows 10 application, which makes it supported for devices like PC’s, tablets, Xbox, Hololens, etc. Great stuff indeed!