Tuesday, January 25, 2022

Azure Automation Runbook: Execute SQL Scripts with Managed Identities

In this post, let's see how we can execute SQL Scripts on an Azure SQL Database with Managed Identities using an Azure Automation Runbook.

I am using PowerShell with ADO.NET instead of Invoke-Sqlcmd, because Invoke-Sqlcmd doesn't support Azure AD Authentication (via an AccessToken).

For the purpose of the demo, I have created the following SP on the target database, which we are going to be executing.

CREATE OR ALTER PROC dbo.TestStoredProcedure 
(
    @Param1 INT,
    @Param2 VARCHAR(100) 
)
AS
BEGIN
SELECT @Param1 As Data1, @Param2 as Data2
END

The first step is to create an Automation Account. In Azure, search for Automation Accounts and click on Create.

Create an Automation Account
I have selected a Subscription, Resource Group, Region and given it a name.

Create an Automation Account: Managed Identities
This is an important step. This is where we are selecting Managed Identities for this Automation Account. For now, I am going to uncheck both the options (System assigned and User assigned) and clicking on Review + Create (or you can proceed through the Wizard, you should be fine with the defaults for the purpose of this post).

Once the Automation Account is created, we are all set to go.

System assigned Managed Identity

First, let's have a look at how to execute a SQL Script System assigned Managed Identity. First step is enabling System assigned Managed Identity in the Automation Account as below.

Automation Account: System assigned Managed Identity
Once that's saved, we need to grant this System assigned Managed Identity the access to our database. The name of the System assigned Managed Identity is the name of the Automation Account. 
CREATE USER [aa-demo] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [aa-demo];
ALTER ROLE db_datawriter ADD MEMBER [aa-demo];
ALTER ROLE db_ddladmin ADD MEMBER [aa-demo];
GO
 
EXEC sp_addrolemember 'db_datareader', 'aa-demo'
EXEC sp_addrolemember 'db_datawriter', 'aa-demo'
EXEC sp_addrolemember 'db_ddladmin', 'aa-demo'
GRANT EXECUTE ON SCHEMA::dbo TO [aa-demo]
We can run the above query to create and grant access to our System assigned Managed Identity. Note: The query needs to be executed after connecting to the database using an Azure AD login.

Now I am adding some variables to my Automation Account as follows. One for the Azure SQL Server and the other for the name of the Database.
Automation Account: Variables
Next, I am creating a Runbook in Automation Account
Create a Runbook
I have selected PowerShell 7.1 (preview). And I am adding the following script.
# Reading from variables
$AzureSqlServer = Get-AutomationVariable -Name "AzureSqlServer"
$ApplicationDatabase = Get-AutomationVariable -Name "ApplicationDatabase"
    
# Getting AccessToken for System assigned Managed Identity
$Resource = "https://database.windows.net/"
$QueryParameter = "?resource=$Resource"
$Url = $env:IDENTITY_ENDPOINT + $QueryParameter
$Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
$Headers.Add("X-IDENTITY-HEADER"$env:IDENTITY_HEADER) 
$Headers.Add("Metadata""True") 
$Content =[System.Text.Encoding]::Default.GetString((Invoke-WebRequest `
    -UseBasicParsing `
    -Uri $Url `
    -Method 'GET' `
    -Headers $Headers).RawContentStream.ToArray()) | ConvertFrom-Json 
$AccessToken = $Content.access_token 
    
# PowerShell/ADO.NET Connected Architecture
$SqlConnection = New-Object System.Data.SqlClient.SQLConnection  
$SqlConnection.ConnectionString = "Server=$AzureSqlServer;Initial Catalog=$ApplicationDatabase;Connect Timeout=30" 
$SqlConnection.AccessToken = $AccessToken 
$SqlCommand = new-object System.Data.SqlClient.SqlCommand("exec dbo.TestStoredProcedure @Param1=100, @Param2='Hello World'"$SqlConnection);
$SqlConnection.Open();
$SqlDataReader = $SqlCommand.ExecuteReader()
$Results = @()
while ($SqlDataReader.Read())
{
    $Row = @{}
    for ($i = 0; $i -lt $SqlDataReader.FieldCount; $i++)
    {
        $Row[$SqlDataReader.GetName($i)] = $SqlDataReader.GetValue($i)
    }
    $Results += new-object psobject -property $Row
}
$SqlConnection.Close()
Write-Output $Results
And that's it. Now when I test this, I am seeing the following output:
Using System assigned Managed Identity: Output
That's working. 

User assigned Managed Identity

Now let's have a look at how to get things done using User Assigned Managed Identity. I am going back to our Automation Account and assigning a User assigned Managed Identity. I have already created a User assigned Managed Identity and I am just going to add it in.

Automation Account: User assigned Managed Identity

Sametime I have added another variable to the Automation Account to hold the ClientId/ObjectId the User assigned Managed Identity we just assigned.

Now we need to grant this User assigned Managed Identity the access to our SQL Database. We could follow the same steps as we did above for System assigned Managed Identity. But this time, I am going to go for a different approach, just to show our options.

I am navigating to the SQL Server where our database is in, and setting our User assigned Managed Identity as the Azure AD admin under SQL Server settings.

AAD Admin
Once that's saved, let's modify our Runbook as follows.
# Reading from variables
$AzureSqlServer = Get-AutomationVariable -Name "AzureSqlServer"
$ApplicationDatabase = Get-AutomationVariable -Name "ApplicationDatabase"
$ClientId = Get-AutomationVariable -Name "AzureSqlManagedIdentityId"
 
# Getting AccessToken for User assigned Managed Identity
# Note: Query Parameter now includes the client_Id
$Resource = "https://database.windows.net/"
$QueryParameter = "?resource=$Resource&client_Id=$ClientId"
$Url = $env:IDENTITY_ENDPOINT + $QueryParameter
$Url = $env:IDENTITY_ENDPOINT + $QueryParameter
$Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 
$Headers.Add("X-IDENTITY-HEADER"$env:IDENTITY_HEADER) 
$Headers.Add("Metadata""True") 
$Content =[System.Text.Encoding]::Default.GetString((Invoke-WebRequest `
	-UseBasicParsing `
	-Uri $Url `
	-Method 'GET' `
	-Headers $Headers).RawContentStream.ToArray()) | ConvertFrom-Json 
$AccessToken = $Content.access_token 
 
# PowerShell/ADO.NET Dicconnected Architecture
$SqlConnection = New-Object System.Data.SqlClient.SQLConnection  
$SqlConnection.ConnectionString = "Server=$AzureSqlServer;Initial Catalog=$ApplicationDatabase;Connect Timeout=30" 
$SqlConnection.AccessToken = $AccessToken 
$SqlCommand = New-Object System.Data.SqlClient.SqlCommand("dbo.TestStoredProcedure"$SqlConnection)
$SqlCommand.CommandType = [System.Data.CommandType]::StoredProcedure
$SqlCommand.Parameters.AddWithValue("@Param1", 100) | Out-Null
$SqlCommand.Parameters.AddWithValue("@Param2""Hello World") | Out-Null
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCommand
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet) | Out-Null
$SqlConnection.Close()
Write-Output $DataSet.Tables[0]
Note: When using User assigned Managed Identity we need to pass in the client_id to AccessToken request (this is not required when using System assigned Managed Identity as it will automatically get resolved). And I have changed PowerShell/ADO.NET to use the Disconnected Architecture to show how to use that approach as well.

And now let's test this. I am seeing the following output:

Using User assigned Managed Identity: Output
And that's also working nicely.

Now you can publish the Runbook and schedule it the way you want.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Thursday, January 20, 2022

Azure Service Bus Simple Request-Reply Pattern

In this post let's have a look at how to implement Simple Request-Reply Pattern with Azure Service Bus.

So first have a look at what this Simple Request-Reply Pattern is. While the Service Buses are used mainly for asynchronous processing, there can be scenarios where the message Publisher/Sender needs to wait for a reply from Consumer/Receiver to the message they sent before proceeding. And that's where the Simple Request-Reply Pattern comes into the picture.

Let's go by an example.

Say a Sender sends the following message into a particular queue and a Receiver is consuming it. The sender populates the Input and expects the Consumer to populate the Output for it to proceed further.

public record ApplicationMessage(string Input)
{
    public string? Output { getset; }
}

So how do we achieve this?

ServiceBusMessage has this ReplyTo Property where the Sender can use to set the address of an entity to send its replies to, something like below.

Sender

ServiceBusAdministrationClient serviceBusAdministrationClient = new(Configuration.CONNECTION_STRING);

// Temporary Queue for Receiver to send their replies into
string replyQueueName = Guid.NewGuid().ToString();
await serviceBusAdministrationClient.CreateQueueAsync(new CreateQueueOptions(replyQueueName)
{
    AutoDeleteOnIdle = TimeSpan.FromSeconds(300)
});
    
// Sending the message
await using ServiceBusClient serviceBusClient = new(Configuration.CONNECTION_STRING);
ServiceBusSender serviceBusSender = serviceBusClient.CreateSender(Configuration.QUEUE_NAME);
    
ApplicationMessage applicationMessage = new("John");
ServiceBusMessage serviceBusMessage = new(JsonSerializer.SerializeToUtf8Bytes(applicationMessage))
{
    ContentType = "application/json",
    ReplyTo = replyQueueName,
};
await serviceBusSender.SendMessageAsync(serviceBusMessage);

Then after sending the message, Sender needs to look for replies in Queue: replyQueueName.

// Creating a receiver and waiting for the Receiver to reply
ServiceBusReceiver serviceBusReceiver = serviceBusClient.CreateReceiver(replyQueueName);
ServiceBusReceivedMessage serviceBusReceivedMessage = await serviceBusReceiver.ReceiveMessageAsync(TimeSpan.FromSeconds(60));
    
if (serviceBusReceivedMessage == null)
{
    WriteLine("Error: Didn't receive a response.");
    return;
}
    
applicationMessage = JsonSerializer.Deserialize<ApplicationMessage>(serviceBusReceivedMessage.Body.ToString());

Consumer

From the Consumer, it's easy. It just needs to send the reply to the entity specified in the incoming ServiceBusMessage.ReplyTo.

await using ServiceBusClient serviceBusClient = new(Configuration.CONNECTION_STRING);
ServiceBusProcessor serviceBusProcessor = serviceBusClient.CreateProcessor(Configuration.QUEUE_NAME);
    
serviceBusProcessor.ProcessMessageAsync += async args =>
{
    // Message received
    ApplicationMessage applicationMessage = JsonSerializer.Deserialize<ApplicationMessage>(args.Message.Body.ToString());
    
    WriteLine($"Message Received: {applicationMessage}.\n");
    
    // Process the message/Update the Output
    applicationMessage.Output = $"Hello {applicationMessage.Input}!.";
    
    // Sending the reply
    ServiceBusSender serviceBusSender = serviceBusClient.CreateSender(args.Message.ReplyTo);
    ServiceBusMessage serviceBusMessage = new(JsonSerializer.Serialize(applicationMessage));
    await serviceBusSender.SendMessageAsync(serviceBusMessage);
};
And when we run this, the output would be something like below.
Simple Request-Reply Pattern Output
You can find the sample code through the below link.

More Read,
   Message Routing and Correlation

Hope this helps.

Happy Coding.

Regards,
Jaliya

Friday, January 14, 2022

Running .NET 6 Isolated Azure Functions in Azure

In this post, let's see how we can run .NET 6 Isolated (Out-of-process) Azure Functions in Azure. 

If you are new to .NET 6 Isolated (Out-of-process) Azure Functions, you can read this post: .NET 6 and Azure Functions.

Let's start off by Creating a Function App. Here you need to select these options.

  • Runtime Stack: .NET
  • Version: 6
Create .NET 6 Function App
Now you can follow the Wizard and create the Function App. Once the Function App is created, we need to deploy a .NET 6 Isolated (Out-of-process) Azure Function App. I am not going to show that step, let's assume we just deployed a sample .NET 6 Isolated (Out-of-process) Azure Function App.

And now, when we navigate into the Function App from Azure, we should be seeing an error, something like this: Microsoft.Azure.WebJobs.Script: Did not find functions with language [dotnet].
Microsoft.Azure.WebJobs.Script: Did not find functions with language [dotnet].
And the reason for this is, by default when Azure creates .NET 6 Function App, it sets FUNCTIONS_WORKER_RUNTIME as dotnet.
FUNCTIONS_WORKER_RUNTIME
In order to run Azure Functions in Isolated (Out-of-process) mode, we need to set FUNCTIONS_WORKER_RUNTIME to dotnet-isolated.

And that should be it. Now the Azure Function is all set up to run in Isolated (Out-of-process) mode.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Thursday, January 6, 2022

Great Start to the Year: Visual Studio 17.1 Preview 2 is Now Available

It's another year and a newer preview version of Visual Studio 2022 is already out, Visual Studio 2022 version 17.1 Preview 2.  This Preview version got shipped with some very nice features related to,
  • .NET Productivity
  • Git Tooling
  • Code Editor
  • IDE
  • Test tools
  • And more
These include some features that I have been waiting for so long.

.NET Productivity: Go to Definition, Navigate to the Original Source File


This is my favorite of them all and it's a fantastic feature. Now when we do Go to Definition, we are being navigated to the original source file. This is tremendously helpful in situations like we are exploring .NET APIs.

For example, say I need to go to the definition of AddDbContext<TContext>(...)
Go to Definition in Visual Studio 17.1 Preview 2
I can see the full implementation, whereas in prior versions, I am not seeing the method body.
Go to Definition in Earlier Version of Visual Studio

Git Tooling: Enable line-staging support


This one is another favorite. All this time, after doing a change in a file, we could only stage the whole file, not hunks. This feature has been there in most of the other Git tools out there for so many years, but was lacking in Visual Studio Git experience. Now finally, it's arrived.

This feature is not enabled by default, you need to explicitly turn this on. You can do it by navigating into Tools -> Options -> Environment -> Preview Features.
Enable line-staging support
And then we can stage hunks individually if we want to.
Stage Changes/Hunks

Editor: Code Cleanup on Save


Another nice One. With this feature we don't have to do  Ctrl+K, Ctrl+E (code cleanup) explicitly. Visual Studio will do the Code Cleanup on File upon Save automatically. And we can even choose which Profile to be used for the Code Cleanup. This feature again is not enabled by default, you need to explicitely enable it. You can do it by navigating into Tools -> Options -> Text Editor -> Code Cleanup.
Code Cleanup on Save
Aren't these nice?

Read more about all the new features in Visual Studio 17.1 Preview 2,

Happy Coding and Happy New year everyone!

Regards,
Jaliya

Wednesday, December 15, 2021

C# 10.0: Nice Little Features

In this post, let's see a couple of nice features that are available with C# 10.0, which are kind of tends to go unnoticed, but are pretty nice.

Constant Interpolated Strings

With C# 10.0, Interpolated strings can be declared as const. But in order to do that, all the interpolated strings need to be const.

const string Language = "C#";
const string Version = "10.0";
const string FullProductName = $"Language: {Language} Version: {Version}";

For the next features, let's consider the below types.

public enum PackageSize
{
    Small,
    Medium,
    Large
}
 
public record Package(PackageSize PackageSizedecimal Widthdecimal Heightdecimal Length);
 
public record Shipment(string Fromstring ToPackage Package);

Now say, we have a method to Calculate the Package Cost based on the PackageSize. And we are doing a null check on the parameter being passed in.

static decimal CalculatePackageCost(Shipment shipment)
{
    if (shipment == null)
    {
        throw new ArgumentNullException(nameof(shipment));
    }
     
    // TODO: Determine Package Cost
};    

ArgumentNullException.ThrowIfNull

With C# 10.0, Argument Null Check can be simplified as follows.

static decimal CalculatePackageCost(Shipment shipment)
{
    ArgumentNullException.ThrowIfNull(shipment, nameof(shipment));
             
    // TODO: Determine Package Cost
}   

That's neat!

Now say, we determine the Package Cost by doing something like below. This code is written prior to C# 10.0.

static decimal CalculatePackageCost(Shipment shipment)
{
    // other code
        
    return shipment switch
    {
        { Package: { PackageSize: PackageSize.Small } } => 100.00m,
        { Package: { PackageSize: PackageSize.Medium } } => 150.00m,
        { Package: { PackageSize: PackageSize.Large } } => 200.00m,
        _ => throw new NotImplementedException()
    };
};

Nested Property Patterns

With C# 10.0, you can simplify the code by using Nested Property Pattern.

static decimal CalculatePackageCost(Shipment shipment)
{
    // other code
    
    return shipment switch
    {
        { Package.PackageSize: PackageSize.Small } => 100.00m,
        { Package.PackageSize: PackageSize.Medium } => 150.00m,
        { Package.PackageSize: PackageSize.Large } => 200.00m,
        _ => throw new NotImplementedException()
    };
}

Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, December 8, 2021

Creating Dapr Enabled Azure Container Apps

Azure Container Apps has native support for Dapr. That means we don't have to deploy Dapr runtime along with our images and have very straightforward integration.

In this post, let's see how we can create Dapr enabled services in Azure Container Apps and do service invocations. If you are new to Azure Container Apps, make sure to read this post: Public Preview: Azure Container Apps, which will help you get up to speed.

I have created 3 Azure Container Apps for the following components.

  • A Web App
  • Customers API
  • Orders API

Web App is public-facing (ingress mode is external) and the two APIs are internal (ingress mode is internal). Web App is consuming Customers API and Orders API.

I can create Dapr enabled Container Apps by running the following script (replacing variables and for APIs ingress mode is internal).

az containerapp create `
   --name $WEB_APP_NAME `
   --resource-group $RESOURCE_GROUP `
   --environment $CONTAINERAPPS_ENVIRONMENT `
   --image mcr.microsoft.com/azuredocs/containerapps-helloworld:latest `
   --target-port 80 `
   --ingress 'external' `
   --min-replicas 1 `
   --max-replicas 1 `
   --enable-dapr `
   --dapr-app-port 80 `
   --dapr-app-id $WEB_APP_SERVICE_ID `
   --dapr-components ./dapr/components.yaml

And then, in the Web App, appsettings.json, I have the Service App IDs defined. These are what you use for --dapr-app-id argument.

{
  ...
  "Services": {
    "Customers": {
"ServiceId""customers-api"
}, "Orders": { "ServiceId""orders-api"
} } }

And finally I can easily consume APIs/Services from WebApp, something like below for an example.

using ContainerAppsDemo.Web.Blazor.Data;
using Dapr.Client;
    
namespace ContainerAppsDemo.Web.Blazor.Services;

public class CustomerService : ICustomerService
{
    private readonly DaprClient _daprClient;
    private readonly string _serviceId;
    
    public CustomerService(DaprClient daprClient, IConfiguration configuration)
    {
        _daprClient = daprClient;
        _serviceId = configuration.GetValue<string>("Services:Customers:ServiceId");     }     public async Task<IEnumerable<Customer>> GetCustomers(CancellationToken cancellationToken = default)     {         return await _daprClient.InvokeMethodAsync<List<Customer>>(HttpMethod.Get,
            _serviceId,
            "customers",
            cancellationToken);     } }
Dapr Enabled Container Apps

Isn't that easy?

You can find the source code for this post here,
   jaliyaudagedara/azure-container-apps-demo

Hope this helps.

Happy Coding.

Regards,
Jaliya

Thursday, December 2, 2021

.NET 6: DateOnly and TimeOnly Types

In this post, let's have a look at two new types that got added to .NET Base Class Library (core set) in .NET 6 and that is DateOnly and TimeOnly. These were added to System namespace just like other DateTime types.

Let's consider the following DateTime.

DateTime datetime = new(2021, 12, 15);
Console.WriteLine(datetime);                 // 15/12/2021 12:00:00 am

Since it's a DateTime, it has the Date component and a Time component which makes perfect sense. Now let's say you want to extract the Date component and the Time component separately. That's where the new DateOnly and TimeOnly types comes into the picture.

DateOnly dateonly = DateOnly.FromDateTime(datetime);
Console.WriteLine(dateonly);                 // 15/12/2021
 
TimeOnly timeonly = TimeOnly.FromDateTime(datetime);
Console.WriteLine(timeonly);                 // 12:00 am

You can construct DateOnly and TimeOnly as you construct DateTime. And you have all the different constructor overloads to create DateOnly and TimeOnly the way you want. For example,

DateOnly dateonly = new(2021, 12, 15);
Console.WriteLine(dateonly);                 // 15/12/2021
 
TimeOnly timeonly = new(13, 30);
Console.WriteLine(timeonly);                 // 1:30 pm

DateOnly and TimeOnly types have their own Properties and Methods that you can use. For example,

// Some of DateOnly Properties
Console.WriteLine(dateonly.Day);             // 15
Console.WriteLine(dateonly.DayOfWeek);       // Wednesday Console.WriteLine(dateonly.Month);           // 12 Console.WriteLine(dateonly.Year);            // 2021 // Some of TimeOnly Properties Console.WriteLine(timeonly.Hour);            // 13 Console.WriteLine(timeonly.Minute);          // 30 Console.WriteLine(timeonly.Second);          // 0 // Some of DateOnly Methods Console.WriteLine(dateonly.AddDays(30));     // 14/01/2022 Console.WriteLine(dateonly.AddYears(1));     // 15/12/2022 // Some of TimeOnly Methods Console.WriteLine(timeonly.AddHours(4));     // 5:30 pm Console.WriteLine(timeonly.AddMinutes(270)); // 6:00 pm

These two new types are going to be super useful, specially DateOnly. There are a lot of scenarios we don't care about the Time component, so it's going to be very handy.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Thursday, November 18, 2021

Public Preview: Azure Container Apps

In this post let's have a look at one of the newest additions to Azure especially to its Container Options and that is Azure Container Apps.  Azure Container Apps is a fully managed serverless environment for running Containers, especially focused on Microservices. It was announced earlier this month at Microsoft Ignite, and as of today, it's in its Public Preview. 

Azure Container Apps is backed by Kubernetes behind the scene and lets us, the developers focus more on the business logic, rather than managing the infrastructure. It's completely serverless and can dynamically scale based on HTTP traffic, event-driven processing (message broker), CPU or memory load, or any KEDA Scalers.

First, let's create a Container App and see how it looks like. From Azure, search for Container App.
Container App
Click on Create.
Create Container App
We need to give the Container App a name and associate with a Container App Environment. Container App Environment is being used to maintain a boundary around groups of container apps. Container Apps in the same environment are deployed in the same virtual network and write logs to the same Log Analytics workspace.
Create Container App Envrionement
We need to select a Region, currently only available regions are Canada Central and North Europe.
Create Container App Envrionement
Then we need to associate a Log Analytics workspace.

Once that is done, we can proceed with configuring the Container.
Container App Settings
I am just proceeding with the default Quickstart image, if you unchecked that, you can select your own container and change the resource specs.

And that's it. You can proceed with the creation and once created, you should be able to get its URL and have your first look at Container Apps.
First Container App
If you want more control over things, you can install the Azure CLI containerapp extension.
az extension add `
  --source https://workerappscliextension.blob.core.windows.net/azure-cli-extension/containerapp-0.2.0-py2.py3-none-any.whl `
  --yes 
I wanted to test with a custom docker image, and I can easily do something like below.
az containerapp update `
    --name capp-web-blazor-demo `
    --resource-group rg-container-apps-demo `
    --image <something>.azurecr.io/containers-apps-demo/web/blazor:latest `
    --registry-login-server <something>.azurecr.io `
    --registry-username  <username> `     --registry-password <password> `     --debug
Custome Container
Hope this helps to get yourself started on exploring Azure Container Apps.

More read,

Happy Coding.

Regards,
Jaliya

Monday, November 15, 2021

Introducing Azure Functions OpenAPI Extension

In this post, let's have a look at a nice feature that's now available with Azure Functions. That is being able to expose HTTP trigger functions via an Open API specification endpoint.

Let's go with an example. I already have an In-Process .NET Azure Function created with a single HTTP Trigger function. 

The first step to enable Open API specification is to install the following NuGet package.

dotnet add package Microsoft.Azure.WebJobs.Extensions.OpenApi -v 1.0.0
Once that is done, if we run the Function App locally, we should be seeing something interesting.
Additional Endpoints
Suddenly we have 4 new endpoints. If I navigated to the SwaggerUI endpoint, I can see this.
OpenAPI Document
That looks good. Now we just need to decorate the function to enrich the OpenAPI specification.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System.Net;
 
namespace FunctionApp1;
 
public static class Function1
{
    [FunctionName("Function1")]
    [OpenApiOperation(operationId: "Run", tags: new[] { "GETTERS" })]
    [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
    [OpenApiParameter(name: "name", 
        In = ParameterLocation.Query, 
        Required = true, 
        Type = typeof(string), 
        Description = "The **Name** parameter")]
    [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, 
        contentType: "text/plain", 
        bodyType: typeof(string), 
        Description = "The OK response")]
    public static IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
        ILogger log)
    {
        string name = req.Query["name"];
 
        string responseMessage = $"Hello, {name}. This HTTP triggered function executed successfully.";
 
        return new OkObjectResult(responseMessage);
    }
}
And now if I run the function app, I should be seeing my HTTP Trigger function.
OpenAPI Document
That looks nice. I can test my function from the Swagger UI itself.
OpenAPI Document
That's quite nice, isn't it? With Visual Studio 2022, you can get yourself a head start by using this template.
.NET 6 (In-Process) - Http trigget with OpenAPI
That was enabling OpenAPI specification for In-Process .NET Functions. 

If you want to enable Open API specification for Isolated (Out-of-process) .NET Azure Functions, you need to install the following NuGet package.
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.OpenApi -v 1.0.0
Once that's done, you need to update the Program.cs as follows.

Program.cs
using Microsoft.Azure.Functions.Worker.Extensions.OpenApi.Extensions;
using Microsoft.Extensions.Hosting;
 
namespace FunctionApp3;
 
public class Program
{
    public static void Main()
    {
        IHost host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults(worker => worker.UseNewtonsoftJson())
            .ConfigureOpenApi()
            .Build();
 
        host.Run();
    }
}
And then decorate the Functions the same way as in In-Process model.
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System.Collections.Specialized;
using System.Net;
using System.Web;
 
namespace FunctionApp3;
 
public class Function1
{
    private readonly ILogger _logger;
 
    public Function1(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<Function1>();
    }
 
    [Function("Function1-Isolated")]
    [OpenApiOperation(operationId: "Run", tags: new[] { "GETTERS" })]
    [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
    [OpenApiParameter(name: "name",
        In = ParameterLocation.Query,
        Required = true,
        Type = typeof(string),
        Description = "The **Name** parameter")]
    [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK,
        contentType: "text/plain",
        bodyType: typeof(string),
        Description = "The OK response")]
    public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req)
    {
        NameValueCollection query = HttpUtility.ParseQueryString(req.Url.Query);
        string name = query["name"];
 
        HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type""text/plain; charset=utf-8");
 
        string responseMessage = $"Hello, {name}. This HTTP triggered function executed successfully.";
 
        response.WriteString(responseMessage);
 
        return response;
    }
}
That's about it. Now if we run this, it should be working the same way as in In-Process mode.
OpenAPI Document
Hope this helps.

Happy Coding.

Regards.
Jaliya