diff --git a/sdk/cloudmachine/Azure.CloudMachine.All/CHANGELOG.md b/sdk/cloudmachine/Azure.CloudMachine.All/CHANGELOG.md new file mode 100644 index 000000000000..13dd08af78ab --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.All/CHANGELOG.md @@ -0,0 +1,11 @@ +# Release History + +## 1.0.0-beta.1 (Unreleased) + +### Features Added + +### Breaking Changes + +### Bugs Fixed + +### Other Changes diff --git a/sdk/cloudmachine/Azure.CloudMachine.All/README.md b/sdk/cloudmachine/Azure.CloudMachine.All/README.md new file mode 100644 index 000000000000..920e25a208f0 --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.All/README.md @@ -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 . + +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 + with any other questions or comments. + + +[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/ diff --git a/sdk/cloudmachine/Azure.CloudMachine.All/api/Azure.CloudMachine.All.net8.0.cs b/sdk/cloudmachine/Azure.CloudMachine.All/api/Azure.CloudMachine.All.net8.0.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/cloudmachine/Azure.CloudMachine.All/src/Azure.CloudMachine.All.csproj b/sdk/cloudmachine/Azure.CloudMachine.All/src/Azure.CloudMachine.All.csproj new file mode 100644 index 000000000000..406fe8cbb1ad --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.All/src/Azure.CloudMachine.All.csproj @@ -0,0 +1,18 @@ + + + + Azure.CloudMachine Metapackage + 1.0.0-beta.1 + net8.0 + net8.0 + enable + 12 + + + + + + + + + diff --git a/sdk/cloudmachine/Azure.CloudMachine.Web/CHANGELOG.md b/sdk/cloudmachine/Azure.CloudMachine.Web/CHANGELOG.md new file mode 100644 index 000000000000..13dd08af78ab --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.Web/CHANGELOG.md @@ -0,0 +1,11 @@ +# Release History + +## 1.0.0-beta.1 (Unreleased) + +### Features Added + +### Breaking Changes + +### Bugs Fixed + +### Other Changes diff --git a/sdk/cloudmachine/Azure.CloudMachine.Web/README.md b/sdk/cloudmachine/Azure.CloudMachine.Web/README.md new file mode 100644 index 000000000000..920e25a208f0 --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.Web/README.md @@ -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 . + +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 + with any other questions or comments. + + +[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/ diff --git a/sdk/cloudmachine/Azure.CloudMachine.Web/api/Azure.CloudMachine.Web.net8.0.cs b/sdk/cloudmachine/Azure.CloudMachine.Web/api/Azure.CloudMachine.Web.net8.0.cs new file mode 100644 index 000000000000..a3a78a09c640 --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.Web/api/Azure.CloudMachine.Web.net8.0.cs @@ -0,0 +1,8 @@ +namespace System.ClientModel.TypeSpec +{ + public static partial class CloudMachineExtensions + { + public static void Map(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; } + } +} diff --git a/sdk/cloudmachine/Azure.CloudMachine.Web/src/Azure.CloudMachine.Web.csproj b/sdk/cloudmachine/Azure.CloudMachine.Web/src/Azure.CloudMachine.Web.csproj new file mode 100644 index 000000000000..70d54cecc233 --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.Web/src/Azure.CloudMachine.Web.csproj @@ -0,0 +1,26 @@ + + + + Azure.CloudMachine.Web is an ASP.NET Core extensions for Azure.CloudMachine + 1.0.0-beta.1 + net8.0 + net8.0 + enable + 12 + + + + + + + + + + + + + + + + + diff --git a/sdk/cloudmachine/Azure.CloudMachine.Web/src/CloudMachineExtensions.cs b/sdk/cloudmachine/Azure.CloudMachine.Web/src/CloudMachineExtensions.cs new file mode 100644 index 000000000000..78aa20c32cec --- /dev/null +++ b/sdk/cloudmachine/Azure.CloudMachine.Web/src/CloudMachineExtensions.cs @@ -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; + +/// +/// ASp.NET Core extension methods for mapping a service implementation to a set of HTTP endpoints. +/// +public static class CloudMachineExtensions +{ + /// + /// Uploads a document to the storage service. + /// + /// + /// + /// + 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); + } + + /// + /// Maps a service implementation to a set of HTTP endpoints. + /// + /// + /// + /// + public static void Map(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 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 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(); + if (fqa != default) + { + string? queryValue = request.Query[parameter.Name!]; + return Convert.ChangeType(queryValue!, parameterType); + } + + FromHeaderAttribute? fha = parameter.GetCustomAttribute(); + 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; + } +} diff --git a/sdk/provisioning/Azure.Provisioning.CloudMachine/Azure.CloudMachine.sln b/sdk/provisioning/Azure.Provisioning.CloudMachine/Azure.CloudMachine.sln index 82d692c72b96..d2a38b3fc057 100644 --- a/sdk/provisioning/Azure.Provisioning.CloudMachine/Azure.CloudMachine.sln +++ b/sdk/provisioning/Azure.Provisioning.CloudMachine/Azure.CloudMachine.sln @@ -13,6 +13,10 @@ 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 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.CloudMachine.All", "..\..\cloudmachine\Azure.CloudMachine.All\src\Azure.CloudMachine.All.csproj", "{4E651FF9-EF43-4302-B77B-83C08D8FD117}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +43,14 @@ 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 + {4E651FF9-EF43-4302-B77B-83C08D8FD117}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E651FF9-EF43-4302-B77B-83C08D8FD117}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E651FF9-EF43-4302-B77B-83C08D8FD117}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E651FF9-EF43-4302-B77B-83C08D8FD117}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE