DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
ControllerRouteBuilder.h
Go to the documentation of this file.
1
3
4#pragma once
5
6#include "Common.h"
7#include "System/Object.h"
9#include "System/String.h"
10#include "System/Func.h"
11#include "System/Convert.h"
16#include <type_traits>
17
18namespace DotNetDupe {
19 namespace WebAppCore {
20 namespace Controllers {
21
24 public:
26 virtual ~IControllerRouteRegistrar() = default;
27
31 };
32
41 template <typename TController>
43 private:
44 DotNetDupe::System::String m_routePrefix;
45
46 struct RouteInfo {
50 };
52
54 auto controller = app->GetServices()->GetRequiredService<TController>();
55 controller->Initialize(context);
56 return controller;
57 }
58
59 static int HexCharToDecimal(char c) {
60 if (c >= '0' && c <= '9') return c - '0';
61 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
62 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
63 return 0;
64 }
65
66 static char DecodeHexByte(char h1, char h2) {
67 return static_cast<char>((HexCharToDecimal(h1) << 4) | HexCharToDecimal(h2));
68 }
69
70 static DotNetDupe::System::String UrlDecode(const DotNetDupe::System::String& input) {
71 const char* pStr = input.GetRawString();
73 int i = 0;
74 while (pStr && pStr[i] != '\0') {
75 if (pStr[i] == '%' && pStr[i + 1] != '\0' && pStr[i + 2] != '\0') {
76 decoded += DecodeHexByte(pStr[i + 1], pStr[i + 2]);
77 i += 3;
78 } else if (pStr[i] == '+') {
79 decoded += ' ';
80 i++;
81 } else {
82 decoded += pStr[i++];
83 }
84 }
85 return decoded;
86 }
87
88 static DotNetDupe::System::String ExtractFromSubPathPattern(const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx, const DotNetDupe::System::String& subPath) {
89 int openBrace = subPath.IndexOf("{");
90 int closeBrace = subPath.IndexOf("}");
91 if (openBrace >= 0 && closeBrace > openBrace + 1) {
92 DotNetDupe::System::String paramName = subPath.Substring(openBrace + 1, closeBrace - openBrace - 1);
94 if (ctx->GetRequest()->GetRouteValues().TryGetValue(paramName, val) && !val.IsEmpty()) {
95 return UrlDecode(val);
96 }
97 if (ctx->GetRequest()->GetQuery().TryGetValue(paramName, val) && !val.IsEmpty()) {
98 return UrlDecode(val);
99 }
100 }
102 }
103
104 static DotNetDupe::System::String ExtractRouteOrQueryParam(const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx, const DotNetDupe::System::String& subPath) {
106 if (ctx->GetRequest()->GetRouteValues().TryGetValue("id", val) && !val.IsEmpty()) {
107 return UrlDecode(val);
108 }
109 DotNetDupe::System::String pathVal = ExtractFromSubPathPattern(ctx, subPath);
110 if (!pathVal.IsEmpty()) return pathVal;
111 if (ctx->GetRequest()->GetQuery().TryGetValue("id", val) && !val.IsEmpty()) {
112 return UrlDecode(val);
113 }
114 auto queryKeys = ctx->GetRequest()->GetQuery().GetKeys();
115 if (queryKeys.GetLength() > 0 && ctx->GetRequest()->GetQuery().TryGetValue(queryKeys[0], val)) {
116 return UrlDecode(val);
117 }
119 }
120
121 public:
123 : m_routePrefix(prefix) {}
124
125 ~ControllerRouteBuilder() override = default;
126
127 // === GET (No params) ===
128
129 // 1. Returns String
132 auto controller = ResolveController(app, ctx);
133 return (controller.Get()->*action)();
134 }});
135 return *this;
136 }
137
138 // 2. Returns TResult (not String)
139 template <typename TResult, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String>>>
140 ControllerRouteBuilder& MapGet(const DotNetDupe::System::String& subPath, TResult (TController::*action)()) {
142 auto controller = ResolveController(app, ctx);
143 auto result = (controller.Get()->*action)();
144 ctx->GetResponse()->SetContentType("application/json");
146 }});
147 return *this;
148 }
149
150 // === GET (With string ID param) ===
151
152 // 3. Returns String
154 m_routes.Add({"GET", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
155 auto controller = ResolveController(app, ctx);
156 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
157 return (controller.Get()->*action)(id);
158 }});
159 return *this;
160 }
161
162 // 4. Returns TResult (not String)
163 template <typename TResult, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String>>>
164 ControllerRouteBuilder& MapGet(const DotNetDupe::System::String& subPath, TResult (TController::*action)(const DotNetDupe::System::String&)) {
165 m_routes.Add({"GET", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
166 auto controller = ResolveController(app, ctx);
167 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
168 auto result = (controller.Get()->*action)(id);
169 ctx->GetResponse()->SetContentType("application/json");
171 }});
172 return *this;
173 }
174
175 // 4b. Returns TResult (action with page & pageSize parameters)
176 template <typename TResult, typename TString, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String> && std::is_same_v<std::decay_t<TString>, DotNetDupe::System::String>>>
177 ControllerRouteBuilder& MapGet(const DotNetDupe::System::String& subPath, TResult (TController::*action)(const TString&, size_t, size_t)) {
178 m_routes.Add({"GET", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
179 auto controller = ResolveController(app, ctx);
180 DotNetDupe::System::String channelName = ExtractRouteOrQueryParam(ctx, subPath);
181
182 size_t page = 1;
183 size_t pageSize = 20;
184
185 DotNetDupe::System::String sPage, sPageSize;
186 if (ctx->GetRequest()->GetQuery().TryGetValue("page", sPage) || ctx->GetRequest()->GetRouteValues().TryGetValue("page", sPage)) {
187 page = static_cast<size_t>(DotNetDupe::System::Convert::ToInt32(sPage));
188 }
189 if (ctx->GetRequest()->GetQuery().TryGetValue("pageSize", sPageSize) || ctx->GetRequest()->GetRouteValues().TryGetValue("pageSize", sPageSize)) {
190 pageSize = static_cast<size_t>(DotNetDupe::System::Convert::ToInt32(sPageSize));
191 }
192
193 auto result = (controller.Get()->*action)(channelName, page, pageSize);
194 ctx->GetResponse()->SetContentType("application/json");
196 }});
197 return *this;
198 }
199
200 // === POST ===
201
202 // 5. Returns String, parameter const TResource&
203 template <typename TResource>
204 ControllerRouteBuilder& MapPost(const DotNetDupe::System::String& subPath, DotNetDupe::System::String (TController::*action)(const TResource&)) {
205 m_routes.Add({"POST", subPath, [action](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
206 try {
207 auto controller = ResolveController(app, ctx);
208 auto body = ctx->GetRequest()->GetBody();
209 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
210 return (controller.Get()->*action)(payload);
211 } catch (const DotNetDupe::System::Exception& ex) {
212 ctx->GetResponse()->SetStatusCode(400);
213 ctx->GetResponse()->SetContentType("application/json");
214 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
215 }
216 }});
217 return *this;
218 }
219
220 // 6. Returns String, parameter TResource (by value)
221 template <typename TResource, typename = std::enable_if_t<!std::is_reference_v<TResource>>>
222 ControllerRouteBuilder& MapPost(const DotNetDupe::System::String& subPath, DotNetDupe::System::String (TController::*action)(TResource)) {
223 m_routes.Add({"POST", subPath, [action](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
224 try {
225 auto controller = ResolveController(app, ctx);
226 auto body = ctx->GetRequest()->GetBody();
227 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
228 return (controller.Get()->*action)(payload);
229 } catch (const DotNetDupe::System::Exception& ex) {
230 ctx->GetResponse()->SetStatusCode(400);
231 ctx->GetResponse()->SetContentType("application/json");
232 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
233 }
234 }});
235 return *this;
236 }
237
238 // 7. Returns TResult (not String), parameter const TResource&
239 template <typename TResult, typename TResource, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String>>>
240 ControllerRouteBuilder& MapPost(const DotNetDupe::System::String& subPath, TResult (TController::*action)(const TResource&)) {
241 m_routes.Add({"POST", subPath, [action](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
242 try {
243 auto controller = ResolveController(app, ctx);
244 auto body = ctx->GetRequest()->GetBody();
245 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
246 auto result = (controller.Get()->*action)(payload);
247 ctx->GetResponse()->SetContentType("application/json");
249 } catch (const DotNetDupe::System::Exception& ex) {
250 ctx->GetResponse()->SetStatusCode(400);
251 ctx->GetResponse()->SetContentType("application/json");
252 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
253 }
254 }});
255 return *this;
256 }
257
258 // 8. Returns TResult (not String), parameter TResource (by value)
259 template <typename TResult, typename TResource, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String> && !std::is_reference_v<TResource>>>
260 ControllerRouteBuilder& MapPost(const DotNetDupe::System::String& subPath, TResult (TController::*action)(TResource)) {
261 m_routes.Add({"POST", subPath, [action](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
262 try {
263 auto controller = ResolveController(app, ctx);
264 auto body = ctx->GetRequest()->GetBody();
265 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
266 auto result = (controller.Get()->*action)(payload);
267 ctx->GetResponse()->SetContentType("application/json");
269 } catch (const DotNetDupe::System::Exception& ex) {
270 ctx->GetResponse()->SetStatusCode(400);
271 ctx->GetResponse()->SetContentType("application/json");
272 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
273 }
274 }});
275 return *this;
276 }
277
278 // === PUT ===
279
280 // 9. Returns String, parameter const TResource&
281 template <typename TResource>
282 ControllerRouteBuilder& MapPut(const DotNetDupe::System::String& subPath, DotNetDupe::System::String (TController::*action)(const DotNetDupe::System::String&, const TResource&)) {
283 m_routes.Add({"PUT", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
284 try {
285 auto controller = ResolveController(app, ctx);
286 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
287 auto body = ctx->GetRequest()->GetBody();
288 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
289 return (controller.Get()->*action)(id, payload);
290 } catch (const DotNetDupe::System::Exception& ex) {
291 ctx->GetResponse()->SetStatusCode(400);
292 ctx->GetResponse()->SetContentType("application/json");
293 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
294 }
295 }});
296 return *this;
297 }
298
299 // 10. Returns String, parameter TResource (by value)
300 template <typename TResource, typename = std::enable_if_t<!std::is_reference_v<TResource>>>
302 m_routes.Add({"PUT", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
303 try {
304 auto controller = ResolveController(app, ctx);
305 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
306 auto body = ctx->GetRequest()->GetBody();
307 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
308 return (controller.Get()->*action)(id, payload);
309 } catch (const DotNetDupe::System::Exception& ex) {
310 ctx->GetResponse()->SetStatusCode(400);
311 ctx->GetResponse()->SetContentType("application/json");
312 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
313 }
314 }});
315 return *this;
316 }
317
318 // 11. Returns TResult (not String), parameter const TResource&
319 template <typename TResult, typename TResource, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String>>>
320 ControllerRouteBuilder& MapPut(const DotNetDupe::System::String& subPath, TResult (TController::*action)(const DotNetDupe::System::String&, const TResource&)) {
321 m_routes.Add({"PUT", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
322 try {
323 auto controller = ResolveController(app, ctx);
324 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
325 auto body = ctx->GetRequest()->GetBody();
326 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
327 auto result = (controller.Get()->*action)(id, payload);
328 ctx->GetResponse()->SetContentType("application/json");
330 } catch (const DotNetDupe::System::Exception& ex) {
331 ctx->GetResponse()->SetStatusCode(400);
332 ctx->GetResponse()->SetContentType("application/json");
333 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
334 }
335 }});
336 return *this;
337 }
338
339 // 12. Returns TResult (not String), parameter TResource (by value)
340 template <typename TResult, typename TResource, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String> && !std::is_reference_v<TResource>>>
341 ControllerRouteBuilder& MapPut(const DotNetDupe::System::String& subPath, TResult (TController::*action)(const DotNetDupe::System::String&, TResource)) {
342 m_routes.Add({"PUT", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
343 try {
344 auto controller = ResolveController(app, ctx);
345 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
346 auto body = ctx->GetRequest()->GetBody();
347 TResource payload = DotNetDupe::System::Text::Json::JsonSerializer::template Deserialize<TResource>(body);
348 auto result = (controller.Get()->*action)(id, payload);
349 ctx->GetResponse()->SetContentType("application/json");
351 } catch (const DotNetDupe::System::Exception& ex) {
352 ctx->GetResponse()->SetStatusCode(400);
353 ctx->GetResponse()->SetContentType("application/json");
354 return DotNetDupe::System::String("{\"error\":\"Invalid JSON request body: ") + ex.What() + "\"}";
355 }
356 }});
357 return *this;
358 }
359
360 // === DELETE ===
361
362 // 13. Returns String
364 m_routes.Add({"DELETE", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
365 auto controller = ResolveController(app, ctx);
366 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
367 return (controller.Get()->*action)(id);
368 }});
369 return *this;
370 }
371
372 // 14. Returns TResult (not String)
373 template <typename TResult, typename = std::enable_if_t<!std::is_same_v<TResult, DotNetDupe::System::String>>>
374 ControllerRouteBuilder& MapDelete(const DotNetDupe::System::String& subPath, TResult (TController::*action)(const DotNetDupe::System::String&)) {
375 m_routes.Add({"DELETE", subPath, [action, subPath](const DotNetDupe::System::SmartPointer<Builder::WebApplication>& app, const DotNetDupe::System::SmartPointer<Http::HttpContext>& ctx) {
376 auto controller = ResolveController(app, ctx);
377 DotNetDupe::System::String id = ExtractRouteOrQueryParam(ctx, subPath);
378 auto result = (controller.Get()->*action)(id);
379 ctx->GetResponse()->SetContentType("application/json");
381 }});
382 return *this;
383 }
384
385 // Registers recorded routes onto WebApplication
387 for (int i = 0; i < m_routes.GetCount(); ++i) {
388 const auto& r = m_routes[i];
389 if (r.Method == "GET") {
390 app->MapGet(m_routePrefix + r.SubPath, [r, app](DotNetDupe::System::SmartPointer<Http::HttpContext> ctx) {
391 return r.Handler(app, ctx);
392 });
393 } else if (r.Method == "POST") {
394 app->MapPost(m_routePrefix + r.SubPath, [r, app](DotNetDupe::System::SmartPointer<Http::HttpContext> ctx) {
395 return r.Handler(app, ctx);
396 });
397 } else if (r.Method == "PUT") {
398 app->MapPut(m_routePrefix + r.SubPath, [r, app](DotNetDupe::System::SmartPointer<Http::HttpContext> ctx) {
399 return r.Handler(app, ctx);
400 });
401 } else if (r.Method == "DELETE") {
402 app->MapDelete(m_routePrefix + r.SubPath, [r, app](DotNetDupe::System::SmartPointer<Http::HttpContext> ctx) {
403 return r.Handler(app, ctx);
404 });
405 }
406 }
407 }
408 };
409
410 }
411 }
412}
Defines common cross-platform macros, export decorators, and fundamental types.
Converts a base data type to another base data type, and encodes/decodes Base64 data.
Encapsulates a method that has parameters and returns a value of the type specified by the TResult pa...
HTTP request, response, and context abstractions for WebAppCore processing pipelines.
Provides functionality to serialize objects to JSON strings and deserialize JSON strings into objects...
Represents a strongly typed list of objects that can be accessed by index mirroring ....
Base object class for DotNetDupe mirroring .NET System.Object.
Provides reference-counted and weak pointer memory management primitives ensuring zero raw ownership.
High-performance UTF-8 / UTF-16 string manipulation class mirroring .NET System.String.
Represents the web application used to configure the HTTP pipeline and routes mirroring ASP....
Represents a strongly typed list of objects accessible by index.
Definition List.h:29
static int ToInt32(bool value)
Definition Convert.cpp:154
Represents errors that occur during application execution.
Definition Exception.h:19
const char * What() const
Gets a message that describes the current exception.
Definition Exception.h:46
Primary template declaration for the Func delegate family.
Definition Func.h:42
Supports all classes in the DotNetDupe class hierarchy.
Definition Object.h:18
A unified smart pointer that supports both unique and shared ownership semantics.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String Substring(int iStartIndex) const
Definition String.cpp:659
int IndexOf(const String &sSubstring) const
Definition String.cpp:341
const char * GetRawString() const
Definition String.cpp:230
static String Serialize(const T &value)
Converts the value of a type specified by a generic type parameter into a JSON string.
ControllerRouteBuilder(const DotNetDupe::System::String &prefix)
ControllerRouteBuilder & MapPut(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)(const DotNetDupe::System::String &, TResource))
ControllerRouteBuilder & MapPost(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)(TResource))
ControllerRouteBuilder & MapPut(const DotNetDupe::System::String &subPath, TResult(TController::*action)(const DotNetDupe::System::String &, TResource))
ControllerRouteBuilder & MapPost(const DotNetDupe::System::String &subPath, TResult(TController::*action)(const TResource &))
ControllerRouteBuilder & MapPost(const DotNetDupe::System::String &subPath, TResult(TController::*action)(TResource))
ControllerRouteBuilder & MapDelete(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)(const DotNetDupe::System::String &))
ControllerRouteBuilder & MapPut(const DotNetDupe::System::String &subPath, TResult(TController::*action)(const DotNetDupe::System::String &, const TResource &))
ControllerRouteBuilder & MapGet(const DotNetDupe::System::String &subPath, TResult(TController::*action)(const DotNetDupe::System::String &))
ControllerRouteBuilder & MapPut(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)(const DotNetDupe::System::String &, const TResource &))
ControllerRouteBuilder & MapPost(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)(const TResource &))
ControllerRouteBuilder & MapDelete(const DotNetDupe::System::String &subPath, TResult(TController::*action)(const DotNetDupe::System::String &))
void Register(const DotNetDupe::System::SmartPointer< Builder::WebApplication > &app) override
Registers the configured controller routes onto the target WebApplication.
ControllerRouteBuilder & MapGet(const DotNetDupe::System::String &subPath, TResult(TController::*action)(const TString &, size_t, size_t))
ControllerRouteBuilder & MapGet(const DotNetDupe::System::String &subPath, TResult(TController::*action)())
ControllerRouteBuilder & MapGet(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)(const DotNetDupe::System::String &))
ControllerRouteBuilder & MapGet(const DotNetDupe::System::String &subPath, DotNetDupe::System::String(TController::*action)())
Interface for late-binding controller routes to a WebApplication instance.
virtual void Register(const DotNetDupe::System::SmartPointer< Builder::WebApplication > &app)=0
Registers the configured controller routes onto the target WebApplication.
virtual ~IControllerRouteRegistrar()=default
Virtual destructor.