Skip to content

Commit

Permalink
Added CM ASP.NET extensions and CM metapackage (#47021)
Browse files Browse the repository at this point in the history
* added ASp.NET extensions package

* added ASP.NET extensions package

* updated apis
  • Loading branch information
KrzysztofCwalina authored Nov 7, 2024
1 parent 99f387a commit f9995e7
Show file tree
Hide file tree
Showing 10 changed files with 391 additions and 0 deletions.
11 changes: 11 additions & 0 deletions sdk/cloudmachine/Azure.CloudMachine.All/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Release History

## 1.0.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Bugs Fixed

### Other Changes
57 changes: 57 additions & 0 deletions sdk/cloudmachine/Azure.CloudMachine.All/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ASP.NET COre extensions for Azure CloudMachine client library for .NET

TODO

## Getting started

### Install the package

Install the client library for .NET with [NuGet](https://www.nuget.org/ ):

```dotnetcli
dotnet add package Azure.CloudMachine --prerelease
```

### Prerequisites

> You must have an [Azure subscription](https://azure.microsoft.com/free/dotnet/).
### Authenticate the Client

## Key concepts

TODO.

## Examples

## Troubleshooting

- File an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-net/issues).
- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+.net) or ask new ones on Stack Overflow using Azure and .NET tags.

## Next steps

## Contributing

For details on contributing to this repository, see the [contributing
guide][cg].

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 <https://cla.microsoft.com>.

When you submit a pull request, a CLA-bot will automatically determine
whether you need to provide a CLA and decorate the PR appropriately
(for example, label, comment). Follow the instructions provided by the
bot. You'll only need to do this action once across all repositories
using our 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> with any other questions or comments.

<!-- LINKS -->
[cg]: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/resourcemanager/Azure.ResourceManager/docs/CONTRIBUTING.md
[coc]: https://opensource.microsoft.com/codeofconduct/
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Azure.CloudMachine Metapackage</Description>
<Version>1.0.0-beta.1</Version>
<TargetFrameworks>net8.0</TargetFrameworks>
<RequiredTargetFrameworks>net8.0</RequiredTargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\provisioning\Azure.Provisioning.CloudMachine\src\Azure.Provisioning.CloudMachine.csproj" />
<ProjectReference Include="..\..\Azure.CloudMachine.Web\src\Azure.CloudMachine.Web.csproj" />
<ProjectReference Include="..\..\Azure.CloudMachine\src\Azure.CloudMachine.csproj" />
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions sdk/cloudmachine/Azure.CloudMachine.Web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Release History

## 1.0.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Bugs Fixed

### Other Changes
57 changes: 57 additions & 0 deletions sdk/cloudmachine/Azure.CloudMachine.Web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ASP.NET COre extensions for Azure CloudMachine client library for .NET

TODO

## Getting started

### Install the package

Install the client library for .NET with [NuGet](https://www.nuget.org/ ):

```dotnetcli
dotnet add package Azure.CloudMachine --prerelease
```

### Prerequisites

> You must have an [Azure subscription](https://azure.microsoft.com/free/dotnet/).
### Authenticate the Client

## Key concepts

TODO.

## Examples

## Troubleshooting

- File an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-net/issues).
- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+.net) or ask new ones on Stack Overflow using Azure and .NET tags.

## Next steps

## Contributing

For details on contributing to this repository, see the [contributing
guide][cg].

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 <https://cla.microsoft.com>.

When you submit a pull request, a CLA-bot will automatically determine
whether you need to provide a CLA and decorate the PR appropriately
(for example, label, comment). Follow the instructions provided by the
bot. You'll only need to do this action once across all repositories
using our 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> with any other questions or comments.

<!-- LINKS -->
[cg]: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/resourcemanager/Azure.ResourceManager/docs/CONTRIBUTING.md
[coc]: https://opensource.microsoft.com/codeofconduct/
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace System.ClientModel.TypeSpec
{
public static partial class CloudMachineExtensions
{
public static void Map<T>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder routeBuilder, T serviceImplementation) where T : class { }
public static System.Threading.Tasks.Task UploadFormAsync(this Azure.CloudMachine.StorageServices storage, Microsoft.AspNetCore.Http.HttpRequest multiPartFormData) { throw null; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Azure.CloudMachine.Web is an ASP.NET Core extensions for Azure.CloudMachine</Description>
<Version>1.0.0-beta.1</Version>
<TargetFrameworks>net8.0</TargetFrameworks>
<RequiredTargetFrameworks>net8.0</RequiredTargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>

<ItemGroup>
<Compile Remove="Properties\**" />
<EmbeddedResource Remove="Properties\**" />
<None Remove="Properties\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Memory.Data" VersionOverride="8.0.0" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Azure.CloudMachine\src\Azure.CloudMachine.csproj" />
</ItemGroup>
</Project>
191 changes: 191 additions & 0 deletions sdk/cloudmachine/Azure.CloudMachine.Web/src/CloudMachineExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Azure.CloudMachine;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Builder;

namespace System.ClientModel.TypeSpec;

/// <summary>
/// ASp.NET Core extension methods for mapping a service implementation to a set of HTTP endpoints.
/// </summary>
public static class CloudMachineExtensions
{
/// <summary>
/// Uploads a document to the storage service.
/// </summary>
/// <param name="storage"></param>
/// <param name="multiPartFormData"></param>
/// <returns></returns>
public static async Task UploadFormAsync(this StorageServices storage, HttpRequest multiPartFormData)
{
IFormCollection form = await multiPartFormData.ReadFormAsync().ConfigureAwait(false);
IFormFile? file = form.Files.GetFile("file");
Stream? fileStram = file!.OpenReadStream();
await storage.UploadAsync(fileStram, file.FileName, file.ContentType, overwrite: true).ConfigureAwait(false);
}

/// <summary>
/// Maps a service implementation to a set of HTTP endpoints.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="routeBuilder"></param>
/// <param name="serviceImplementation"></param>
public static void Map<T>(this IEndpointRouteBuilder routeBuilder, T serviceImplementation) where T : class
{
Type serviceImplementationType = typeof(T);
Type serviceDescriptor = GetServiceDescriptor(serviceImplementationType);
MethodInfo[] serviceOperations = serviceDescriptor.GetMethods();
foreach (MethodInfo serviceOperation in serviceOperations)
{
RequestDelegate handler = CreateRequestDelegate(serviceImplementation, serviceOperation);
string name = serviceOperation.Name;
if (name.EndsWith("Async"))
name = name.Substring(0, name.Length - "Async".Length);
routeBuilder.Map($"/{name}", handler);
}
}

private static Type GetServiceDescriptor(Type serviceImplementation)
{
Type[] interfaces = serviceImplementation.GetInterfaces();
if (interfaces.Length != 1)
throw new InvalidOperationException($"Service {serviceImplementation} must implement exactly one interface");
Type interfaceType = interfaces[0];
return interfaceType;
}

private static RequestDelegate CreateRequestDelegate<T>(T service, MethodInfo implementationMethod) where T : class
{
return async (HttpContext context) => {
HttpRequest request = context.Request;

Type serviceType = service.GetType();
Type interfaceType = GetServiceDescriptor(serviceType);
MethodInfo? interfaceMethod = interfaceType.GetMethod(implementationMethod.Name, BindingFlags.Public | BindingFlags.Instance);

ParameterInfo[] parameters = interfaceMethod!.GetParameters();
object[] implementationArguments = new object[parameters.Length];

foreach (var parameter in parameters)
{
implementationArguments[0] = await CreateArgumentAsync(parameter, request).ConfigureAwait(false);
}

// deal with async APIs
object? implementationReturnValue = implementationMethod.Invoke(service, implementationArguments);
if (implementationReturnValue != default)
{
Task? task = implementationReturnValue as Task;
if (task != default)
{
await task.ConfigureAwait(false);
implementationReturnValue = task.GetType().GetProperty("Result")!.GetValue(task);
}
else
{ // TODO: we need to deal with ValueTask too
implementationReturnValue = default;
}
}
else
{
Debug.Assert(implementationArguments.Length == 0);
}

HttpResponse response = context.Response;
response.StatusCode = 200;
if (implementationReturnValue != default)
{
BinaryData responseBody = Serialize(implementationReturnValue);
response.ContentLength = responseBody.ToMemory().Length;
response.ContentType = "application/json";
await response.Body.WriteAsync(responseBody.ToArray()).ConfigureAwait(false);
}
};
}

private static async ValueTask<object> CreateArgumentAsync(ParameterInfo parameter, HttpRequest request)
{
Type parameterType = parameter.ParameterType;

if (parameterType == typeof(HttpRequest))
{
return request;
}

if (parameterType == typeof(Stream))
{
return request.Body;
}

if (parameterType == typeof(byte[]))
{
var bd = await BinaryData.FromStreamAsync(request.Body).ConfigureAwait(false);
return bd.ToArray();
}
if (parameterType == typeof(BinaryData))
{
string? contentType = request.ContentType;
var bd = await BinaryData.FromStreamAsync(request.Body, contentType).ConfigureAwait(false);
return bd;
}
if (parameterType == typeof(string))
{
return await new StreamReader(request.Body).ReadToEndAsync().ConfigureAwait(false);
}

FromQueryAttribute? fqa = parameter.GetCustomAttribute<FromQueryAttribute>();
if (fqa != default)
{
string? queryValue = request.Query[parameter.Name!];
return Convert.ChangeType(queryValue!, parameterType);
}

FromHeaderAttribute? fha = parameter.GetCustomAttribute<FromHeaderAttribute>();
if (fha != default)
{
var headerName = fha.Name ?? parameter.Name;
string? headerValue = request.Headers[headerName!];
return Convert.ChangeType(headerValue!, parameterType);
}

object deserialized = DeserializeModel(parameterType, request.Body);
return deserialized;
}

// TODO: this is a hack. We should use MRW
private static object DeserializeModel(Type modelType, Stream stream)
{
var fromJson = modelType.GetMethod("FromJson", BindingFlags.Static);
if (fromJson == default)
throw new InvalidOperationException($"{modelType} does not provide FromJson static method");
object? deserialized = fromJson.Invoke(null, new object[] { stream });
if (deserialized == default)
throw new InvalidOperationException($"Failed to deserialize {modelType}");
return deserialized;
}

private static BinaryData Serialize(object implementationReturnValue)
{
Type type = implementationReturnValue.GetType();
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(ValueTask<>))
{
}
if (type.GetGenericTypeDefinition() == typeof(Task<>))
{
}
}

BinaryData bd = BinaryData.FromObjectAsJson(implementationReturnValue);
return bd;
}
}
Loading

0 comments on commit f9995e7

Please sign in to comment.