Skip to content

Commit 2b59444

Browse files
sjuarezgxSabrina Juarez Garcia
andauthored
Refactor Azure Functions to use declarative REST services instead of manual routing (#1099)
--------- Co-authored-by: Sabrina Juarez Garcia <sabrina.juarez@globant.com>
1 parent 73f1132 commit 2b59444

File tree

15 files changed

+460
-216
lines changed

15 files changed

+460
-216
lines changed

dotnet/DotNetStandardClasses.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GxSoapHandler", "src\extens
271271
EndProject
272272
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Functions", "Functions", "{E59B3248-4C26-4DB0-96CB-67437319E22B}"
273273
EndProject
274+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeneXus.Deploy.AzureFunctionsLibraries", "src\extensions\Azure\Libraries\GeneXus.Deploy.AzureFunctionsLibraries.csproj", "{B3DC39F8-39F0-4200-A971-77E26FFDB2CA}"
274275
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "kafka", "kafka", "{7CABA1C5-F531-4DC7-AEFC-A33900D15E9D}"
275276
EndProject
276277
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{26132DE8-B551-4A79-9363-696277DDB803}"
@@ -665,6 +666,10 @@ Global
665666
{58C84EC7-A0B3-4C1B-BD78-989AEE87EA32}.Debug|Any CPU.Build.0 = Debug|Any CPU
666667
{58C84EC7-A0B3-4C1B-BD78-989AEE87EA32}.Release|Any CPU.ActiveCfg = Release|Any CPU
667668
{58C84EC7-A0B3-4C1B-BD78-989AEE87EA32}.Release|Any CPU.Build.0 = Release|Any CPU
669+
{B3DC39F8-39F0-4200-A971-77E26FFDB2CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
670+
{B3DC39F8-39F0-4200-A971-77E26FFDB2CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
671+
{B3DC39F8-39F0-4200-A971-77E26FFDB2CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
672+
{B3DC39F8-39F0-4200-A971-77E26FFDB2CA}.Release|Any CPU.Build.0 = Release|Any CPU
668673
{8FE70544-0128-4C22-8C84-28D6842E110A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
669674
{8FE70544-0128-4C22-8C84-28D6842E110A}.Debug|Any CPU.Build.0 = Debug|Any CPU
670675
{8FE70544-0128-4C22-8C84-28D6842E110A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -807,6 +812,7 @@ Global
807812
{F8959289-4ED7-430C-97B7-FAAA29829708} = {B5C28D81-BCD9-4B29-9B68-EDD81D1018D5}
808813
{58C84EC7-A0B3-4C1B-BD78-989AEE87EA32} = {F8959289-4ED7-430C-97B7-FAAA29829708}
809814
{E59B3248-4C26-4DB0-96CB-67437319E22B} = {41E1D031-799F-484F-85DE-7A30AF1A6FBA}
815+
{B3DC39F8-39F0-4200-A971-77E26FFDB2CA} = {41E1D031-799F-484F-85DE-7A30AF1A6FBA}
810816
{7CABA1C5-F531-4DC7-AEFC-A33900D15E9D} = {C6AFB6A3-FF0B-4970-B1F1-10BCD3D932B2}
811817
{26132DE8-B551-4A79-9363-696277DDB803} = {7CABA1C5-F531-4DC7-AEFC-A33900D15E9D}
812818
{57F168E9-6A77-4263-BF61-E1FF7BCE855E} = {7CABA1C5-F531-4DC7-AEFC-A33900D15E9D}

dotnet/src/dotnetcore/GxClasses.Web/Middleware/GXRestServices.cs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net;
66
using System.Net.Http;
77
using System.Text;
8+
using System.Threading.Tasks;
89
using GeneXus.Application;
910
using GeneXus.Configuration;
1011
using GeneXus.Data;
@@ -14,9 +15,8 @@
1415
using Microsoft.AspNetCore.Http;
1516
using Microsoft.AspNetCore.Mvc;
1617
using Microsoft.AspNetCore.Mvc.Filters;
17-
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
1818
using Microsoft.AspNetCore.Mvc.ModelBinding;
19-
using System.Threading.Tasks;
19+
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
2020

2121

2222
namespace GeneXus.Utils
@@ -82,7 +82,7 @@ protected GxRestService()
8282
[NonAction]
8383
internal void Initialize()
8484
{
85-
context.HttpContext = HttpContext;
85+
context.HttpContext = GetHttpContext();
8686
context.HttpContext.NewSessionCheck();
8787
ServiceHeaders();
8888
}
@@ -158,7 +158,7 @@ protected string RestStringParameter(string parameterName, string parameterValue
158158
{
159159
try
160160
{
161-
if (HttpContext.Request.Query.TryGetValue(parameterName, out var value))
161+
if (GetHttpContext().Request.Query.TryGetValue(parameterName, out var value))
162162
return value.FirstOrDefault();
163163
else
164164
return parameterValue;
@@ -172,7 +172,7 @@ protected bool RestParameter(string parameterName, string parameterValue)
172172
{
173173
try
174174
{
175-
if (HttpContext.Request.Query.TryGetValue(parameterName, out var value))
175+
if (GetHttpContext().Request.Query.TryGetValue(parameterName, out var value))
176176
return value.FirstOrDefault().Equals(parameterValue, StringComparison.OrdinalIgnoreCase);
177177
return false;
178178
}
@@ -188,9 +188,9 @@ protected ActionResult UploadImpl()
188188
string fileToken;
189189
using (Stream stream = Request.Body)
190190
{
191-
fileToken = gxobject.ReadFileFromStream(stream, Request.ContentType, Request.ContentLength.GetValueOrDefault(), Request.Headers[HttpHeader.XGXFILENAME], out fileGuid);
191+
fileToken = gxobject.ReadFileFromStream(stream, GetHttpContext().Request.ContentType, GetHttpContext().Request.ContentLength.GetValueOrDefault(), GetHttpContext().Request.Headers[HttpHeader.XGXFILENAME], out fileGuid);
192192
}
193-
Response.Headers.Append(HttpHeader.GX_OBJECT_ID, fileGuid);
193+
GetHttpContext().Response.Headers.Append(HttpHeader.GX_OBJECT_ID, fileGuid);
194194
SetStatusCode(HttpStatusCode.Created);
195195
return GetResponse(new {object_id = fileToken});
196196
}
@@ -282,12 +282,12 @@ protected ObjectResult HandleException(Exception ex)
282282

283283
if (ex is FormatException || ex is NullReferenceException)
284284
{
285-
WrappedJsonError jsonError = HttpHelper.HandleUnexpectedError(HttpContext, HttpStatusCode.BadRequest, ex);
285+
WrappedJsonError jsonError = HttpHelper.HandleUnexpectedError(GetHttpContext(), HttpStatusCode.BadRequest, ex);
286286
return BadRequest(jsonError);
287287
}
288288
else
289289
{
290-
WrappedJsonError jsonError = HttpHelper.HandleUnexpectedError(HttpContext, HttpStatusCode.InternalServerError, ex);
290+
WrappedJsonError jsonError = HttpHelper.HandleUnexpectedError(GetHttpContext(), HttpStatusCode.InternalServerError, ex);
291291
return StatusCode((int)HttpStatusCode.InternalServerError, jsonError);
292292
}
293293
}
@@ -297,11 +297,11 @@ protected void WebException(Exception ex)
297297

298298
if (ex is FormatException)
299299
{
300-
HttpHelper.SetUnexpectedError(HttpContext, HttpStatusCode.BadRequest, ex);
300+
HttpHelper.SetUnexpectedError(GetHttpContext(), HttpStatusCode.BadRequest, ex);
301301
}
302302
else
303303
{
304-
HttpHelper.SetUnexpectedError(HttpContext, HttpStatusCode.InternalServerError, ex);
304+
HttpHelper.SetUnexpectedError(GetHttpContext(), HttpStatusCode.InternalServerError, ex);
305305
}
306306
}
307307

@@ -407,21 +407,21 @@ private bool IsAuthenticated(GAMSecurityLevel objIntegratedSecurityLevel, bool o
407407
internal WrappedJsonError HandleGamError(string code, string message, HttpStatusCode defaultCode = HttpStatusCode.Unauthorized)
408408
{
409409
HttpStatusCode httpStatusCode = HttpHelper.GamCodeToHttpStatus(code, defaultCode);
410-
SetErrorHeaders(HttpContext, httpStatusCode, message);
410+
SetErrorHeaders(GetHttpContext(), httpStatusCode, message);
411411
return HttpHelper.GetJsonError(code, message);
412412
}
413413
internal WrappedJsonError HandleError(string code, string message)
414414
{
415415
HttpStatusCode httpStatusCode = HttpHelper.MapStatusCode(code);
416-
SetErrorHeaders(HttpContext, httpStatusCode, message);
416+
SetErrorHeaders(GetHttpContext(), httpStatusCode, message);
417417
return HttpHelper.GetJsonError(code, message);
418418
}
419419
private void SetErrorHeaders(HttpContext httpContext, HttpStatusCode httpStatusCode, string message)
420420
{
421-
if (httpContext != null)
421+
if (GetHttpContext() != null)
422422
{
423423
SetStatusCode(httpStatusCode);
424-
HttpHelper.HandleUnauthorized(httpStatusCode, httpContext);
424+
HttpHelper.HandleUnauthorized(httpStatusCode, GetHttpContext());
425425
httpContext.SetReasonPhrase(message);
426426
GXLogging.Error(log, String.Format("ErrCode {0}, ErrDsc {1}", httpStatusCode, message));
427427
}
@@ -440,33 +440,33 @@ protected void SetStatusCode(HttpStatusCode code)
440440
}
441441
IHeaderDictionary GetHeaders()
442442
{
443-
if (HttpContext != null)
443+
if (GetHttpContext() != null)
444444
{
445-
return HttpContext.Request.Headers;
445+
return GetHttpContext().Request.Headers;
446446
}
447447
else return null;
448448
}
449449
string GetHeader(string header)
450450
{
451-
if (HttpContext != null)
451+
if (GetHttpContext() != null)
452452
{
453-
return HttpContext.Request.Headers[header];
453+
return GetHttpContext().Request.Headers[header];
454454
}
455455
else return null;
456456
}
457457
bool IsPost()
458458
{
459-
if (HttpContext != null)
459+
if (GetHttpContext() != null)
460460
{
461-
return HttpMethod.Post.Method == HttpContext.Request.GetMethod();
461+
return HttpMethod.Post.Method == GetHttpContext().Request.GetMethod();
462462
}
463463
else return false;
464464
}
465465
void AddHeader(string header, string value)
466466
{
467-
if (HttpContext != null)
467+
if (GetHttpContext() != null)
468468
{
469-
HttpContext.Response.Headers[header]= value;
469+
GetHttpContext().Response.Headers[header]= value;
470470
}
471471
}
472472
protected bool ProcessHeaders(string queryId)
@@ -521,9 +521,9 @@ private void SendCacheHeaders()
521521
private void ServiceHeaders()
522522
{
523523
SendCacheHeaders();
524-
if (HttpContext != null)
524+
if (GetHttpContext() != null)
525525
{
526-
HttpHelper.CorsHeaders(HttpContext);
526+
HttpHelper.CorsHeaders(GetHttpContext());
527527
}
528528

529529
}
@@ -539,6 +539,11 @@ string dateTimeToHTMLDate(DateTime dt)
539539
{
540540
return dt.ToUniversalTime().ToString(DateTimeFormatInfo.InvariantInfo.RFC1123Pattern, DateTimeFormatInfo.InvariantInfo);
541541
}
542+
543+
protected virtual HttpContext GetHttpContext()
544+
{
545+
return base.HttpContext;
546+
}
542547
protected virtual bool IsSynchronizer { get { return false; } }
543548
protected virtual bool IntegratedSecurityEnabled { get { return false; } }
544549
protected virtual GAMSecurityLevel ApiIntegratedSecurityLevel(string gxMethod) { return IntegratedSecurityLevel; }

dotnet/src/dotnetcore/GxClasses.Web/Middleware/GXRouting.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public Task ProcessRestRequest(HttpContext context)
183183
{
184184
try
185185
{
186-
if (context.GetType()==typeof(DefaultHttpContext))
186+
if (!AzureRuntime && context.GetType() == typeof(DefaultHttpContext))
187187
{
188188
IHttpContextAccessor contextAccessor = context.RequestServices.GetService<IHttpContextAccessor>();
189189
context = new GxHttpContextAccesor(contextAccessor);

dotnet/src/dotnetcore/GxClasses.Web/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[assembly: InternalsVisibleTo("DotNetCoreWebUnitTest")]
66
[assembly: InternalsVisibleTo("GxNetCoreStartup")]
77
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctions.Handlers")]
8+
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctionsLibraries")]
89
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
910
[assembly: InternalsVisibleTo("DotNetCoreAttackMitigationTest")]
1011
[assembly: InternalsVisibleTo("DotNetCoreChunkedTest")]

dotnet/src/dotnetcore/GxClasses/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[assembly: InternalsVisibleTo("GxSearch")]
55
[assembly: InternalsVisibleTo("GxNetCoreStartup")]
66
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctions.Handlers")]
7+
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctionsLibraries")]
78
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
89
[assembly: InternalsVisibleTo("GXQueue")]
910
[assembly: InternalsVisibleTo("GXMessageBroker")]
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.IO;
3+
using System.Reflection;
4+
using System.Threading.Tasks;
5+
using GeneXus.Cache;
6+
using GeneXus.Deploy.AzureFunctions.HttpHandler;
7+
using GxClasses.Web;
8+
using GxClasses.Web.Middleware;
9+
using Microsoft.AspNetCore.Http;
10+
using Microsoft.Azure.Functions.Worker;
11+
using Microsoft.Azure.Functions.Worker.Http;
12+
using Microsoft.Extensions.Logging;
13+
using HttpRequestData = Microsoft.Azure.Functions.Worker.Http.HttpRequestData;
14+
15+
namespace GeneXus.Deploy.AzureFunctions.GAM
16+
{
17+
public class GAMAzureFunctions
18+
{
19+
private IGXRouting _gxRouting;
20+
private ICacheService2 _redis;
21+
22+
public GAMAzureFunctions(IGXRouting gxRouting, ICacheService2 redis)
23+
{
24+
_gxRouting = gxRouting;
25+
if (redis != null && redis.GetType() == typeof(Redis))
26+
_redis = redis;
27+
}
28+
29+
[Function("oauth_access_token")]
30+
public async Task<HttpResponseData> Accesstoken([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/access_token")] HttpRequestData req,
31+
FunctionContext executionContext)
32+
{
33+
return await ExecuteHttpFunction(req, executionContext);
34+
}
35+
36+
[Function("oauth_logout")]
37+
public async Task<HttpResponseData> Logout([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/logout")] HttpRequestData req,
38+
FunctionContext executionContext)
39+
{
40+
return await ExecuteHttpFunction(req, executionContext);
41+
}
42+
43+
[Function("oauth_userinfo")]
44+
public async Task<HttpResponseData> UserInfo([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/userinfo")] HttpRequestData req,
45+
FunctionContext executionContext)
46+
{
47+
return await ExecuteHttpFunction(req, executionContext);
48+
}
49+
50+
[Function("oauth_gam_signin")]
51+
public async Task<HttpResponseData> Signin([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/gam/signin")] HttpRequestData req,
52+
FunctionContext executionContext)
53+
{
54+
return await ExecuteHttpFunction(req, executionContext);
55+
}
56+
57+
[Function("oauth_gam_callback")]
58+
public async Task<HttpResponseData> Callback([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/gam/callback")] HttpRequestData req,
59+
FunctionContext executionContext)
60+
{
61+
return await ExecuteHttpFunction(req, executionContext);
62+
}
63+
64+
[Function("oauth_gam_access_token")]
65+
public async Task<HttpResponseData> GAMAccessToken([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/gam/access_token")] HttpRequestData req,
66+
FunctionContext executionContext)
67+
{
68+
return await ExecuteHttpFunction(req, executionContext);
69+
}
70+
71+
[Function("oauth_gam_userinfo")]
72+
public async Task<HttpResponseData> GAMUserInfo([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/gam/userinfo")] HttpRequestData req,
73+
FunctionContext executionContext)
74+
{
75+
return await ExecuteHttpFunction(req, executionContext);
76+
}
77+
78+
[Function("oauth_gam_signout")]
79+
public async Task<HttpResponseData> GAMSignout([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/gam/signout")] HttpRequestData req,
80+
FunctionContext executionContext)
81+
{
82+
return await ExecuteHttpFunction(req, executionContext);
83+
}
84+
85+
[Function("oauth_RequestTokenService")]
86+
public async Task<HttpResponseData> RequestTokenService([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/RequestTokenService")] HttpRequestData req,
87+
FunctionContext executionContext)
88+
{
89+
return await ExecuteHttpFunction(req, executionContext);
90+
}
91+
92+
[Function("oauth_QueryAccessToken")]
93+
public async Task<HttpResponseData> QueryAccessToken([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "oauth/QueryAccessToken")] HttpRequestData req,
94+
FunctionContext executionContext)
95+
{
96+
return await ExecuteHttpFunction(req, executionContext);
97+
}
98+
99+
[Function("saml_gam_signout")]
100+
public async Task<HttpResponseData> SAMLGAMSignout([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "saml/gam/signout")] HttpRequestData req,
101+
FunctionContext executionContext)
102+
{
103+
return await ExecuteHttpFunction(req, executionContext);
104+
}
105+
106+
[Function("saml_gam_signin")]
107+
public async Task<HttpResponseData> SAMLGAMSigin([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "saml/gam/signin")] HttpRequestData req,
108+
FunctionContext executionContext)
109+
{
110+
return await ExecuteHttpFunction(req, executionContext);
111+
}
112+
113+
private async Task<HttpResponseData> ExecuteHttpFunction(HttpRequestData req, FunctionContext executionContext)
114+
{
115+
116+
var logger = executionContext.GetLogger("GAMAzureFunctions");
117+
logger.LogInformation($"GeneXus Http trigger handler. Function processed: {executionContext.FunctionDefinition.Name}.");
118+
119+
HttpResponseData httpResponseData = req.CreateResponse();
120+
HttpContext httpAzureContextAccessor = new GXHttpAzureContextAccessor(req, httpResponseData, _redis);
121+
122+
GXRouting.ContentRootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
123+
GXRouting.AzureFunctionName = executionContext.FunctionDefinition.Name;
124+
125+
await _gxRouting.ProcessRestRequest(httpAzureContextAccessor);
126+
return httpResponseData;
127+
}
128+
}
129+
130+
}

dotnet/src/extensions/Azure/Handlers/GeneXus.Deploy.AzureFunctions.Handlers.csproj

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<HttpSupport>true</HttpSupport>
1919
<BlobSupport>true</BlobSupport>
2020
<EventGridSupport>true</EventGridSupport>
21+
<IntegratedSecuritySupport>true</IntegratedSecuritySupport>
2122
</PropertyGroup>
2223

2324
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
@@ -58,17 +59,23 @@
5859
</ItemGroup>
5960

6061
<ItemGroup Condition="'$(HttpSupport)' == 'true'">
61-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
62-
<Compile Include="HttpHandler\*" />
62+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
6363
</ItemGroup>
6464

6565
<ItemGroup Condition="'$(EventGridSupport)' == 'true'">
6666
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventGrid" Version="3.4.2" />
6767
<Compile Include="EventGridHandler\EventGridTriggerHandlerCloud.cs;EventGridHandler\EventGridTriggerHandlerAzure.cs" />
6868
</ItemGroup>
6969

70+
<ItemGroup>
71+
<Compile Include="GAMAzureFunctions\GAMFunctions.cs" Condition="'$(IntegratedSecuritySupport)' == 'true'" />
72+
<Compile Include="HttpHandler\GXHttpAzureContextAccessor.cs" />
73+
<Compile Include="HttpHandler\RedisHttpSession.cs" />
74+
</ItemGroup>
75+
7076
<ItemGroup>
7177
<PackageReference Include="Azure.Core" Version="1.42.0" />
78+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.3.2" />
7279
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.4" OutputItemType="Analyzer" />
7380
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.23.0" />
7481
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />

0 commit comments

Comments
 (0)