public virtual ApiServiceDesc AddService(Type controller) { if (!IsSupportedController(controller)) { throw new Exception($"Unsupported controller type: {controller.FullName}"); } var service = new ApiServiceDesc(); Api.Services.Add(service); AddAssembly(controller.Assembly); service.Controller = controller; service.Name = GetServiceName(controller); foreach (var method in GetMethods(controller).Where(MethodFilter)) { var desc = GetMethodDesc(service, method); if (desc == null) { continue; } service.Methods.Add(desc); } PostProcessService(service); return(service); }
protected void OutputService(TypeScriptWriter writer, ApiServiceDesc s) { _convertersUsedFrom = new HashSet <TypeConverter>(); _convertersUsedTo = new HashSet <TypeConverter>(); writer.WriteLine($"export class {s.Name}Service extends ApiServiceBase {{"); writer.WriteLine(); using (writer.Indent()) { writer.WriteLine("private _hostname: string;"); writer.WriteLine(); writer.WriteLine("public constructor(hostname: string) {"); using (writer.Indent()) { writer.WriteLine("super();"); writer.WriteLine("this._hostname = (hostname.substr(-1) == '/') ? hostname : hostname + '/';"); } writer.WriteLine("}"); writer.WriteLine(); foreach (var method in s.Methods.OrderBy(m => m.TsName)) { foreach (var httpMethod in method.HttpMethods.Order()) { bool canDirectReturn = getConverter(method.TsReturnType) == null; writer.Write($"public {(canDirectReturn ? "" : "async ")}{getMethodName(method, httpMethod)}("); bool first = true; foreach (var p in method.Parameters) { if (!first) { writer.Write(", "); } writer.Write($"{p.TsName}: {p.TsType.GetTypeScript()}"); first = false; } writer.WriteLine($"): {string.Format(ReturnTypeTemplate, method.TsReturnType.GetTypeScript())} {{"); using (writer.Indent()) { var url = method.UrlPath; bool first2 = true; foreach (var p in method.Parameters.Where(p => p.Location == ParameterLocation.QueryString).OrderBy(p => p.TsName)) { url += (first2 ? '?' : '&') + p.TsName + "=${encodeURIComponent('' + " + p.TsName + ")}"; first2 = false; } writer.WriteLine($"let url = this._hostname + `{url}`;"); // Output parameter type conversions foreach (var p in method.Parameters) { OutputTypeConversion(writer, p.TsName, p.TsType, toTypeScript: false); } // Build request body var bodyParams = method.Parameters.Where(p => p.Location == ParameterLocation.RequestBody).OrderBy(p => p.TsName).ToList(); if (bodyParams.Count > 0 && httpMethod == "GET") { throw new InvalidOperationException($"GET requests must not have any body parameters. Offending parameter: {bodyParams[0].TsName}, method {method.Method.Name}, controller {s.Controller.FullName}"); } if (bodyParams.Count > 1 && (method.BodyEncoding == BodyEncoding.Raw || method.BodyEncoding == BodyEncoding.Json)) { throw new InvalidOperationException($"The body encoding for this method allows for at most one body parameter. Offending parameters: [{bodyParams.Select(p => p.TsName).JoinString(", ")}], method {method.Method.Name}, controller {s.Controller.FullName}"); } var bodyCode = ""; if (bodyParams.Count > 0) { if (method.BodyEncoding == BodyEncoding.Raw) { bodyCode = $", body: {bodyParams[0].TsName}"; } else if (method.BodyEncoding == BodyEncoding.Json) { bodyCode = $", body: JSON.stringify({bodyParams[0].TsName}), headers: {{ 'Content-Type': 'application/json' }}"; } else if (method.BodyEncoding == BodyEncoding.FormUrlEncoded) { writer.WriteLine("let __body = new URLSearchParams();"); foreach (var bp in bodyParams) { writer.WriteLine($"__body.append('{bp.TsName}', '' + {bp.TsName});"); } bodyCode = ", body: __body"; } else if (method.BodyEncoding == BodyEncoding.MultipartFormData) { writer.WriteLine("let __body = new FormData();"); foreach (var bp in bodyParams) { writer.WriteLine($"__body.append('{bp.TsName}', '' + {bp.TsName});"); } bodyCode = ", body: __body"; throw new NotImplementedException("FormData encoding is not fully implemented."); // no support for file name, parameter is always stringified with no support for Blob } else { throw new Exception($"Unexpected {nameof(method.BodyEncoding)}: {method.BodyEncoding}"); } } // Output call var sendCookies = s.SendCookies == SendCookies.Always ? "include" : s.SendCookies == SendCookies.SameOriginOnly ? "same-origin" : s.SendCookies == SendCookies.Never ? "omit" : throw new Exception(); if (canDirectReturn) { writer.Write("return "); } else { writer.Write("let result = await "); } writer.WriteLine($"this.{httpMethod}<{method.TsReturnType.GetTypeScript()}>(url, {{ credentials: '{sendCookies}'{bodyCode} }});"); if (!canDirectReturn) { // Output return type conversion OutputTypeConversion(writer, "result", method.TsReturnType, toTypeScript: true); writer.WriteLine("return result;"); } } writer.WriteLine("}"); writer.WriteLine(); } } // Output type converters foreach (var tc in _typeConverters.Values.Where(c => c != null).OrderBy(c => c.ForType.GetHash())) { if (_convertersUsedFrom.Contains(tc)) { writer.WriteLine($"private {tc.FunctionName(toTypeScript: false)}(val: {tc.ForType.GetTypeScript()}): any {{"); using (writer.Indent()) tc.WriteFunctionBody(writer, false); writer.WriteLine("}"); writer.WriteLine(); } if (_convertersUsedTo.Contains(tc)) { writer.WriteLine($"private {tc.FunctionName(toTypeScript: true)}(val: any): {tc.ForType.GetTypeScript()} {{"); using (writer.Indent()) tc.WriteFunctionBody(writer, true); writer.WriteLine("}"); writer.WriteLine(); } } } writer.WriteLine("}"); }
protected override ApiMethodDesc GetMethodDesc(ApiServiceDesc service, MethodInfo method) { var controllerRoute = service.Controller.GetAttributes("System.Web.Http.RouteAttribute").SingleOrDefault(); var controllerRoutePrefix = service.Controller.GetAttributes("System.Web.Http.RoutePrefixAttribute").SingleOrDefault(); var methodRoute = method.GetAttributes("System.Web.Http.RouteAttribute").SingleOrDefault(); if (controllerRoute == null && methodRoute == null) { return(null); } var iHttpMethod = "System.Web.Http.Controllers.IActionHttpMethodProvider"; var httpMethodsAttr = method.GetAttributesByInterface(iHttpMethod).SingleOrDefault(); if (httpMethodsAttr == null) { return(null); } var m = new ApiMethodDesc(); m.Method = method; m.Service = service; m.HttpMethods = ((IEnumerable <object>)httpMethodsAttr.ReadProperty("HttpMethods", Reflection.KnownTypes[iHttpMethod])).Select(hm => ((string)hm.ReadProperty("Method")).ToUpper()).ToList(); m.BodyEncoding = BodyEncoding.Json; m.TsName = method.Name; m.TsReturnType = MapType(method.ReturnType); if (m.TsReturnType == null) { throw new InvalidOperationException($"Cannot map return type {method.ReturnType.FullName} of method {method.Name}, controller {service.Controller.FullName}"); } foreach (var par in method.GetParameters()) { var p = new ApiMethodParameterDesc(); m.Parameters.Add(p); p.Method = m; p.Parameter = par; p.TsName = par.Name; p.TsType = MapType(par.ParameterType); if (p.TsType == null) { throw new InvalidOperationException($"Cannot map parameter type {par.ParameterType.FullName} of parameter {par.Name}, method {method.Name}, controller {service.Controller.FullName}"); } p.Location = IsUrlParameter(p) ? ParameterLocation.QueryString : ParameterLocation.RequestBody; } m.UrlPath = "/"; if (controllerRoutePrefix != null) { m.UrlPath += (string)controllerRoutePrefix.ReadProperty("Prefix") + "/"; } if (methodRoute != null) { m.UrlPath += (string)methodRoute.ReadProperty("Template"); } else // controllerRoute != null { m.UrlPath += (string)controllerRoute.ReadProperty("Template"); } m.UrlPath = m.UrlPath.Substring(1); var pars = m.Parameters.ToDictionary(p => p.TsName); m.UrlPath = Regex.Replace(m.UrlPath, @"{([a-zA-Z0-9]+)}", match => { var parName = match.Groups[1].Value; if (!pars.ContainsKey(parName)) { throw new InvalidOperationException($"No matching method parameter found for URL segment \"{match.Value}\", method {method.Name}, controller {service.Controller.FullName}"); } pars[parName].Location = ParameterLocation.UrlSegment; return("${encodeURIComponent('' + " + parName + ")}"); }); return(m); }
protected abstract ApiMethodDesc GetMethodDesc(ApiServiceDesc service, MethodInfo method);
protected virtual void PostProcessService(ApiServiceDesc service) { }