├── ApirLib ├── swaGUI.js ├── NuGet │ ├── lib │ │ └── ApirLib.dll │ ├── ApirIO.nuspec │ └── content │ │ └── App_Start │ │ ├── ApirAssemblyResolver.cs.pp │ │ └── ApirConfig.cs.pp ├── Credentials.cs ├── IProvidePrincipal.cs ├── AppConfig.cs ├── NullFilter.cs ├── SqlProcBuilder.cs ├── NLogExceptionLogger.cs ├── DbExtensions.cs ├── Properties │ └── AssemblyInfo.cs ├── ApirModel.cd ├── ProcWatcher.cs ├── App.config ├── ApirBuilder.cs ├── ApiKeyAuthHandler.cs ├── DataTableExtensions.cs ├── UserValidator.cs ├── BasicAuthMessageHandler.cs ├── Models.cs ├── CorsHandler.cs ├── TypeMapper.cs ├── SwaPrincipalProvider.cs ├── ApirSettings.cs ├── DllBuilder.cs ├── packages.config ├── BasicAuthModule.cs ├── IonicGenerator.cs ├── ApirLib.csproj ├── App_Start │ └── SwaggerConfig.cs ├── ModelBuilder.cs └── CodeBuilder.cs ├── ApirIO.sln ├── .gitignore ├── README.md └── LICENSE /ApirLib/swaGUI.js: -------------------------------------------------------------------------------- 1 | Alert('Hello'); 2 | 3 | -------------------------------------------------------------------------------- /ApirLib/NuGet/lib/ApirLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oeoren/ApirIO/HEAD/ApirLib/NuGet/lib/ApirLib.dll -------------------------------------------------------------------------------- /ApirLib/Credentials.cs: -------------------------------------------------------------------------------- 1 | namespace Apir 2 | { 3 | public class Credentials 4 | { 5 | public string Username { get; set; } 6 | public string Password { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /ApirLib/IProvidePrincipal.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | 3 | namespace Apir 4 | { 5 | public interface IProvidePrincipal 6 | { 7 | IPrincipal CreatePrincipal(string username, string password); 8 | } 9 | } -------------------------------------------------------------------------------- /ApirLib/AppConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ApirLib 8 | { 9 | class AppConfig 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ApirLib/NullFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web.Http; 8 | using System.Web.Http.Filters; 9 | 10 | namespace ApirLib 11 | { 12 | public class NullFilter : ActionFilterAttribute 13 | { 14 | public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 15 | { 16 | var response = actionExecutedContext.Response; 17 | 18 | //object responseValue; 19 | //bool hasContent = response.TryGetContentValue(out responseValue); 20 | 21 | //if (!hasContent) 22 | // throw new HttpResponseException(HttpStatusCode.NotFound); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ApirLib/NuGet/ApirIO.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ApirIO 5 | 2.2.4 6 | Ole Oren 7 | Ole Oren 8 | http://apir.io 9 | http://apir.azurewebsites.net/favicon.ico 10 | false 11 | Create WebApi from SQL Server Stored Procedures 12 | Documentation. 13 | Copyright 2018 14 | SQL REST WebApi AspNet AspNetWebApi SelfHost Apir 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ApirLib/SqlProcBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace ApirLib 9 | { 10 | public class SqlProcBuilder 11 | { 12 | static public void Exec(string conString, string src) 13 | { 14 | var con = new SqlConnection(conString); 15 | SqlCommand cmd = new SqlCommand(src, con); 16 | try 17 | { 18 | con.Open(); 19 | cmd.ExecuteNonQuery(); 20 | } 21 | catch (Exception ex) 22 | { 23 | throw ex; 24 | } 25 | finally 26 | { 27 | cmd.Dispose(); 28 | cmd = null; 29 | con.Close(); 30 | } 31 | 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ApirIO.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApirLib", "ApirLib\ApirLib.csproj", "{0D91302B-D4DD-4708-A426-504AF1820D59}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {0D91302B-D4DD-4708-A426-504AF1820D59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {0D91302B-D4DD-4708-A426-504AF1820D59}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {0D91302B-D4DD-4708-A426-504AF1820D59}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {0D91302B-D4DD-4708-A426-504AF1820D59}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ApirLib/NLogExceptionLogger.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Web.Http.ExceptionHandling; 9 | 10 | namespace ApirLib 11 | { 12 | public class NLogExceptionLogger : ExceptionLogger 13 | { 14 | private static readonly Logger logger = LogManager.GetCurrentClassLogger(); 15 | public override void Log(ExceptionLoggerContext context) 16 | { 17 | logger.Error(RequestToString(context.Request) + " " + context.Exception.Message, context.Exception, new List()); 18 | } 19 | 20 | private static string RequestToString(HttpRequestMessage request) 21 | { 22 | var message = new StringBuilder(); 23 | if (request.Method != null) 24 | message.Append(request.Method); 25 | 26 | if (request.RequestUri != null) 27 | message.Append(" ").Append(request.RequestUri); 28 | 29 | return message.ToString(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ApirLib/DbExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ApirLib 10 | { 11 | public static class DbExtensions 12 | { 13 | public static List ToListCollection(this DataTable dt) 14 | { 15 | List lst = new System.Collections.Generic.List(); 16 | Type tClass = typeof(T); 17 | PropertyInfo[] pClass = tClass.GetProperties(); 18 | List dc = dt.Columns.Cast().ToList(); 19 | T cn; 20 | foreach (DataRow item in dt.Rows) 21 | { 22 | cn = (T)Activator.CreateInstance(tClass); 23 | foreach (PropertyInfo pc in pClass) 24 | { 25 | // Can comment try catch block. 26 | try 27 | { 28 | DataColumn d = dc.Find(c => c.ColumnName == pc.Name); 29 | if (d != null) 30 | pc.SetValue(cn, item[pc.Name], null); 31 | } 32 | catch (Exception ex) 33 | { 34 | throw ex; 35 | } 36 | } 37 | lst.Add(cn); 38 | } 39 | return lst; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ApirLib/NuGet/content/App_Start/ApirAssemblyResolver.cs.pp: -------------------------------------------------------------------------------- 1 | using ApirLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Web.Http.Dispatcher; 9 | 10 | namespace $rootnamespace$.App_Start 11 | { 12 | public class ApirAssemblyResolver : DefaultAssembliesResolver 13 | { 14 | private string _connectionString; 15 | private string _sourcePath; 16 | private string _userValidator; 17 | 18 | 19 | public ApirAssemblyResolver(string connectionString, string sourcePath, string userValidator) 20 | { 21 | _connectionString = connectionString; 22 | _sourcePath = sourcePath; 23 | _userValidator = userValidator; 24 | } 25 | 26 | public override ICollection GetAssemblies() 27 | { 28 | ICollection baseAssemblies = base.GetAssemblies(); 29 | List assemblies = new List(baseAssemblies); 30 | try 31 | { 32 | Assembly onTheFly = ApirBuilder.Build(_connectionString, _sourcePath, _userValidator); 33 | if (onTheFly != null) 34 | { 35 | assemblies.Add(onTheFly); 36 | } 37 | } 38 | catch (Exception ex) 39 | { 40 | throw (ex); 41 | } 42 | return assemblies; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ApirLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ApirLib")] 9 | [assembly: AssemblyDescription("Rest API from SQL")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Apir")] 12 | [assembly: AssemblyProduct("ApirLib")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("0ad298f6-42f7-4724-85f0-c7f5e5d1123e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.2.3.0")] 36 | [assembly: AssemblyFileVersion("2.2.3.0")] 37 | -------------------------------------------------------------------------------- /ApirLib/ApirModel.cd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAA= 7 | Models.cs 8 | 9 | 10 | 11 | 12 | 13 | AAAAAAAAAAAAAAAAAACAAAAAAAAEAAAgAAAAAQAAAAA= 14 | Models.cs 15 | 16 | 17 | 18 | 19 | 20 | AAAAAAAAAAAAAAAAAAAAAAAAQAAEAAAACAAAAAAAAAA= 21 | Models.cs 22 | 23 | 24 | 25 | 26 | 27 | AAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAACAAAA= 28 | Models.cs 29 | 30 | 31 | 32 | 33 | 34 | AAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAQAAAAA= 35 | Models.cs 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ApirLib/ProcWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Timers; 6 | using System.Threading.Tasks; 7 | using System.Data.SqlClient; 8 | using System.Data; 9 | 10 | namespace ApirLib 11 | { 12 | public class ProcWatcher 13 | { 14 | Timer _timer; 15 | string _connectionString; 16 | Func _whenProcChanged; 17 | DateTime? _lastDate; 18 | 19 | public void DoWatch(string connectionString, Func WhenProcChanged) 20 | { 21 | _whenProcChanged = WhenProcChanged; 22 | _lastDate = null; 23 | _connectionString = connectionString; 24 | _timer = new Timer(10000) { AutoReset = true }; 25 | _timer.Elapsed += (sender, eventArgs) => CheckProcs(); 26 | _timer.Enabled = true; 27 | } 28 | 29 | 30 | void CheckProcs() 31 | { 32 | 33 | SqlConnection con = null; 34 | SqlCommand cmd; 35 | try 36 | { 37 | con = new SqlConnection(_connectionString); 38 | con.Open(); 39 | } 40 | catch (Exception ex) 41 | { 42 | return; 43 | } 44 | cmd = con.CreateCommand(); 45 | cmd.CommandType = CommandType.Text; 46 | cmd.CommandText = "select MAX(modify_date) from sys.procedures where name like 'API%'"; 47 | var dt = (DateTime?) cmd.ExecuteScalar(); 48 | if (_lastDate == null) 49 | _lastDate = dt; 50 | else if (dt > _lastDate) 51 | _whenProcChanged(); 52 | } 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /ApirLib/NuGet/content/App_Start/ApirConfig.cs.pp: -------------------------------------------------------------------------------- 1 | using Apir; 2 | using ApirLib; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Configuration; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Web; 10 | using System.Web.Http; 11 | using System.Web.Http.Dispatcher; 12 | 13 | [assembly: WebActivatorEx.PreApplicationStartMethod( 14 | typeof($rootnamespace$.App_Start.ApirPackage), "PreStart")] 15 | 16 | namespace $rootnamespace$.App_Start { 17 | 18 | public static class ApirPackage { 19 | public static void PreStart() { 20 | ApirConfig.RegisterAssemblyResolver(GlobalConfiguration.Configuration); 21 | } 22 | } 23 | 24 | public static class ApirConfig 25 | { 26 | public static void RegisterAssemblyResolver(HttpConfiguration config) 27 | { 28 | string connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString; 29 | string sourcePath = ConfigurationManager.AppSettings["OutPath"]; 30 | if (sourcePath == null) 31 | sourcePath = AppDomain.CurrentDomain.GetData("DataDirectory").ToString() + "\\"; 32 | string userValidator = ConfigurationManager.AppSettings["UserValidator"]; 33 | ApirAssemblyResolver assemblyResolver = new ApirAssemblyResolver(connectionString, sourcePath, userValidator); 34 | config.Services.Replace(typeof(IAssembliesResolver), assemblyResolver); 35 | /* 36 | if (userValidator != null && userValidator.Length > 0) 37 | config 38 | .MessageHandlers.Add(new BasicAuthMessageHandler() 39 | { 40 | PrincipalProvider = new SwaPrincipalProvider(connectionString, userValidator) 41 | }); 42 | */ 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /ApirLib/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ApirLib/ApirBuilder.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace ApirLib 11 | { 12 | public static class ApirBuilder 13 | { 14 | private static Logger logger = LogManager.GetCurrentClassLogger(); 15 | public static Assembly Build(string connectionString, string sourcePath,string userValidator) 16 | { 17 | 18 | Model model = ModelBuilder.ConstructModel(connectionString); 19 | LogModel(model); 20 | bool fAuthorize = (userValidator != null && userValidator.Length > 1); 21 | String code = CodeBuilder.BuildCode(model, fAuthorize ); 22 | 23 | if (sourcePath != null && sourcePath.Length > 0) 24 | { 25 | //Console.WriteLine(code); 26 | 27 | using (StreamWriter outfile = new StreamWriter(sourcePath + "swaSource.cs")) 28 | { 29 | outfile.Write(code); 30 | } 31 | logger.Debug("Code built and saved at {0}", sourcePath + "swaSource.cs"); 32 | } 33 | 34 | Assembly assembly = DllBuilder.BuildDll(code, sourcePath); 35 | if (assembly == null) 36 | logger.Fatal("Compile Error. See errorfile at:{0} and source at {1} ", sourcePath + "swaError.txt", sourcePath + "swaFile.cs"); 37 | else 38 | logger.Debug("Compiled OK"); 39 | return assembly; 40 | } 41 | 42 | private static void LogModel(Model model) 43 | { 44 | int procs = 0; 45 | foreach (var c in model.controllers) 46 | foreach (var p in c.procs) 47 | procs++; 48 | logger.Debug("API Info read from database. Resources={0}, Procedures={1}", model.controllers.Count, procs); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ApirLib/ApiKeyAuthHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Security.Principal; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace ApirLib 12 | { 13 | 14 | public class ApiKeyAuthHandler : DelegatingHandler 15 | { 16 | private const string ApiKeySchemeName = "ApiKey"; 17 | private const string AuthResponseHeader = "WWW-Authenticate"; 18 | 19 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 20 | { 21 | var authHeader = request.Headers.Authorization; 22 | 23 | if (authHeader != null && authHeader.Scheme == ApiKeySchemeName) 24 | { 25 | var principal = ValidateApiKey(authHeader.Parameter); 26 | 27 | if (principal != null) 28 | { 29 | Thread.CurrentPrincipal = principal; 30 | } 31 | } 32 | 33 | return base.SendAsync(request, cancellationToken) 34 | .ContinueWith(task => 35 | { 36 | var response = task.Result; 37 | 38 | if (response.StatusCode == HttpStatusCode.Unauthorized && !response.Headers.Contains(AuthResponseHeader)) 39 | { 40 | response.Headers.Add(AuthResponseHeader, ApiKeySchemeName); 41 | } 42 | 43 | return response; 44 | }); 45 | } 46 | 47 | 48 | IPrincipal ValidateApiKey(string authParameter) 49 | { 50 | if (String.IsNullOrEmpty(authParameter) || authParameter != "1234-5678") 51 | { 52 | return null; 53 | } 54 | 55 | return new GenericPrincipal(new GenericIdentity("Test User", ApiKeySchemeName), null); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /ApirLib/DataTableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace CustomExtensions 10 | { 11 | 12 | public static class MyExtensionClass 13 | { 14 | public static List ToCollection(this DataTable dt) 15 | { 16 | List lst = new System.Collections.Generic.List(); 17 | Type tClass = typeof(T); 18 | PropertyInfo[] pClass = tClass.GetProperties(); 19 | List dc = dt.Columns.Cast().ToList(); 20 | T cn; 21 | foreach (DataRow item in dt.Rows) 22 | { 23 | cn = (T)Activator.CreateInstance(tClass); 24 | foreach (PropertyInfo pc in pClass) 25 | { 26 | // Can comment try catch block. 27 | try 28 | { 29 | DataColumn d = dc.Find(c => c.ColumnName == pc.Name); 30 | if (d != null) 31 | pc.SetValue(cn, item[pc.Name], null); 32 | } 33 | catch 34 | { 35 | } 36 | } 37 | lst.Add(cn); 38 | } 39 | return lst; 40 | } 41 | } 42 | 43 | static class DataTableExtensions 44 | { 45 | // Get a list of custom class from datatable 46 | // Usage: List customers = dsCustomers.Tables[0].ToGenericList(); 47 | public static List ToGenericList(this DataTable datatable) where T : new() 48 | { 49 | return (from row in datatable.AsEnumerable() 50 | select Convert(row)).ToList(); 51 | } 52 | 53 | private static T Convert(DataRow row) where T : new() 54 | { 55 | var result = new T(); 56 | var type = result.GetType(); 57 | 58 | foreach (var fieldInfo in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) 59 | { 60 | var rowValue = row[fieldInfo.Name]; 61 | if (rowValue != null) 62 | { 63 | fieldInfo.SetValue(result, rowValue); 64 | } 65 | } 66 | 67 | return result; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ApirLib/UserValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Data.SqlClient; 6 | using System.DirectoryServices.AccountManagement; 7 | using System.IdentityModel.Selectors; 8 | using System.IdentityModel.Tokens; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace ApirLib 14 | { 15 | public class UserValidator : UserNamePasswordValidator 16 | { 17 | private string _procName; 18 | private string _connectionString; 19 | public UserValidator(string connectionString, string procName) 20 | { 21 | _procName = procName; 22 | _connectionString = connectionString; 23 | } 24 | 25 | public override void Validate(string userName, string password) 26 | { 27 | string userValidator = ConfigurationManager.AppSettings["UserValidator"]; 28 | string domainValidate = ConfigurationManager.AppSettings["DomainValidate"]; 29 | if (domainValidate != null && domainValidate.Length > 0) 30 | DomainValidate(userName, password, domainValidate); 31 | else 32 | SqlValidata(userName, password); 33 | } 34 | 35 | private void DomainValidate(string userName, string password, string domainName) 36 | { 37 | using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domainName)) 38 | { 39 | bool isValid = pc.ValidateCredentials(userName, password); 40 | if (!isValid) 41 | throw new SecurityTokenException("Unknown Username or Incorrect Password in domain"); 42 | } 43 | } 44 | 45 | private void SqlValidata(string userName, string password) 46 | { 47 | SqlConnection con = new SqlConnection(_connectionString); 48 | SqlCommand com = new SqlCommand(_procName, con); 49 | com.CommandType = CommandType.StoredProcedure; 50 | SqlParameter RetVal = com.Parameters.Add 51 | ("RetVal", SqlDbType.Int); 52 | RetVal.Direction = ParameterDirection.ReturnValue; 53 | com.Parameters.Add("UserName", SqlDbType.VarChar, 60).Value = userName; 54 | com.Parameters.Add("Password", SqlDbType.VarChar, 60).Value = password; 55 | con.Open(); 56 | try 57 | { 58 | com.ExecuteNonQuery(); 59 | } 60 | catch (SqlException) 61 | { 62 | throw new SecurityTokenException("Unknown Username or Incorrect Password"); 63 | } 64 | if ((int)RetVal.Value != 1) 65 | throw new SecurityTokenException("Unknown Username or Incorrect Password"); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ApirLib/BasicAuthMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Security.Principal; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Web; 9 | using System.Web.Http.Controllers; 10 | 11 | namespace Apir 12 | { 13 | public class BasicAuthMessageHandler : DelegatingHandler 14 | { 15 | private const string BasicAuthResponseHeader = "WWW-Authenticate"; 16 | private const string BasicAuthResponseHeaderValue = "Basic"; 17 | 18 | public IProvidePrincipal PrincipalProvider { get; set; } 19 | 20 | protected override System.Threading.Tasks.Task SendAsync( 21 | HttpRequestMessage request, 22 | CancellationToken cancellationToken) 23 | { 24 | AuthenticationHeaderValue authValue = request.Headers.Authorization; 25 | if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter)) 26 | { 27 | Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter); 28 | if (parsedCredentials != null) 29 | { 30 | request.GetRequestContext().Principal = PrincipalProvider.CreatePrincipal(parsedCredentials.Username, parsedCredentials.Password); 31 | } 32 | } 33 | return base.SendAsync(request, cancellationToken) 34 | .ContinueWith(task => 35 | { 36 | var response = task.Result; 37 | if (response.StatusCode == HttpStatusCode.Unauthorized 38 | && !response.Headers.Contains(BasicAuthResponseHeader)) 39 | { 40 | response.Headers.Add(BasicAuthResponseHeader 41 | , BasicAuthResponseHeaderValue); 42 | } 43 | return response; 44 | }); 45 | } 46 | 47 | private Credentials ParseAuthorizationHeader(string authHeader) 48 | { 49 | string[] credentials = Encoding.ASCII.GetString(Convert 50 | .FromBase64String(authHeader)) 51 | .Split( 52 | new[] { ':' }); 53 | if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) 54 | || string.IsNullOrEmpty(credentials[1])) return null; 55 | return new Credentials() 56 | { 57 | Username = credentials[0], 58 | Password = credentials[1], 59 | }; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /ApirLib/Models.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Data; 7 | 8 | namespace ApirLib 9 | { 10 | public enum HttpVerb 11 | { 12 | httpUnknown = -1, 13 | httpGet = 0, 14 | httpPut = 1, 15 | httpPost = 2, 16 | httpDelete = 3 17 | } 18 | 19 | public class Model 20 | { 21 | public Model() 22 | { 23 | this.controllers = new List(); 24 | } 25 | public List controllers { get; set; } 26 | public string dbConnection { get; set; } 27 | 28 | } 29 | 30 | 31 | public class TableModel 32 | { 33 | public TableModel() 34 | { 35 | this.columns = new List(); 36 | } 37 | public string tableName; 38 | internal string resource; 39 | public string KeyColum { get; set; } 40 | public bool isIdentity; 41 | public List columns { get; set; } 42 | public List nonKeyColumns { get; set; } 43 | 44 | } 45 | 46 | public class ControllerInfo 47 | { 48 | public ControllerInfo() 49 | { 50 | this.columns = new List(); 51 | this.procs = new List(); 52 | } 53 | public string name { get; set; } 54 | public List columns { get; set; } 55 | public List procs { get; set; } 56 | } 57 | 58 | public class ProcInfo 59 | { 60 | public ProcInfo() 61 | { 62 | parameters = new List(); 63 | } 64 | 65 | public string name { get; set; } 66 | public List parameters { get; set; } 67 | public string xmlComments; 68 | public HttpVerb GetVerb() 69 | { 70 | if (name.ToLower().EndsWith("get")) 71 | return HttpVerb.httpGet; 72 | if (name.ToLower().EndsWith("put")) 73 | return HttpVerb.httpPut; 74 | if (name.ToLower().EndsWith("post")) 75 | return HttpVerb.httpPost; 76 | if (name.ToLower().EndsWith("delete")) 77 | return HttpVerb.httpDelete; 78 | return HttpVerb.httpUnknown; 79 | } 80 | } 81 | 82 | public class ColumnInfo 83 | { 84 | public string name { get; set; } 85 | public string sqlType { get; set; } 86 | public int? maxLen { get; set; } 87 | public string csType { get; set; } 88 | } 89 | 90 | public class ParameterInfo 91 | { 92 | public string name { get; set; } 93 | public string sqlType { get; set; } 94 | public string csType { get; set; } 95 | public int precision { get; set; } 96 | public int scale { get; set; } 97 | public int maxLen { get; set; } 98 | public bool isOutput { get; set; } 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /ApirLib/CorsHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ApirLib 10 | { 11 | public class CorsDelegatingHandler : DelegatingHandler 12 | { 13 | protected override Task SendAsync( 14 | HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) 15 | { 16 | const string origin = "Origin"; 17 | const string accessControlRequestMethod = "Access-Control-Request-Method"; 18 | const string accessControlRequestHeaders = "Access-Control-Request-Headers"; 19 | const string accessControlAllowOrigin = "Access-Control-Allow-Origin"; 20 | const string accessControlAllowMethods = "Access-Control-Allow-Methods"; 21 | const string accessControlAllowHeaders = "Access-Control-Allow-Headers"; 22 | const string accessControlExposeHeaders = "Access-Control-Expose-Headers"; 23 | 24 | bool isCorsRequest = request.Headers.Contains(origin); 25 | bool isPreflightRequest = request.Method == HttpMethod.Options; 26 | 27 | if (isCorsRequest) 28 | { 29 | if (isPreflightRequest) 30 | { 31 | return Task.Factory.StartNew(() => 32 | { 33 | var response = new HttpResponseMessage(HttpStatusCode.OK); 34 | response.Headers.Add(accessControlAllowOrigin, request.Headers.GetValues(origin).First()); 35 | 36 | string controlRequestMethod = 37 | request.Headers.GetValues(accessControlRequestMethod).FirstOrDefault(); 38 | 39 | if (controlRequestMethod != null) 40 | { 41 | response.Headers.Add(accessControlAllowMethods, controlRequestMethod); 42 | } 43 | 44 | 45 | IEnumerable requestedHeaders; 46 | request.Headers.TryGetValues(accessControlRequestHeaders, out requestedHeaders); 47 | 48 | if (requestedHeaders == null) 49 | return response; 50 | 51 | string requestedHeader = string.Join(", ", requestedHeaders); 52 | 53 | if (!string.IsNullOrEmpty(requestedHeader)) 54 | { 55 | response.Headers.Add(accessControlAllowHeaders, requestedHeaders); 56 | } 57 | return response; 58 | 59 | }, cancellationToken); 60 | } 61 | return base.SendAsync(request, cancellationToken).ContinueWith(t => 62 | { 63 | HttpResponseMessage response = t.Result; 64 | response.Headers.Add(accessControlAllowOrigin, 65 | request.Headers.GetValues(origin).First()); 66 | // request.Headers.GetValues("Host").First()); 67 | response.Headers.Add(accessControlExposeHeaders, "Location"); 68 | return response; 69 | }); 70 | } 71 | return base.SendAsync(request, cancellationToken); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ApirLib/TypeMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ApirLib 8 | { 9 | public static class TypeMapper 10 | { 11 | public static string GetCSTypeName(string sqlName) 12 | { 13 | switch (sqlName.ToLower()) 14 | { 15 | case "varchar": return "string"; 16 | case "string": return "string"; 17 | case "bigint": return "int?"; 18 | case "smallint": return "short?"; 19 | case "int": return "int?"; 20 | case "int32": return "int?"; 21 | case "int16": return "int?"; 22 | case "int64": return "long?"; 23 | case "binary": return "binary"; 24 | case "bit": return "bool?"; 25 | case "date": return "DateTime?"; 26 | case "datetime": return "DateTime?"; 27 | case "decimal": return "decimal?"; 28 | case "single": return "float?"; 29 | case "real": return "float?"; 30 | case "float": return "float?"; 31 | case "double": return "float?"; 32 | case "money": return "decimal?"; 33 | case "nchar": return "string"; 34 | case "char": return "string"; 35 | case "ntext": return "string"; 36 | case "boolean": return "bool?"; 37 | case "uniqueidentifier": return "Guid?"; 38 | case "image": return "binary"; 39 | default: 40 | if (sqlName.ToLower().EndsWith("char")) 41 | return "string"; 42 | else throw(new Exception("Unknown SQL Datatype: " + sqlName)); 43 | 44 | } 45 | } 46 | 47 | public static string GetTSTypeName(string sqlName) 48 | { 49 | switch (sqlName.ToLower()) 50 | { 51 | case "varchar": return "String"; 52 | case "string": return "String"; 53 | case "bigint": return "Number"; 54 | case "smallint": return "Number"; 55 | case "int": return "Number"; 56 | case "int32": return "Number"; 57 | case "int16": return "Number"; 58 | case "int64": return "Number"; 59 | case "binary": return "binary"; 60 | case "bit": return "bool?"; 61 | case "date": return "Date"; 62 | case "datetime": return "Date"; 63 | case "decimal": return "Number"; 64 | case "single": return "Number"; 65 | case "real": return "Number"; 66 | case "float": return "Number"; 67 | case "double": return "Number"; 68 | case "money": return "Number"; 69 | case "nchar": return "String"; 70 | case "char": return "String"; 71 | case "ntext": return "String"; 72 | case "boolean": return "boolean"; 73 | case "uniqueidentifier": return "String"; 74 | case "image": return "binary"; 75 | default: 76 | if (sqlName.ToLower().EndsWith("char")) 77 | return "string"; 78 | else throw (new Exception("Unknown SQL Datatype: " + sqlName)); 79 | 80 | } 81 | } 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ApirLib/SwaPrincipalProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Data.SqlClient; 6 | using System.DirectoryServices.AccountManagement; 7 | using System.Security.Principal; 8 | 9 | 10 | namespace Apir 11 | { 12 | public class SwaPrincipalProvider : IProvidePrincipal 13 | { 14 | 15 | 16 | private string _procName; 17 | private string _connectionString; 18 | private string _domainName; 19 | private string _machineName; 20 | 21 | public SwaPrincipalProvider(string connectionString, string procName, string domainName=null, string machineName =null) 22 | { 23 | _procName = procName; 24 | _domainName = domainName; 25 | _machineName = machineName; 26 | _connectionString = connectionString; 27 | } 28 | 29 | private bool SqlValidate(string userName, string password) 30 | { 31 | if (_procName == null || _procName.Length == 0) 32 | return true; 33 | SqlConnection con = new SqlConnection(_connectionString); 34 | SqlCommand com = new SqlCommand(_procName, con); 35 | com.CommandType = CommandType.StoredProcedure; 36 | SqlParameter RetVal = com.Parameters.Add 37 | ("RetVal", SqlDbType.Int); 38 | RetVal.Direction = ParameterDirection.ReturnValue; 39 | com.Parameters.Add("UserName", SqlDbType.VarChar,60).Value = userName; 40 | com.Parameters.Add("Password", SqlDbType.VarChar, 60).Value = password; 41 | con.Open(); 42 | try 43 | { 44 | com.ExecuteNonQuery(); 45 | int r = (int)RetVal.Value; 46 | bool ret = (r == 1); 47 | return (ret) ; 48 | } 49 | catch (SqlException ex) 50 | { 51 | throw(ex); 52 | } 53 | } 54 | public bool ValidateCredentials(string userName, string password) 55 | { 56 | 57 | if (_machineName != null && _machineName.Length > 0) 58 | return DomainValidate(userName, password, null, _machineName); 59 | else if (_domainName != null && _domainName.Length > 0) 60 | return DomainValidate(userName, password, _domainName, null); 61 | else 62 | return SqlValidate(userName, password); 63 | } 64 | 65 | private bool DomainValidate(string userName, string password, string domainName, string machineName) 66 | { 67 | if (machineName != null && machineName.Length > 0) 68 | using (PrincipalContext pc = new PrincipalContext(ContextType.Machine, machineName)) 69 | { 70 | return pc.ValidateCredentials(userName, password); 71 | } 72 | else 73 | using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domainName)) 74 | { 75 | return pc.ValidateCredentials(userName, password); 76 | } 77 | } 78 | 79 | 80 | 81 | public IPrincipal CreatePrincipal(string username, string password) 82 | { 83 | if (!ValidateCredentials(username, password)) 84 | { 85 | return null; 86 | } 87 | 88 | var identity = new GenericIdentity(username); 89 | IPrincipal principal = new GenericPrincipal(identity, new[] { "User" }); 90 | return principal; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /ApirLib/ApirSettings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace ApirLib 12 | { 13 | public class ApirSettings 14 | { 15 | public string BaseAddress; 16 | public string OutPath; 17 | public bool Swagger; 18 | public bool SwaggerUI; 19 | public string UserValidator; 20 | public string MachineValidate; 21 | public string DomainValidate; 22 | public bool RelayHost; 23 | public string ApirKey; 24 | public string ServiceKey; 25 | public string FreeResources; 26 | public string apirApi; 27 | public string apirTenant; 28 | public string apirUser; 29 | public string apirPassword; 30 | public string ClientSettingsProviderServiceUri; 31 | public string ConnectionString; 32 | public string SettingsReadFrom { get; set; } = null; 33 | public bool? AutoRestart; 34 | 35 | private static bool hasLocalSettings() 36 | { 37 | string path = Directory.GetCurrentDirectory(); 38 | bool hasLocalFile = File.Exists(path + "\\apir.json"); 39 | return hasLocalFile; 40 | 41 | } 42 | 43 | public static string AssemblyDirectory 44 | { 45 | get 46 | { 47 | string codeBase = Assembly.GetExecutingAssembly().CodeBase; 48 | UriBuilder uri = new UriBuilder(codeBase); 49 | string path = Uri.UnescapeDataString(uri.Path); 50 | return Path.GetDirectoryName(path); 51 | } 52 | } 53 | public static ApirSettings Read() 54 | { 55 | ApirSettings apirSettings; 56 | var settingsFileName = "apir.json"; 57 | if (!File.Exists(@".\" + settingsFileName)) 58 | { 59 | settingsFileName = AssemblyDirectory + "\\" + settingsFileName; 60 | } 61 | using (StreamReader r = new StreamReader(settingsFileName)) 62 | { 63 | string json = r.ReadToEnd(); 64 | apirSettings = JsonConvert.DeserializeObject(json); 65 | } 66 | apirSettings.SettingsReadFrom = settingsFileName; 67 | return apirSettings; 68 | } 69 | 70 | public static bool Write(ApirSettings apirSettings) 71 | { 72 | using (StreamWriter file = File.CreateText("apir.json")) 73 | { 74 | JsonSerializer serializer = new JsonSerializer(); 75 | serializer.Formatting = Formatting.Indented; 76 | serializer.Serialize(file, apirSettings); 77 | } 78 | return true; 79 | } 80 | 81 | static ApirSettings settings = null; 82 | public static ApirSettings Get() 83 | { 84 | if (settings == null) 85 | settings = Read(); 86 | return settings; 87 | } 88 | 89 | public static string xGet(string key) 90 | { 91 | Console.WriteLine("local settings = {0}", hasLocalSettings() ? "true" : "false"); 92 | var val = ConfigurationManager.AppSettings[key]; 93 | return val; 94 | } 95 | 96 | public static void Put(string key, string val) 97 | { 98 | 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ApirLib/DllBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CSharp; 2 | using System; 3 | using System.CodeDom.Compiler; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace ApirLib 12 | { 13 | public class DllBuilder 14 | { 15 | static public string AssemblyDirectory 16 | { 17 | get 18 | { 19 | string codeBase = Assembly.GetExecutingAssembly().CodeBase; 20 | UriBuilder uri = new UriBuilder(codeBase); 21 | string path = Uri.UnescapeDataString(uri.Path); 22 | return Path.GetDirectoryName(path); 23 | } 24 | } 25 | 26 | static public Assembly BuildDll(string code, string sourcePath) 27 | { 28 | //IDictionary compParams = 29 | // new Dictionary() { { "CompilerVersion", "v4.0" } }; 30 | //CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp", compParams); 31 | CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp"); 32 | string outputDll = sourcePath + "ApirController.dll"; 33 | 34 | string path = DllBuilder.AssemblyDirectory; 35 | 36 | 37 | System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters(); 38 | parameters.GenerateExecutable = false; 39 | parameters.OutputAssembly = outputDll; 40 | parameters.ReferencedAssemblies.Add(@"System.dll"); 41 | parameters.ReferencedAssemblies.Add(@"System.Core.dll"); 42 | parameters.ReferencedAssemblies.Add(@"System.Net.Http.dll"); 43 | parameters.ReferencedAssemblies.Add(@"System.Net.Http.WebRequest.dll"); 44 | parameters.ReferencedAssemblies.Add(path + @"\System.Net.Http.Formatting.dll"); 45 | parameters.ReferencedAssemblies.Add(path + @"\System.Web.Http.dll"); 46 | //parameters.ReferencedAssemblies.Add(@"System.Net.Http.Formatting.dll"); 47 | //parameters.ReferencedAssemblies.Add(@"System.Web.Http.dll"); 48 | parameters.ReferencedAssemblies.Add(@"System.Data.dll"); 49 | parameters.ReferencedAssemblies.Add(@"System.Data.DataSetExtensions.dll"); 50 | parameters.ReferencedAssemblies.Add(@"System.xml.dll"); 51 | parameters.ReferencedAssemblies.Add(@"System.xml.Linq.dll"); 52 | parameters.ReferencedAssemblies.Add(@"System.Configuration.dll"); 53 | 54 | parameters.ReferencedAssemblies.Add(path + @"\NLog.dll"); 55 | 56 | parameters.CompilerOptions = "/doc:\"" + sourcePath + "xmlComments.xml\""; 57 | #if DEBUG 58 | parameters.IncludeDebugInformation = true; 59 | parameters.GenerateInMemory = false; 60 | var fileName = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\swaDebug.cs")); 61 | File.WriteAllText(fileName, code); 62 | CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, fileName); 63 | #else 64 | CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code); 65 | #endif 66 | 67 | 68 | using (StreamWriter outfile = new StreamWriter(sourcePath + "swaError.txt")) 69 | { 70 | if (results.Errors.Count > 0) 71 | { 72 | if (sourcePath != null) 73 | foreach (CompilerError CompErr in results.Errors) 74 | { 75 | outfile.WriteLine( 76 | "Line number " + CompErr.Line + 77 | ", Error Number: " + CompErr.ErrorNumber + 78 | ", '" + CompErr.ErrorText + ";" + 79 | Environment.NewLine + Environment.NewLine); 80 | } 81 | throw new Exception("Compile Error"); 82 | } 83 | else 84 | { 85 | outfile.WriteLine("Build Succeeded"); 86 | return Assembly.LoadFrom(outputDll); 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ApirLib/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ApirLib/BasicAuthModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.IdentityModel.Tokens; 5 | using System.Linq; 6 | using System.Net.Http.Headers; 7 | using System.Security.Principal; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Web; 12 | 13 | namespace ApirLib 14 | { 15 | public class BasicAuthHttpModule : IHttpModule 16 | { 17 | private const string Realm = "Apir"; 18 | 19 | private static UserValidator userValidator; 20 | public void Init(HttpApplication context) 21 | { 22 | string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString; 23 | string userValidatorProcName = ConfigurationManager.AppSettings["UserValidator"]; 24 | if (userValidatorProcName != null && userValidatorProcName.Length > 0) 25 | userValidator = new UserValidator(connectionString, userValidatorProcName); 26 | else 27 | userValidator = null; 28 | 29 | // Register event handlers 30 | context.AuthenticateRequest += OnApplicationAuthenticateRequest; 31 | context.EndRequest += OnApplicationEndRequest; 32 | } 33 | 34 | private static void SetPrincipal(IPrincipal principal) 35 | { 36 | Thread.CurrentPrincipal = principal; 37 | if (HttpContext.Current != null) 38 | { 39 | HttpContext.Current.User = principal; 40 | } 41 | } 42 | 43 | private static bool CheckPassword(string username, string password) 44 | { 45 | if (userValidator == null) 46 | return false; 47 | 48 | try 49 | { 50 | userValidator.Validate(username, password); 51 | return true; 52 | } 53 | catch (SecurityTokenException) 54 | { 55 | return false; 56 | } 57 | } 58 | 59 | private static bool AuthenticateUser(string credentials) 60 | { 61 | bool validated = false; 62 | try 63 | { 64 | var encoding = Encoding.GetEncoding("iso-8859-1"); 65 | credentials = encoding.GetString(Convert.FromBase64String(credentials)); 66 | 67 | int separator = credentials.IndexOf(':'); 68 | string name = credentials.Substring(0, separator); 69 | string password = credentials.Substring(separator + 1); 70 | 71 | validated = CheckPassword(name, password); 72 | if (validated) 73 | { 74 | var identity = new GenericIdentity(name); 75 | SetPrincipal(new GenericPrincipal(identity, null)); 76 | } 77 | } 78 | catch (FormatException) 79 | { 80 | // Credentials were not formatted correctly. 81 | validated = false; 82 | 83 | } 84 | return validated; 85 | } 86 | 87 | private static void OnApplicationAuthenticateRequest(object sender, EventArgs e) 88 | { 89 | var request = HttpContext.Current.Request; 90 | var authHeader = request.Headers["Authorization"]; 91 | if (authHeader != null) 92 | { 93 | var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader); 94 | 95 | // RFC 2617 sec 1.2, "scheme" name is case-insensitive 96 | if (authHeaderVal.Scheme.Equals("basic", 97 | StringComparison.OrdinalIgnoreCase) && 98 | authHeaderVal.Parameter != null) 99 | { 100 | AuthenticateUser(authHeaderVal.Parameter); 101 | } 102 | } 103 | } 104 | 105 | // If the request was unauthorized, add the WWW-Authenticate header 106 | // to the response. 107 | private static void OnApplicationEndRequest(object sender, EventArgs e) 108 | { 109 | var response = HttpContext.Current.Response; 110 | if (response.StatusCode == 401) 111 | { 112 | response.Headers.Add("WWW-Authenticate", 113 | string.Format("Basic realm=\"{0}\"", Realm)); 114 | } 115 | } 116 | 117 | public void Dispose() 118 | { 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /ApirLib/IonicGenerator.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet; 2 | using Humanizer; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ApirLib 10 | { 11 | class TSGenColumn 12 | { 13 | public string name { get; set; } 14 | public string label { get; set; } 15 | public string sqlType { get; set; } 16 | public int? maxLen { get; set; } 17 | public string tsType { get; set; } 18 | public string endChar { get; set; } 19 | public string resourceName { get; set; } 20 | public string hs = "{{"; 21 | public string he = "}}"; 22 | 23 | 24 | } 25 | class CodeGenModel 26 | { 27 | public string ResourceName { get; set; } 28 | public string ResourceNamePlural { get; set; } 29 | public string ResourceClass { get; set; } 30 | 31 | public string RestResourceName { get; set; } 32 | 33 | public string ProviderResourceClass { get; set; } 34 | 35 | public string ResourceId { get; set; } 36 | 37 | public string PageListClass { get; set; } 38 | public string PageAddClass { get; set; } 39 | public string PageEditClass { get; set; } 40 | 41 | public string PageListFile { get; set; } 42 | public string PageAddFile { get; set; } 43 | public string PageEditFile { get; set; } 44 | 45 | public string PageListModule { get; set; } 46 | public string PageAddModule { get; set; } 47 | public string PageEditModule { get; set; } 48 | 49 | public string Url { get; set; } 50 | public List Columns { get; set; } 51 | public bool GetDefined { get; set; } 52 | public bool PutDefined { get; set; } 53 | public bool PostDefined { get; set; } 54 | public bool DeleteDefined { get; set; } 55 | public string ListText1 { get; set; } 56 | public string ListText2 { get; set; } 57 | public string hs = "{{"; 58 | public string he = "}}"; 59 | } 60 | public class IonicGenerator 61 | { 62 | static string UppercaseFirst(string str) 63 | { 64 | if (string.IsNullOrEmpty(str)) 65 | return string.Empty; 66 | return char.ToUpper(str[0]) + str.Substring(1); 67 | } 68 | 69 | static string LowercaseFirst(string str) 70 | { 71 | if (string.IsNullOrEmpty(str)) 72 | return string.Empty; 73 | return char.ToLower(str[0]) + str.Substring(1); 74 | } 75 | 76 | static public string Build(Model model, string type, string resource, string templateString, string url) 77 | { 78 | CodeGenModel ioModel = null; 79 | var lastName = ""; 80 | var restResource = resource; 81 | if (resource.EndsWith("s")) 82 | resource = resource.Substring(0, resource.Length - 1); 83 | foreach(var controller in model.controllers) 84 | { 85 | if (controller.name.ToLower() == restResource.ToLower()) 86 | { 87 | ioModel = new CodeGenModel { 88 | ResourceId = controller.columns[0].name, 89 | RestResourceName = restResource, 90 | ResourceName = LowercaseFirst(resource), 91 | Columns = new List(), 92 | ResourceClass = UppercaseFirst(resource), 93 | ResourceNamePlural = LowercaseFirst(resource) + "s", 94 | ProviderResourceClass = UppercaseFirst(resource) + "s", 95 | Url = url, 96 | PageAddClass = UppercaseFirst(resource) + "AddPage", 97 | PageEditClass = UppercaseFirst(resource) + "EditPage", 98 | PageListClass = UppercaseFirst(resource) + "ListPage", 99 | PageAddFile = LowercaseFirst(resource) + "-add", 100 | PageEditFile = LowercaseFirst(resource) + "-edit", 101 | PageListFile = LowercaseFirst(resource) + "-list", 102 | PageAddModule = UppercaseFirst(resource) + "AddPageModule", 103 | PageEditModule = UppercaseFirst(resource) + "EditPageModule", 104 | PageListModule = UppercaseFirst(resource) + "ListPageModule", 105 | }; 106 | foreach (var column in controller.columns) 107 | lastName = column.name; 108 | int i = 0; 109 | foreach (var column in controller.columns) 110 | { 111 | ioModel.Columns.Add(new TSGenColumn { name = column.name, maxLen = column.maxLen, sqlType = column.sqlType, 112 | endChar = (column.name == lastName) ? "" : ",", tsType = TypeMapper.GetTSTypeName(column.sqlType), 113 | resourceName = ioModel.ResourceName, 114 | label = column.name.Humanize(LetterCasing.Title) 115 | }); 116 | if (i == 1) 117 | ioModel.ListText1 = "{{" + ioModel.ResourceName + "." + column.name + "}}"; 118 | if (i == 2) 119 | ioModel.ListText2 = "{{" + ioModel.ResourceName + "." + column.name + "}}"; 120 | i++; 121 | } 122 | foreach (var proc in controller.procs) 123 | { 124 | if (proc.GetVerb() == HttpVerb.httpGet) 125 | ioModel.GetDefined = true; 126 | if (proc.GetVerb() == HttpVerb.httpPut) 127 | ioModel.PutDefined = true; 128 | if (proc.GetVerb() == HttpVerb.httpPost) 129 | ioModel.PostDefined = true; 130 | if (proc.GetVerb() == HttpVerb.httpDelete) 131 | ioModel.DeleteDefined = true; 132 | } 133 | } 134 | } 135 | if (ioModel == null) 136 | throw new Exception("Unknown resource:" + resource); 137 | var template = Handlebars.Compile(templateString); 138 | var result = template(ioModel); 139 | return result; 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ApirLib/ApirLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0D91302B-D4DD-4708-A426-504AF1820D59} 8 | Library 9 | Properties 10 | ApirLib 11 | ApirLib 12 | v4.7.1 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll 41 | 42 | 43 | ..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll 44 | 45 | 46 | ..\packages\Handlebars.Net.1.9.0\lib\net40\Handlebars.dll 47 | 48 | 49 | ..\packages\Humanizer.Core.2.2.0\lib\netstandard1.0\Humanizer.dll 50 | 51 | 52 | ..\packages\JsonFx.2.0.1209.2802\lib\net40\JsonFx.dll 53 | 54 | 55 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 56 | True 57 | 58 | 59 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 60 | 61 | 62 | ..\packages\NLog.4.4.12\lib\net45\NLog.dll 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.4\lib\net45\System.Net.Http.Formatting.dll 74 | 75 | 76 | 77 | 78 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.4\lib\net45\System.Web.Http.dll 79 | 80 | 81 | ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.4\lib\net45\System.Web.Http.WebHost.dll 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ..\packages\WebActivatorEx.2.2.0\lib\net40\WebActivatorEx.dll 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Designer 117 | 118 | 119 | 120 | 121 | 122 | 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use SQL to develop your REST API 2 | 3 | A WebApi for a SQL database is traditionally developed with c#, Entity Framework and Linq. 4 | 5 | Apir lets you use SQL and then generates and compiles the c# code when your web app starts. 6 | 7 | If you prefer TSQL to c# for database tasks, you should try apir. 8 | 9 | There is a command line tool with an installer at [apir][2]. It will help develop and test the TSQL procedures. 10 | 11 | The nuget package [apirIO][1] lets you use apir as part of your ASP.NET website. 12 | 13 | # Add apir to your ASP.NET app 14 | 15 | From the tools menu start package manager console 16 | ~~~~ 17 | Install-Package ApirIO 18 | ~~~~ 19 | 20 | Add a connection to you database with connection string in web.config 21 | 22 | ~~~~ 23 | 24 | 25 | 26 | 27 | ~~~~ 28 | 29 | 30 | 31 | # REST, Resources and HTTP 32 | 33 | REST is about resources. A resource is an object such as a product or a list of products. 34 | 35 | Http is used to manipulate resources with words such as GET, PUT, POST, DELETE. 36 | 37 | Verb URI Description 38 | 39 | GET http://myserver/products Retrieve an array of products 40 | GET http://myserver/products/2 Retrieve product with ID=2 41 | PUT http://myserver/products/2 Update product1, values in body 42 | POST http://myserver/products Add product 43 | DELETE http://myserver/products/99 Remove product 44 | 45 | ## Resources and SQL 46 | 47 | Using SQL we develop procedures to Create, Read, Update and DELETE products. 48 | 49 | The HTTP verbs we need are POST, GET, PUT, DELETE 50 | 51 | And the stored procedures for the Products Resource are defined as: 52 | 53 | ```sql 54 | CREATE PROCEDURE API_Products_POST(@Name VARCHAR(100)) 55 | CREATE PROCEDURE API_Products_GET(@ID int = NULL) 56 | CREATE PROCEDURE API_Products_PUT(@ID,@Name VARCHAR(100)) 57 | CREATE PROCEDURE API_Products_DELETE(@ID int) 58 | 59 | ``` 60 | 61 | ## The sample table 62 | You need a test database where we can create the products sample table. 63 | ~~~~ 64 | CREATE TABLE Products 65 | ( 66 | ProductID int IDENTITY(1,1) PRIMARY KEY, 67 | ProductName varchar(100) NOT NULL 68 | ) 69 | GO 70 | INSERT INTO Products(ProductName) VALUES ('Widget 1') 71 | GO 72 | INSERT INTO Products(ProductName) VALUES ('Widget 2') 73 | GO 74 | ~~~~ 75 | ## Reading and defining a resource. 76 | 77 | In the GET procedure we return one or a list of resources. 78 | It also defines the structure of the resurce. 79 | 80 | ~~~~ 81 | CREATE PROCEDURE API_Products_Get (@ID int = NULL) 82 | AS 83 | SELECT ProductId, ProductName 84 | FROM Products 85 | WHERE ProductID = @ID OR @ID IS NULL 86 | ~~~~ 87 | 88 | ## Updating a resource 89 | An update procedure will respond to the Put HTTP verb. In this example we want to be able to change the name of the product. 90 | ~~~~ 91 | CREATE PROCEDURE API_Products_Put(@ID int, @ProductName VARCHAR(100)) 92 | AS 93 | UPDATE Products SET ProductName = @ProductName 94 | WHERE ProductID = @ID 95 | ~~~~ 96 | 97 | This may be a good place to introduce som error handling. We see that the two parameters have no default values. A runtime error will occur if this is no value is set. 98 | 99 | We could check for a valid @ID 100 | 101 | ~~~~ 102 | ALTER PROCEDURE API_Products_Put(@ID int, @ProductName VARCHAR(100)) 103 | AS 104 | IF NOT EXISTS(SELECT ProductID FROM Products 105 | WHERE ProductID = @ID) 106 | BEGIN 107 | RAISERROR('Unknown Product',1,1) 108 | RETURN 400 109 | END 110 | UPDATE Products SET ProductName = @ProductName 111 | WHERE ProductID = @ID 112 | RETURN 200 –- OK 113 | ~~~~ 114 | 115 | The RAISERROR uses a severity level of 1 which means a warning in TSQL. The execution continues and returns 400 which will be the HTTP return code. The message “Unknown Product” is returned to the caller as a message. 116 | If the UPDATE was successful a 200 is returned. 117 | 118 | Note: If you leave out the RETURN 200 a zero will be returned which will be translated to 200 before returning to the caller. 119 | 120 | 121 | ## Creating a resource 122 | 123 | The POST procedure may be simply 124 | 125 | ~~~~ 126 | CREATE PROCEDURE API_Products_Post(@ProductName VARCHAR(100)) 127 | AS 128 | INSERT INTO Products(ProductName) VALUES(@ProductName) 129 | ~~~~ 130 | 131 | 132 | 133 | It is useful to be able to return the ID of the newly created row. 134 | We can do this like: 135 | 136 | ~~~~ 137 | CREATE PROCEDURE API_Products_Post( 138 | @ProductName VARCHAR(100), @NewId int OUTPUT) 139 | AS 140 | INSERT INTO Products(ProductName) VALUES(@ProductName) 141 | SET @NewId = @@IDENTITY 142 | RETURN 200 143 | ~~~~ 144 | Apir constructs the URI of the new Product and returns it in the HTTP header to the client. 145 | 146 | ## Deleting a resource 147 | 148 | Finally the delete procedure may be simple: 149 | ~~~~ 150 | CREATE PROCEDURE API_Products_Delete(@ID int) 151 | AS 152 | DELETE FROM Products WHERE ProductID = @ID 153 | ~~~~ 154 | 155 | 156 | ## Start you WebApi 157 | 158 | Just hit F5 to start a debug run of the project. 159 | 160 | You will get the standard HomePage. If you click on API in the heading, 161 | the ApiControllers are shown. The example ValuesController are there **and** 162 | the new products. 163 | 164 | Try running the API\_Products\_Get procedure by entering the URL 165 | ~~~~ 166 | http://localhost:50608/api/products 167 | ~~~~ 168 | 169 | You will get a list of products in XML or JSON depending on your browser. 170 | 171 | 172 | ## Swagger test and documentation harness 173 | 174 | Swashbuckle is a great nuget package that will give you a GUI for testing the API 175 | 176 | From the tools menu start package manager console 177 | 178 | ~~~~ 179 | Install-Package Swashbuckle 180 | ~~~~ 181 | 182 | Restart you project and open the URL 183 | 184 | ~~~~ 185 | http://localhost:50608/swagger 186 | ~~~~ 187 | 188 | 189 | You have added the Swagger GUI for the API. 190 | 191 | 192 | ## How ApirIO works 193 | 194 | When the app starts controllers are generated from the Stored Procedures. 195 | The generated controllers inherit from ASP.NET ApiControllers. 196 | 197 | The code is compiled and loaded into the project at runtime. The c# source file is 198 | located in the App_Data folder by default. 199 | 200 | If ApirIO is unable to compile the generated c# code an error log, swaError.txt, is written to the same folder. 201 | 202 | ApirIO uses ADO.NET for database access. The connection named "DefaultConnection" is used. 203 | 204 | The code is generated each time you start the project. If you add stored procedures or 205 | changes parameters, you will need to restart the app. 206 | 207 | 208 | ## Documenting the API 209 | 210 | c# developers have documented code with XML comments for years. 211 | These comments are used by tools such as Visual Studio and also Swashbuckle. 212 | ApirIO lets you comment the stored procedures and moves any comments to the generated c-sharp code. It is a great for API documentation since it can be used in Swagger and lives with the database. 213 | 214 | Lets change the procedure for inserting new products to make it more production ready. 215 | 216 | ~~~~ 217 | --- 218 | --- Add a new product to the database 219 | --- 220 | --- A link to the new product is returned 221 | --- OK 222 | --- Bad productname 223 | --- Product with name exists 224 | CREATE PROCEDURE API_Products_Post( 225 | @ProductName VARCHAR(100), 226 | @NewId int OUTPUT) 227 | AS 228 | IF (@ProductName IS NULL OR LEN(@ProductName) < 2) 229 | BEGIN 230 | RAISERROR('Bad product name',1,1) 231 | RETURN 521 232 | END 233 | IF EXISTS (SELECT ProductId FROM Products WHERE ProductName = @ProductName) 234 | BEGIN 235 | RAISERROR('Product with that name already exists',1,1) 236 | RETURN 522 237 | END 238 | 239 | INSERT INTO Products(ProductName) VALUES(@ProductName) 240 | SET @NewId = @@IDENTITY 241 | RETURN 201 242 | ~~~~ 243 | 244 | One code change is needed for adding XML comments to Swagger. 245 | In the App_Start\SwaggerConfig.cs file at line 100 insert the line: 246 | 247 | ~~~~ 248 | //c.IncludeXmlComments(GetXmlCommentsPath()); 249 | c.IncludeXmlComments(AppDomain.CurrentDomain.GetData("DataDirectory").ToString() + "\\xmlComments.xml"); 250 | ~~~~ 251 | 252 | 253 | You will also need using "System" for AppDomain. 254 | 255 | Try adding products. You will get a 201 when you have successfully added a product. 256 | Watch the Response Headers. It will have 257 | 258 | ~~~~ 259 | "location": "http://localhost:50608/api/products/2", 260 | ~~~~ 261 | 262 | which is the URI of the created resource. 263 | 264 | 265 | ## Using Azure Web Sites 266 | 267 | You can publish a website with ApirIO to Azure. The only issue we found was that you need something in the App_Data folder. If it contains a file which is part of the project, the folder will be created. It us used by ApirIO at runtime. 268 | 269 | [1]: https://www.nuget.org/packages?q=apirio 270 | [2]: http://apir.io 271 | 272 | 273 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ApirLib/App_Start/SwaggerConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http; 2 | using WebActivatorEx; 3 | using ApirLib; 4 | using Swashbuckle.Application; 5 | 6 | [assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")] 7 | 8 | namespace ApirLib 9 | { 10 | public class SwaggerConfig 11 | { 12 | public static void Register() 13 | { 14 | var thisAssembly = typeof(SwaggerConfig).Assembly; 15 | 16 | GlobalConfiguration.Configuration 17 | .EnableSwagger(c => 18 | { 19 | // By default, the service root url is inferred from the request used to access the docs. 20 | // However, there may be situations (e.g. proxy and load-balanced environments) where this does not 21 | // resolve correctly. You can workaround this by providing your own code to determine the root URL. 22 | // 23 | //c.RootUrl(req => GetRootUrlFromAppConfig()); 24 | 25 | // If schemes are not explicitly provided in a Swagger 2.0 document, then the scheme used to access 26 | // the docs is taken as the default. If your API supports multiple schemes and you want to be explicit 27 | // about them, you can use the "Schemes" option as shown below. 28 | // 29 | //c.Schemes(new[] { "http", "https" }); 30 | 31 | // Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to 32 | // hold additional metadata for an API. Version and title are required but you can also provide 33 | // additional fields by chaining methods off SingleApiVersion. 34 | // 35 | c.SingleApiVersion("v1", "ApirLib"); 36 | 37 | // If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion". 38 | // In this case, you must provide a lambda that tells Swashbuckle which actions should be 39 | // included in the docs for a given API version. Like "SingleApiVersion", each call to "Version" 40 | // returns an "Info" builder so you can provide additional metadata per API version. 41 | // 42 | //c.MultipleApiVersions( 43 | // (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion), 44 | // (vc) => 45 | // { 46 | // vc.Version("v2", "Swashbuckle Dummy API V2"); 47 | // vc.Version("v1", "Swashbuckle Dummy API V1"); 48 | // }); 49 | 50 | // You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API. 51 | // See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details. 52 | // NOTE: These only define the schemes and need to be coupled with a corresponding "security" property 53 | // at the document or operation level to indicate which schemes are required for an operation. To do this, 54 | // you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties 55 | // according to your specific authorization implementation 56 | // 57 | //c.BasicAuth("basic") 58 | // .Description("Basic HTTP Authentication"); 59 | // 60 | //c.ApiKey("apiKey") 61 | // .Description("API Key Authentication") 62 | // .Name("apiKey") 63 | // .In("header"); 64 | // 65 | //c.OAuth2("oauth2") 66 | // .Description("OAuth2 Implicit Grant") 67 | // .Flow("implicit") 68 | // .AuthorizationUrl("http://petstore.swagger.wordnik.com/api/oauth/dialog") 69 | // //.TokenUrl("https://tempuri.org/token") 70 | // .Scopes(scopes => 71 | // { 72 | // scopes.Add("read", "Read access to protected resources"); 73 | // scopes.Add("write", "Write access to protected resources"); 74 | // }); 75 | 76 | // Set this flag to omit descriptions for any actions decorated with the Obsolete attribute 77 | //c.IgnoreObsoleteActions(); 78 | 79 | // Each operation be assigned one or more tags which are then used by consumers for various reasons. 80 | // For example, the swagger-ui groups operations according to the first tag of each operation. 81 | // By default, this will be controller name but you can use the "GroupActionsBy" option to 82 | // override with any value. 83 | // 84 | //c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString()); 85 | 86 | // You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate 87 | // the order in which operations are listed. For example, if the default grouping is in place 88 | // (controller name) and you specify a descending alphabetic sort order, then actions from a 89 | // ProductsController will be listed before those from a CustomersController. This is typically 90 | // used to customize the order of groupings in the swagger-ui. 91 | // 92 | //c.OrderActionGroupsBy(new DescendingAlphabeticComparer()); 93 | 94 | // Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types 95 | // exposed in your API. However, there may be occasions when more control of the output is needed. 96 | // This is supported through the "MapType" and "SchemaFilter" options: 97 | // 98 | // Use the "MapType" option to override the Schema generation for a specific type. 99 | // It should be noted that the resulting Schema will be placed "inline" for any applicable Operations. 100 | // While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not. 101 | // It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only 102 | // use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a 103 | // complex Schema, use a Schema filter. 104 | // 105 | //c.MapType(() => new Schema { type = "integer", format = "int32" }); 106 | // 107 | // If you want to post-modify "complex" Schemas once they've been generated, across the board or for a 108 | // specific type, you can wire up one or more Schema filters. 109 | // 110 | //c.SchemaFilter(); 111 | 112 | // Set this flag to omit schema property descriptions for any type properties decorated with the 113 | // Obsolete attribute 114 | //c.IgnoreObsoleteProperties(); 115 | 116 | // In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique 117 | // Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this 118 | // works well because it prevents the "implementation detail" of type namespaces from leaking into your 119 | // Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll 120 | // need to opt out of this behavior to avoid Schema Id conflicts. 121 | // 122 | //c.UseFullTypeNameInSchemaIds(); 123 | 124 | // In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers. 125 | // You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given 126 | // enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different 127 | // approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings. 128 | // 129 | //c.DescribeAllEnumsAsStrings(); 130 | 131 | // Similar to Schema filters, Swashbuckle also supports Operation and Document filters: 132 | // 133 | // Post-modify Operation descriptions once they've been generated by wiring up one or more 134 | // Operation filters. 135 | // 136 | //c.OperationFilter(); 137 | // 138 | // If you've defined an OAuth2 flow as described above, you could use a custom filter 139 | // to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required 140 | // to execute the operation 141 | // 142 | //c.OperationFilter(); 143 | 144 | // Post-modify the entire Swagger document by wiring up one or more Document filters. 145 | // This gives full control to modify the final SwaggerDocument. You should have a good understanding of 146 | // the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md 147 | // before using this option. 148 | // 149 | //c.DocumentFilter(); 150 | 151 | // If you annonate Controllers and API Types with 152 | // Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate 153 | // those comments into the generated docs and UI. You can enable this by providing the path to one or 154 | // more Xml comment files. 155 | // 156 | //c.IncludeXmlComments(GetXmlCommentsPath()); 157 | 158 | // In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL 159 | // to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions 160 | // with the same path (sans query string) and HTTP method. You can workaround this by providing a 161 | // custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs 162 | // 163 | //c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); 164 | }) 165 | .EnableSwaggerUi(c => 166 | { 167 | // Use the "InjectStylesheet" option to enrich the UI with one or more additional CSS stylesheets. 168 | // The file must be included in your project as an "Embedded Resource", and then the resource's 169 | // "Logical Name" is passed to the method as shown below. 170 | // 171 | //c.InjectStylesheet(containingAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testStyles1.css"); 172 | 173 | // Use the "InjectJavaScript" option to invoke one or more custom JavaScripts after the swagger-ui 174 | // has loaded. The file must be included in your project as an "Embedded Resource", and then the resource's 175 | // "Logical Name" is passed to the method as shown above. 176 | // 177 | //c.InjectJavaScript(thisAssembly, "Swashbuckle.Dummy.SwaggerExtensions.testScript1.js"); 178 | 179 | // The swagger-ui renders boolean data types as a dropdown. By default, it provides "true" and "false" 180 | // strings as the possible choices. You can use this option to change these to something else, 181 | // for example 0 and 1. 182 | // 183 | //c.BooleanValues(new[] { "0", "1" }); 184 | 185 | // By default, swagger-ui will validate specs against swagger.io's online validator and display the result 186 | // in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the 187 | // feature entirely. 188 | //c.SetValidatorUrl("http://localhost/validator"); 189 | //c.DisableValidator(); 190 | 191 | // Use this option to control how the Operation listing is displayed. 192 | // It can be set to "None" (default), "List" (shows operations for each resource), 193 | // or "Full" (fully expanded: shows operations and their details). 194 | // 195 | //c.DocExpansion(DocExpansion.List); 196 | 197 | // Use the CustomAsset option to provide your own version of assets used in the swagger-ui. 198 | // It's typically used to instruct Swashbuckle to return your version instead of the default 199 | // when a request is made for "index.html". As with all custom content, the file must be included 200 | // in your project as an "Embedded Resource", and then the resource's "Logical Name" is passed to 201 | // the method as shown below. 202 | // 203 | //c.CustomAsset("index", containingAssembly, "YourWebApiProject.SwaggerExtensions.index.html"); 204 | 205 | // If your API has multiple versions and you've applied the MultipleApiVersions setting 206 | // as described above, you can also enable a select box in the swagger-ui, that displays 207 | // a discovery URL for each version. This provides a convenient way for users to browse documentation 208 | // for different API versions. 209 | // 210 | //c.EnableDiscoveryUrlSelector(); 211 | 212 | // If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to 213 | // the Swagger 2.0 specification, you can enable UI support as shown below. 214 | // 215 | //c.EnableOAuth2Support("test-client-id", "test-realm", "Swagger UI"); 216 | }); 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /ApirLib/ModelBuilder.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Data.SqlClient; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using System.Threading.Tasks; 10 | 11 | namespace ApirLib 12 | { 13 | public class ModelBuilder 14 | { 15 | private static Logger logger = LogManager.GetCurrentClassLogger(); 16 | 17 | 18 | static public TableModel ConstructTableModel(string conString, string tableName, string resource) 19 | { 20 | TableModel tableModel = new TableModel(); 21 | tableModel.tableName = tableName; 22 | tableModel.resource = resource; 23 | 24 | var con = new SqlConnection(conString); 25 | SqlCommand cmd = con.CreateCommand(); 26 | SqlCommand cmd2 = con.CreateCommand(); 27 | SqlCommand cmd3 = con.CreateCommand(); 28 | 29 | cmd.CommandType = CommandType.Text; 30 | string query = string.Format(@" 31 | SELECT 32 | b.column_name 33 | FROM information_schema.table_constraints a, 34 | information_schema.key_column_usage b 35 | WHERE a.table_name = '{0}' 36 | AND a.table_name = b.table_name 37 | AND a.table_schema = b.table_schema 38 | AND a.constraint_name = b.constraint_name 39 | AND a.constraint_type = 'PRIMARY KEY' 40 | ", tableName); 41 | cmd.CommandText = query; 42 | 43 | cmd2.CommandType = CommandType.Text; 44 | query = string.Format(@" 45 | select column_name, data_type, is_nullable, 46 | character_maximum_length 47 | from information_schema.columns 48 | where table_name = '{0}'; 49 | ", tableName); 50 | cmd2.CommandText = query; 51 | 52 | DataTable table = new DataTable(); 53 | DataTable table2 = new DataTable(); 54 | 55 | cmd3.CommandType = CommandType.Text; 56 | con.Open(); 57 | try 58 | { 59 | SqlDataAdapter da = null; 60 | using (da = new SqlDataAdapter(cmd)) 61 | { 62 | da.Fill(table); 63 | } 64 | if (table.Rows.Count != 1) 65 | throw new Exception("Table must have one primary key column"); 66 | tableModel.KeyColum = table.Rows[0]["column_name"].ToString(); 67 | 68 | using (da = new SqlDataAdapter(cmd2)) 69 | { 70 | da.Fill(table2); 71 | } 72 | 73 | foreach (DataRow row in table2.Rows) 74 | { 75 | tableModel.columns.Add(new ColumnInfo 76 | { 77 | name = row["column_name"].ToString(), 78 | sqlType = row["data_type"].ToString(), 79 | maxLen = (DBNull.Value.Equals(row["character_maximum_length"])) ? null : (int?)row["character_maximum_length"] 80 | }); 81 | } 82 | 83 | cmd3.CommandText = string.Format("select columnproperty(object_id('{0}'),'{1}','IsIdentity')", tableModel.tableName, tableModel.KeyColum); 84 | int? isIdentity = (int?)cmd3.ExecuteScalar(); 85 | tableModel.isIdentity = (isIdentity == 1); 86 | 87 | tableModel.nonKeyColumns = new List(); 88 | foreach (var col in tableModel.columns) 89 | if (col.name != tableModel.KeyColum) // || !tableModel.isIdentity) 90 | tableModel.nonKeyColumns.Add(col); 91 | 92 | 93 | 94 | 95 | } 96 | catch (Exception ex) 97 | { 98 | throw ex; 99 | } 100 | finally 101 | { 102 | cmd.Dispose(); 103 | cmd = null; 104 | cmd2.Dispose(); 105 | cmd2 = null; 106 | cmd3.Dispose(); 107 | cmd3 = null; 108 | con.Close(); 109 | } 110 | 111 | 112 | return tableModel; 113 | } 114 | 115 | 116 | static public Model ConstructModel(string conString) 117 | { 118 | Model model = new Model(); 119 | SqlConnection con=null; 120 | SqlCommand cmd; 121 | try 122 | { 123 | con = new SqlConnection(conString); 124 | } catch (Exception ex) 125 | { 126 | logger.Fatal(ex); 127 | return null; 128 | } 129 | cmd = con.CreateCommand(); 130 | cmd.CommandType = CommandType.Text; 131 | string query = @"SELECT 132 | ProcedureName = ir.ROUTINE_NAME, 133 | ParameterName = COALESCE(ip.PARAMETER_NAME, ''), 134 | SqlType = ip.DATA_TYPE, Precision = ip.NUMERIC_PRECISION, Scale = ip.NUMERIC_SCALE, MaxLen = ip.CHARACTER_MAXIMUM_LENGTH, 135 | DataType = COALESCE(UPPER(ip.DATA_TYPE) + CASE 136 | WHEN ip.DATA_TYPE IN ('NUMERIC', 'DECIMAL') THEN 137 | '(' + CAST(ip.NUMERIC_PRECISION AS VARCHAR) 138 | + ', ' + CAST(ip.NUMERIC_SCALE AS VARCHAR) + ')' 139 | WHEN RIGHT(ip.DATA_TYPE, 4) = 'CHAR' THEN 140 | '(' + CAST(ip.CHARACTER_MAXIMUM_LENGTH AS VARCHAR) + ')' 141 | ELSE '' END + CASE ip.PARAMETER_MODE 142 | WHEN 'INOUT' THEN ' OUTPUT' ELSE ' ' END, '-'), 143 | ParameterMode = ip.PARAMETER_MODE 144 | FROM 145 | INFORMATION_SCHEMA.ROUTINES ir 146 | LEFT OUTER JOIN 147 | INFORMATION_SCHEMA.PARAMETERS ip 148 | ON ir.ROUTINE_NAME = ip.SPECIFIC_NAME 149 | WHERE 150 | ir.ROUTINE_NAME LIKE 'API%' 151 | AND ir.ROUTINE_TYPE = 'PROCEDURE' 152 | AND COALESCE(OBJECTPROPERTY 153 | ( 154 | OBJECT_ID(ip.SPECIFIC_NAME), 155 | 'IsMsShipped' 156 | ), 0) = 0 157 | ORDER BY 158 | ir.ROUTINE_NAME, 159 | ip.ORDINAL_POSITION"; 160 | cmd.CommandText = query; 161 | DataTable table = new DataTable(); 162 | con.Open(); 163 | 164 | try 165 | { 166 | SqlDataAdapter da = null; 167 | using (da = new SqlDataAdapter(cmd)) 168 | { 169 | da.Fill(table); 170 | } 171 | 172 | foreach (DataRow row in table.Rows) 173 | { 174 | string procName = row["ProcedureName"].ToString(); 175 | ControllerInfo controller = GetControllerInfo(model, procName); 176 | if (controller != null) 177 | { 178 | ProcInfo proc = GetProcInfo(controller, procName); 179 | string parameterName = row["ParameterName"].ToString(); 180 | if (parameterName != "") 181 | proc.parameters.Add(new ParameterInfo 182 | { 183 | name = parameterName, 184 | precision = (DBNull.Value == row["Precision"]) ? 0 : int.Parse(row["Precision"].ToString()), 185 | scale = (DBNull.Value == row["Scale"]) ? 0 : int.Parse(row["Scale"].ToString()), 186 | maxLen = (DBNull.Value == row["Maxlen"]) ? 0 : int.Parse(row["MaxLen"].ToString()), 187 | sqlType = row["SqlType"].ToString(), 188 | isOutput = (row["ParameterMode"].ToString() != "IN") 189 | }); 190 | } 191 | } 192 | 193 | foreach (ControllerInfo controller in model.controllers) 194 | { 195 | foreach (ProcInfo proc in controller.procs) 196 | { 197 | if ((proc.name == controller.name + "get") || (proc.name == controller.name + "_get")) 198 | if (controller.columns.Count == 0) 199 | { 200 | ConstructEntity(con, controller, proc); 201 | } 202 | 203 | foreach (var par in proc.parameters) 204 | par.csType = TypeMapper.GetCSTypeName(par.sqlType); 205 | GetXmlComments(con, proc); 206 | } 207 | } 208 | } 209 | catch (Exception ex) 210 | { 211 | throw ex; 212 | } 213 | finally 214 | { 215 | cmd.Dispose(); 216 | cmd = null; 217 | con.Close(); 218 | } 219 | 220 | return model; 221 | } 222 | 223 | private static void GetXmlComments(SqlConnection con, ProcInfo proc) 224 | { 225 | SqlCommand sqlCommand = new SqlCommand("sys.sp_helptext", con); 226 | sqlCommand.CommandType = CommandType.StoredProcedure; 227 | sqlCommand.Parameters.AddWithValue("@objname", "api_" + proc.name); 228 | DataSet ds = new DataSet(); 229 | SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(); 230 | sqlDataAdapter.SelectCommand = sqlCommand; 231 | sqlDataAdapter.Fill(ds); 232 | string comments = ""; 233 | if (ds.Tables.Count > 0) 234 | foreach (DataRow r in ds.Tables[0].Rows) 235 | { 236 | if (r.ItemArray.Length > 0) 237 | { 238 | string l = r[0].ToString(); 239 | l = l.Trim(); 240 | if (l != null && l.Length > 3 && l.Substring(0, 3) == "---") 241 | comments += "///" + l.Substring(3) + "\n"; 242 | } 243 | } 244 | proc.xmlComments = comments; 245 | } 246 | 247 | 248 | static void ConstructEntity(SqlConnection con, ControllerInfo controller, ProcInfo procGet) 249 | { 250 | string vars = ""; bool firstVar = true; 251 | foreach (var p in procGet.parameters) 252 | { 253 | if (!firstVar) 254 | vars += ", "; 255 | vars += p.name + "=null"; 256 | firstVar = false; 257 | } 258 | SqlCommand cmd = con.CreateCommand(); 259 | cmd.CommandType = CommandType.Text; 260 | string query = string.Format(@" 261 | SET FMTONLY ON; 262 | EXEC dbo.api_{0} {1} 263 | SET FMTONLY OFF; 264 | ", procGet.name, vars); 265 | cmd.CommandText = query; 266 | DataTable table = new DataTable(); 267 | // con.Open(); 268 | 269 | try 270 | { 271 | SqlDataAdapter da = null; 272 | using (da = new SqlDataAdapter(cmd)) 273 | { 274 | da.Fill(table); 275 | } 276 | foreach (DataColumn col in table.Columns) 277 | { 278 | ColumnInfo newColumn = new ColumnInfo 279 | { 280 | name = col.ColumnName, 281 | sqlType = col.DataType.Name, 282 | maxLen = col.MaxLength 283 | }; 284 | controller.columns.Add(newColumn); 285 | } 286 | } 287 | catch (Exception ex) 288 | { 289 | Console.WriteLine("ConstructEntity: ", ex.Message); 290 | throw ex; 291 | } 292 | finally 293 | { 294 | cmd.Dispose(); 295 | cmd = null; 296 | } 297 | } 298 | 299 | private static string SkipAPI(string name) 300 | { 301 | name = name.ToLower(); 302 | if (!name.StartsWith("api")) 303 | return null; 304 | name = name.Substring(3); 305 | if (name.StartsWith("_")) 306 | name = name.Substring(1); 307 | return name; 308 | } 309 | 310 | private static ProcInfo GetProcInfo(ControllerInfo controller, string procName) 311 | { 312 | procName = SkipAPI(procName); 313 | foreach (var proc in controller.procs) 314 | if (proc.name == procName) 315 | return proc; 316 | ProcInfo newProc = new ProcInfo { name = procName }; 317 | controller.procs.Add(newProc); 318 | return newProc; 319 | } 320 | 321 | private static ControllerInfo GetControllerInfo(Model model, string procName) 322 | { 323 | procName = SkipAPI(procName); 324 | if (procName.EndsWith("delete")) 325 | procName = procName.Substring(0, procName.Length - 6); 326 | else if (procName.EndsWith("get")) 327 | procName = procName.Substring(0, procName.Length - 3); 328 | else if (procName.EndsWith("put")) 329 | procName = procName.Substring(0, procName.Length - 3); 330 | else if (procName.EndsWith("post")) 331 | procName = procName.Substring(0, procName.Length - 4); 332 | else 333 | return null; 334 | 335 | if (procName.EndsWith("_")) 336 | procName = procName.Substring(0, procName.Length - 1); 337 | 338 | foreach (var controller in model.controllers) 339 | if (controller.name == procName) 340 | return controller; 341 | ControllerInfo newController = new ControllerInfo { name = procName }; 342 | model.controllers.Add(newController); 343 | return newController; 344 | } 345 | 346 | static public string RetrieveApiSource(string conString, string resource=null) 347 | { 348 | string filter = (resource == null) ? "API_%" : "API_" + resource + "_%"; 349 | var con = new SqlConnection(conString); 350 | SqlCommand cmd = con.CreateCommand(); 351 | cmd.CommandType = CommandType.Text; 352 | string query = @"SELECT 353 | Name, Definition From sys.procedures p 354 | inner join sys.sql_modules m on p.object_id = m.object_id 355 | Where [Type] = 'P' AND NAME Like '" + filter + "' " + 356 | "ORDER BY Name"; 357 | cmd.CommandText = query; 358 | DataTable table = new DataTable(); 359 | con.Open(); 360 | string source = string.Format("-- Apir Source captured at " + DateTime.Now.ToShortDateString() + "\n\n"); 361 | 362 | try 363 | { 364 | SqlDataAdapter da = null; 365 | using (da = new SqlDataAdapter(cmd)) 366 | { 367 | da.Fill(table); 368 | } 369 | 370 | foreach (DataRow row in table.Rows) 371 | { 372 | string name = row["name"].ToString(); 373 | string definition = row["definition"].ToString(); 374 | var search = "CREATE PROCEDURE"; 375 | var replacement = "ALTER PROCEDURE"; 376 | definition = Regex.Replace( 377 | definition, 378 | Regex.Escape(search), 379 | replacement.Replace("$", "$$"), 380 | RegexOptions.IgnoreCase 381 | ); 382 | source += string.Format("IF NOT EXISTS(SELECT * FROM sys.objects WHERE type = 'P' AND OBJECT_ID = OBJECT_ID('dbo.{0}')) \n", name); 383 | source += string.Format(" exec('CREATE PROCEDURE [dbo].[{0}] AS BEGIN SET NOCOUNT ON; END')\n", name); 384 | source += string.Format("GO\n"); 385 | source += definition; 386 | source += string.Format("\nGO\n"); 387 | } 388 | } 389 | catch (Exception ex) 390 | { 391 | Console.WriteLine(ex.ToString()); 392 | } 393 | return source; 394 | } 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /ApirLib/CodeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlTypes; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Configuration; 8 | using System.Net; 9 | using System.Data; 10 | 11 | 12 | namespace ApirLib 13 | { 14 | public class CodeBuilder 15 | { 16 | static public string BuildDropProc(string resource, string verb) 17 | { 18 | var srcDrop = string.Format("if exists (select * from sysobjects where type = 'P' and name = 'API_{0}_{1}') \n ", resource, verb); 19 | srcDrop += string.Format(" DROP PROCEDURE [dbo].[API_{0}_{1}]", resource, verb); 20 | return srcDrop; 21 | } 22 | static public string BuildTableProc(TableModel m, string verb, bool filter, bool page) 23 | { 24 | switch (verb.ToLower()) { 25 | case "get": 26 | return BuildGetTableProc(m, filter, page); 27 | case "put": 28 | return BuildPutTableProc(m); 29 | case "post": 30 | return BuildPostTableProc(m); 31 | case "delete": 32 | return BuildDeleteTableProc(m); 33 | } 34 | return ""; 35 | } 36 | 37 | 38 | static public string BuildPostTableProc(TableModel m) 39 | { 40 | var sb = new StringBuilder(6000) 41 | .AppendFormat("--- \r\n") 42 | .AppendFormat("--- Insert {0} \r\n", m.resource) 43 | .AppendFormat("--- \r\n") 44 | .AppendFormat("--- Insert new {0} \r\n", m.tableName) 45 | .AppendFormat("--- OK\r\n") 46 | .AppendFormat(" CREATE PROCEDURE[dbo].[API_{0}_Post]( \r\n", m.resource); 47 | int j = 1; 48 | foreach (var col in m.nonKeyColumns) 49 | { 50 | char sep = (j < m.nonKeyColumns.Count) ? ',' : ' '; 51 | if (col.sqlType.ToLower().IndexOf("char") >= 0) 52 | sb.AppendFormat("@{0} {1}({2}) = NULL{3}", col.name, col.sqlType, 53 | (col.maxLen == -1) ? "max" : col.maxLen.ToString(), sep); 54 | else { 55 | 56 | sb.AppendFormat("@{0} {1} = NULL{2}", col.name, col.sqlType, sep); 57 | sb.AppendLine(""); 58 | } 59 | ++j; 60 | } 61 | sb.AppendLine(") AS"); 62 | sb.AppendFormat("INSERT INTO {0} ( ", m.tableName); 63 | int i = 1; 64 | foreach (var col in m.nonKeyColumns) 65 | { 66 | char sep = (i < m.nonKeyColumns.Count) ? ',' : ' '; 67 | sb.AppendFormat(" {0} {1} ", col.name, sep); 68 | i += 1; 69 | } 70 | sb.AppendLine(") VALUES ("); 71 | i = 1; 72 | foreach (var col in m.nonKeyColumns) 73 | { 74 | sb.AppendFormat(" @{0} {1} ", col.name, (i < m.nonKeyColumns.Count) ? ',' : ' '); 75 | i += 1; 76 | } 77 | 78 | sb.AppendLine(")"); 79 | sb.AppendLine(" RETURN 200 -- OK"); 80 | return sb.ToString(); 81 | } 82 | static public string BuildDeleteTableProc(TableModel m) 83 | { 84 | var sb = new StringBuilder(6000) 85 | .AppendFormat("--- \r\n") 86 | .AppendFormat("--- Delete {0} \r\n", m.resource) 87 | .AppendFormat("--- \r\n") 88 | .AppendFormat("--- {0} ID \r\n", m.tableName) 89 | .AppendFormat("--- Delete {0} \r\n", m.tableName) 90 | .AppendFormat("--- OK\r\n") 91 | .AppendFormat("--- Not Found\r\n") 92 | .AppendFormat(" CREATE PROCEDURE[dbo].[API_{0}_Delete](@ID varchar(max) = NULL) \r\n", m.resource); 93 | sb.AppendLine("AS"); 94 | sb.AppendFormat("IF NOT EXISTS(SELECT {0} FROM {1} WHERE @ID = {0}) \r\n", m.KeyColum, m.tableName); 95 | sb.AppendLine("BEGIN"); 96 | sb.AppendFormat(" RAISERROR('Unknown {0}',1,1) \r\n", m.resource); 97 | sb.AppendLine(" RETURN 404"); 98 | sb.AppendLine("END"); 99 | sb.AppendFormat("DELETE {0} \r\n", m.tableName); 100 | sb.AppendFormat(" WHERE @ID = {0} \r\n", m.KeyColum); 101 | sb.AppendLine(" RETURN 200 -- OK"); 102 | return sb.ToString(); 103 | } 104 | 105 | 106 | static public string BuildPutTableProc(TableModel m) 107 | { 108 | var sb = new StringBuilder(6000) 109 | .AppendFormat("--- \r\n") 110 | .AppendFormat("--- Update {0} \r\n", m.resource) 111 | .AppendFormat("--- \r\n") 112 | .AppendFormat("--- {0} ID \r\n", m.tableName) 113 | .AppendFormat("--- Updates {0} \r\n", m.tableName) 114 | .AppendFormat("--- OK\r\n") 115 | .AppendFormat("--- Not Found\r\n") 116 | .AppendFormat(" CREATE PROCEDURE[dbo].[API_{0}_Put](@ID varchar(max) = NULL \r\n", m.resource); 117 | foreach (var col in m.nonKeyColumns) 118 | { 119 | if (col.sqlType.ToLower().IndexOf("char") >=0 ) 120 | sb.AppendFormat(", @{0} {1}({2}) = NULL ", col.name, col.sqlType,(col.maxLen == -1) ? "max" : col.maxLen.ToString()); 121 | else 122 | sb.AppendFormat(", @{0} {1} = NULL ", col.name, col.sqlType); 123 | sb.AppendLine(""); 124 | } 125 | sb.AppendLine(") AS"); 126 | sb.AppendFormat("IF NOT EXISTS(SELECT {0} FROM {1} WHERE @ID = {0}) \r\n", m.KeyColum, m.tableName); 127 | sb.AppendLine("BEGIN"); 128 | sb.AppendFormat(" RAISERROR('Unknown {0}',1,1) \r\n", m.resource); 129 | sb.AppendLine (" RETURN 404"); 130 | sb.AppendLine("END"); 131 | sb.AppendFormat("UPDATE {0} SET \r\n", m.tableName); 132 | int i = 1; 133 | foreach (var col in m.nonKeyColumns) 134 | { 135 | sb.AppendFormat(" {0} = COALESCE(@{0},{0}){1} \r\n ", col.name, (i \r\n") 147 | .AppendFormat("--- Retrieve {0} \r\n", m.resource) 148 | .AppendFormat("--- \r\n") 149 | .AppendFormat("--- {0} ID \r\n", m.tableName) 150 | .AppendFormat("--- Returns all or a single {0} \r\n", m.tableName) 151 | .AppendFormat(" CREATE PROCEDURE[dbo].[API_{0}_Get](@ID varchar(max) = NULL \r\n", m.resource); 152 | if (filter) 153 | sb.Append(" , @filter varchar(max)=NULL \r\n"); 154 | if (page) 155 | sb.Append(" , @pageNo int = 1, @pageSize int=40 \r\n"); 156 | sb.Append(" ) AS \r\n"); 157 | sb.Append(" SELECT "); 158 | int i = 0; 159 | foreach (var col in m.columns) 160 | { 161 | sb.Append(col.name); 162 | i += 1; 163 | if (i < m.columns.Count) 164 | sb.Append(", "); 165 | } 166 | sb.AppendFormat("\r\n FROM {0} \r\n", m.tableName); 167 | sb.AppendFormat(" WHERE (@ID IS NULL OR @ID = {0}) \r\n", m.KeyColum); 168 | if (filter) 169 | sb.AppendFormat(" AND (@filter IS NULL OR CHARINDEX(@filter,CAST({0} AS varchar)) > 0)\r\n ", m.columns[1].name); 170 | sb.AppendFormat(" ORDER BY {0}\r\n ", m.columns[1].name); 171 | if (page) 172 | { 173 | sb.Append(" OFFSET (@pageNo-1)*@pageSize ROW \r\n"); 174 | sb.Append(" FETCH NEXT @pageSize ROW ONLY \r\n"); 175 | } 176 | return sb.ToString(); 177 | } 178 | static public string BuildCode(Model model, bool fAuthorize, string freeResources = null, string connectionString = null) 179 | { 180 | List _freeResources = null; 181 | 182 | if (freeResources != null) 183 | { 184 | _freeResources = freeResources.Split(new string[] { ",", " " }, StringSplitOptions.RemoveEmptyEntries).ToList(); 185 | } 186 | 187 | 188 | var sb = new StringBuilder(60000) 189 | .AppendLine("using System;") 190 | .AppendLine("using System.Linq;") 191 | .AppendLine("using System.Web.Http;") 192 | .AppendLine("using System.Data;") 193 | .AppendLine("using System.Data.SqlClient;") 194 | .AppendLine("using System.Configuration;") 195 | .AppendLine("using System.Collections.Generic;") 196 | .AppendLine("using System.Reflection;") 197 | .AppendLine("using System.Net.Http;") 198 | .AppendLine("using System.Net;") 199 | .AppendLine("using System.Diagnostics;") 200 | .AppendLine("using NLog;") 201 | .AppendLine("namespace ControllerLibrary") 202 | .AppendLine("{"); 203 | 204 | foreach (var controller in model.controllers) 205 | { 206 | // Create Entity 207 | sb.AppendFormat("public class {0} \n {{ \n", controller.name); 208 | foreach (var column in controller.columns) 209 | { 210 | string csTypeName = GetCSTypeName(column); 211 | sb.AppendFormat(" public {0} {1} {{ get; set;}} \n", csTypeName, column.name); 212 | } 213 | sb.AppendLine("}"); 214 | } 215 | 216 | sb.AppendLine("static public class DbUtil \n"); 217 | sb.AppendLine("{"); 218 | sb.AppendLine("static public string message;"); 219 | 220 | sb.AppendLine("\tstatic public SqlConnection GetConnection() {"); 221 | if (connectionString != null) 222 | sb.AppendLine("\t\tstring conStr = @\"" + connectionString.Replace("\"","\\\"") + "\";"); 223 | else 224 | sb.AppendLine("\t\tstring conStr = ConfigurationManager.ConnectionStrings[\"DefaultConnection\"].ConnectionString;"); 225 | sb.AppendLine("\t\tSqlConnection con = new SqlConnection(conStr);"); 226 | sb.AppendLine("DbUtil.message = \"\";"); 227 | sb.AppendLine("con.InfoMessage += delegate(object sender, SqlInfoMessageEventArgs e)"); 228 | sb.AppendLine("{"); 229 | sb.AppendLine("if (DbUtil.message.Length > 0) DbUtil.message += \"\\n\";"); 230 | sb.AppendLine("DbUtil.message += e.Message;"); 231 | sb.AppendLine("};"); 232 | 233 | 234 | sb.AppendLine("\t\treturn con; "); 235 | sb.AppendLine("\t}"); 236 | sb.AppendLine("}"); 237 | 238 | sb.AppendLine(@" 239 | public static class DbExtensions 240 | { 241 | public static List ToListCollection(this DataTable dt) 242 | { 243 | List lst = new System.Collections.Generic.List(); 244 | Type tClass = typeof(T); 245 | PropertyInfo[] pClass = tClass.GetProperties(); 246 | List dc = dt.Columns.Cast().ToList(); 247 | T cn; 248 | foreach (DataRow item in dt.Rows) 249 | { 250 | cn = (T)Activator.CreateInstance(tClass); 251 | foreach (PropertyInfo pc in pClass) 252 | { 253 | // Can comment try catch block. 254 | try 255 | { 256 | DataColumn d = dc.Find(c => c.ColumnName == pc.Name); 257 | if (d != null && item[pc.Name] != DBNull.Value) 258 | pc.SetValue(cn, item[pc.Name], null); 259 | } 260 | catch (Exception ex) 261 | { 262 | throw ex; 263 | } 264 | } 265 | lst.Add(cn); 266 | } 267 | return lst; 268 | } 269 | } 270 | "); 271 | 272 | 273 | foreach (var controller in model.controllers) 274 | { 275 | if (AddAuthorize(fAuthorize, controller.name, _freeResources)) 276 | sb.AppendLine("[Authorize]"); 277 | sb.AppendFormat("public class {0}Controller:ApiController \n", controller.name); 278 | sb.AppendLine("{"); 279 | sb.AppendLine("\t private static Logger logger = LogManager.GetCurrentClassLogger();"); 280 | 281 | foreach (var proc in controller.procs) 282 | { 283 | sb.Append(proc.xmlComments); 284 | if (proc.name.ToLower().EndsWith("get")) 285 | { 286 | sb.AppendFormat(" public IEnumerable<{0}> Get(", controller.name); 287 | 288 | AddParameters(true, sb, controller, proc, false, true); 289 | sb.AppendLine(")"); 290 | sb.AppendLine("\t{"); 291 | 292 | 293 | AddProc(sb, proc); 294 | AddAdoParams(sb, controller, proc, "", true); 295 | 296 | sb.AppendLine("\t\tSqlDataAdapter da = new SqlDataAdapter(com);"); 297 | sb.AppendLine("\t\tcon.Open();"); 298 | 299 | sb.AppendLine("\t\tDataSet ds = new DataSet();"); 300 | sb.AppendLine("\t\tda.Fill(ds);"); 301 | sb.AppendLine("\t\tda.Dispose();"); 302 | 303 | AddTrace(sb, controller, proc, true); 304 | 305 | sb.AppendLine("\t\tDataTable dt = ds.Tables[0];"); 306 | sb.AppendFormat("\t\tList<{0}> ret = dt.ToListCollection<{0}>();\n", controller.name); 307 | sb.AppendFormat("\t\treturn ret.AsEnumerable<{0}>();\n", controller.name); 308 | sb.AppendLine("\t\t}\n"); 309 | sb.AppendLine("\t}\n\n"); 310 | 311 | 312 | 313 | // Get single 314 | if (HasIdParam(proc)) 315 | { 316 | sb.AppendFormat(" public {0} Get(", controller.name); 317 | 318 | AddParameters(true, sb, controller, proc, false, false); 319 | sb.AppendLine(")"); 320 | sb.AppendLine("\t{"); 321 | 322 | AddProc(sb, proc); 323 | AddAdoParams(sb, controller, proc, "", false); 324 | 325 | sb.AppendLine("\t\tSqlDataAdapter da = new SqlDataAdapter(com);"); 326 | sb.AppendLine("\t\tcon.Open();"); 327 | 328 | sb.AppendLine("\t\tDataSet ds = new DataSet();"); 329 | sb.AppendLine("\t\tda.Fill(ds);"); 330 | sb.AppendLine("\t\tda.Dispose();"); 331 | 332 | AddTrace(sb, controller, proc, false); 333 | 334 | sb.AppendLine("\t\tif ( ds.Tables.Count == 0)"); 335 | sb.AppendLine("\t\t\tthrow new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));"); 336 | sb.AppendLine("\t\tDataTable dt = ds.Tables[0];"); 337 | sb.AppendLine("\t\tif (dt.Rows.Count > 0) "); 338 | sb.AppendFormat("\t\t\treturn dt.ToListCollection<{0}>()[0];\n", controller.name); 339 | sb.AppendLine("\t\telse "); 340 | sb.AppendLine("\t\t\tthrow new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));"); 341 | sb.AppendLine("\t\t}\n"); 342 | sb.AppendLine("\t}\n\n"); 343 | 344 | } 345 | } 346 | else if (proc.name.ToLower().EndsWith("put")) 347 | { 348 | sb.AppendFormat(" public HttpResponseMessage Put("); 349 | if (controller.columns.Count != 0) 350 | sb.AppendFormat("{0} res ", controller.name); 351 | AddParameters((controller.columns.Count == 0), sb, controller, proc, true); 352 | sb.AppendLine(")\n \t{"); 353 | AddProc(sb, proc); 354 | AddAdoParams(sb, controller, proc, "res"); 355 | 356 | sb.AppendLine("\tcon.Open();"); 357 | sb.AppendLine("\tcom.ExecuteNonQuery();"); 358 | AddTrace(sb, controller, proc); 359 | AddReturn(sb); 360 | sb.AppendLine("\t\t}\n"); 361 | sb.AppendLine("\t}\n\n"); 362 | } 363 | else if (proc.name.ToLower().EndsWith("post")) 364 | { 365 | sb.AppendFormat(" public HttpResponseMessage Post({0} res ", controller.name); 366 | AddParameters(false, sb, controller, proc, true, true); 367 | sb.AppendLine(")\n \t{ "); 368 | sb.AppendLine("\tif (res == null) { "); 369 | sb.AppendLine("\t\tlogger.Fatal(\"" + proc.name + ": Cannot parse resource. Check parameters\" );"); 370 | sb.AppendLine("\t\tthrow new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));"); 371 | sb.AppendLine("\t}"); 372 | 373 | bool hasNewId = AddNewId(sb, controller, proc); 374 | AddProc(sb, proc); 375 | AddAdoParams(sb, controller, proc, "res", false); 376 | sb.AppendLine("\ttry {"); 377 | sb.AppendLine("\t\tcon.Open();"); 378 | sb.AppendLine("\t\tcom.ExecuteNonQuery();"); 379 | sb.AppendLine("\t\t} catch (Exception ex) {"); 380 | AddTrace(sb, controller, proc,true); 381 | sb.AppendLine("\t\t\tlogger.Fatal(\"" + proc.name + ": SqlException:\" + ex.Message );"); 382 | sb.AppendLine("\t\t\t return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.Message);"); 383 | sb.AppendLine("\t\t} "); 384 | AddTrace(sb, controller, proc, true); 385 | AddReturn(sb, hasNewId); 386 | sb.AppendLine("\t\t}\n"); 387 | sb.AppendLine("\t}\n\n"); 388 | } 389 | else if (proc.name.ToLower().EndsWith("delete")) 390 | { 391 | sb.AppendFormat(" public HttpResponseMessage Delete("); 392 | AddParameters(true, sb, controller, proc, false); 393 | sb.AppendLine(")\n \t{"); 394 | 395 | AddProc(sb, proc); 396 | AddAdoParams(sb, controller, proc, ""); 397 | 398 | sb.AppendLine("\t\tcon.Open();"); 399 | sb.AppendLine("\t\tcom.ExecuteNonQuery();"); 400 | AddTrace(sb, controller, proc); 401 | AddReturn(sb); 402 | sb.AppendLine("\t\t}\n"); 403 | sb.AppendLine("\t}\n\n"); 404 | } 405 | 406 | } 407 | sb.AppendLine("}"); 408 | } 409 | 410 | sb.AppendLine("}"); 411 | sb.AppendLine(""); 412 | string code = sb.ToString(); 413 | return code; 414 | } 415 | 416 | // Check if authorize attribute should be used. 417 | private static bool AddAuthorize(bool fAuthorize, string className, List freeResources) 418 | { 419 | if (!fAuthorize) 420 | return false; 421 | if (freeResources == null) 422 | return true; 423 | foreach (var name in freeResources) 424 | if (name.ToLower() == className) 425 | return false; 426 | return true; 427 | } 428 | 429 | private static bool AddNewId(StringBuilder sb, ControllerInfo controller, ProcInfo proc) 430 | { 431 | foreach (var param in proc.parameters) 432 | if (param.name.ToLower() == "@newid") 433 | { 434 | sb.AppendFormat("\t {0} {1} = null;\n", param.csType, param.name.Substring(1)); 435 | return true; 436 | } 437 | return false; 438 | } 439 | 440 | private static bool HasIdParam(ProcInfo proc) 441 | { 442 | foreach (var p in proc.parameters) 443 | if (p.name.ToLower() == "@id") 444 | return true; 445 | return false; 446 | } 447 | 448 | 449 | private static void AddReturn(StringBuilder sb, bool newId = false) 450 | { 451 | sb.AppendLine("\tif (0 == (int) RetVal.Value)"); 452 | sb.AppendLine("\t\t RetVal.Value = 200;"); 453 | sb.AppendLine("\tif (200 == (int) RetVal.Value || 201 == (int)RetVal.Value) "); 454 | sb.AppendLine("\t{"); 455 | sb.AppendLine("\t\tvar response = Request.CreateResponse((HttpStatusCode)RetVal.Value, \"null\");"); 456 | if (newId) 457 | { 458 | sb.AppendLine("\t\tstring uri=Url.Link(\"DefaultApi\", new { id = com.Parameters[\"NewID\"].Value.ToString() });"); 459 | sb.AppendLine("\t\tresponse.Headers.Location = new Uri(uri);"); 460 | } 461 | sb.AppendLine("\t\treturn response;"); 462 | sb.AppendLine("\t}"); 463 | sb.AppendLine("\tif (DbUtil.message.Length > 0) "); 464 | sb.AppendLine("\t\treturn Request.CreateErrorResponse((HttpStatusCode)RetVal.Value, DbUtil.message);"); 465 | sb.AppendLine("\telse"); 466 | sb.AppendLine("\t\treturn Request.CreateResponse((HttpStatusCode)RetVal.Value);"); 467 | } 468 | 469 | private static void AddAdoParams(StringBuilder sb, ControllerInfo controller, ProcInfo proc, String varName, bool skipId = false) 470 | { 471 | bool hasMemberNamedId = false; 472 | foreach (var col in controller.columns) 473 | if (col.name.ToLower() == "id") 474 | hasMemberNamedId = true; 475 | 476 | foreach (var param in proc.parameters) 477 | { 478 | if (!(param.name.ToLower() == "@id" && skipId)) 479 | { 480 | var resourceFieldName = ""; 481 | if (varName != null && varName.Length > 0) 482 | foreach (var col in controller.columns) 483 | if (col.name.ToLower() == param.name.Substring(1).ToLower()) 484 | resourceFieldName = varName + "." + col.name; 485 | var paramType = param.sqlType; 486 | if (!(varName.ToLower() == "id" && skipId)) 487 | { 488 | if (param.name.ToLower() == "@username") 489 | sb.AppendFormat("\tcom.Parameters.Add(\"{0}\", SqlDbType.{1}, {2}).Value = User.Identity.Name;\n", 490 | param.name.Substring(1), GetTypeName(param.sqlType), param.maxLen); 491 | else if (param.name.ToLower() == "@id" && (!hasMemberNamedId || proc.name.EndsWith("put")) ) 492 | sb.AppendFormat("\tcom.Parameters.Add(\"{0}\", SqlDbType.{1}).Value = {2};\n", 493 | param.name.Substring(1), GetTypeName(param.sqlType), param.name.Substring(1)); 494 | else if (paramType.ToLower().EndsWith("char")) 495 | sb.AppendFormat("\tcom.Parameters.Add(\"{0}\", SqlDbType.{1}, {2}).Value = {3};\n", 496 | param.name.Substring(1), GetTypeName(param.sqlType), param.maxLen, 497 | (resourceFieldName.Length > 0) ? resourceFieldName : param.name.Substring(1)); 498 | else 499 | sb.AppendFormat("\tcom.Parameters.Add(\"{0}\", SqlDbType.{1}).Value = {2};\n", param.name.Substring(1), GetTypeName(param.sqlType), 500 | (resourceFieldName.Length > 0) ? resourceFieldName : param.name.Substring(1)); 501 | if (param.isOutput) 502 | sb.AppendFormat("\tcom.Parameters[\"{0}\"].Direction = ParameterDirection.Output; \n", 503 | (resourceFieldName.Length > 0) ? resourceFieldName : param.name.Substring(1)); 504 | 505 | } 506 | } 507 | } 508 | } 509 | 510 | private static void AddProc(StringBuilder sb, ProcInfo proc) 511 | { 512 | sb.AppendLine("\tusing(SqlConnection con = DbUtil.GetConnection()) {"); 513 | sb.AppendFormat("\t\tSqlCommand com = new SqlCommand(\"API_{0}\",con);\n", proc.name); 514 | sb.AppendLine("\t\tcom.CommandType = CommandType.StoredProcedure;"); 515 | 516 | sb.AppendLine("\t\tSqlParameter RetVal = com.Parameters.Add(\"RetVal\", SqlDbType.Int);"); 517 | sb.AppendLine("\t\tRetVal.Direction = ParameterDirection.ReturnValue;"); 518 | } 519 | 520 | private static void AddParameters(bool first, StringBuilder sb, ControllerInfo controller, ProcInfo proc, bool skipMembers, bool skipId = false) 521 | { 522 | // Add parameters not in resource 523 | foreach (var param in proc.parameters) 524 | { 525 | var paramName = param.name.Substring(1); 526 | if (skipMembers) 527 | foreach (var col in controller.columns) 528 | if (col.name.ToLower() == param.name.Substring(1).ToLower() && 529 | col.name.ToLower() != "id") 530 | paramName = ""; 531 | if (paramName.Length > 0) 532 | { 533 | if (paramName.ToLower() != "username") 534 | if (!(skipId && paramName.ToLower() == "id") && 535 | !(proc.name.ToLower().EndsWith("post") && paramName.ToLower() == "newid")) 536 | { 537 | if (!first) 538 | sb.Append(", "); 539 | if (proc.name.ToLower().EndsWith("get") && paramName.ToLower() == "id") 540 | sb.AppendFormat(" {0} {1} ", param.csType, paramName); 541 | else 542 | sb.AppendFormat(" {0} {1} = null", param.csType, paramName); 543 | first = false; 544 | } 545 | } 546 | } 547 | 548 | } 549 | 550 | private static bool IsMember(ControllerInfo controller, string varName, out string memberName) 551 | { 552 | foreach (var col in controller.columns) 553 | if (col.name.ToLower() == varName.ToLower()) 554 | { 555 | memberName = col.name; 556 | return true; 557 | } 558 | memberName = ""; 559 | return false; 560 | } 561 | 562 | private static void AddTrace(StringBuilder sb, ControllerInfo controller, ProcInfo proc, bool skipId = false) 563 | { 564 | string fmt = ""; int pi = 0; 565 | foreach (var param in proc.parameters) 566 | { 567 | if (!(param.name.ToLower() == "@id" && skipId)) 568 | { 569 | fmt += param.name + "={" + (pi).ToString() + "}"; 570 | pi += 1; 571 | if (pi < (proc.parameters.Count)) 572 | fmt += ", "; 573 | } 574 | } 575 | fmt += ", return={" + (pi).ToString() + "}\","; 576 | pi = 0; 577 | foreach (var param in proc.parameters) 578 | { 579 | if (!(param.name.ToLower() == "@id" && skipId)) 580 | { 581 | if (pi > 0) 582 | fmt += ","; 583 | if (param.name.ToLower() == "@username") 584 | fmt += "User.Identity.Name"; 585 | else 586 | { 587 | if (!proc.name.ToLower().EndsWith("get")) 588 | { 589 | string memberName = ""; 590 | if (IsMember(controller, param.name.Substring(1), out memberName) && !(param.name.ToLower() == "@id")) 591 | fmt += "res." + memberName; 592 | else 593 | fmt += param.name.Substring(1); 594 | } 595 | else 596 | fmt += param.name.Substring(1); 597 | } 598 | pi++; 599 | } 600 | } 601 | if (pi > 0) 602 | fmt += ", "; 603 | fmt += " RetVal.Value "; 604 | fmt += ")"; 605 | sb.Append("\tlogger.Info(\"" + proc.name + ":" + fmt + ";\n"); 606 | } 607 | 608 | 609 | public static string GetCSTypeName(ColumnInfo column) 610 | { 611 | switch (column.sqlType.ToLower()) 612 | { 613 | case "bigint": return "long?"; 614 | case "binary": return "binary"; // test?? 615 | case "bit": return "bool?"; 616 | case "boolean": return "bool?"; 617 | case "char": return "string"; 618 | case "date": return "DateTime?"; 619 | case "datetime": return "DateTime?"; 620 | case "datetimeoffset": return "DateTimeOffset?"; 621 | case "decimal": return "decimal?"; 622 | case "filestream": return "byte?[]"; 623 | case "single": return "float?"; 624 | case "float": return "double?"; 625 | case "guid": return "Guid?"; 626 | case "image": return "byte?[]"; 627 | case "int": return "int?"; 628 | case "int32": return "int?"; 629 | case "int16": return "short?"; 630 | case "int64": return "long?"; 631 | case "money": return "decimal?"; 632 | case "nchar": return "string"; 633 | case "ntext": return "string"; 634 | case "numeric": return "decimal?"; 635 | case "nvarchar": return "string"; 636 | case "real": return "Single"; 637 | case "smalldatetime": return "DateTime?"; 638 | case "smallint": return "short?"; 639 | case "smallmoney": return "decimal?"; 640 | case "string": return "string"; 641 | case "text": return "string"; 642 | case "timestamp": return "byte?[]"; 643 | case "tinyint": return "byte"; 644 | case "uniqueidentifier": return "Guid?"; 645 | case "varbinary": return "byte?[]"; 646 | case "varchar": return "string"; 647 | case "xml": return "string"; 648 | case "": return ""; 649 | case "byte[]": return "byte[]"; 650 | default: throw (new Exception("Unknown SQL Datatype: " + column.sqlType)); 651 | } 652 | } 653 | 654 | private static string GetTypeName(string TypeName) 655 | { 656 | switch (TypeName) 657 | { 658 | case "nvarchar": return "NVarChar"; 659 | case "varchar": return "VarChar"; 660 | case "bigint": return "BigInt"; 661 | case "binary": return "Binary"; 662 | case "image": return "Binary"; 663 | case "bit": return "Bit"; 664 | case "date": return "Date"; 665 | case "datetime": return "DateTime"; 666 | case "decimal": return "Decimal"; 667 | case "real": return "Float"; 668 | case "float": return "Float"; 669 | case "money": return "Money"; 670 | case "nchar": return "NChar"; 671 | case "char": return "Char"; 672 | case "smallint": return "SmallInt"; 673 | case "int": return "Int"; 674 | case "ntext": return "NText"; 675 | case "uniqueidentifier": return "UniqueIdentifier"; 676 | case "guid": return "UniqueIdentifier"; 677 | case "": return ""; 678 | default: return TypeName; 679 | } 680 | } 681 | 682 | } 683 | } 684 | --------------------------------------------------------------------------------