Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CM ASP.NET extensions and CM metapackage #47021

Merged
merged 3 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/
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>
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