Skip to content

Commit f9995e7

Browse files
Added CM ASP.NET extensions and CM metapackage (Azure#47021)
* added ASp.NET extensions package * added ASP.NET extensions package * updated apis
1 parent 99f387a commit f9995e7

File tree

10 files changed

+391
-0
lines changed

10 files changed

+391
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Release History
2+
3+
## 1.0.0-beta.1 (Unreleased)
4+
5+
### Features Added
6+
7+
### Breaking Changes
8+
9+
### Bugs Fixed
10+
11+
### Other Changes
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# ASP.NET COre extensions for Azure CloudMachine client library for .NET
2+
3+
TODO
4+
5+
## Getting started
6+
7+
### Install the package
8+
9+
Install the client library for .NET with [NuGet](https://www.nuget.org/ ):
10+
11+
```dotnetcli
12+
dotnet add package Azure.CloudMachine --prerelease
13+
```
14+
15+
### Prerequisites
16+
17+
> You must have an [Azure subscription](https://azure.microsoft.com/free/dotnet/).
18+
19+
### Authenticate the Client
20+
21+
## Key concepts
22+
23+
TODO.
24+
25+
## Examples
26+
27+
## Troubleshooting
28+
29+
- File an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-net/issues).
30+
- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+.net) or ask new ones on Stack Overflow using Azure and .NET tags.
31+
32+
## Next steps
33+
34+
## Contributing
35+
36+
For details on contributing to this repository, see the [contributing
37+
guide][cg].
38+
39+
This project welcomes contributions and suggestions. Most contributions
40+
require you to agree to a Contributor License Agreement (CLA) declaring
41+
that you have the right to, and actually do, grant us the rights to use
42+
your contribution. For details, visit <https://cla.microsoft.com>.
43+
44+
When you submit a pull request, a CLA-bot will automatically determine
45+
whether you need to provide a CLA and decorate the PR appropriately
46+
(for example, label, comment). Follow the instructions provided by the
47+
bot. You'll only need to do this action once across all repositories
48+
using our CLA.
49+
50+
This project has adopted the [Microsoft Open Source Code of Conduct][coc]. For
51+
more information, see the [Code of Conduct FAQ][coc_faq] or contact
52+
<[email protected]> with any other questions or comments.
53+
54+
<!-- LINKS -->
55+
[cg]: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/resourcemanager/Azure.ResourceManager/docs/CONTRIBUTING.md
56+
[coc]: https://opensource.microsoft.com/codeofconduct/
57+
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/

sdk/cloudmachine/Azure.CloudMachine.All/api/Azure.CloudMachine.All.net8.0.cs

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>Azure.CloudMachine Metapackage</Description>
5+
<Version>1.0.0-beta.1</Version>
6+
<TargetFrameworks>net8.0</TargetFrameworks>
7+
<RequiredTargetFrameworks>net8.0</RequiredTargetFrameworks>
8+
<Nullable>enable</Nullable>
9+
<LangVersion>12</LangVersion>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\..\provisioning\Azure.Provisioning.CloudMachine\src\Azure.Provisioning.CloudMachine.csproj" />
14+
<ProjectReference Include="..\..\Azure.CloudMachine.Web\src\Azure.CloudMachine.Web.csproj" />
15+
<ProjectReference Include="..\..\Azure.CloudMachine\src\Azure.CloudMachine.csproj" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Release History
2+
3+
## 1.0.0-beta.1 (Unreleased)
4+
5+
### Features Added
6+
7+
### Breaking Changes
8+
9+
### Bugs Fixed
10+
11+
### Other Changes
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# ASP.NET COre extensions for Azure CloudMachine client library for .NET
2+
3+
TODO
4+
5+
## Getting started
6+
7+
### Install the package
8+
9+
Install the client library for .NET with [NuGet](https://www.nuget.org/ ):
10+
11+
```dotnetcli
12+
dotnet add package Azure.CloudMachine --prerelease
13+
```
14+
15+
### Prerequisites
16+
17+
> You must have an [Azure subscription](https://azure.microsoft.com/free/dotnet/).
18+
19+
### Authenticate the Client
20+
21+
## Key concepts
22+
23+
TODO.
24+
25+
## Examples
26+
27+
## Troubleshooting
28+
29+
- File an issue via [GitHub Issues](https://github.com/Azure/azure-sdk-for-net/issues).
30+
- Check [previous questions](https://stackoverflow.com/questions/tagged/azure+.net) or ask new ones on Stack Overflow using Azure and .NET tags.
31+
32+
## Next steps
33+
34+
## Contributing
35+
36+
For details on contributing to this repository, see the [contributing
37+
guide][cg].
38+
39+
This project welcomes contributions and suggestions. Most contributions
40+
require you to agree to a Contributor License Agreement (CLA) declaring
41+
that you have the right to, and actually do, grant us the rights to use
42+
your contribution. For details, visit <https://cla.microsoft.com>.
43+
44+
When you submit a pull request, a CLA-bot will automatically determine
45+
whether you need to provide a CLA and decorate the PR appropriately
46+
(for example, label, comment). Follow the instructions provided by the
47+
bot. You'll only need to do this action once across all repositories
48+
using our CLA.
49+
50+
This project has adopted the [Microsoft Open Source Code of Conduct][coc]. For
51+
more information, see the [Code of Conduct FAQ][coc_faq] or contact
52+
<[email protected]> with any other questions or comments.
53+
54+
<!-- LINKS -->
55+
[cg]: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/resourcemanager/Azure.ResourceManager/docs/CONTRIBUTING.md
56+
[coc]: https://opensource.microsoft.com/codeofconduct/
57+
[coc_faq]: https://opensource.microsoft.com/codeofconduct/faq/
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace System.ClientModel.TypeSpec
2+
{
3+
public static partial class CloudMachineExtensions
4+
{
5+
public static void Map<T>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder routeBuilder, T serviceImplementation) where T : class { }
6+
public static System.Threading.Tasks.Task UploadFormAsync(this Azure.CloudMachine.StorageServices storage, Microsoft.AspNetCore.Http.HttpRequest multiPartFormData) { throw null; }
7+
}
8+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>Azure.CloudMachine.Web is an ASP.NET Core extensions for Azure.CloudMachine</Description>
5+
<Version>1.0.0-beta.1</Version>
6+
<TargetFrameworks>net8.0</TargetFrameworks>
7+
<RequiredTargetFrameworks>net8.0</RequiredTargetFrameworks>
8+
<Nullable>enable</Nullable>
9+
<LangVersion>12</LangVersion>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Compile Remove="Properties\**" />
14+
<EmbeddedResource Remove="Properties\**" />
15+
<None Remove="Properties\**" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<PackageReference Include="System.Memory.Data" VersionOverride="8.0.0" />
20+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
21+
</ItemGroup>
22+
23+
<ItemGroup>
24+
<ProjectReference Include="..\..\Azure.CloudMachine\src\Azure.CloudMachine.csproj" />
25+
</ItemGroup>
26+
</Project>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Reflection;
5+
using Microsoft.AspNetCore.Mvc;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Threading.Tasks;
9+
using Azure.CloudMachine;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.AspNetCore.Routing;
12+
using Microsoft.AspNetCore.Builder;
13+
14+
namespace System.ClientModel.TypeSpec;
15+
16+
/// <summary>
17+
/// ASp.NET Core extension methods for mapping a service implementation to a set of HTTP endpoints.
18+
/// </summary>
19+
public static class CloudMachineExtensions
20+
{
21+
/// <summary>
22+
/// Uploads a document to the storage service.
23+
/// </summary>
24+
/// <param name="storage"></param>
25+
/// <param name="multiPartFormData"></param>
26+
/// <returns></returns>
27+
public static async Task UploadFormAsync(this StorageServices storage, HttpRequest multiPartFormData)
28+
{
29+
IFormCollection form = await multiPartFormData.ReadFormAsync().ConfigureAwait(false);
30+
IFormFile? file = form.Files.GetFile("file");
31+
Stream? fileStram = file!.OpenReadStream();
32+
await storage.UploadAsync(fileStram, file.FileName, file.ContentType, overwrite: true).ConfigureAwait(false);
33+
}
34+
35+
/// <summary>
36+
/// Maps a service implementation to a set of HTTP endpoints.
37+
/// </summary>
38+
/// <typeparam name="T"></typeparam>
39+
/// <param name="routeBuilder"></param>
40+
/// <param name="serviceImplementation"></param>
41+
public static void Map<T>(this IEndpointRouteBuilder routeBuilder, T serviceImplementation) where T : class
42+
{
43+
Type serviceImplementationType = typeof(T);
44+
Type serviceDescriptor = GetServiceDescriptor(serviceImplementationType);
45+
MethodInfo[] serviceOperations = serviceDescriptor.GetMethods();
46+
foreach (MethodInfo serviceOperation in serviceOperations)
47+
{
48+
RequestDelegate handler = CreateRequestDelegate(serviceImplementation, serviceOperation);
49+
string name = serviceOperation.Name;
50+
if (name.EndsWith("Async"))
51+
name = name.Substring(0, name.Length - "Async".Length);
52+
routeBuilder.Map($"/{name}", handler);
53+
}
54+
}
55+
56+
private static Type GetServiceDescriptor(Type serviceImplementation)
57+
{
58+
Type[] interfaces = serviceImplementation.GetInterfaces();
59+
if (interfaces.Length != 1)
60+
throw new InvalidOperationException($"Service {serviceImplementation} must implement exactly one interface");
61+
Type interfaceType = interfaces[0];
62+
return interfaceType;
63+
}
64+
65+
private static RequestDelegate CreateRequestDelegate<T>(T service, MethodInfo implementationMethod) where T : class
66+
{
67+
return async (HttpContext context) => {
68+
HttpRequest request = context.Request;
69+
70+
Type serviceType = service.GetType();
71+
Type interfaceType = GetServiceDescriptor(serviceType);
72+
MethodInfo? interfaceMethod = interfaceType.GetMethod(implementationMethod.Name, BindingFlags.Public | BindingFlags.Instance);
73+
74+
ParameterInfo[] parameters = interfaceMethod!.GetParameters();
75+
object[] implementationArguments = new object[parameters.Length];
76+
77+
foreach (var parameter in parameters)
78+
{
79+
implementationArguments[0] = await CreateArgumentAsync(parameter, request).ConfigureAwait(false);
80+
}
81+
82+
// deal with async APIs
83+
object? implementationReturnValue = implementationMethod.Invoke(service, implementationArguments);
84+
if (implementationReturnValue != default)
85+
{
86+
Task? task = implementationReturnValue as Task;
87+
if (task != default)
88+
{
89+
await task.ConfigureAwait(false);
90+
implementationReturnValue = task.GetType().GetProperty("Result")!.GetValue(task);
91+
}
92+
else
93+
{ // TODO: we need to deal with ValueTask too
94+
implementationReturnValue = default;
95+
}
96+
}
97+
else
98+
{
99+
Debug.Assert(implementationArguments.Length == 0);
100+
}
101+
102+
HttpResponse response = context.Response;
103+
response.StatusCode = 200;
104+
if (implementationReturnValue != default)
105+
{
106+
BinaryData responseBody = Serialize(implementationReturnValue);
107+
response.ContentLength = responseBody.ToMemory().Length;
108+
response.ContentType = "application/json";
109+
await response.Body.WriteAsync(responseBody.ToArray()).ConfigureAwait(false);
110+
}
111+
};
112+
}
113+
114+
private static async ValueTask<object> CreateArgumentAsync(ParameterInfo parameter, HttpRequest request)
115+
{
116+
Type parameterType = parameter.ParameterType;
117+
118+
if (parameterType == typeof(HttpRequest))
119+
{
120+
return request;
121+
}
122+
123+
if (parameterType == typeof(Stream))
124+
{
125+
return request.Body;
126+
}
127+
128+
if (parameterType == typeof(byte[]))
129+
{
130+
var bd = await BinaryData.FromStreamAsync(request.Body).ConfigureAwait(false);
131+
return bd.ToArray();
132+
}
133+
if (parameterType == typeof(BinaryData))
134+
{
135+
string? contentType = request.ContentType;
136+
var bd = await BinaryData.FromStreamAsync(request.Body, contentType).ConfigureAwait(false);
137+
return bd;
138+
}
139+
if (parameterType == typeof(string))
140+
{
141+
return await new StreamReader(request.Body).ReadToEndAsync().ConfigureAwait(false);
142+
}
143+
144+
FromQueryAttribute? fqa = parameter.GetCustomAttribute<FromQueryAttribute>();
145+
if (fqa != default)
146+
{
147+
string? queryValue = request.Query[parameter.Name!];
148+
return Convert.ChangeType(queryValue!, parameterType);
149+
}
150+
151+
FromHeaderAttribute? fha = parameter.GetCustomAttribute<FromHeaderAttribute>();
152+
if (fha != default)
153+
{
154+
var headerName = fha.Name ?? parameter.Name;
155+
string? headerValue = request.Headers[headerName!];
156+
return Convert.ChangeType(headerValue!, parameterType);
157+
}
158+
159+
object deserialized = DeserializeModel(parameterType, request.Body);
160+
return deserialized;
161+
}
162+
163+
// TODO: this is a hack. We should use MRW
164+
private static object DeserializeModel(Type modelType, Stream stream)
165+
{
166+
var fromJson = modelType.GetMethod("FromJson", BindingFlags.Static);
167+
if (fromJson == default)
168+
throw new InvalidOperationException($"{modelType} does not provide FromJson static method");
169+
object? deserialized = fromJson.Invoke(null, new object[] { stream });
170+
if (deserialized == default)
171+
throw new InvalidOperationException($"Failed to deserialize {modelType}");
172+
return deserialized;
173+
}
174+
175+
private static BinaryData Serialize(object implementationReturnValue)
176+
{
177+
Type type = implementationReturnValue.GetType();
178+
if (type.IsGenericType)
179+
{
180+
if (type.GetGenericTypeDefinition() == typeof(ValueTask<>))
181+
{
182+
}
183+
if (type.GetGenericTypeDefinition() == typeof(Task<>))
184+
{
185+
}
186+
}
187+
188+
BinaryData bd = BinaryData.FromObjectAsJson(implementationReturnValue);
189+
return bd;
190+
}
191+
}

0 commit comments

Comments
 (0)