Skip to content

Commit

Permalink
WCF Client/CoreWCF extension packages to use Azure Queue Storage as t…
Browse files Browse the repository at this point in the history
…ransport (#42124)

* Add Azure Queue Storage support

* Added custom Credentials classes with binding support to choose credential type. Added tests

* First round of PR feedback changes

* Addressing Review comments and fix test pipeline  break

* Update CoreWCF dependent package version

* Fix problem with version pinning where type was removed in later version that NetFx asp.net core 2.1 needed

* Changes to resolve PR comments

* Read me updated to fix validation errors

* Enable split build in extensions folder for aspnetcore and wcf

* Remove Snippet annotation from documentation samples

* Fix old package version specified by existing SDK versions which breaks the WCF build

* Fix readme url's to be relative so they don't point to main branch which doesn't exist yet

* Added samples with snippets, fixed yml definitions

* Adding WCF docs to .docsettings.yml as well as CoreWCF

* Fix end2end dlq test which didn't have credentials configured

* Apply suggestions from code review

Co-authored-by: Wes Haggard <weshaggard@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Scott Addie <10702007+scottaddie@users.noreply.github.com>

* Fixed readme, fixed folder separator to be / instead of \ in service.projects

---------

Co-authored-by: subhra231 <subhra.nits@gmail.com>
Co-authored-by: Wes Haggard <weshaggard@users.noreply.github.com>
Co-authored-by: Scott Addie <10702007+scottaddie@users.noreply.github.com>
  • Loading branch information
4 people authored Apr 19, 2024
1 parent 0de35d0 commit 82cc7cf
Show file tree
Hide file tree
Showing 122 changed files with 7,808 additions and 5 deletions.
3 changes: 3 additions & 0 deletions eng/.docsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ known_content_issues:
- ['sdk/storage/Azure.Storage.Files.DataLake/README.md', 'Storage: #11492: Needs auth section']
- ['sdk/storage/Azure.Storage.Files.Shares/README.md', 'Storage: #11492: Needs auth section']

- ['sdk/extensions/wcf/Microsoft.CoreWCF.Azure.StorageQueues/README.md', 'https://github.com/Azure/azure-sdk-tools/issues/404']
- ['sdk/extensions/wcf/Microsoft.WCF.Azure.StorageQueues/README.md', 'https://github.com/Azure/azure-sdk-tools/issues/404']

# .net climbs upwards. placing these to prevent assigning readmes to the wrong project
package_indexing_exclusion_list:
- 'AutoRest-AzureDotNetSDK'
Expand Down
28 changes: 23 additions & 5 deletions eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@
<PackageReference Update="ApprovalUtilities" Version="3.0.22" />
<PackageReference Update="Azure.Identity" Version="1.11.1" />
<PackageReference Update="Azure.Messaging.EventGrid" Version="4.17.0" />
<PackageReference Update="Azure.Messaging.EventHubs.Processor" Version="5.11.2" />
<PackageReference Update="Azure.Messaging.EventHubs.Processor" Version="5.11.2" />
<PackageReference Update="Azure.Messaging.ServiceBus" Version="7.17.5" />
<PackageReference Update="Azure.ResourceManager.Compute" Version="1.2.0" />
<PackageReference Update="Azure.ResourceManager.CognitiveServices" Version="1.3.0" />
Expand All @@ -257,9 +257,9 @@
<PackageReference Update="Azure.ResourceManager.RecoveryServices" Version="1.1.0" />
<PackageReference Update="Azure.ResourceManager.Resources" Version="1.6.0" />
<PackageReference Update="Azure.ResourceManager.Storage" Version="1.1.1" />
<PackageReference Update="Azure.ResourceManager.Kubernetes" Version="1.0.0-beta.3" />
<PackageReference Update="Azure.ResourceManager.KubernetesConfiguration" Version="1.2.0-beta.1" />
<PackageReference Update="Azure.ResourceManager.ExtendedLocations" Version="1.1.0-beta.1" />
<PackageReference Update="Azure.ResourceManager.Kubernetes" Version="1.0.0-beta.3" />
<PackageReference Update="Azure.ResourceManager.KubernetesConfiguration" Version="1.2.0-beta.1" />
<PackageReference Update="Azure.ResourceManager.ExtendedLocations" Version="1.1.0-beta.1" />
<PackageReference Update="Azure.ResourceManager.EventHubs" Version="1.0.0" />
<PackageReference Update="Azure.ResourceManager.ContainerRegistry" Version="1.1.0" />
<PackageReference Update="Azure.Search.Documents" Version="11.2.0" />
Expand Down Expand Up @@ -357,11 +357,29 @@
<PackageReference Update="xunit.runner.visualstudio" Version="2.4.2" />
<PackageReference Update="xunit" Version="2.4.2" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageDownload Update="NETStandard.Library.Ref" Version="[2.1.0]" />
</ItemGroup>

<!-- IsWcfLibrary is set in "..\sdk\extensions\wcf\Directory.Build.props". This property indicates that WCF/CoreWCF extensions project is being built.-->
<ItemGroup Condition="'$(IsWcfLibrary)' == 'true'">
<PackageReference Update="CoreWCF.Primitives" Version="[1.5.2,2.0)" />
<PackageReference Update="CoreWCF.Queue" Version="[1.5.2,2.0)" />
<PackageReference Update="System.IO.Pipelines" Version="6.0.3" />
<PackageReference Update="Microsoft.AspNetCore" Version="2.1.0" />
<PackageReference Update="System.ServiceModel.Primitives" Version="6.2.0" />
<PackageReference Update="Azure.Storage.Files.Shares" Version="12.17.1" />
<PackageReference Update="Azure.Storage.Queues" Version="12.17.1" />
<PackageReference Update="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Update="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Update="Microsoft.Extensions.Logging.Configuration" Version="2.1.1" />
</ItemGroup>
<ItemGroup Condition="('$(IsWcfLibrary)' == 'true') AND ($(TargetFramework.StartsWith('net4')))">
<PackageReference Update="Microsoft.Extensions.Configuration.UserSecrets" Version="2.1.1" />
<PackageReference Update="Microsoft.Extensions.Http" Version="2.1.1" />
<PackageReference Update="Microsoft.Extensions.Logging.Configuration" Version="2.1.1" />
</ItemGroup>

<PropertyGroup>
<TestProxyVersion>1.0.0-dev.20240410.2</TestProxyVersion>
</PropertyGroup>
Expand Down
37 changes: 37 additions & 0 deletions sdk/extensions/ci.wcf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file.

trigger:
branches:
include:
- main
- hotfix/*
- release/*
paths:
include:
- sdk/extensions/wcf

pr:
branches:
include:
- main
- feature/*
- hotfix/*
- release/*
paths:
include:
- sdk/extensions/wcf

extends:
template: /eng/pipelines/templates/stages/archetype-sdk-client.yml
parameters:
SDKType: wcf
ServiceDirectory: extensions/wcf
ArtifactName: packages
Artifacts:
- name: Microsoft.CoreWCF.Azure.StorageQueues
safeName: MicrosoftCoreWCFAzureStorageQueues
- name: Microsoft.WCF.Azure.StorageQueues
safeName: MicrosoftWCFAzureStorageQueues
CheckAOTCompat: false
TestSetupSteps:
- template: /sdk/storage/tests-install-azurite.yml
5 changes: 5 additions & 0 deletions sdk/extensions/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ trigger:
paths:
include:
- sdk/extensions/
exclude:
- sdk/extensions/wcf/

pr:
branches:
Expand All @@ -20,10 +22,13 @@ pr:
paths:
include:
- sdk/extensions/
exclude:
- sdk/extensions/wcf/

extends:
template: /eng/pipelines/templates/stages/archetype-sdk-client.yml
parameters:
SDKType: client
ServiceDirectory: extensions
ArtifactName: packages
Artifacts:
Expand Down
23 changes: 23 additions & 0 deletions sdk/extensions/service.projects
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--
This file allows overrides and extensions to the build configuration defined
by the central engineering system, and is considered during pipeline builds for
CI, test runs, and package release.

Based on the SDKType variable (msbuild property or env variable) this will filter
the set of projects to to match what type of SDK we are interested in building.

SDKType values
- "all" will build all projects
- "client" will build the asp.net core extension libraries contained at the top level extensions folder
- "wcf" will build the WCF/CoreWCF extension libraries contained in the wcf folder
-->

<Project>
<ItemGroup Condition="'$(SDKType)' == 'client'">
<ProjectsToIncludeBySDKType Include="$(MSBuildThisFileDirectory)**/*.csproj" Exclude="$(MSBuildThisFileDirectory)wcf/**/*.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(SDKType)' == 'wcf'">
<ProjectsToIncludeBySDKType Include="$(MSBuildThisFileDirectory)wcf/**/*.csproj" />
</ItemGroup>
</Project>
8 changes: 8 additions & 0 deletions sdk/extensions/tests.wcf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
trigger: none

extends:
template: ../../eng/pipelines/templates/stages/archetype-sdk-tests.yml
parameters:
SDKType: wcf
ServiceDirectory: extensions
SupportedClouds: 'Public,Canary,UsGov,China'
1 change: 1 addition & 0 deletions sdk/extensions/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ trigger: none
extends:
template: ../../eng/pipelines/templates/stages/archetype-sdk-tests.yml
parameters:
SDKType: client
ServiceDirectory: extensions
SupportedClouds: 'Public,Canary,UsGov,China'
24 changes: 24 additions & 0 deletions sdk/extensions/wcf/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project>
<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<!--When parsing the None Item, output path doesn't include the TFM as part of the path
The tfm gets added when building the target, so CopyIconFile has to specify the parent
folder to have it be the same location-->
<None Include="$(OutputPath)\Icon.png" Pack="true" PackagePath="\" />
</ItemGroup>

<Target Condition="('$(IsTestProject)' != 'true') and ('$(IsSamplesProject)' != 'true')" Name="CopyIconFile" AfterTargets="AfterCompile">
<ItemGroup>
<SystemServiceModelIconFile Include="$(PkgSystem_ServiceModel_Primitives)\Icon.png"/>
</ItemGroup>
<Copy SourceFiles="@(SystemServiceModelIconFile)"
DestinationFolder="$(OutputPath)\.." />
</Target>
<!-- Need to set IsClientLibrary for CentralPackageManagement versions file in $(root)\eng\Packages.Data.props -->
<PropertyGroup>
<IsClientLibrary Condition="'$(IsClientLibrary)' == '' and $(MSBuildProjectName.StartsWith('Microsoft.'))">true</IsClientLibrary>
<IsTestProject Condition="'$(IsTestProject)' == '' and $(MSBuildProjectName.EndsWith('.Tests'))">true</IsTestProject>
<IsWcfLibrary>true</IsWcfLibrary>
</PropertyGroup>

<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props, $(MSBuildThisFileDirectory)..))" Condition="'$([MSBuild]::GetPathOfFileAbove(Directory.Build.props, $(MSBuildThisFileDirectory)..))' != ''"/>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Release History

## 1.0.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Bugs Fixed

### Other Changes

152 changes: 152 additions & 0 deletions sdk/extensions/wcf/Microsoft.CoreWCF.Azure.StorageQueues/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# CoreWCF Azure Queue Storage library for .NET

CoreWCF Azure Queue Storage is the service side library that will help existing WCF services to be able to use Azure Queue Storage to communicate with clients as a modern replacement to using MSMQ.

## Getting started

### Install the package

Install the Microsoft.CoreWCF.Azure.StorageQueues library for .NET with [NuGet][nuget]:

```dotnetcli
dotnet add package Microsoft.CoreWCF.Azure.StorageQueues
```

### Prerequisites

You need an [Azure subscription][azure_sub] and a
[Storage Account][storage_account_docs] to use this package.

To create a new Storage Account, you can use the [Azure portal][storage_account_create_portal],
[Azure PowerShell][storage_account_create_ps], or the [Azure CLI][storage_account_create_cli].
Here's an example using the Azure CLI:

```azurecli
az storage account create --name MyStorageAccount --resource-group MyResourceGroup --location westus --sku Standard_LRS
```

### Configure ASP.NET Core to use CoreWCF with Queue transports

In order to receive requests from the Azure Queue Storage service, you'll need to configure CoreWCF to use queue transports.

```C# Snippet:Configure_CoreWCF_QueueTransport
public void ConfigureServices(IServiceCollection services)
{
services.AddServiceModelServices()
.AddQueueTransport();
}
```

### Authenticate the service to Azure Queue Storage

To receive requests from the Azure Queue Storage service, you'll need to configure CoreWCF with the appropriate endpoint, and configure credentials. The [Azure Identity library][identity] makes it easy to add Microsoft Entra ID support for authenticating with Azure services.

There are multiple authentication mechanisms for Azure Queue Storage. Which mechanism to use is configured via the property `AzureQueueStorageBinding.Security.Transport.ClientCredentialType`. The default authentication mechanism is `Default`, which uses `DefaultAzureCredential`.

```C# Snippet:CoreWCF_Azure_Storage_Queues_Sample_DefaultAzureCredential
public void Configure(IApplicationBuilder app)
{
app.UseServiceModel(serviceBuilder =>
{
// Configure CoreWCF to dispatch to service type Service
serviceBuilder.AddService<Service>();

// Create a binding instance to use Azure Queue Storage, passing an optional queue name for the dead letter queue
// The default client credential type is Default, which uses DefaultAzureCredential
var aqsBinding = new AzureQueueStorageBinding("DEADLETTERQUEUENAME");

// Add a service endpoint using the AzureQueueStorageBinding
string queueEndpointString = "https://MYSTORAGEACCOUNT.queue.core.windows.net/QUEUENAME";
serviceBuilder.AddServiceEndpoint<Service, IService>(aqsBinding, queueEndpointString);
});
}
```

Learn more about enabling Microsoft Entra ID for authentication with Azure Storage in [our documentation][storage_ad].

### Other authentication credential mechanisms

If you are using a different credential mechanism such as `StorageSharedKeyCredential`, you configure the appropriate `ClientCredentialType` on the binding and set the credential on an `AzureServiceCredentials` instance via an extension method.

```C# Snippet:CoreWCF_Azure_Storage_Queus_Sample_StorageSharedKey
public void Configure(IApplicationBuilder app)
{
app.UseServiceModel(serviceBuilder =>
{
// Configure CoreWCF to dispatch to service type Service
serviceBuilder.AddService<Service>();

// Create a binding instance to use Azure Queue Storage, passing an optional queue name for the dead letter queue
var aqsBinding = new AzureQueueStorageBinding("DEADLETTERQUEUENAME");

// Configure the client credential type to use StorageSharedKeyCredential
aqsBinding.Security.Transport.ClientCredentialType = AzureClientCredentialType.StorageSharedKey;

// Add a service endpoint using the AzureQueueStorageBinding
string queueEndpointString = "https://MYSTORAGEACCOUNT.queue.core.windows.net/QUEUENAME";
serviceBuilder.AddServiceEndpoint<Service, IService>(aqsBinding, queueEndpointString);

// Use extension method to configure CoreWCF to use AzureServiceCredentials and set the
// StorageSharedKeyCredential instance.
serviceBuilder.UseAzureCredentials<Service>(credentials =>
{
credentials.StorageSharedKey = GetStorageSharedKey();
});
});
}

public StorageSharedKeyCredential GetStorageSharedKey()
{
// Fetch shared key using a secure mechanism such as Azure Key Vault
// and construct an instance of StorageSharedKeyCredential to return;
return default;
}
```

Other values for ClientCredentialType are `Sas`, `Token`, and `ConnectionString`. The credentials class `AzureServiceCredentials` has corresponding properties to set each of these credential types.

## Troubleshooting

If there are any errors receiving a message from Azure Queue Storage, the CoreWCF transport will log the details at the `Debug` log level. You can configure logging for the category `Microsoft.CoreWCF` at the `Debug` level to see any errors.

```C# Snippet:CoreWCF_Azure_Storage_Queus_Sample_Logging
.ConfigureLogging((logging) =>
{
logging.AddFilter("Microsoft.CoreWCF", LogLevel.Debug);
});
```

If a message was recevied from the queue, but wasn't able to be processed, it will be placed in the dead letter queue. You can sepcify the dead letter queue name by passing it to the constructor of `AzureQueueStorageBinding`. If not specified, the default value of `default-dead-letter-queue` will be used.

## Key concepts

CoreWCF is an implementation of the service side features of Windows Communication Foundation (WCF) for .NET. The goal of this project is to enable migrating existing WCF services to .NET that are currently using MSMQ and wish to deploy their service to Azure, replacing MSMQ with Azure Queue Storage.

More general samples of using CoreWCF can be found in the [CoreWCF samples repository][corewcf_repo]. To create a client to send messages to an Azure Storage Queue, see the[Microsoft.WCF.Azure.StorageQueues documentation][wcf_docs].

## Contributing

See the [Storage CONTRIBUTING.md][storage_contrib] for details on building,testing, and contributing to this library.

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [cla.microsoft.com][cla].

This project has adopted the [Microsoft Open Source Code of Conduct][coc].
For more information see the [Code of Conduct FAQ][coc_faq] or contact [opencode@microsoft.com][coc_contact] with any additional questions or comments.

<!-- LINKS -->
[nuget]: https://www.nuget.org/
[storage_account_docs]: https://learn.microsoft.com/azure/storage/common/storage-account-overview
[storage_account_create_ps]: https://learn.microsoft.com/azure/storage/common/storage-account-create?tabs=azure-powershell
[storage_account_create_cli]: https://learn.microsoft.com/azure/storage/common/storage-account-create?tabs=azure-cli
[storage_account_create_portal]: https://learn.microsoft.com/azure/storage/common/storage-account-create?tabs=azure-portal
[azure_cli]: https://learn.microsoft.com/cli/azure/
[azure_sub]: https://azure.microsoft.com/free/dotnet/
[identity]: https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/identity/Azure.Identity/README.md
[storage_ad]: https://learn.microsoft.com/azure/storage/blobs/authorize-access-azure-active-directory
[storage_contrib]: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/CONTRIBUTING.md
[cla]: https://opensource.microsoft.com/cla/
[coc]: https://opensource.microsoft.com/codeofconduct/
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
[coc_contact]: mailto:opencode@microsoft.com
[corewcf_repo]: https://github.com/CoreWCF/samples/
[wcf_docs]: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/extensions/wcf/Microsoft.WCF.Azure.StorageQueues
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Usage", "AZC0001:Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): Azure.AI, Azure.Analytics, Azure.Communication, Azure.Containers, Azure.Core.Expressions, Azure.Data, Azure.DigitalTwins, Azure.Identity, Azure.IoT, Azure.Learn, Azure.Management, Azure.Media, Azure.Messaging, Azure.MixedReality, Azure.Monitor, Azure.ResourceManager, Azure.Search, Azure.Security, Azure.Storage, Azure.Template, Microsoft.Extensions.Azure", Justification = "<Pending>", Scope = "namespace", Target = "~N:Microsoft.CoreWCF.Azure.StorageQueues")]
[assembly: SuppressMessage("Usage", "AZC0001:Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): Azure.AI, Azure.Analytics, Azure.Communication, Azure.Containers, Azure.Core.Expressions, Azure.Data, Azure.DigitalTwins, Azure.Identity, Azure.IoT, Azure.Learn, Azure.Management, Azure.Media, Azure.Messaging, Azure.MixedReality, Azure.Monitor, Azure.ResourceManager, Azure.Search, Azure.Security, Azure.Storage, Azure.Template, Microsoft.Extensions.Azure", Justification = "<Pending>", Scope = "namespace", Target = "~N:Microsoft.CoreWCF.Azure")]
[assembly: SuppressMessage("Usage", "AZC0001:Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): Azure.AI, Azure.Analytics, Azure.Communication, Azure.Containers, Azure.Core.Expressions, Azure.Data, Azure.DigitalTwins, Azure.Identity, Azure.IoT, Azure.Learn, Azure.Management, Azure.Media, Azure.Messaging, Azure.MixedReality, Azure.Monitor, Azure.ResourceManager, Azure.Search, Azure.Security, Azure.Storage, Azure.Template, Microsoft.Extensions.Azure", Justification = "<Pending>", Scope = "namespace", Target = "~N:Microsoft.CoreWCF.Azure.Tokens")]
[assembly: SuppressMessage("Usage", "AZC0001:Use one of the following pre-approved namespace groups (https://azure.github.io/azure-sdk/registered_namespaces.html): Azure.AI, Azure.Analytics, Azure.Communication, Azure.Containers, Azure.Core.Expressions, Azure.Data, Azure.DigitalTwins, Azure.Identity, Azure.IoT, Azure.Learn, Azure.Management, Azure.Media, Azure.Messaging, Azure.MixedReality, Azure.Monitor, Azure.ResourceManager, Azure.Search, Azure.Security, Azure.Storage, Azure.Template, Microsoft.Extensions.Azure", Justification = "<Pending>", Scope = "namespace", Target = "~N:Microsoft.CoreWCF.Azure.StorageQueues.Channels")]
Loading

0 comments on commit 82cc7cf

Please sign in to comment.