Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
added ASp.NET extensions package
  • Loading branch information
KrzysztofCwalina committed Nov 7, 2024
commit 8813e14c07f5922cba9b955574727cb51339f885
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
<[email protected]> 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,24 @@
<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>
<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>

<ItemGroup>
<Folder Include="Properties\" />
</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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.CloudMachine.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Provisioning.Deployment", "..\Azure.Provisioning.Deployment\src\Azure.Provisioning.Deployment.csproj", "{4562F8C1-9FE3-4B68-BBB2-D052B49C915A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.CloudMachine.Web", "..\..\cloudmachine\Azure.CloudMachine.Web\src\Azure.CloudMachine.Web.csproj", "{DD18D434-4665-43CA-97C0-4D63894EE23C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -39,6 +41,10 @@ Global
{4562F8C1-9FE3-4B68-BBB2-D052B49C915A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4562F8C1-9FE3-4B68-BBB2-D052B49C915A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4562F8C1-9FE3-4B68-BBB2-D052B49C915A}.Release|Any CPU.Build.0 = Release|Any CPU
{DD18D434-4665-43CA-97C0-4D63894EE23C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD18D434-4665-43CA-97C0-4D63894EE23C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD18D434-4665-43CA-97C0-4D63894EE23C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD18D434-4665-43CA-97C0-4D63894EE23C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down