├── .cr └── personal │ └── Editor │ └── Painting │ └── Structural Highlighting.xml ├── .gitignore ├── .vscode └── settings.json ├── AspNetCoreExtensions.sln ├── AspNetCoreExtensions ├── AppCache`1.cs ├── AspNetCoreExtensions.csproj ├── AspNetCoreExtensions.csproj.user ├── AssemblyRegistrationExtensions.cs ├── CSVArrayModelProvider.cs ├── Class1.cs ├── FileLock.cs ├── FromCookieAttribute.cs ├── Http404NotFoundException.cs ├── HttpStatusException.cs └── JsonExtensions.cs ├── EF.Core.Bulk ├── EF.Core.Bulk.Tests │ ├── BaseTest.cs │ ├── EF.Core.Bulk.Tests.csproj │ ├── ShopContext.cs │ └── UnitTest1.cs ├── EF.Core.Bulk.sln ├── EF.Core.Bulk │ ├── .editorconfig │ ├── EF.Core.Bulk.csproj │ └── Model │ │ ├── ContextHelper.cs │ │ ├── DbContextHelper.cs │ │ ├── EFStringHelper.cs │ │ ├── FixCastErrorExpressionVisitor.cs │ │ ├── LiteralExpressionVisitor.cs │ │ ├── Parameters.cs │ │ └── QueryInfo.cs └── LICENSE ├── EFCore.Mock ├── NeuroSpeech.EFCore.Mock │ ├── MockDatabaseContext.cs │ ├── MockSqlDatabaseContext.cs │ ├── NeuroSpeech.EFCore.Mock.csproj │ ├── OriginalSqlDatabase.cs │ └── SqlHelper.cs └── README.md ├── NeuroSpeech.AppCache ├── AppCache.cs └── NeuroSpeech.AppCache.csproj ├── NeuroSpeech.DependencyInjectionExtensions ├── AssemblyRegistrationExtensions.cs ├── DIFactory.cs ├── DIRegisterAttribute.cs ├── NeuroSpeech.DependencyInjectionExtensions.csproj └── RegisterAssemblyAttribute.cs ├── NeuroSpeech.ESDetector ├── ESDetector.cs └── NeuroSpeech.ESDetector.csproj ├── NeuroSpeech.IOExtensions ├── FileLock.cs └── NeuroSpeech.IOExtensions.csproj ├── NeuroSpeech.Workflows ├── .vscode │ └── settings.json ├── Attributes │ ├── ActivityAttribute.cs │ ├── InjectAttribute.cs │ └── WorkflowAttribute.cs ├── BaseWorkflowService.cs ├── CancelTokenExtensions.cs ├── EventQueue.cs ├── EventResult.cs ├── Impl │ ├── BaseWorkflow.cs │ ├── ClrHelper.cs │ ├── IWorkflowExecutor.cs │ ├── WorkflowActivity.cs │ └── WorkflowExecutor.cs ├── NeuroSpeech.Workflows.csproj ├── README.md ├── ServiceProviderExtensions.cs ├── TaskHubExtensions.cs ├── TypeExtensions.cs ├── Workflow.cs └── WorkflowResult.cs ├── NodePackageService ├── LICENSE ├── NeuroSpeech.AspNet.NodeServices │ ├── Configuration │ │ ├── NodeServicesFactory.cs │ │ ├── NodeServicesOptions.cs │ │ └── NodeServicesServiceCollectionExtensions.cs │ ├── Content │ │ └── Node │ │ │ └── entrypoint-http.js │ ├── HostingModels │ │ ├── HttpNodeInstance.cs │ │ ├── INodeInstance.cs │ │ ├── NodeInvocationException.cs │ │ ├── NodeInvocationInfo.cs │ │ ├── NodeServicesOptionsExtensions.cs │ │ └── OutOfProcessNodeInstance.cs │ ├── INodeServices.cs │ ├── NeuroSpeech.AspNet.NodeServices.csproj │ ├── NodePackageService.cs │ ├── NodePackageServiceExtensions.cs │ ├── NodeServerOptions.cs │ ├── NodeServicesImpl.cs │ ├── TypeScript │ │ ├── HttpNodeInstanceEntryPoint.ts │ │ ├── Util │ │ │ ├── ArgsUtil.ts │ │ │ ├── ExitWhenParentExits.ts │ │ │ ├── OverrideStdOutputs.ts │ │ │ └── PatchModuleResolutionLStat.ts │ │ └── tsconfig.json │ ├── Util │ │ ├── EmbeddedResourceReader.cs │ │ ├── StringAsTempFile.cs │ │ └── TaskExtensions.cs │ ├── package.json │ └── webpack.config.js ├── NeuroSpeech.NodePackageInstaller │ ├── AtomicCache.cs │ ├── Extensions.cs │ ├── NeuroSpeech.NodePackageInstaller.csproj │ ├── NpmRegistry.cs │ ├── PackageInstallerOptions.cs │ ├── PackageInstallerService.cs │ ├── PackagePath.cs │ └── Tasks │ │ ├── FileTransactionTask.cs │ │ ├── PackageInstallTask.cs │ │ └── ProcessTask.cs ├── NodePackageService.sln1 ├── NodePackageService │ ├── NeuroSpeech.NodePackageService.csproj │ ├── NeuroSpeech.NodePackageService.csproj.user │ ├── NodePackageService.cs │ ├── NodePackageServiceExtensions.cs │ ├── NodeServerOptions.cs │ ├── Properties │ │ └── launchSettings.json │ └── Tasks │ │ └── HtmlProcessor.cs ├── NodeServerTest │ ├── InstallTest.cs │ ├── NodeServerTest.csproj │ └── UnitTest1.cs ├── README.md └── SampleWebApp │ ├── Controllers │ ├── NodeController.cs │ └── ValuesController.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── SampleWebApp.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── NuGet.Config ├── README.md ├── RetroFit ├── README.md ├── RetroCoreFit.Tests │ ├── ApiImpl.cs │ ├── RetroCoreFit.Tests.csproj │ └── UnitTest1.cs └── RetroCoreFit │ ├── ApiResponse.cs │ ├── BaseService.cs │ ├── HeaderAttribute.cs │ ├── InterfaceBuilder.cs │ ├── RestCall.cs │ ├── RestParameter.cs │ ├── RetroClient.cs │ ├── RetroCoreFit.csproj │ └── TypeExtensions.cs ├── StringExtensions ├── CacheExtensions.cs ├── StringExtensions.cs └── StringExtensions.csproj └── WKHtmlToXSharp ├── NeuroSpeech.WKHtmlToXSharp ├── NeuroSpeech.WKHtmlToXSharp.csproj └── WKHtmlToX.cs └── WKHtmlToXSharpTests ├── UnitTest1.cs └── WKHtmlToXSharpTests.csproj /.cr/personal/Editor/Painting/Structural Highlighting.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | True 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vs/ 3 | **/obj/ 4 | **/bin/ 5 | **/.vs/ 6 | /AspNetCoreExtensions/Properties/launchSettings.json 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Signup", 4 | "nameof" 5 | ] 6 | } -------------------------------------------------------------------------------- /AspNetCoreExtensions/AppCache`1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Extensions.Caching 8 | { 9 | /// 10 | /// 11 | /// 12 | [DIRegister(Extensions.DependencyInjection.ServiceLifetime.Singleton)] 13 | public class AppCache 14 | { 15 | private IMemoryCache cache; 16 | private string prefix; 17 | 18 | /// 19 | /// 20 | /// 21 | /// 22 | public AppCache(IMemoryCache cache) 23 | { 24 | this.cache = cache; 25 | this.prefix = typeof(T).Name + ":"; 26 | } 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | public T GetOrCreate(object key, Func factory) 35 | { 36 | return cache.GetOrCreate(prefix + key, ci => 37 | { 38 | // by default minimum expiration is one minute 39 | ci.SetSlidingExpiration(TimeSpan.FromMinutes(1)); 40 | return factory(ci); 41 | }); 42 | } 43 | 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | public Task GetOrCreateAsync(object key, Func> factory) 51 | { 52 | return cache.GetOrCreateAsync(prefix + key, ci => 53 | { 54 | // by default minimum expiration is one minute 55 | ci.SetSlidingExpiration(TimeSpan.FromMinutes(1)); 56 | return factory(ci); 57 | }); 58 | } 59 | 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | public Task GetOrCreateLargeTTLAsync(object key, Func> factory) 67 | { 68 | return cache.GetOrCreateAsync(prefix + key, ci => 69 | { 70 | // by default minimum expiration is one minute 71 | ci.SetSlidingExpiration(TimeSpan.FromMinutes(60)); 72 | return factory(ci); 73 | }); 74 | } 75 | 76 | /// 77 | /// 78 | /// 79 | /// 80 | public void Remove(string key) 81 | { 82 | cache.Remove(prefix + key); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/AspNetCoreExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.0.1 6 | true 7 | NeuroSpeech.AspNetCoreExtensions 8 | Akash Kava 9 | NeuroSpeech Technologies Pvt Ltd 10 | Useful Asp.Net Core Extensions 11 | Dependency Injection helper attributes 12 | Useful String Extensions 13 | Useful Dependency Injection Extensions 14 | CSVArrayModel Provider 15 | Useful Cache Extensions 16 | https://github.com/neurospeech/asp-net-core-extensions 17 | true 18 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 19 | 20 | Library 21 | true 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 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/AspNetCoreExtensions.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | IIS Express 5 | 6 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/AssemblyRegistrationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace NeuroSpeech 12 | { 13 | public class AssemblyRegistration 14 | { 15 | public Assembly Assembly; 16 | public bool ApplicationPart; 17 | public Type StartupType; 18 | } 19 | 20 | public interface IStartup 21 | { 22 | void Configure(IServiceCollection services); 23 | } 24 | 25 | public class AssemblyRegistrationList 26 | { 27 | public readonly List List = new List(); 28 | private readonly List Registered = new List(); 29 | private readonly IServiceCollection services; 30 | 31 | public AssemblyRegistrationList(IServiceCollection services) 32 | { 33 | this.services = services; 34 | } 35 | 36 | public void Add(Assembly assembly, bool registerApplicationPart, Type startupType = null) 37 | { 38 | var r = Registered.FirstOrDefault(x => x == assembly); 39 | if (r != null) 40 | return; 41 | Registered.Add(assembly); 42 | if (registerApplicationPart) 43 | { 44 | List.Add(assembly); 45 | } 46 | if (startupType != null) 47 | { 48 | var s = Activator.CreateInstance(startupType) as IStartup; 49 | s.Configure(services); 50 | } 51 | } 52 | 53 | } 54 | 55 | 56 | [ExcludeFromCodeCoverage] 57 | public static class AssemblyRegistrationExtensions 58 | { 59 | public static IMvcBuilder RegisterApplicationParts(this IMvcBuilder mvcBuilder, AssemblyRegistrationList list, params Assembly[] other ) 60 | { 61 | foreach(var o in other) 62 | { 63 | mvcBuilder.AddApplicationPart(o); 64 | } 65 | foreach (var a in list.List) 66 | { 67 | mvcBuilder.AddApplicationPart(a); 68 | } 69 | return mvcBuilder; 70 | } 71 | public static AssemblyRegistrationList RegisterAssemblies(this IServiceCollection services, AppDomain domain = null) 72 | { 73 | var all = (domain ?? AppDomain.CurrentDomain).GetAssemblies() 74 | .Select(x => new { 75 | Assembly = x, 76 | Core = x.GetCustomAttribute() 77 | }) 78 | .Where(x => x.Core != null); 79 | var list = new AssemblyRegistrationList(services); 80 | foreach (var item in all) 81 | { 82 | list.Add(item.Assembly, item.Core?.RegisterParts ?? false, item.Core?.StartupType); 83 | } 84 | 85 | list.Add(Assembly.GetEntryAssembly(), true); 86 | return list; 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/CSVArrayModelProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace Microsoft.AspNetCore.Mvc 12 | { 13 | 14 | /// 15 | /// Excluded by Akash Kava 16 | /// 17 | [ExcludeFromCodeCoverage] 18 | public class CSVArrayModelProvider : IModelBinderProvider 19 | { 20 | private static Dictionary binders = new Dictionary(); 21 | 22 | public IModelBinder GetBinder(ModelBinderProviderContext context) 23 | { 24 | var type = context.Metadata.ModelType; 25 | if (!type.IsArray) 26 | return null; 27 | IModelBinder binder; 28 | if (binders.TryGetValue(type, out binder)) 29 | return binder; 30 | binder = new CSVArrayModelBinder(type, context.Services.GetService()); 31 | binders[type] = binder; 32 | return binder; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/Class1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Reflection; 9 | using Newtonsoft.Json.Linq; 10 | using Microsoft.AspNetCore.Mvc.ModelBinding; 11 | using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace Microsoft.AspNetCore.Mvc 15 | { 16 | /// 17 | /// Excluded by Akash Kava 18 | /// 19 | [ExcludeFromCodeCoverage] 20 | public class CSVArrayModelBinder : IModelBinder 21 | { 22 | private Type type; 23 | private SimpleTypeModelBinder fallbackBinder; 24 | 25 | public CSVArrayModelBinder(Type type, ILoggerFactory loggerFactory) 26 | { 27 | this.type = type; 28 | this.fallbackBinder = new SimpleTypeModelBinder(type, loggerFactory); 29 | } 30 | 31 | public Task BindModelAsync(ModelBindingContext bindingContext) 32 | { 33 | var name = bindingContext.ModelName; 34 | var value = bindingContext.ValueProvider.GetValue(name); 35 | if (value.Length == 0 || !bindingContext.IsTopLevelObject) 36 | { 37 | return fallbackBinder.BindModelAsync(bindingContext); 38 | } 39 | 40 | var v = value.FirstValue; 41 | var tokens = v.Split(",").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)); 42 | 43 | var elementType = type.GetElementType(); 44 | 45 | var list = new ArrayList(); 46 | foreach (var token in tokens) 47 | { 48 | var id = Convert.ChangeType(token, elementType); 49 | list.Add(id); 50 | } 51 | Array a = list.ToArray(elementType); 52 | bindingContext.ModelState.SetModelValue(bindingContext.ModelName, a, v); 53 | bindingContext.Result = ModelBindingResult.Success(a); 54 | return Task.CompletedTask; 55 | } 56 | 57 | Task IModelBinder.BindModelAsync(ModelBindingContext bindingContext) 58 | { 59 | throw new NotImplementedException(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/FileLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspNetCoreExtensions 8 | { 9 | public class FileLock : IDisposable 10 | { 11 | readonly FileInfo file; 12 | readonly FileStream fs; 13 | private FileLock(FileInfo file, FileStream fs) 14 | { 15 | this.fs = fs; 16 | this.file = file; 17 | } 18 | 19 | public static async Task AcquireAsync( 20 | string filePath, 21 | TimeSpan? maxWait = null, 22 | TimeSpan? poolDelay = null 23 | ) 24 | { 25 | FileInfo lockFile = new FileInfo(filePath); 26 | 27 | if (!lockFile.Directory.Exists) 28 | { 29 | lockFile.Directory.Create(); 30 | } 31 | 32 | maxWait = maxWait ?? TimeSpan.FromMinutes(15); 33 | var start = DateTime.UtcNow; 34 | 35 | TimeSpan delay = poolDelay ?? TimeSpan.FromSeconds(5); 36 | 37 | while(true) 38 | { 39 | // try to open file... 40 | try 41 | { 42 | var fs = new FileStream(filePath, 43 | FileMode.OpenOrCreate, 44 | FileAccess.ReadWrite, FileShare.None); 45 | fs.Seek(0, SeekOrigin.Begin); 46 | await fs.WriteAsync(new byte[] { 1 }); 47 | return new FileLock(lockFile, fs); 48 | } catch 49 | { 50 | 51 | } 52 | 53 | await Task.Delay(delay); 54 | var diff = DateTime.UtcNow - start; 55 | if (diff > maxWait) 56 | { 57 | throw new TimeoutException(); 58 | } 59 | } 60 | } 61 | 62 | public void Dispose() 63 | { 64 | fs.Dispose(); 65 | // there is a possibility of other process to acquire 66 | // lock immediately so we will ignore exceptions here 67 | try 68 | { 69 | file.Delete(); 70 | } 71 | catch { } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/FromCookieAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.AspNetCore.Mvc 9 | { 10 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] 11 | public class FromCookieAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider 12 | { 13 | public BindingSource BindingSource => CookieBindingSource.Instance; 14 | 15 | public string Name { get; set; } 16 | } 17 | 18 | public static class CookieBindingSource 19 | { 20 | public static readonly BindingSource Instance = new BindingSource( 21 | "Cookie", 22 | "Cookie", 23 | isGreedy: false, 24 | isFromRequest: true); 25 | } 26 | 27 | public class CookieValueProviderFactory : IValueProviderFactory 28 | { 29 | public Task CreateValueProviderAsync(ValueProviderFactoryContext context) 30 | { 31 | var cookies = context.ActionContext.HttpContext.Request.Cookies; 32 | 33 | context.ValueProviders.Add(new CookieValueProvider(CookieBindingSource.Instance, cookies)); 34 | 35 | return Task.CompletedTask; 36 | } 37 | 38 | Task IValueProviderFactory.CreateValueProviderAsync(ValueProviderFactoryContext context) 39 | { 40 | return this.CreateValueProviderAsync(context); 41 | } 42 | } 43 | 44 | public class CookieValueProvider : BindingSourceValueProvider 45 | { 46 | public CookieValueProvider(BindingSource bindingSource, IRequestCookieCollection cookies) : base(bindingSource) 47 | { 48 | Cookies = cookies; 49 | } 50 | 51 | private IRequestCookieCollection Cookies { get; } 52 | 53 | public override bool ContainsPrefix(string prefix) 54 | { 55 | return Cookies.ContainsKey(prefix); 56 | } 57 | 58 | public override ValueProviderResult GetValue(string key) 59 | { 60 | return Cookies.TryGetValue(key, out var value) ? new ValueProviderResult(value) : ValueProviderResult.None; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/Http404NotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Linq; 4 | 5 | namespace Microsoft.AspNetCore.Mvc 6 | { 7 | /// 8 | /// 9 | /// 10 | [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 11 | public class Http404NotFoundException : HttpStatusException 12 | { 13 | /// 14 | /// 15 | /// 16 | /// 17 | public Http404NotFoundException(string message) : base(404, message) 18 | { 19 | 20 | } 21 | 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | [ExcludeFromCodeCoverage] 28 | public Http404NotFoundException(string message, Exception inner) : base(404, message, inner) 29 | { 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/HttpStatusException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Microsoft.AspNetCore.Mvc 5 | { 6 | /// 7 | /// 8 | /// 9 | public class HttpStatusException : Exception 10 | { 11 | 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | public HttpStatusException(int code, string message) : base(message) 18 | { 19 | this.Code = code; 20 | } 21 | 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 29 | public HttpStatusException(int code, string message, Exception inner) : base(message, inner) 30 | { 31 | this.Code = code; 32 | } 33 | 34 | /// 35 | /// 36 | /// 37 | public int Code { get; private set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AspNetCoreExtensions/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Linq; 6 | 7 | namespace NeuroSpeech 8 | { 9 | /// 10 | /// 11 | /// 12 | public static class JsonExtensions 13 | { 14 | 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// As method simply converts key value pair in different form, no code coverage is needed. 20 | /// 21 | /// 22 | /// 23 | /// 24 | [ExcludeFromCodeCoverage] 25 | public static Dictionary ToDictionary(this JObject jObject) 26 | { 27 | var dict = new Dictionary(); 28 | 29 | foreach (var p in jObject.Properties()) 30 | { 31 | dict[p.Name] = p.Value.ToObject(); 32 | } 33 | return dict; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk.Tests/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using EFCoreBulk.Model; 2 | using Microsoft.EntityFrameworkCore; 3 | using NeuroSpeech.EFCore.Mock; 4 | using NeuroSpeech.EFCoreLiveMigration; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using Xunit.Abstractions; 9 | 10 | namespace EFCoreBulk.Tests 11 | { 12 | // Assuming you have `AppDbContext` as your EF DbContext in side your actual application project 13 | // For test purposes, you will have to use AppDbTestContext 14 | // or you can use AppDbTestContext as dependency in your DI container 15 | public class AppDbTestContext : ShopContext 16 | { 17 | 18 | // this is important 19 | // since databases are dynamically created and destroyed 20 | // MockDatabaseContext.Current.ConnectionString contains 21 | // correct database for current test context 22 | 23 | // MockDatabaseContext.Current will work correctly with async await 24 | // without worrying about passing context 25 | 26 | public AppDbTestContext(DbContextOptions options): base(options) 27 | { 28 | 29 | } 30 | 31 | public AppDbTestContext(): base(Create(MockDatabaseContext.Current.ConnectionString)) 32 | { 33 | 34 | } 35 | 36 | public AppDbTestContext(string cnstr) : base(Create(cnstr)) { 37 | 38 | } 39 | 40 | public static DbContextOptions Create(string connectionString) { 41 | DbContextOptionsBuilder builder = new DbContextOptionsBuilder(); 42 | builder.UseSqlServer(connectionString); 43 | return builder.Options; 44 | } 45 | } 46 | 47 | 48 | public abstract class BaseTest : 49 | MockSqlDatabaseContext 50 | { 51 | 52 | public BaseTest(ITestOutputHelper writer) 53 | { 54 | this.Writer = writer; 55 | } 56 | 57 | protected override void DumpLogs() 58 | { 59 | this.Writer.WriteLine(base.GeneratedLog); 60 | } 61 | 62 | public ITestOutputHelper Writer { get; private set; } 63 | 64 | public AppDbTestContext CreateContext() 65 | { 66 | AppDbTestContext db = new AppDbTestContext(ConnectionString); 67 | // db.Database.EnsureCreated(); 68 | db.MigrateSqlServer() 69 | .Migrate(); 70 | Seed(db); 71 | return db; 72 | } 73 | 74 | public void Seed(AppDbTestContext db) { 75 | db.Products.AddRange(new Product { 76 | Name = "Apple iPhone" 77 | }, 78 | new Product 79 | { 80 | Name = "Google Pixel" 81 | }, 82 | new Product 83 | { 84 | Name = "Microsoft Surface" 85 | }, 86 | new Product { 87 | Name = "Google Pixel Large" 88 | }); 89 | 90 | db.SaveChanges(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk.Tests/EF.Core.Bulk.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 1.1.14 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk.Tests/ShopContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Diagnostics; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Query; 5 | using Microsoft.EntityFrameworkCore.Query.Internal; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.ComponentModel; 10 | using System.ComponentModel.DataAnnotations; 11 | using System.ComponentModel.DataAnnotations.Schema; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Threading.Tasks; 15 | 16 | namespace EFCoreBulk.Model 17 | { 18 | public class ShopContext : DbContext 19 | { 20 | public ShopContext(DbContextOptions options) 21 | :base(options) 22 | { 23 | 24 | } 25 | 26 | public ShopContext():base(new DbContextOptionsBuilder() 27 | .UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Shop2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False") 28 | .Options) 29 | { 30 | 31 | } 32 | 33 | 34 | public DbSet Products { get; set; } 35 | 36 | public DbSet Accounts { get; set; } 37 | 38 | public DbSet ProductAccounts { get; set; } 39 | 40 | protected override void OnModelCreating(ModelBuilder modelBuilder) 41 | { 42 | base.OnModelCreating(modelBuilder); 43 | 44 | modelBuilder.Entity().HasKey(x => new { x.AccountID, x.ProductID }); 45 | 46 | modelBuilder.Entity() 47 | .Property(x => x.Archived) 48 | .HasDefaultValueSql("0") 49 | .ValueGeneratedNever(); 50 | } 51 | } 52 | 53 | [Table("Products")] 54 | public class Product { 55 | 56 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 57 | public long ProductID { get; set; } 58 | 59 | public string Name { get; set; } 60 | 61 | 62 | public bool Archived { get; set; } 63 | 64 | public DateTime? LastUpdated { get; set; } 65 | 66 | [InverseProperty(nameof(ProductAccount.Product))] 67 | public ICollection ProductAccounts { get; set; } 68 | 69 | // [InverseProperty(nameof(Email.Product))] 70 | // public ICollection Emails { get; set; } 71 | 72 | } 73 | 74 | [Table("ProductAccounts")] 75 | public class ProductAccount { 76 | 77 | public long ProductID { get; set; } 78 | 79 | public long AccountID { get; set; } 80 | 81 | [ForeignKey(nameof(ProductID))] 82 | public Product Product { get; set; } 83 | 84 | [ForeignKey(nameof(AccountID))] 85 | public Account Account { get; set; } 86 | } 87 | 88 | [Table("Accounts")] 89 | public class Account { 90 | 91 | [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 92 | public long AccountID { get; set; } 93 | 94 | public string Name { get; set; } 95 | 96 | public bool Archived { get; set; } 97 | 98 | [InverseProperty(nameof(ProductAccount.Account))] 99 | public ICollection ProductAccounts { get; set; } 100 | } 101 | 102 | //[Table("Emails")] 103 | //public class Email 104 | //{ 105 | 106 | // [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 107 | // public long EmailID { get; set; } 108 | 109 | // public string Subject { get; set; } 110 | 111 | // public long? ProductID { get; set; } 112 | 113 | // [ForeignKey(nameof(ProductID))] 114 | // [InverseProperty(nameof(Model.Product.Emails))] 115 | // public Product Product { get; set; } 116 | 117 | // [InverseProperty(nameof(EmailRecipient.Email))] 118 | // public List EmailRecipients { get; set; } 119 | 120 | //} 121 | 122 | //[Table("EmailRecipients")] 123 | //public class EmailRecipient 124 | //{ 125 | // [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 126 | // public long EmailRecipientID { get; set; } 127 | 128 | // public long EmailID { get; set; } 129 | 130 | // public DateTime? TimeRead { get; set; } 131 | 132 | // [ForeignKey(nameof(EmailID))] 133 | // [InverseProperty(nameof(Model.Email.EmailRecipients))] 134 | // public Email Email { get; set; } 135 | 136 | 137 | 138 | //} 139 | } 140 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using EFCoreBulk.Model; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace EFCoreBulk.Tests 11 | { 12 | public class UnitTest1: BaseTest 13 | { 14 | public UnitTest1(ITestOutputHelper writer) : base(writer) 15 | { 16 | 17 | } 18 | 19 | [Fact] 20 | public async Task BulkUpdate() 21 | { 22 | using (var db = this.CreateContext()) 23 | { 24 | await db.ProductAccounts 25 | .Where(x => x.Account.Archived != false) 26 | .Select(x => x.Product) 27 | .Select(x => new Product { 28 | Archived = true 29 | }) 30 | .UpdateAsync(); 31 | } 32 | } 33 | 34 | [Fact] 35 | public async Task BulkUpdate3() 36 | { 37 | using (var db = this.CreateContext()) 38 | { 39 | await db.ProductAccounts 40 | .Where(x => x.Account.Archived != false) 41 | .Select(x => x.Product) 42 | .Select(x => new Product 43 | { 44 | Archived = true 45 | }) 46 | .UpdateAsync(); 47 | } 48 | } 49 | 50 | 51 | [Fact] 52 | public async Task BulkUpdateDate() 53 | { 54 | using (var db = this.CreateContext()) 55 | { 56 | var now = DateTime.UtcNow; 57 | 58 | await db.ProductAccounts 59 | .Where(x => x.Account.Archived != false) 60 | .Select(x => x.Product) 61 | .Select(x => new Product 62 | { 63 | LastUpdated = now 64 | }) 65 | .UpdateAsync(); 66 | } 67 | } 68 | 69 | [Fact] 70 | public async Task ContainsTest() 71 | { 72 | using (var db = this.CreateContext()) 73 | { 74 | var list = new long[] { 1, 2, 3 }; 75 | await db.Products.Where(x => list.Contains(x.ProductID)).DeleteAsync(); 76 | } 77 | } 78 | 79 | [Fact] 80 | public async Task BulkInsert() 81 | { 82 | using (var db = this.CreateContext()) { 83 | 84 | var googleProducts = db.Products.Where(p => p.Name.StartsWith("google")); 85 | 86 | int count = await db.InsertAsync(googleProducts.Select(p => new Product { 87 | Name = p.Name + " V2" 88 | })); 89 | 90 | Assert.Equal(2, count); 91 | 92 | count = await db.Products.CountAsync(); 93 | 94 | Assert.Equal(6, count); 95 | 96 | count = await googleProducts.Select(p => new Product { 97 | Archived = true 98 | }).UpdateAsync(); 99 | 100 | Assert.Equal(4, count); 101 | 102 | count = await db.Products.Where(x => x.Archived == true).CountAsync(); 103 | 104 | Assert.Equal(4, count); 105 | 106 | count = await db.DeleteAsync(db.Products.Where(x => x.Archived == true)); 107 | 108 | Assert.Equal(4, count); 109 | 110 | count = await db.Products.Where(x => x.Archived == true).CountAsync(); 111 | 112 | Assert.Equal(0, count); 113 | 114 | count = await db.Products.CountAsync(); 115 | 116 | Assert.Equal(2, count); 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2037 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EF.Core.Bulk", "EF.Core.Bulk\EF.Core.Bulk.csproj", "{87CAFD46-0D7C-4EEA-AC0C-9E2A85DFDB07}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EF.Core.Bulk.Tests", "EF.Core.Bulk.Tests\EF.Core.Bulk.Tests.csproj", "{9E496FAE-A9D6-486B-A7DB-ED532F8AEF70}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {87CAFD46-0D7C-4EEA-AC0C-9E2A85DFDB07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {87CAFD46-0D7C-4EEA-AC0C-9E2A85DFDB07}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {87CAFD46-0D7C-4EEA-AC0C-9E2A85DFDB07}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {87CAFD46-0D7C-4EEA-AC0C-9E2A85DFDB07}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9E496FAE-A9D6-486B-A7DB-ED532F8AEF70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9E496FAE-A9D6-486B-A7DB-ED532F8AEF70}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9E496FAE-A9D6-486B-A7DB-ED532F8AEF70}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9E496FAE-A9D6-486B-A7DB-ED532F8AEF70}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {A44FE483-D7ED-47CC-80D9-9D5D8787D819} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # EF1001: Internal EF Core API usage. 4 | dotnet_diagnostic.EF1001.severity = none 5 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/EF.Core.Bulk.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | netcoreapp3.1 6 | 7 | 8 | 1.0.1 9 | Akash Kava 10 | NeuroSpeech Technologies Pvt Ltd 11 | https://github.com/neurospeech/ef-core-bulk/blob/master/LICENSE 12 | https://github.com/neurospeech/ef-core-bulk/blob/master/README.md 13 | https://github.com/neurospeech/ef-core-bulk 14 | 2018, NeuroSpeech Technologies Pvt Ltd 15 | EF Core bulk operations - INSERT SELECT, UPDATE SELECT , DELETE SELECT 16 | true 17 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 18 | EFCoreBulk 19 | EFCoreBulk 20 | 21 | 22 | 23 | 7.2 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/Model/ContextHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; 3 | using Microsoft.EntityFrameworkCore.Query; 4 | using Microsoft.EntityFrameworkCore.Query.Internal; 5 | using System; 6 | using System.Linq; 7 | using System.Reflection; 8 | 9 | namespace EFCoreBulk 10 | { 11 | public static class ContextHelper 12 | { 13 | public static DbContext GetDbContext(this IQueryable source) 14 | { 15 | var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance; 16 | var queryCompiler = typeof(EntityQueryProvider).GetField("_queryCompiler", bindingFlags).GetValue(source.Provider); 17 | var queryContextFactory = queryCompiler.GetType().GetField("_queryContextFactory", bindingFlags).GetValue(queryCompiler); 18 | 19 | var dependencies = typeof(RelationalQueryContextFactory).GetField("_dependencies", bindingFlags).GetValue(queryContextFactory); 20 | var queryContextDependencies = typeof(DbContext).Assembly.GetType(typeof(QueryContextDependencies).FullName); 21 | var stateManagerProperty = queryContextDependencies.GetProperty("StateManager", bindingFlags | BindingFlags.Public).GetValue(dependencies); 22 | var stateManager = (IStateManager)stateManagerProperty; 23 | 24 | return stateManager.Context; 25 | } 26 | 27 | 28 | internal static Type GetTypeFromAssembly_Core(this Type fromType, string name) 29 | { 30 | return fromType.Assembly.GetType(name); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/Model/EFStringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace EFCoreBulk 6 | { 7 | internal static class EFStringHelper 8 | { 9 | public static StringBuilder AppendWithSeparator(this StringBuilder sb, string text, string separator = ", ") 10 | { 11 | if (sb.Length > 0) 12 | { 13 | sb.Append(separator); 14 | } 15 | return sb.Append(text); 16 | } 17 | 18 | public static RT Reduce(this IEnumerable list, Func func, RT start) 19 | { 20 | RT x = start; 21 | int i = 0; 22 | foreach(var item in list) 23 | { 24 | x = func(item, x, i); 25 | } 26 | return x; 27 | } 28 | 29 | public static StringBuilder ReduceToString(this IEnumerable list, Func func, StringBuilder start = null) 30 | { 31 | start = start ?? new StringBuilder(); 32 | int index = 0; 33 | foreach(var item in list) 34 | { 35 | start = func(item, start, index); 36 | } 37 | return start; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/Model/LiteralExpressionVisitor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Query; 3 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 4 | using Microsoft.EntityFrameworkCore.Storage; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Reflection; 10 | using System.Text; 11 | 12 | namespace EFCoreBulk 13 | { 14 | internal class LiteralExpressionVisitor : ExpressionVisitor 15 | { 16 | 17 | 18 | public IEnumerable<(PropertyInfo,object)> GetLiteralAssignments(Expression exp) 19 | { 20 | Visit(exp); 21 | 22 | foreach(var b in initList) 23 | { 24 | var r = Expression 25 | .Lambda(b.Expression, plist) 26 | .Compile() 27 | .DynamicInvoke(new object[] { null }); 28 | 29 | yield return (b.Member as PropertyInfo, r); 30 | 31 | } 32 | } 33 | 34 | private List plist = new List(); 35 | private LinkedList initList = new LinkedList(); 36 | 37 | private ConstantExpression currentConstant = null; 38 | readonly EntityProjectionExpression ke; 39 | 40 | protected override Expression VisitMemberInit(MemberInitExpression node) 41 | { 42 | foreach (var b in node.Bindings.OfType()) 43 | { 44 | currentConstant = null; 45 | this.Visit(b.Expression); 46 | if (currentConstant != null) 47 | { 48 | initList.AddLast(b); 49 | } 50 | } 51 | return base.VisitMemberInit(node); 52 | } 53 | 54 | protected override Expression VisitConstant(ConstantExpression node) 55 | { 56 | currentConstant = node; 57 | return base.VisitConstant(node); 58 | } 59 | 60 | protected override Expression VisitParameter(ParameterExpression node) 61 | { 62 | currentConstant = null; 63 | if (!plist.Any(x => x.Name == node.Name)) 64 | plist.Add(node); 65 | return base.VisitParameter(node); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/Model/Parameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query.Internal; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace EFCoreBulk 7 | { 8 | //public class Parameters : IParameterValues 9 | //{ 10 | // private Dictionary d = new Dictionary(); 11 | // public IReadOnlyDictionary ParameterValues => d; 12 | 13 | // public void AddParameter(string name, object value) 14 | // { 15 | // d.Add(name, value); 16 | // } 17 | 18 | // public object RemoveParameter(string name) 19 | // { 20 | // if (d.Remove(name, out object v)) 21 | // return v; 22 | // return null; 23 | // } 24 | 25 | // public void SetParameter(string name, object value) 26 | // { 27 | // d[name] = value; 28 | // } 29 | //} 30 | } 31 | -------------------------------------------------------------------------------- /EF.Core.Bulk/EF.Core.Bulk/Model/QueryInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 2 | using Microsoft.EntityFrameworkCore.Storage; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace EFCoreBulk 8 | { 9 | public class QueryInfo 10 | { 11 | public string Command { get; set; } 12 | public SelectExpression Sql { get; set; } 13 | public IReadOnlyDictionary ParameterValues { get; set; } 14 | public List<(string, object)> ConstantProjections { get; set; } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EFCore.Mock/NeuroSpeech.EFCore.Mock/MockDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Threading; 6 | 7 | namespace NeuroSpeech.EFCore.Mock 8 | { 9 | /// 10 | /// Provides mock context for EF database provider. 11 | /// 12 | /// For example, look at MockSqlDatabaseContext 13 | /// 14 | public abstract class MockDatabaseContext : IDisposable 15 | { 16 | 17 | private static AsyncLocal currentGuid = new AsyncLocal(); 18 | 19 | private const string ContextName = "MockDatabaseContext"; 20 | 21 | public virtual string Version => throw new NotImplementedException("Please implement Version property"); 22 | 23 | protected List TempFiles { get; } = new List(); 24 | 25 | public string CreateTempFile() 26 | { 27 | string tmp = Path.GetTempFileName(); 28 | TempFiles.Add(tmp); 29 | return tmp; 30 | } 31 | 32 | private StringWriter logger = new StringWriter(); 33 | 34 | public virtual void WriteLine(string line) 35 | { 36 | logger.WriteLine(line); 37 | } 38 | 39 | internal MockDatabaseContext() 40 | { 41 | //CallContext.LogicalSetData(ContextName, guid); 42 | currentGuid.Value = guid; 43 | Contexts[guid] = this; 44 | 45 | OnBeforeInitialize(); 46 | 47 | Initialize(); 48 | 49 | OnInitialized(); 50 | } 51 | 52 | internal abstract void Initialize(); 53 | 54 | protected virtual void OnBeforeInitialize() 55 | { 56 | 57 | } 58 | 59 | 60 | 61 | protected virtual void OnInitialized() 62 | { 63 | 64 | } 65 | 66 | public string ConnectionString { get; internal protected set; } 67 | 68 | /// 69 | /// Gets or sets a value indicating whether to delete database after all tests are finished 70 | /// 71 | /// 72 | /// true if [do not delete]; otherwise, false. 73 | /// 74 | public bool DoNotDelete { get; set; } 75 | 76 | public static MockDatabaseContext Current 77 | { 78 | get 79 | { 80 | string guid = currentGuid.Value; 81 | if (guid == null) 82 | return null; 83 | if (Contexts.TryGetValue(guid, out MockDatabaseContext val)) 84 | return val; 85 | return null; 86 | } 87 | } 88 | 89 | 90 | private static ConcurrentDictionary Contexts = new ConcurrentDictionary(); 91 | 92 | 93 | public string DBName { get; protected set; } 94 | 95 | private string guid = Guid.NewGuid().ToString(); 96 | 97 | public virtual void Dispose() 98 | { 99 | DumpLogs(); 100 | 101 | //CallContext.FreeNamedDataSlot(ContextName); 102 | currentGuid.Value = null; 103 | Contexts.TryRemove(guid, out MockDatabaseContext mv); 104 | 105 | if (!DoNotDelete) 106 | { 107 | DeleteDatabase(); 108 | } 109 | 110 | foreach (var temp in TempFiles) 111 | { 112 | try 113 | { 114 | if (System.IO.File.Exists(temp)) 115 | System.IO.File.Delete(temp); 116 | } 117 | catch 118 | { 119 | // swallow.... 120 | } 121 | } 122 | 123 | } 124 | 125 | internal abstract void DeleteDatabase(); 126 | 127 | protected virtual void DumpLogs() 128 | { 129 | System.Diagnostics.Debug.WriteLine(GeneratedLog); 130 | } 131 | 132 | public string GeneratedLog => logger.GetStringBuilder().ToString(); 133 | 134 | public Dictionary Bag { get; } = new Dictionary(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /EFCore.Mock/NeuroSpeech.EFCore.Mock/MockSqlDatabaseContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Data.SqlClient; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace NeuroSpeech.EFCore.Mock 7 | { 8 | /// 9 | /// This class will create a temporary database inside SQL Server LocalDB instance. 10 | /// 11 | /// In order to avoid multiple creation and initial seeding, this class will create 12 | /// database and perform migration including seeding only once per change in version of 13 | /// assembly containing the context. 14 | /// 15 | /// For every subsequent calls, MDF/LDF files of initial version will be copied as a 16 | /// new database for every test. 17 | /// 18 | /// The type of the database context. 19 | /// The type of the context configuration. 20 | /// 21 | public class MockSqlDatabaseContext : MockDatabaseContext 22 | where TDbContext : DbContext, new() 23 | 24 | { 25 | 26 | public override string Version => 27 | typeof(TDbContext).BaseType == typeof(DbContext) ? 28 | typeof(TDbContext).Assembly.GetName().Version.ToString() : 29 | typeof(TDbContext).BaseType.Assembly.GetName().Version.ToString(); 30 | 31 | protected override void OnBeforeInitialize() 32 | { 33 | //try 34 | //{ 35 | // Database.SetInitializer(new MigrateDatabaseToLatestVersion()); 36 | //} 37 | //catch (Exception ex) 38 | //{ 39 | // WriteLine(ex.ToString()); 40 | 41 | //} 42 | 43 | } 44 | 45 | internal override void DeleteDatabase() 46 | { 47 | if (!string.IsNullOrWhiteSpace(DBName)) 48 | { 49 | SqlHelper.Execute("USE master;"); 50 | SqlHelper.Execute($"ALTER DATABASE [{DBName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"); 51 | SqlHelper.Execute($"DROP DATABASE [{DBName}]"); 52 | DBName = null; 53 | } 54 | 55 | try 56 | { 57 | foreach (var t in TempFiles.ToList()) 58 | { 59 | var file = new System.IO.FileInfo(t); 60 | if (file.Exists) 61 | { 62 | file.Delete(); 63 | } 64 | TempFiles.Remove(t); 65 | } 66 | //TempFiles.Clear(); 67 | } 68 | catch (Exception ex) 69 | { 70 | WriteLine(ex.ToString()); 71 | } 72 | } 73 | 74 | internal override void Initialize() 75 | { 76 | 77 | try 78 | { 79 | 80 | #pragma warning disable CS0436 // Type conflicts with imported type 81 | //SqlServerTypes.Utilities.LoadNativeAssemblies(AppDomain.CurrentDomain.BaseDirectory); 82 | #pragma warning restore CS0436 // Type conflicts with imported type 83 | 84 | 85 | OnBeforeInitialize(); 86 | 87 | 88 | 89 | var od = OriginalSqlDatabase.GetInstance(() => Version); 90 | 91 | TempFiles.Add(od.DbFile); 92 | TempFiles.Add(od.LogFile); 93 | 94 | 95 | DBName = od.DBName; 96 | 97 | ConnectionString = (new SqlConnectionStringBuilder() 98 | { 99 | DataSource = "(localdb)\\MSSQLLocalDB", 100 | //sqlCnstr.AttachDBFilename = t; 101 | InitialCatalog = od.DBName, 102 | IntegratedSecurity = true, 103 | ApplicationName = "EntityFramework" 104 | }).ToString(); 105 | } 106 | catch (Exception ex) 107 | { 108 | WriteLine(ex.ToString()); 109 | throw; 110 | } 111 | 112 | 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /EFCore.Mock/NeuroSpeech.EFCore.Mock/NeuroSpeech.EFCore.Mock.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Akash Kava 6 | NeuroSpeech Technologies Pvt Ltd 7 | Mock for EFCore with LocalDB 8 | https://github.com/neurospeech/ef-core-localdb-mock 9 | https://github.com/neurospeech/ef-core-localdb-mock 10 | EF Core, LocalDB, Mock 11 | 2017 NeuroSpeech 12 | https://github.com/neurospeech/ef-core-localdb-mock/blob/master/LICENSE 13 | 1.0.1 14 | true 15 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /EFCore.Mock/NeuroSpeech.EFCore.Mock/SqlHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.SqlClient; 2 | using System.Collections.Generic; 3 | 4 | namespace NeuroSpeech.EFCore.Mock 5 | { 6 | public class SqlHelper 7 | { 8 | 9 | 10 | public static void Execute(string command) 11 | { 12 | using (var c = new SqlConnection($"server=(localdb)\\MSSQLLocalDB")) 13 | { 14 | c.Open(); 15 | 16 | 17 | using (var cmd = c.CreateCommand()) 18 | { 19 | cmd.CommandText = command; 20 | cmd.ExecuteNonQuery(); 21 | } 22 | } 23 | 24 | } 25 | 26 | 27 | public static void ExecuteSP(string command, params KeyValuePair[] pairs) 28 | { 29 | using (var c = new SqlConnection($"server=(localdb)\\MSSQLLocalDB")) 30 | { 31 | c.Open(); 32 | 33 | 34 | using (var cmd = c.CreateCommand()) 35 | { 36 | cmd.CommandText = command; 37 | 38 | foreach (var v in pairs) 39 | { 40 | cmd.Parameters.AddWithValue(v.Key, v.Value); 41 | } 42 | 43 | cmd.ExecuteNonQuery(); 44 | } 45 | } 46 | 47 | } 48 | 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /EFCore.Mock/README.md: -------------------------------------------------------------------------------- 1 | # EFCore Mock 2 | 3 | Mocking EF Core is difficult with InMemory as it does not support Raw SQL. Real life projects are way too complicated to be tested directly on in memory. 4 | 5 | So we have made a Sql LocalDB mocking library which you can use to create unit tests against localdb. 6 | 7 | ## Features 8 | 9 | 1. Create a new database for given version first time. And run migrations. Since creation of database will be slower, to speed up process, we detach the database and keep copy. 10 | 2. For every unit test, we copy the detached database as a new database and attach to server as a new localdb instance. 11 | 3. Run the unit test and delete the database after end of it. 12 | 4. To retain copy of database, you can set `MockDatabaseContext.Current.DoNotDelete = true` in your unit test. 13 | 5. You can also get ConnectionString from `MockDatabaseContext.Current.ConnectionString` of currently active databse. 14 | 15 | 16 | ## Create Base Test 17 | 18 | You need to create an Context that derives from your existing context. And you can write up few lines in `BaseTest` to write your unit tests. 19 | 20 | ```c# 21 | 22 | // Assuming you have `ShopContext` as your EF DbContext in side your actual application project 23 | // For test purposes, you will have to use AppDbTestContext 24 | // or you can use AppDbTestContext as dependency in your DI container 25 | public class AppDbTestContext : ShopContext 26 | { 27 | 28 | // this is important 29 | // since databases are dynamically created and destroyed 30 | // MockDatabaseContext.Current.ConnectionString contains 31 | // correct database for current test context 32 | 33 | // MockDatabaseContext.Current will work correctly with async await 34 | // without worrying about passing context 35 | 36 | public AppDbTestContext(DbContextOptions options): base(options) 37 | { 38 | 39 | } 40 | 41 | public AppDbTestContext(): base(Create(MockDatabaseContext.Current.ConnectionString)) 42 | { 43 | 44 | } 45 | 46 | public AppDbTestContext(string cnstr) : base(Create(cnstr)) { 47 | 48 | } 49 | 50 | public static DbContextOptions Create(string connectionString) { 51 | DbContextOptionsBuilder builder = new DbContextOptionsBuilder(); 52 | builder.UseSqlServer(connectionString); 53 | return builder.Options; 54 | } 55 | } 56 | 57 | 58 | 59 | public abstract class BaseTest : 60 | MockSqlDatabaseContext 61 | { 62 | 63 | public BaseTest() 64 | { 65 | } 66 | 67 | protected override void DumpLogs() 68 | { 69 | System.Diagnostics.Debug.WriteLine(base.GeneratedLog); 70 | } 71 | 72 | public ITestOutputHelper Writer { get; private set; } 73 | 74 | public AppDbTestContext CreateContext() 75 | { 76 | AppDbTestContext db = new AppDbTestContext(ConnectionString); 77 | 78 | // use EFCoreLiveMigration to create database structure 79 | db.MigrateSqlServer() 80 | .Migrate(); 81 | Seed(db); 82 | return db; 83 | } 84 | 85 | public void Seed(AppDbTestContext db) { 86 | } 87 | } 88 | 89 | ``` 90 | 91 | ## Write Unit Test 92 | 93 | ```c# 94 | 95 | [TestClass] 96 | public class UnitTest1: BaseTest { 97 | 98 | 99 | // Each unit test instance runs in a separate 100 | // database instance 101 | // After success or failed test, database will be deleted 102 | 103 | 104 | [Test] 105 | public async Task SampleTest1() { 106 | 107 | 108 | using(var db = this.CreateContext()) { 109 | // database is only alive within this scope... 110 | } 111 | } 112 | 113 | [Test] 114 | public async Task KeepDatabase() { 115 | 116 | // following line will keep database in the sql localdb 117 | MockDatabaseContext.Current.DoNotDelete = true; 118 | 119 | 120 | } 121 | 122 | } 123 | 124 | 125 | ``` -------------------------------------------------------------------------------- /NeuroSpeech.AppCache/AppCache.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Extensions.Caching 8 | { 9 | /// 10 | /// 11 | /// 12 | [DIRegister(ServiceLifetime.Singleton)] 13 | public class AppCache 14 | { 15 | private IMemoryCache cache; 16 | private string prefix; 17 | 18 | /// 19 | /// 20 | /// 21 | /// 22 | public AppCache(IMemoryCache cache) 23 | { 24 | this.cache = cache; 25 | this.prefix = typeof(T).FullName + ":"; 26 | } 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | public T GetOrCreate(object key, Func factory) 35 | { 36 | return cache.GetOrCreate(prefix + key, ci => 37 | { 38 | // by default minimum expiration is one minute 39 | ci.SetSlidingExpiration(TimeSpan.FromMinutes(1)); 40 | return factory(ci); 41 | }); 42 | } 43 | 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | public Task GetOrCreateAsync(object key, Func> factory) 51 | { 52 | return AtomicGetOrCreateAsync(prefix + key, ci => 53 | { 54 | // by default minimum expiration is one minute 55 | ci.SetSlidingExpiration(TimeSpan.FromMinutes(1)); 56 | return factory(ci); 57 | }); 58 | } 59 | 60 | /// 61 | /// 62 | /// 63 | /// 64 | public void Remove(string key) 65 | { 66 | cache.Remove(prefix + key); 67 | } 68 | 69 | private object lockObject = new object(); 70 | 71 | private Task AtomicGetOrCreateAsync( 72 | string key, 73 | Func> factory) 74 | { 75 | return cache.GetOrCreate>(key, (entry) => 76 | { 77 | lock (lockObject) 78 | { 79 | Func> fx = async (e2) => { 80 | try 81 | { 82 | return await factory(e2); 83 | } 84 | catch 85 | { 86 | // forcing getting out of lock... 87 | await Task.Delay(10); 88 | // it is stale... 89 | lock (lockObject) 90 | { 91 | cache.Remove(key); 92 | } 93 | 94 | // awaiter must know that 95 | // first request was unsuccessful 96 | // and must try again... 97 | throw; 98 | } 99 | }; 100 | return cache.GetOrCreate>(key, (e1) => 101 | { 102 | return fx(e1); 103 | }); 104 | } 105 | }); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /NeuroSpeech.AppCache/NeuroSpeech.AppCache.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech 6 | NeuroSpeech.AppCache 7 | Akash Kava 8 | NeuroSpeech Technologies Pvt Ltd 9 | 1.0.1 10 | true 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /NeuroSpeech.DependencyInjectionExtensions/AssemblyRegistrationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.CompilerServices; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace NeuroSpeech 13 | { 14 | 15 | [ExcludeFromCodeCoverage] 16 | public static class AssemblyRegistrationExtensions 17 | { 18 | 19 | /// 20 | /// Reads embedded string resource from given assembly 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static async Task GetStringResourceAsync( 27 | this Assembly assembly, 28 | string name, 29 | Encoding encoding = null) 30 | { 31 | using (var rs = assembly.GetManifestResourceStream(name)) 32 | { 33 | using (var reader = new StreamReader(rs, encoding ?? Encoding.UTF8)) 34 | { 35 | return await reader.ReadToEndAsync(); 36 | } 37 | } 38 | } 39 | 40 | /// 41 | /// Reads embedded string resource from given assembly 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | public static string GetStringResource( 48 | this Assembly assembly, 49 | string name, 50 | Encoding encoding = null) 51 | { 52 | using (var rs = assembly.GetManifestResourceStream(name)) 53 | { 54 | using (var reader = new StreamReader(rs, encoding ?? Encoding.UTF8)) 55 | { 56 | return reader.ReadToEnd(); 57 | } 58 | } 59 | } 60 | 61 | private static List initializations 62 | = new List(); 63 | 64 | public static void InitializeSingletons(this IServiceProvider serviceProvider) 65 | { 66 | foreach (var i in initializations) 67 | { 68 | serviceProvider.GetService(i); 69 | } 70 | 71 | } 72 | 73 | /// 74 | /// Registers given assembly types for DI 75 | /// 76 | /// 77 | /// 78 | public static void RegisterAssembly( 79 | this IServiceCollection services, 80 | Assembly assembly, 81 | Func getRegisterAttribute = null) 82 | { 83 | 84 | getRegisterAttribute = getRegisterAttribute ?? ((t) => t.GetCustomAttribute()); 85 | 86 | foreach (var type in assembly.GetExportedTypes()) 87 | { 88 | 89 | try 90 | { 91 | var a = getRegisterAttribute?.Invoke(type); 92 | if (a == null) 93 | continue; 94 | 95 | Type baseType = a.BaseType; 96 | 97 | if(a.Init) 98 | { 99 | initializations.Add(a.BaseType ?? type); 100 | } 101 | 102 | Func factory = null; 103 | if (a.Factory != null) 104 | { 105 | BaseDIFactory f = Activator.CreateInstance(a.Factory) as BaseDIFactory; 106 | factory = (sp) => f.CreateService(sp); 107 | } 108 | 109 | if (baseType != null) 110 | { 111 | services.Add(new ServiceDescriptor(baseType, type, a.Type)); 112 | } 113 | else 114 | { 115 | if (factory != null) 116 | { 117 | services.Add(new ServiceDescriptor(type, factory, a.Type)); 118 | } 119 | else 120 | { 121 | services.Add(new ServiceDescriptor(type, type, a.Type)); 122 | } 123 | } 124 | } 125 | catch { } 126 | } 127 | 128 | } 129 | 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /NeuroSpeech.DependencyInjectionExtensions/DIFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | [ExcludeFromCodeCoverage] 9 | public abstract class BaseDIFactory 10 | { 11 | public abstract object CreateService(IServiceProvider sp); 12 | } 13 | 14 | [ExcludeFromCodeCoverage] 15 | public abstract class DIFactory : BaseDIFactory 16 | { 17 | public abstract T Create(IServiceProvider sp); 18 | 19 | public override object CreateService(IServiceProvider sp) 20 | { 21 | return Create(sp); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /NeuroSpeech.DependencyInjectionExtensions/DIRegisterAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | [System.AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = true, AllowMultiple = false)] 9 | [ExcludeFromCodeCoverage] 10 | public sealed class DIRegisterAttribute : Attribute 11 | { 12 | // public static List Registrations => new List(); 13 | 14 | public ServiceLifetime Type { get; } 15 | 16 | public DIRegisterAttribute(ServiceLifetime type) 17 | { 18 | this.Type = type; 19 | 20 | // Registrations.Add(this); 21 | } 22 | 23 | /// 24 | /// 25 | /// 26 | public Type BaseType { get; set; } 27 | 28 | public Type Factory { get; set; } 29 | 30 | /// 31 | /// Automatically initializes when services.InitializeSingletons() is called 32 | /// 33 | public bool Init { get; set; } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /NeuroSpeech.DependencyInjectionExtensions/NeuroSpeech.DependencyInjectionExtensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech 6 | NeuroSpeech.DependencyInjectionExtensions 7 | Akash Kava 8 | NeuroSpeech Technologies Pvt Ltd 9 | 1.0.1 10 | true 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /NeuroSpeech.DependencyInjectionExtensions/RegisterAssemblyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NeuroSpeech 4 | { 5 | /// 6 | /// Registers current assembly with Assembly parts 7 | /// 8 | [System.AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] 9 | public class RegisterAssemblyAttribute : Attribute 10 | { 11 | public readonly bool RegisterParts; 12 | public readonly Type StartupType; 13 | 14 | public RegisterAssemblyAttribute(bool registerParts = true, Type startupType = null) 15 | { 16 | this.StartupType = startupType; 17 | this.RegisterParts = registerParts; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NeuroSpeech.ESDetector/ESDetector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace NeuroSpeech 7 | { 8 | public static class ESDetector 9 | { 10 | 11 | private static (bool? mobile, string browser, int major)[] minimum = new (bool? mobile, string browser, int major)[] { 12 | (null, "chrome", 49), 13 | (null, "edge", 14), 14 | (null, "firefox", 45), 15 | (null, "safari", 10), 16 | (null, "samsungbrowser", 13), 17 | (null, "ucbrowser", 12), 18 | (true, "opera", 62), 19 | (false, "opera", 38) 20 | }; 21 | 22 | public static bool SupportsES6(string userAgent) 23 | { 24 | var parser = UAParser.Parser.GetDefault(); 25 | var ua = parser.ParseUserAgent(userAgent); 26 | foreach(var (mobile, browser, version) in minimum) 27 | { 28 | if(mobile != null) 29 | { 30 | var isMobile = userAgent.ContainsIgnoreCase("mobile"); 31 | if (isMobile != mobile.Value) 32 | continue; 33 | } 34 | if(userAgent.ContainsIgnoreCase(browser)) 35 | { 36 | if (ParseInt(ua.Major) >= version) 37 | return true; 38 | return false; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | private static int ParseInt(this string text) 45 | { 46 | int i = 0; 47 | text = text.TrimStart(); 48 | foreach(var ch in text) 49 | { 50 | if (!char.IsDigit(ch)) 51 | break; 52 | i = i * 10 + (ch - '0'); 53 | } 54 | return i; 55 | } 56 | 57 | /// 58 | /// Equals (Case Insensitive) 59 | /// 60 | /// 61 | /// 62 | /// 63 | private static bool ContainsIgnoreCase(this string text, string test) 64 | { 65 | if (string.IsNullOrWhiteSpace(text)) 66 | return false; 67 | if (string.IsNullOrWhiteSpace(test)) 68 | return false; 69 | return text.IndexOf(test, StringComparison.OrdinalIgnoreCase) != -1; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /NeuroSpeech.ESDetector/NeuroSpeech.ESDetector.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech.ESDetector 6 | Akash Kava 7 | NeuroSpeech Technologies Pvt Ltd 8 | 1.0.1 9 | true 10 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /NeuroSpeech.IOExtensions/FileLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace NeuroSpeech.IO 9 | { 10 | public class FileLock : IDisposable 11 | { 12 | readonly FileInfo file; 13 | readonly FileStream fs; 14 | 15 | private FileLock( 16 | FileInfo file, 17 | FileStream fs) 18 | { 19 | this.fs = fs; 20 | this.file = file; 21 | } 22 | 23 | public static async Task AcquireAsync( 24 | string filePath, 25 | TimeSpan? maxWait = null, 26 | TimeSpan? poolDelay = null, 27 | CancellationToken cancellationToken = default 28 | ) 29 | { 30 | FileInfo lockFile = new FileInfo(filePath); 31 | 32 | if (!lockFile.Directory.Exists) 33 | { 34 | lockFile.Directory.Create(); 35 | } 36 | 37 | maxWait = maxWait ?? TimeSpan.FromMinutes(15); 38 | var start = DateTime.UtcNow; 39 | 40 | TimeSpan delay = poolDelay ?? TimeSpan.FromSeconds(5); 41 | 42 | while(true) 43 | { 44 | // try to open file... 45 | try 46 | { 47 | var fs = new FileStream(filePath, 48 | FileMode.OpenOrCreate, 49 | FileAccess.ReadWrite, FileShare.None); 50 | fs.Seek(0, SeekOrigin.Begin); 51 | await fs.WriteAsync(new byte[] { 1 }, 0, 1); 52 | return new FileLock(lockFile, fs); 53 | } catch 54 | { 55 | 56 | } 57 | 58 | try 59 | { 60 | await Task.Delay(delay, cancellationToken); 61 | } catch (TaskCanceledException) 62 | { 63 | continue; 64 | } 65 | var diff = DateTime.UtcNow - start; 66 | if (diff > maxWait) 67 | { 68 | throw new TimeoutException(); 69 | } 70 | } 71 | } 72 | 73 | public void Dispose() 74 | { 75 | fs.Dispose(); 76 | // there is a possibility of other process to acquire 77 | // lock immediately so we will ignore exceptions here 78 | try 79 | { 80 | file.Delete(); 81 | } 82 | catch { } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /NeuroSpeech.IOExtensions/NeuroSpeech.IOExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech 6 | NeuroSpeech.IOExtensions 7 | Akash Kava 8 | NeuroSpeech Technologies Pvt Ltd 9 | 1.0.1 10 | true 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Signup" 4 | ] 5 | } -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/Attributes/ActivityAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NeuroSpeech.Workflows 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | public class ActivityAttribute: Attribute 7 | { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/Attributes/InjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NeuroSpeech.Workflows 4 | { 5 | [AttributeUsage(AttributeTargets.Parameter)] 6 | public class InjectAttribute: Attribute 7 | { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/Attributes/WorkflowAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NeuroSpeech.Workflows 4 | { 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class WorkflowAttribute : Attribute 7 | { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/BaseWorkflowService.cs: -------------------------------------------------------------------------------- 1 | using DurableTask.Core; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NeuroSpeech.Workflows 8 | { 9 | public class BaseWorkflowService 10 | { 11 | internal protected TaskHubClient client; 12 | 13 | public async Task RaiseEvent(string id, string name, object data = null) 14 | { 15 | var state = await client.GetOrchestrationStateAsync(id); 16 | await client.RaiseEventAsync(state.OrchestrationInstance, name, data ?? ""); 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/CancelTokenExtensions.cs: -------------------------------------------------------------------------------- 1 | using DurableTask.Core; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | namespace NeuroSpeech.Workflows 9 | { 10 | internal class CancelTokenRegistry 11 | { 12 | 13 | public static CancelTokenRegistry Instance = new CancelTokenRegistry(); 14 | 15 | private ConcurrentDictionary sources = new ConcurrentDictionary(); 16 | 17 | internal CancellationTokenSource Get(string id) 18 | { 19 | return sources.GetOrAdd(id, (k) => new CancellationTokenSource()); 20 | } 21 | 22 | internal void Remove(string id) 23 | { 24 | sources.TryRemove(id, out var _); 25 | } 26 | 27 | } 28 | 29 | public static class CancelTokenExtensions 30 | { 31 | internal static CancellationTokenSource GetCancellationTokenSource(this OrchestrationContext context) 32 | { 33 | return CancelTokenRegistry.Instance.Get(context.OrchestrationInstance.InstanceId); 34 | } 35 | 36 | internal static void RemoveCancellationTokenSource(this OrchestrationContext context) 37 | { 38 | CancelTokenRegistry.Instance.Remove(context.OrchestrationInstance.InstanceId); 39 | } 40 | 41 | public static CancellationToken GetCancellationToken(this TaskContext context) 42 | { 43 | return CancelTokenRegistry.Instance.Get(context.OrchestrationInstance.InstanceId).Token; 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/EventQueue.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using Newtonsoft.Json; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace NeuroSpeech.Workflows 8 | { 9 | public class EventQueue 10 | { 11 | private Queue? pending; 12 | private TaskCompletionSource? taskSoruce; 13 | private CancellationTokenSource? cancelSource; 14 | 15 | public EventQueue() 16 | { 17 | } 18 | 19 | public void Reset() 20 | { 21 | cancelSource?.Cancel(); 22 | taskSoruce = null; 23 | cancelSource = null; 24 | } 25 | 26 | public void SetEvent(string result) 27 | { 28 | if (taskSoruce == null) 29 | { 30 | pending ??= new Queue(); 31 | pending.Enqueue(result); 32 | return; 33 | } 34 | this.taskSoruce.TrySetResult(result); 35 | cancelSource?.Cancel(); 36 | } 37 | 38 | public (Task waiter, CancellationToken token) Request() 39 | { 40 | if(pending?.Count > 0) 41 | { 42 | return (Task.FromResult(pending.Dequeue()), default); 43 | } 44 | taskSoruce = new TaskCompletionSource(); 45 | cancelSource = new CancellationTokenSource(); 46 | return (taskSoruce.Task, cancelSource.Token); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/EventResult.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System.Threading.Tasks; 3 | 4 | namespace NeuroSpeech.Workflows 5 | { 6 | public class EventResult 7 | { 8 | /// 9 | /// Result if event was not timedout 10 | /// 11 | public readonly string? Result; 12 | 13 | /// 14 | /// True if event was timed out 15 | /// 16 | public readonly bool TimedOut; 17 | 18 | /// 19 | /// True if event was raised 20 | /// 21 | public readonly bool Raised; 22 | 23 | 24 | public EventResult(string result) 25 | { 26 | this.Result = result; 27 | TimedOut = false; 28 | Raised = !TimedOut; 29 | } 30 | 31 | private EventResult(string? result, bool timedout) 32 | { 33 | this.Result = result; 34 | this.TimedOut = timedout; 35 | } 36 | 37 | 38 | public static readonly EventResult TimedOutValue = new EventResult(default, true); 39 | 40 | public static readonly Task Empty = Task.FromResult(new EventResult(null!)); 41 | 42 | public static EventResult From(Task result) 43 | => result.IsCompleted ? new EventResult(result.Result) : EventResult.TimedOutValue; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/Impl/BaseWorkflow.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using DurableTask.Core; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace NeuroSpeech.Workflows.Impl 7 | { 8 | internal interface IBaseWorkflow 9 | { 10 | string? WorkflowID { set; } 11 | } 12 | 13 | public abstract class BaseWorkflow 14 | : IBaseWorkflow 15 | { 16 | 17 | public OrchestrationContext? context; 18 | 19 | internal IServiceProvider? serviceProvider; 20 | 21 | internal protected string? WorkflowID { get; internal set; } 22 | string? IBaseWorkflow.WorkflowID { set => this.WorkflowID = value; } 23 | 24 | public abstract Task RunTask(TInput input); 25 | 26 | internal abstract void OnEvent(string name, string input); 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/Impl/IWorkflowExecutor.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using DurableTask.Core; 3 | using System.Threading.Tasks; 4 | 5 | namespace NeuroSpeech.Workflows.Impl 6 | { 7 | internal interface IWorkflowExecutor 8 | { 9 | Task RunAsync(OrchestrationContext context, object input); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/Impl/WorkflowExecutor.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using DurableTask.Core; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace NeuroSpeech.Workflows.Impl 9 | { 10 | 11 | 12 | 13 | public class WorkflowExecutor : TaskOrchestration, IWorkflowExecutor 14 | where T: BaseWorkflow 15 | { 16 | private readonly IServiceProvider sp; 17 | private readonly T workflow; 18 | 19 | public WorkflowExecutor(IServiceProvider sp) 20 | { 21 | this.sp = sp; 22 | this.workflow = sp.Build(); 23 | } 24 | public override async Task RunTask(OrchestrationContext context, TInput input) 25 | { 26 | 27 | TOutput result = default!; 28 | 29 | async Task RunInternalAsync(TInput input) { 30 | result = await workflow.RunTask(input); 31 | } 32 | var ct = context.GetCancellationTokenSource().Token; 33 | workflow.context = context; 34 | workflow.serviceProvider = sp; 35 | workflow.WorkflowID = context.OrchestrationInstance.InstanceId; 36 | 37 | var completed = new CancellationTokenSource(); 38 | 39 | await Task.WhenAny( Task.Delay(TimeSpan.FromMilliseconds(-1), completed.Token), RunInternalAsync(input) ); 40 | 41 | context.RemoveCancellationTokenSource(); 42 | completed.Cancel(); 43 | 44 | if(ct.IsCancellationRequested) 45 | { 46 | throw new TaskCanceledException($"Task was cancelled by cancel event"); 47 | } 48 | return result; 49 | } 50 | 51 | public override void OnEvent(OrchestrationContext context, string name, string input) 52 | { 53 | if(name == "__CANCEL") 54 | { 55 | context.GetCancellationTokenSource().Cancel(); 56 | return; 57 | } 58 | 59 | workflow.context = context; 60 | workflow.serviceProvider = sp; 61 | workflow.WorkflowID = context.OrchestrationInstance.InstanceId; 62 | workflow.OnEvent(name, input); 63 | } 64 | 65 | Task IWorkflowExecutor.RunAsync(OrchestrationContext context, object input) 66 | { 67 | return RunTask(context, (TInput)input); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/NeuroSpeech.Workflows.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech.Workflows 6 | Akash Kava 7 | NeuroSpeech Technologies Pvt Ltd 8 | 1.0.1 9 | latest 10 | true 11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/README.md: -------------------------------------------------------------------------------- 1 | # Workflow? 2 | 3 | Durable Tasks created by Azure team is great, but it requires too many classes. And for very small logic, the amount of code to be written is huge. We have created this package to reduce amount of code required to write Durable Functions easily in your own premises. 4 | 5 | # NuGet 6 | 7 | `NeuroSpeech.Workflows` 8 | 9 | # Example 10 | 11 | Lets assume, we want to write following logic. 12 | 13 | 1. After user sign ups we want to verify the email address. 14 | 2. User can enter the code or request for resending the email. 15 | 3. If verification fails for 3 times or times out within 45 minutes, we delete the user. 16 | 17 | # Declare Workflow 18 | 19 | ```c# 20 | 21 | [Workflow] 22 | public class SignupVerification: Workflow { 23 | 24 | 25 | /// Event fired when user enters the code 26 | /// Event must be marked as a static 27 | [Event] 28 | public static WorkflowEvent UserCode; 29 | 30 | 31 | /// User request for resend 32 | [Event] 33 | public static WorkflowEvent Resend; 34 | 35 | /// This is the main method of workflow 36 | public async Task RunAsync(SignupModel model) { 37 | string code = (context.CurrentDateTimeUtc.Ticks & 0xFFFF).ToString(); 38 | 39 | await EmailUserAsync(model.EmailAddress, code); 40 | 41 | var maxWait = TimeSpan.FromMinutes(15); 42 | 43 | for(int i = 0; i< 3; i++) { 44 | var (resendEvent, codeEvent) = await this.WaitForEvents(Resend, UserCode, maxWait); 45 | if(resendEvent.Raised) { 46 | // i is important for durable task framework 47 | // to know that it is not same call, otherwise it will 48 | // not execute this method 49 | await EmailUserAsync(model.EmailAddress, code, i); 50 | continue; 51 | } 52 | if(codeEvent.Raised) { 53 | if(code == codeEvent.Result) { 54 | await ActivateUserAsync(model.AccountID); 55 | return "User Activated"; 56 | } 57 | } 58 | } 59 | 60 | // failed for 3 attempts... 61 | await DeleteUserAsync(model.AccountID); 62 | 63 | return "User Deleted"; 64 | } 65 | 66 | 67 | /// 68 | /// Activities must be marked with [Activity] attribute 69 | /// and they must be public virtual methods 70 | /// 71 | [Activity] 72 | public virtual async Task EmailUserAsync( 73 | string emailAddress, 74 | string code, 75 | int attempt = -1, 76 | // dependency injection !! 77 | [Inject] MailService mailService) { 78 | ... send email.... 79 | } 80 | 81 | [Activity] 82 | public virtual async Task DeleteUserAsync( 83 | long userId, 84 | [Inject] AppDbContext db) { 85 | ... delete user... 86 | } 87 | 88 | [Activity] 89 | public virtual async Task ActivateUserAsync( 90 | long userId, 91 | [Inject] AppDbContext db) { 92 | ... mark user as active ... 93 | } 94 | 95 | } 96 | 97 | ``` 98 | 99 | # Declare WorkflowService 100 | 101 | ```c# 102 | 103 | public class WorkflowService: BaseWorkflowService { 104 | public WorkflowService() { 105 | ... other setup... 106 | 107 | // this is important 108 | this.client = new TaskHubClient ...... 109 | 110 | // this method basically registers all activities in the assembly 111 | this.client.Register(typeof(WorkflowService).Assembly); 112 | } 113 | } 114 | 115 | ``` 116 | 117 | # Create New Instance of Workflow 118 | 119 | ```c# 120 | 121 | // CreateInstanceAsync will force you to enter correct input type 122 | // intellisense will show up correctly 123 | var id = await SignupVerification.CreteInstanceAsync(workflowService, new SignupModel { 124 | EmailAddress = .... 125 | AccountId = ... 126 | }); 127 | 128 | ``` 129 | 130 | # Raise an Event 131 | ```c# 132 | 133 | SignupVerification.UserCode.RaiseAsync(workflowService, id, "data"); 134 | 135 | ``` 136 | 137 | # Reuse Some other workflow 138 | Often we have common activities we would want to use it in same workflow without creating sub orchestrations. You can easily do it as follow. 139 | 140 | ```c# 141 | 142 | // you can only call this from "RunAsync" method only 143 | 144 | var result = await ForgotPassword.RunInAsync(this, new EmailRequest { 145 | EmailAddress = ... 146 | }); 147 | 148 | ``` 149 | 150 | # Restrictions 151 | 1. You cannot wait for any event in the Activity 152 | 2. You cannot call activity from another activity 153 | 3. Activities must return the result back to `RunAsync` method. 154 | 4. You can call any other method within `RunAsync` and inside activity methods. 155 | 156 | # How it works? 157 | 158 | 1. New class inherits `Workflow` class automatically and rewrites all virtual methods. Those methods basically calls Schedule on context and pass parameter encapsulating type. 159 | 2. More than one parameters of method are actually passed as Tuples internally. 160 | 3. You can wait on multiple events at same time with single timeout. Timers are destroyed instantly if any of the event fires successfully. Events are queued so replay works correctly. 161 | 162 | 163 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace NeuroSpeech.Workflows 7 | { 8 | public static class ServiceProviderExtensions 9 | { 10 | public static T Build(this IServiceProvider services) 11 | => (T)Build(services, typeof(T)); 12 | 13 | public static object Build(this IServiceProvider services, Type type) { 14 | var c = type.GetConstructors()[0]; 15 | var cp = c.GetParameters(); 16 | var args = new object[cp.Length]; 17 | for (int i = 0; i < cp.Length; i++) 18 | { 19 | var t = cp[i].ParameterType; 20 | args[i] = services.GetRequiredService(t); 21 | } 22 | return c.Invoke(args); 23 | } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/TaskHubExtensions.cs: -------------------------------------------------------------------------------- 1 | using DurableTask.Core; 2 | using NeuroSpeech.Workflows; 3 | using NeuroSpeech.Workflows.Impl; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace NeuroSpeech.Workflows 10 | { 11 | public static class TaskHubExtensions 12 | { 13 | 14 | public static void Register(this TaskHubWorker worker, IServiceProvider sp, Assembly assembly) 15 | { 16 | foreach(var t in assembly.GetExportedTypes()) 17 | { 18 | var w = t.GetCustomAttribute(); 19 | if (w == null) 20 | continue; 21 | 22 | var (factory, name, activities) = ClrHelper.Instance.Factory(t); 23 | worker.AddTaskOrchestrations(new WFactory(name, sp, factory)); 24 | 25 | foreach(var a in activities) 26 | { 27 | worker.AddTaskActivities(new WFactory(a.Name,sp, factory)); 28 | } 29 | } 30 | } 31 | 32 | public class WFactory : ObjectCreator 33 | { 34 | private readonly IServiceProvider sp; 35 | private readonly Func factory; 36 | 37 | public WFactory(string name, IServiceProvider sp, Func factory) 38 | { 39 | this.Name = name; 40 | this.sp = sp; 41 | this.factory = factory; 42 | } 43 | public override T Create() 44 | { 45 | return (T)factory(Name, sp); 46 | } 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace NeuroSpeech.Workflows 7 | { 8 | internal static class TypeExtensions 9 | { 10 | 11 | public static Type ToTuple(this Type[] types) 12 | { 13 | switch(types.Length) 14 | { 15 | case 1: 16 | throw new NotSupportedException(); 17 | case 2: 18 | return typeof(Tuple<,>).MakeGenericType(types); 19 | case 3: 20 | return typeof(Tuple<,,>).MakeGenericType(types); 21 | case 4: 22 | return typeof(Tuple<,,,>).MakeGenericType(types); 23 | case 5: 24 | return typeof(Tuple<,,,,>).MakeGenericType(types); 25 | case 6: 26 | return typeof(Tuple<,,,,,>).MakeGenericType(types); 27 | case 7: 28 | return typeof(Tuple<,,,,,,>).MakeGenericType(types); 29 | case 8: 30 | return typeof(Tuple<,,,,,,,>).MakeGenericType(types); 31 | } 32 | throw new NotSupportedException(); 33 | } 34 | 35 | public static (Type t1, Type t2) Get2GenericArguments(this Type type) 36 | { 37 | var bt = type; 38 | while (!bt.IsGenericType || bt.GetGenericArguments().Length != 2) 39 | bt = bt.BaseType; 40 | var ta = bt.GetGenericArguments(); 41 | return (ta[0], ta[1]); 42 | } 43 | 44 | public static (Type t1, Type t2, Type t3) Get3GenericArguments(this Type type) 45 | { 46 | var bt = type; 47 | while (!bt.IsGenericType || bt.GetGenericArguments().Length != 3) 48 | bt = bt.BaseType; 49 | var ta = bt.GetGenericArguments(); 50 | return (ta[0], ta[1], ta[2]); 51 | } 52 | 53 | 54 | public static Type GetArgument(this Type type, Type genericType) 55 | { 56 | while (!type.IsConstructedGenericType) 57 | { 58 | type = type.BaseType; 59 | if (type == null) 60 | break; 61 | } 62 | if(type == null || type.GetGenericTypeDefinition() != genericType) 63 | throw new InvalidOperationException($"{type} does not construct {genericType}"); 64 | return type.GetGenericArguments()[0]; 65 | } 66 | 67 | public static string GetFriendlyName(this Type type) 68 | { 69 | if (type.IsArray) 70 | { 71 | return type.GetFriendlyName() + "[]"; 72 | } 73 | 74 | if(type.IsConstructedGenericType) 75 | { 76 | return type.GetGenericTypeDefinition().GetFriendlyName() + "<" + 77 | string.Join(",", type.GetGenericArguments().Select(x => x.GetFriendlyName())) + ">"; 78 | } 79 | 80 | return type.Namespace + "." + type.Name; 81 | } 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /NeuroSpeech.Workflows/WorkflowResult.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using DurableTask.Core; 3 | using Newtonsoft.Json; 4 | 5 | namespace NeuroSpeech.Workflows 6 | { 7 | public class WorkflowResult 8 | { 9 | public WorkflowResult(OrchestrationState ctx) 10 | { 11 | OrchestrationStatus = ctx.OrchestrationStatus; 12 | switch (OrchestrationStatus) 13 | { 14 | case OrchestrationStatus.Completed: 15 | Result = JsonConvert.DeserializeObject(ctx.Output); 16 | break; 17 | case OrchestrationStatus.Failed: 18 | Error = ctx.Output; 19 | break; 20 | 21 | } 22 | Status = ctx.Status; 23 | } 24 | 25 | public T? Result { get; internal set; } 26 | 27 | public string? Status { get; set; } 28 | 29 | public string? Error { get; set; } 30 | 31 | public OrchestrationStatus OrchestrationStatus { get; set; } 32 | 33 | [JsonIgnore] 34 | public bool Success => OrchestrationStatus == OrchestrationStatus.Completed; 35 | 36 | [JsonIgnore] 37 | public bool Failed => OrchestrationStatus == OrchestrationStatus.Failed; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /NodePackageService/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 NeuroSpeech Technologies Pvt Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/Configuration/NodeServicesFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NeuroSpeech.AspNet.NodeServices 4 | { 5 | /// 6 | /// Supplies INodeServices instances. 7 | /// 8 | public static class NodeServicesFactory 9 | { 10 | /// 11 | /// Create an instance according to the supplied options. 12 | /// 13 | /// Options for creating the instance. 14 | /// An instance. 15 | public static INodeServices CreateNodeServices(NodeServicesOptions options) 16 | { 17 | if (options == null) 18 | { 19 | throw new ArgumentNullException(nameof (options)); 20 | } 21 | 22 | return new NodeServicesImpl(options.NodeInstanceFactory); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/Configuration/NodeServicesServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NeuroSpeech.AspNet.NodeServices; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection 5 | { 6 | /// 7 | /// Extension methods for setting up NodeServices in an . 8 | /// 9 | public static class NodeServicesServiceCollectionExtensions 10 | { 11 | /// 12 | /// Adds NodeServices support to the . 13 | /// 14 | /// The . 15 | public static void AddNodeServices(this IServiceCollection serviceCollection) 16 | => AddNodeServices(serviceCollection, _ => {}); 17 | 18 | /// 19 | /// Adds NodeServices support to the . 20 | /// 21 | /// The . 22 | /// A callback that will be invoked to populate the . 23 | public static void AddNodeServices(this IServiceCollection serviceCollection, Action setupAction) 24 | { 25 | if (setupAction == null) 26 | { 27 | throw new ArgumentNullException(nameof (setupAction)); 28 | } 29 | 30 | serviceCollection.AddSingleton(typeof(INodeServices), serviceProvider => 31 | { 32 | // First we let NodeServicesOptions take its defaults from the IServiceProvider, 33 | // then we let the developer override those options 34 | var options = new NodeServicesOptions(serviceProvider); 35 | setupAction(options); 36 | 37 | return NodeServicesFactory.CreateNodeServices(options); 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/HostingModels/INodeInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace NeuroSpeech.AspNet.NodeServices.HostingModels 6 | { 7 | /// 8 | /// Represents an instance of Node.js to which Remote Procedure Calls (RPC) may be sent. 9 | /// 10 | public interface INodeInstance : IDisposable 11 | { 12 | /// 13 | /// Asynchronously invokes code in the Node.js instance. 14 | /// 15 | /// The JSON-serializable data type that the Node.js code will asynchronously return. 16 | /// A that can be used to cancel the invocation. 17 | /// The path to the Node.js module (i.e., JavaScript file) relative to your project root that contains the code to be invoked. 18 | /// If set, specifies the CommonJS export to be invoked. If not set, the module's default CommonJS export itself must be a function to be invoked. 19 | /// Any sequence of JSON-serializable arguments to be passed to the Node.js function. 20 | /// A representing the completion of the RPC call. 21 | Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportNameOrNull, params object[] args); 22 | } 23 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/HostingModels/NodeInvocationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NeuroSpeech.AspNet.NodeServices.HostingModels 4 | { 5 | /// 6 | /// Represents an exception caused by invoking Node.js code. 7 | /// 8 | public class NodeInvocationException : Exception 9 | { 10 | /// 11 | /// If true, indicates that the invocation failed because the Node.js instance could not be reached. For example, 12 | /// it might have already shut down or previously crashed. 13 | /// 14 | public bool NodeInstanceUnavailable { get; private set; } 15 | 16 | /// 17 | /// If true, indicates that even though the invocation failed because the Node.js instance could not be reached 18 | /// or needs to be restarted, that Node.js instance may remain alive for a period in order to complete any 19 | /// outstanding requests. 20 | /// 21 | public bool AllowConnectionDraining { get; private set;} 22 | 23 | /// 24 | /// Creates a new instance of . 25 | /// 26 | /// A description of the exception. 27 | /// Additional information, such as a Node.js stack trace, representing the exception. 28 | public NodeInvocationException(string message, string details) 29 | : base(message + Environment.NewLine + details) 30 | { 31 | } 32 | 33 | /// 34 | /// Creates a new instance of . 35 | /// 36 | /// A description of the exception. 37 | /// Additional information, such as a Node.js stack trace, representing the exception. 38 | /// Specifies a value for the flag. 39 | /// Specifies a value for the flag. 40 | public NodeInvocationException(string message, string details, bool nodeInstanceUnavailable, bool allowConnectionDraining) 41 | : this(message, details) 42 | { 43 | // Reject a meaningless combination of flags 44 | if (allowConnectionDraining && !nodeInstanceUnavailable) 45 | { 46 | throw new ArgumentException( 47 | $"The '${ nameof(allowConnectionDraining) }' parameter cannot be true " + 48 | $"unless the '${ nameof(nodeInstanceUnavailable) }' parameter is also true."); 49 | } 50 | 51 | NodeInstanceUnavailable = nodeInstanceUnavailable; 52 | AllowConnectionDraining = allowConnectionDraining; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/HostingModels/NodeInvocationInfo.cs: -------------------------------------------------------------------------------- 1 | namespace NeuroSpeech.AspNet.NodeServices.HostingModels 2 | { 3 | /// 4 | /// Describes an RPC call sent from .NET code to Node.js code. 5 | /// 6 | public class NodeInvocationInfo 7 | { 8 | /// 9 | /// Specifies the path to the Node.js module (i.e., .js file) relative to the project root. 10 | /// 11 | public string ModuleName { get; set; } 12 | 13 | /// 14 | /// If set, specifies the name of CommonJS function export to be invoked. 15 | /// If not set, the Node.js module's default export must itself be a function to be invoked. 16 | /// 17 | public string ExportedFunctionName { get; set; } 18 | 19 | /// 20 | /// A sequence of JSON-serializable arguments to be passed to the Node.js function being invoked. 21 | /// 22 | public object[] Args { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/HostingModels/NodeServicesOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace NeuroSpeech.AspNet.NodeServices.HostingModels 2 | { 3 | /// 4 | /// Extension methods that help with populating a object. 5 | /// 6 | public static class NodeServicesOptionsExtensions 7 | { 8 | /// 9 | /// Configures the service so that it will use out-of-process 10 | /// Node.js instances and perform RPC calls over HTTP. 11 | /// 12 | public static void UseHttpHosting(this NodeServicesOptions options) 13 | { 14 | options.NodeInstanceFactory = () => new HttpNodeInstance(options); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/INodeServices.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace NeuroSpeech.AspNet.NodeServices 6 | { 7 | /// 8 | /// Represents the ability to invoke code in a Node.js environment. Although the underlying Node.js instance 9 | /// might change over time (e.g., the process might be restarted), the instance 10 | /// will remain constant. 11 | /// 12 | public interface INodeServices : IDisposable 13 | { 14 | /// 15 | /// Asynchronously invokes code in the Node.js instance. 16 | /// 17 | /// The JSON-serializable data type that the Node.js code will asynchronously return. 18 | /// The path to the Node.js module (i.e., JavaScript file) relative to your project root whose default CommonJS export is the function to be invoked. 19 | /// Any sequence of JSON-serializable arguments to be passed to the Node.js function. 20 | /// A representing the completion of the RPC call. 21 | Task InvokeAsync(string moduleName, params object[] args); 22 | 23 | /// 24 | /// Asynchronously invokes code in the Node.js instance. 25 | /// 26 | /// The JSON-serializable data type that the Node.js code will asynchronously return. 27 | /// A that can be used to cancel the invocation. 28 | /// The path to the Node.js module (i.e., JavaScript file) relative to your project root whose default CommonJS export is the function to be invoked. 29 | /// Any sequence of JSON-serializable arguments to be passed to the Node.js function. 30 | /// A representing the completion of the RPC call. 31 | Task InvokeAsync(CancellationToken cancellationToken, string moduleName, params object[] args); 32 | 33 | /// 34 | /// Asynchronously invokes code in the Node.js instance. 35 | /// 36 | /// The JSON-serializable data type that the Node.js code will asynchronously return. 37 | /// The path to the Node.js module (i.e., JavaScript file) relative to your project root that contains the code to be invoked. 38 | /// Specifies the CommonJS export to be invoked. 39 | /// Any sequence of JSON-serializable arguments to be passed to the Node.js function. 40 | /// A representing the completion of the RPC call. 41 | Task InvokeExportAsync(string moduleName, string exportedFunctionName, params object[] args); 42 | 43 | /// 44 | /// Asynchronously invokes code in the Node.js instance. 45 | /// 46 | /// The JSON-serializable data type that the Node.js code will asynchronously return. 47 | /// A that can be used to cancel the invocation. 48 | /// The path to the Node.js module (i.e., JavaScript file) relative to your project root that contains the code to be invoked. 49 | /// Specifies the CommonJS export to be invoked. 50 | /// Any sequence of JSON-serializable arguments to be passed to the Node.js function. 51 | /// A representing the completion of the RPC call. 52 | Task InvokeExportAsync(CancellationToken cancellationToken, string moduleName, string exportedFunctionName, params object[] args); 53 | } 54 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/NeuroSpeech.AspNet.NodeServices.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Invoke Node.js modules at runtime in ASP.NET Core applications. 4 | netstandard2.0 5 | NeuroSpeech.AspNet.NodeServices 6 | NeuroSpeech.AspNet.NodeServices 7 | Library 8 | 1.0.1 9 | NeuroSpeech.AspNet.NodeServices 10 | true 11 | Akash Kava 12 | NeuroSpeech Technologies Pvt Ltd 13 | NeuroSpeech NodePackageService 14 | true 15 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/NodePackageService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.IO; 7 | using Microsoft.Extensions.Logging; 8 | using System.Diagnostics; 9 | using NeuroSpeech.AspNet.NodeServices; 10 | 11 | namespace NeuroSpeech 12 | { 13 | 14 | 15 | public class NodePackageService: PackageInstallerService 16 | { 17 | 18 | private NodePackageServiceOptions _Options; 19 | 20 | public NodePackageService( 21 | IServiceProvider services, 22 | NodePackageServiceOptions options, 23 | Func> versionProvider = null) 24 | : base(services, options, versionProvider) 25 | { 26 | this._Options = options; 27 | } 28 | 29 | public async Task GetInstalledPackageAsync(string path) 30 | { 31 | return await this.InstalledPackageAsync(path) as NodePackage; 32 | } 33 | 34 | protected override NodeInstalledPackage CreatePackage(PackagePath pp) 35 | { 36 | 37 | var options = this._Options.NodeServicesOptions ?? new NodeServicesOptions(services) 38 | { 39 | ProjectPath = pp.TagFolder, 40 | NodeInstanceOutputLogger = services.GetService>() 41 | }; 42 | 43 | options.ProjectPath = pp.TagFolder; 44 | 45 | if (this.Options.EnvironmentVariables != null) 46 | { 47 | if (options.EnvironmentVariables == null) 48 | { 49 | options.EnvironmentVariables = new Dictionary(); 50 | } 51 | foreach (var item in this.Options.EnvironmentVariables) 52 | { 53 | options.EnvironmentVariables[item.Key] = item.Value; 54 | } 55 | } 56 | 57 | var s = NodeServicesFactory.CreateNodeServices(options); 58 | 59 | return new NodePackage { 60 | Path = pp, 61 | NodeServices = s 62 | }; 63 | } 64 | 65 | } 66 | 67 | public class NodePackage: NodeInstalledPackage 68 | { 69 | public INodeServices NodeServices; 70 | 71 | public override void Dispose() 72 | { 73 | NodeServices?.Dispose(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/NodePackageServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Net.Http.Headers; 4 | using System.Threading.Tasks; 5 | 6 | namespace NeuroSpeech 7 | { 8 | public interface IVersionProvider 9 | { 10 | 11 | Task GetVersionAsync(PackagePathSegments path); 12 | 13 | } 14 | 15 | public static class NodePackageServiceExtensions 16 | { 17 | 18 | public static void AddNodePackageService(this IServiceCollection services, 19 | NodePackageServiceOptions options) 20 | { 21 | services.AddSingleton(sp => new NodePackageService(sp, options, null)); 22 | } 23 | 24 | public static void AddNodePackageService(this IServiceCollection services, 25 | NodePackageServiceOptions options) 26 | where T: IVersionProvider 27 | { 28 | services.AddSingleton(sp => new NodePackageService(sp, options, (s, path) => { 29 | var st = s.GetRequiredService(); 30 | return st.GetVersionAsync(path); 31 | })); 32 | } 33 | 34 | //public static IApplicationBuilder UseNpmDistribution( 35 | // this IApplicationBuilder app, 36 | // string route = "js-pkg/", 37 | // bool crossBrowser = false) 38 | //{ 39 | // app.Map(route, a => 40 | // { 41 | // a.Use(async (context, next) => 42 | // { 43 | 44 | // HttpRequest request = context.Request; 45 | // if (!request.Method.EqualsIgnoreCase("GET")) 46 | // { 47 | // await next(); 48 | // return; 49 | // } 50 | 51 | // PathString path = request.Path; 52 | 53 | // IHeaderDictionary headers = context.Response.Headers; 54 | // if (crossBrowser) 55 | // { 56 | // headers.Add("access-control-allow-origin", "*"); 57 | // headers.Add("access-control-expose-headers", "*"); 58 | // headers.Add("access-control-allow-methods", "*"); 59 | // headers.Add("access-control-allow-headers", "*"); 60 | // headers.Add("access-control-max-age", TimeSpan.FromDays(30).TotalSeconds.ToString()); 61 | // } 62 | 63 | // var nodeServer = context.RequestServices.GetService(); 64 | 65 | // string sp = path.Value.Substring(route.Length + 1); 66 | 67 | // var packageSegment = sp.ParseNPMPath(); 68 | 69 | // var package = await nodeServer.GetInstalledPackageAsync(sp); 70 | 71 | // string folder = package.Path.TagFolder; 72 | 73 | // string filePath = $"{folder}\\{packageSegment.Path}".Replace("/","\\"); 74 | 75 | // string host = context.Request.Query["host"]; 76 | 77 | // string contentType = MimeKit.MimeTypes.GetMimeType(filePath); 78 | 79 | // var ct = MediaTypeHeaderValue.Parse(contentType); 80 | // ct.CharSet = System.Text.Encoding.UTF8.EncodingName; 81 | // context.Response.Headers.SetCommaSeparatedValues("content-type", ct.ToString()); 82 | 83 | // // await context.Response.SendFileAsync(filePath, context.RequestAborted); 84 | 85 | // if (!string.IsNullOrWhiteSpace(host)) 86 | // { 87 | // string lang = context.Request.Query["lang"]; 88 | // if (string.IsNullOrWhiteSpace(lang)) 89 | // { 90 | // lang = "en-US"; 91 | // } 92 | // string designMode = context.Request.Query["designMode"]; 93 | // bool design = designMode.EqualsIgnoreCase("true"); 94 | // var startScript = $"UMD.lang = \"{lang}\";\r\n" + 95 | // $"UMD.hostView(\"{host}\",\"{packageSegment.Package}/{path}\", {(design? "true" : "false")})"; 96 | // await context.Response.WriteAsync(startScript); 97 | // } 98 | // }); 99 | 100 | // }); 101 | 102 | // return app; 103 | //} 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/NodeServerOptions.cs: -------------------------------------------------------------------------------- 1 | using NeuroSpeech.AspNet.NodeServices; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace NeuroSpeech 7 | { 8 | public class NodePackageServiceOptions: PackageInstallerOptions 9 | { 10 | 11 | /// 12 | /// 13 | /// 14 | public NodeServicesOptions NodeServicesOptions { get; set; } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/TypeScript/HttpNodeInstanceEntryPoint.ts: -------------------------------------------------------------------------------- 1 | // Limit dependencies to core Node modules. This means the code in this file has to be very low-level and unattractive, 2 | // but simplifies things for the consumer of this module. 3 | import './Util/PatchModuleResolutionLStat'; 4 | import './Util/OverrideStdOutputs'; 5 | import * as http from 'http'; 6 | import * as path from 'path'; 7 | import { parseArgs } from './Util/ArgsUtil'; 8 | import { exitWhenParentExits } from './Util/ExitWhenParentExits'; 9 | import { AddressInfo } from 'net'; 10 | 11 | // Webpack doesn't support dynamic requires for files not present at compile time, so grab a direct 12 | // reference to Node's runtime 'require' function. 13 | const dynamicRequire: (name: string) => any = eval('require'); 14 | 15 | const server = http.createServer((req, res) => { 16 | readRequestBodyAsJson(req, bodyJson => { 17 | let hasSentResult = false; 18 | const callback = (errorValue, successValue) => { 19 | if (!hasSentResult) { 20 | hasSentResult = true; 21 | if (errorValue) { 22 | respondWithError(res, errorValue); 23 | } else if (typeof successValue !== 'string') { 24 | // Arbitrary object/number/etc - JSON-serialize it 25 | let successValueJson: string; 26 | try { 27 | successValueJson = JSON.stringify(successValue); 28 | } catch (ex) { 29 | // JSON serialization error - pass it back to .NET 30 | respondWithError(res, ex); 31 | return; 32 | } 33 | res.setHeader('Content-Type', 'application/json'); 34 | res.end(successValueJson); 35 | } else { 36 | // String - can bypass JSON-serialization altogether 37 | res.setHeader('Content-Type', 'text/plain'); 38 | res.end(successValue); 39 | } 40 | } 41 | }; 42 | 43 | // Support streamed responses 44 | Object.defineProperty(callback, 'stream', { 45 | enumerable: true, 46 | get: function() { 47 | if (!hasSentResult) { 48 | hasSentResult = true; 49 | res.setHeader('Content-Type', 'application/octet-stream'); 50 | } 51 | 52 | return res; 53 | } 54 | }); 55 | 56 | try { 57 | const resolvedPath = path.resolve(process.cwd(), bodyJson.moduleName); 58 | const invokedModule = dynamicRequire(resolvedPath); 59 | const func = bodyJson.exportedFunctionName ? invokedModule[bodyJson.exportedFunctionName] : invokedModule; 60 | if (!func) { 61 | throw new Error('The module "' + resolvedPath + '" has no export named "' + bodyJson.exportedFunctionName + '"'); 62 | } 63 | 64 | func.apply(null, [callback].concat(bodyJson.args)); 65 | } catch (synchronousException) { 66 | callback(synchronousException, null); 67 | } 68 | }); 69 | }); 70 | 71 | const parsedArgs = parseArgs(process.argv); 72 | const requestedPortOrZero = parsedArgs.port || 0; // 0 means 'let the OS decide' 73 | server.listen(requestedPortOrZero, 'localhost', function () { 74 | const addressInfo = server.address() as AddressInfo; 75 | 76 | // Signal to HttpNodeHost which loopback IP address (IPv4 or IPv6) and port it should make its HTTP connections on 77 | console.log('[NeuroSpeech.AspNet.NodeServices.HttpNodeHost:Listening on {' + addressInfo.address + '} port ' + addressInfo.port + '\]'); 78 | 79 | // Signal to the NodeServices base class that we're ready to accept invocations 80 | console.log('[NeuroSpeech.AspNet.NodeServices:Listening]'); 81 | }); 82 | 83 | exitWhenParentExits(parseInt(parsedArgs.parentPid), /* ignoreSigint */ true); 84 | 85 | function readRequestBodyAsJson(request, callback) { 86 | let requestBodyAsString = ''; 87 | request.on('data', chunk => { requestBodyAsString += chunk; }); 88 | request.on('end', () => { callback(JSON.parse(requestBodyAsString)); }); 89 | } 90 | 91 | function respondWithError(res: http.ServerResponse, errorValue: any) { 92 | res.statusCode = 500; 93 | res.end(JSON.stringify({ 94 | errorMessage: errorValue.message || errorValue, 95 | errorDetails: errorValue.stack || null 96 | })); 97 | } 98 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/TypeScript/Util/ArgsUtil.ts: -------------------------------------------------------------------------------- 1 | export function parseArgs(args: string[]): any { 2 | // Very simplistic parsing which is sufficient for the cases needed. We don't want to bring in any external 3 | // dependencies (such as an args-parsing library) to this file. 4 | const result = {}; 5 | let currentKey = null; 6 | args.forEach(arg => { 7 | if (arg.indexOf('--') === 0) { 8 | const argName = arg.substring(2); 9 | result[argName] = undefined; 10 | currentKey = argName; 11 | } else if (currentKey) { 12 | result[currentKey] = arg; 13 | currentKey = null; 14 | } 15 | }); 16 | 17 | return result; 18 | } 19 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/TypeScript/Util/ExitWhenParentExits.ts: -------------------------------------------------------------------------------- 1 | /* 2 | In general, we want the Node child processes to be terminated as soon as the parent .NET processes exit, 3 | because we have no further use for them. If the .NET process shuts down gracefully, it will run its 4 | finalizers, one of which (in OutOfProcessNodeInstance.cs) will kill its associated Node process immediately. 5 | 6 | But if the .NET process is terminated forcefully (e.g., on Linux/OSX with 'kill -9'), then it won't have 7 | any opportunity to shut down its child processes, and by default they will keep running. In this case, it's 8 | up to the child process to detect this has happened and terminate itself. 9 | 10 | There are many possible approaches to detecting when a parent process has exited, most of which behave 11 | differently between Windows and Linux/OS X: 12 | 13 | - On Windows, the parent process can mark its child as being a 'job' that should auto-terminate when 14 | the parent does (http://stackoverflow.com/a/4657392). Not cross-platform. 15 | - The child Node process can get a callback when the parent disconnects (process.on('disconnect', ...)). 16 | But despite http://stackoverflow.com/a/16487966, no callback fires in any case I've tested (Windows / OS X). 17 | - The child Node process can get a callback when its stdin/stdout are disconnected, as described at 18 | http://stackoverflow.com/a/15693934. This works well on OS X, but calling stdout.resume() on Windows 19 | causes the process to terminate prematurely. 20 | - I don't know why, but on Windows, it's enough to invoke process.stdin.resume(). For some reason this causes 21 | the child Node process to exit as soon as the parent one does, but I don't see this documented anywhere. 22 | - You can poll to see if the parent process, or your stdin/stdout connection to it, is gone 23 | - You can directly pass a parent process PID to the child, and then have the child poll to see if it's 24 | still running (e.g., using process.kill(pid, 0), which doesn't kill it but just tests whether it exists, 25 | as per https://nodejs.org/api/process.html#process_process_kill_pid_signal) 26 | - Or, on each poll, you can try writing to process.stdout. If the parent has died, then this will throw. 27 | However I don't see this documented anywhere. It would be nice if you could just poll for whether or not 28 | process.stdout is still connected (without actually writing to it) but I haven't found any property whose 29 | value changes until you actually try to write to it. 30 | 31 | Of these, the only cross-platform approach that is actually documented as a valid strategy is simply polling 32 | to check whether the parent PID is still running. So that's what we do here. 33 | */ 34 | 35 | const pollIntervalMs = 1000; 36 | 37 | export function exitWhenParentExits(parentPid: number, ignoreSigint: boolean) { 38 | setInterval(() => { 39 | if (!processExists(parentPid)) { 40 | // Can't log anything at this point, because out stdout was connected to the parent, 41 | // but the parent is gone. 42 | process.exit(); 43 | } 44 | }, pollIntervalMs); 45 | 46 | if (ignoreSigint) { 47 | // Pressing ctrl+c in the terminal sends a SIGINT to all processes in the foreground process tree. 48 | // By default, the Node process would then exit before the .NET process, because ASP.NET implements 49 | // a delayed shutdown to allow ongoing requests to complete. 50 | // 51 | // This is problematic, because if Node exits first, the CopyToAsync code in ConditionalProxyMiddleware 52 | // will experience a read fault, and logs a huge load of errors. Fortunately, since the Node process is 53 | // already set up to shut itself down if it detects the .NET process is terminated, all we have to do is 54 | // ignore the SIGINT. The Node process will then terminate automatically after the .NET process does. 55 | // 56 | // A better solution would be to have WebpackDevMiddleware listen for SIGINT and gracefully close any 57 | // ongoing EventSource connections before letting the Node process exit, independently of the .NET 58 | // process exiting. However, doing this well in general is very nontrivial (see all the discussion at 59 | // https://github.com/nodejs/node/issues/2642). 60 | process.on('SIGINT', () => { 61 | console.log('Received SIGINT. Waiting for .NET process to exit...'); 62 | }); 63 | } 64 | } 65 | 66 | function processExists(pid: number) { 67 | try { 68 | // Sending signal 0 - on all platforms - tests whether the process exists. As long as it doesn't 69 | // throw, that means it does exist. 70 | process.kill(pid, 0); 71 | return true; 72 | } catch (ex) { 73 | // If the reason for the error is that we don't have permission to ask about this process, 74 | // report that as a separate problem. 75 | if (ex.code === 'EPERM') { 76 | throw new Error(`Attempted to check whether process ${pid} was running, but got a permissions error.`); 77 | } 78 | 79 | return false; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/TypeScript/Util/OverrideStdOutputs.ts: -------------------------------------------------------------------------------- 1 | // When Node writes to stdout/strerr, we capture that and convert the lines into calls on the 2 | // active .NET ILogger. But by default, stdout/stderr don't have any way of distinguishing 3 | // linebreaks inside log messages from the linebreaks that delimit separate log messages, 4 | // so multiline strings will end up being written to the ILogger as multiple independent 5 | // log messages. This makes them very hard to make sense of, especially when they represent 6 | // something like stack traces. 7 | // 8 | // To fix this, we intercept stdout/stderr writes, and replace internal linebreaks with a 9 | // marker token. When .NET receives the lines, it converts the marker tokens back to regular 10 | // linebreaks within the logged messages. 11 | // 12 | // Note that it's better to do the interception at the stdout/stderr level, rather than at 13 | // the console.log/console.error (etc.) level, because this takes place after any native 14 | // message formatting has taken place (e.g., inserting values for % placeholders). 15 | const findInternalNewlinesRegex = /\n(?!$)/g; 16 | const encodedNewline = '__ns_newline__'; 17 | 18 | encodeNewlinesWrittenToStream(process.stdout); 19 | encodeNewlinesWrittenToStream(process.stderr); 20 | 21 | function encodeNewlinesWrittenToStream(outputStream: NodeJS.WritableStream) { 22 | const origWriteFunction = outputStream.write; 23 | outputStream.write = function (value: any) { 24 | // Only interfere with the write if it's definitely a string 25 | if (typeof value === 'string') { 26 | const argsClone = Array.prototype.slice.call(arguments, 0); 27 | argsClone[0] = encodeNewlinesInString(value); 28 | origWriteFunction.apply(this, argsClone); 29 | } else { 30 | origWriteFunction.apply(this, arguments); 31 | } 32 | }; 33 | } 34 | 35 | function encodeNewlinesInString(str: string): string { 36 | return str.replace(findInternalNewlinesRegex, encodedNewline); 37 | } 38 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/TypeScript/Util/PatchModuleResolutionLStat.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | const startsWith = (str: string, prefix: string) => str.substring(0, prefix.length) === prefix; 3 | const appRootDir = process.cwd(); 4 | 5 | function patchedLStat(pathToStatLong: string, fsReqWrap?: any) { 6 | try { 7 | // If the lstat completes without errors, we don't modify its behavior at all 8 | return origLStat.apply(this, arguments); 9 | } catch(ex) { 10 | const shouldOverrideError = 11 | startsWith(ex.message, 'EPERM') // It's a permissions error 12 | && typeof appRootDirLong === 'string' 13 | && startsWith(appRootDirLong, pathToStatLong) // ... for an ancestor directory 14 | && ex.stack.indexOf('Object.realpathSync ') >= 0; // ... during symlink resolution 15 | 16 | if (shouldOverrideError) { 17 | // Fake the result to give the same result as an 'lstat' on the app root dir. 18 | // This stops Node failing to load modules just because it doesn't know whether 19 | // ancestor directories are symlinks or not. If there's a genuine file 20 | // permissions issue, it will still surface later when Node actually 21 | // tries to read the file. 22 | return origLStat.call(this, appRootDir, fsReqWrap); 23 | } else { 24 | // In any other case, preserve the original error 25 | throw ex; 26 | } 27 | } 28 | }; 29 | 30 | // It's only necessary to apply this workaround on Windows 31 | let appRootDirLong: string = null; 32 | let origLStat: Function = null; 33 | if (/^win/.test(process.platform)) { 34 | try { 35 | // Get the app's root dir in Node's internal "long" format (e.g., \\?\C:\dir\subdir) 36 | appRootDirLong = (path as any)._makeLong(appRootDir); 37 | 38 | // Actually apply the patch, being as defensive as possible 39 | const bindingFs = (process as any).binding('fs'); 40 | origLStat = bindingFs.lstat; 41 | if (typeof origLStat === 'function') { 42 | bindingFs.lstat = patchedLStat; 43 | } 44 | } catch(ex) { 45 | // If some future version of Node throws (e.g., to prevent use of process.binding()), 46 | // don't apply the patch, but still let the application run. 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/TypeScript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es3", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "types": ["node"] 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/Util/EmbeddedResourceReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | 5 | namespace NeuroSpeech.AspNet.NodeServices 6 | { 7 | /// 8 | /// Contains methods for reading embedded resources. 9 | /// 10 | public static class EmbeddedResourceReader 11 | { 12 | /// 13 | /// Reads the specified embedded resource from a given assembly. 14 | /// 15 | /// Any in the assembly whose resource is to be read. 16 | /// The path of the resource to be read. 17 | /// The contents of the resource. 18 | public static string Read(Type assemblyContainingType, string path) 19 | { 20 | var asm = assemblyContainingType.GetTypeInfo().Assembly; 21 | var embeddedResourceName = asm.GetName().Name + path.Replace("/", "."); 22 | 23 | using (var stream = asm.GetManifestResourceStream(embeddedResourceName)) 24 | using (var sr = new StreamReader(stream)) 25 | { 26 | return sr.ReadToEnd(); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/Util/StringAsTempFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | 5 | namespace NeuroSpeech.AspNet.NodeServices 6 | { 7 | /// 8 | /// Makes it easier to pass script files to Node in a way that's sure to clean up after the process exits. 9 | /// 10 | public sealed class StringAsTempFile : IDisposable 11 | { 12 | private bool _disposedValue; 13 | private bool _hasDeletedTempFile; 14 | private object _fileDeletionLock = new object(); 15 | private IDisposable _applicationLifetimeRegistration; 16 | 17 | /// 18 | /// Create a new instance of . 19 | /// 20 | /// The contents of the temporary file to be created. 21 | /// A token that indicates when the host application is stopping. 22 | public StringAsTempFile(string content, CancellationToken applicationStoppingToken) 23 | { 24 | FileName = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 25 | File.WriteAllText(FileName, content); 26 | 27 | // Because .NET finalizers don't reliably run when the process is terminating, also 28 | // add event handlers for other shutdown scenarios. 29 | _applicationLifetimeRegistration = applicationStoppingToken.Register(EnsureTempFileDeleted); 30 | } 31 | 32 | /// 33 | /// Specifies the filename of the temporary file. 34 | /// 35 | public string FileName { get; } 36 | 37 | /// 38 | /// Disposes the instance and deletes the associated temporary file. 39 | /// 40 | public void Dispose() 41 | { 42 | DisposeImpl(true); 43 | GC.SuppressFinalize(this); 44 | } 45 | 46 | private void DisposeImpl(bool disposing) 47 | { 48 | if (!_disposedValue) 49 | { 50 | if (disposing) 51 | { 52 | // Dispose managed state 53 | _applicationLifetimeRegistration.Dispose(); 54 | } 55 | 56 | EnsureTempFileDeleted(); 57 | 58 | _disposedValue = true; 59 | } 60 | } 61 | 62 | private void EnsureTempFileDeleted() 63 | { 64 | lock (_fileDeletionLock) 65 | { 66 | if (!_hasDeletedTempFile) 67 | { 68 | File.Delete(FileName); 69 | _hasDeletedTempFile = true; 70 | } 71 | } 72 | } 73 | 74 | /// 75 | /// Implements the finalization part of the IDisposable pattern by calling Dispose(false). 76 | /// 77 | ~StringAsTempFile() 78 | { 79 | DisposeImpl(false); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/Util/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace NeuroSpeech.AspNet.NodeServices 5 | { 6 | internal static class TaskExtensions 7 | { 8 | public static Task OrThrowOnCancellation(this Task task, CancellationToken cancellationToken) 9 | { 10 | return task.IsCompleted 11 | ? task // If the task is already completed, no need to wrap it in a further layer of task 12 | : task.ContinueWith( 13 | _ => {}, // If the task completes, allow execution to continue 14 | cancellationToken, 15 | TaskContinuationOptions.ExecuteSynchronously, 16 | TaskScheduler.Default); 17 | } 18 | 19 | public static Task OrThrowOnCancellation(this Task task, CancellationToken cancellationToken) 20 | { 21 | return task.IsCompleted 22 | ? task // If the task is already completed, no need to wrap it in a further layer of task 23 | : task.ContinueWith( 24 | t => t.Result, // If the task completes, pass through its result 25 | cancellationToken, 26 | TaskContinuationOptions.ExecuteSynchronously, 27 | TaskScheduler.Default); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeservices", 3 | "version": "1.0.0", 4 | "description": "This is not really an NPM package and will not be published. This file exists only to reference compilation tools.", 5 | "private": true, 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "webpack --mode production" 10 | }, 11 | "author": "Microsoft", 12 | "license": "Apache-2.0" 13 | } 14 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.AspNet.NodeServices/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | target: 'node', 5 | resolve: { 6 | extensions: [ '.ts' ] 7 | }, 8 | module: { 9 | rules: [ 10 | { test: /\.ts$/, use: 'ts-loader' }, 11 | ] 12 | }, 13 | entry: { 14 | 'entrypoint-http': ['./TypeScript/HttpNodeInstanceEntryPoint'] 15 | }, 16 | output: { 17 | libraryTarget: 'commonjs', 18 | path: path.join(__dirname, 'Content', 'Node'), 19 | filename: '[name].js' 20 | }, 21 | optimization: { 22 | minimize: false 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.NodePackageInstaller/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Text; 6 | 7 | namespace NeuroSpeech 8 | { 9 | 10 | /// 11 | /// Creates PackagePathSegments from string 12 | /// 13 | public struct PackagePathSegments : IEquatable 14 | { 15 | /// 16 | /// Returns only package name including scope 17 | /// 18 | public string Package; 19 | 20 | /// 21 | /// Returns only version if any 22 | /// 23 | public string Version; 24 | 25 | /// 26 | /// Returns parsed path after package 27 | /// 28 | public string Path; 29 | 30 | /// 31 | /// Returns package name and version in {Package}@{Version} format if version is present 32 | /// 33 | public string PackageAndVersion => 34 | string.IsNullOrWhiteSpace(Version) ? 35 | Package : 36 | $"{Package}@{Version}"; 37 | 38 | public PackagePathSegments(string p, string v, string path) 39 | { 40 | this.Package = p; 41 | this.Version = v; 42 | this.Path = path; 43 | } 44 | 45 | public static implicit operator PackagePathSegments (string value) { 46 | return NPSStringExtensions.ParseNPMPath(value); 47 | } 48 | 49 | public override bool Equals(object obj) 50 | { 51 | return obj is PackagePathSegments && Equals((PackagePathSegments)obj); 52 | } 53 | 54 | public bool Equals(PackagePathSegments other) 55 | { 56 | return Package == other.Package && 57 | Version == other.Version && 58 | Path == other.Path; 59 | } 60 | 61 | public override int GetHashCode() 62 | { 63 | return Package.GetHashCode(); 64 | } 65 | 66 | public static bool operator == (PackagePathSegments a, PackagePathSegments b) 67 | { 68 | return a.Package == b.Package && a.Version == b.Version && a.Path == b.Path; 69 | } 70 | 71 | public static bool operator != (PackagePathSegments a, PackagePathSegments b) 72 | { 73 | return !(a.Package == b.Package && a.Version == b.Version && a.Path == b.Path); 74 | } 75 | 76 | public (string package, string version, string path) Deconstruct 77 | { 78 | get { 79 | return (Package, Version, Path); 80 | } 81 | } 82 | 83 | } 84 | 85 | public static class NPSStringExtensions 86 | { 87 | 88 | public static PackagePathSegments ParseNPMPath(this string input) 89 | { 90 | string package = ""; 91 | string version = ""; 92 | string path = ""; 93 | if (input.StartsWith("@")) 94 | { 95 | var (scope, packagePath) = input.ExtractTill("/"); 96 | 97 | (package, path) = packagePath.ExtractTill("/"); 98 | (package, version) = package.ExtractTill("@"); 99 | package = scope + "/" + package; 100 | } 101 | else 102 | { 103 | (package, path) = input.ExtractTill("/"); 104 | (package, version) = package.ExtractTill("@"); 105 | } 106 | 107 | if(version.StartsWith("v")) 108 | { 109 | version = version.Substring(1); 110 | } 111 | 112 | return new PackagePathSegments (package, version, path); 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.NodePackageInstaller/NeuroSpeech.NodePackageInstaller.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | Library 7 | NeuroSpeech.NodePackageInstaller 8 | 1.0.1 9 | 10 | true 11 | Akash Kava 12 | NeuroSpeech Technologies Pvt Ltd 13 | NeuroSpeech NodePackageInstaller 14 | true 15 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 16 | NeuroSpeech 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.NodePackageInstaller/NpmRegistry.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NeuroSpeech 9 | { 10 | public class NpmRegistry 11 | { 12 | private readonly HttpClient client; 13 | 14 | public NpmRegistry(HttpClient client) 15 | { 16 | this.client = client; 17 | } 18 | 19 | public async Task GetTarBallAsync(string url, string package, string version) 20 | { 21 | url = url.TrimEnd('/') + "/" + package; 22 | try 23 | { 24 | try 25 | { 26 | return await GetTarBallForAsync(url + "/" + version, version); 27 | } 28 | catch { } 29 | return await GetTarBallForAsync(url, version); 30 | }catch(Exception ex) 31 | { 32 | throw new ArgumentException($"Fetch Failed for {url} {version} {ex.Message}\r\n{ex.ToString()}"); 33 | } 34 | } 35 | 36 | private async Task GetTarBallForAsync(string url, string v) 37 | { 38 | v = v.Trim('v', 'V'); 39 | var json = await client.GetStringAsync(url); 40 | var package = JObject.Parse(json); 41 | if(package.TryGetValue("dist", out var token)) 42 | { 43 | var dist = token as JObject; 44 | return dist.GetValue("tarball").ToString(); 45 | } 46 | 47 | if(package.TryGetValue("versions", out token)) 48 | { 49 | var versions = token as JObject; 50 | if(versions.TryGetValue(v, out token)) 51 | { 52 | 53 | var dist = (token as JObject).GetValue("dist") as JObject; 54 | return dist.GetValue("tarball").ToString(); 55 | } 56 | } 57 | throw new KeyNotFoundException($"Version {v} not found in {json}"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.NodePackageInstaller/PackageInstallerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace NeuroSpeech 6 | { 7 | public class PackageInstallerOptions 8 | { 9 | 10 | /// 11 | /// If set, starts the Node.js instance with the specified environment variables. 12 | /// 13 | public IDictionary EnvironmentVariables { get; set; } 14 | 15 | /// 16 | /// Folder where node packages will be downloaded 17 | /// 18 | public string TempFolder { get; set; } = "d:\\temp\\ns-npm"; 19 | 20 | 21 | /// 22 | /// NPM Registry used to download packages 23 | /// 24 | public string NPMRegistry { get; set; } 25 | 26 | 27 | /// 28 | /// White list of packages to execute 29 | /// 30 | public string[] PrivatePackages { get; set; } 31 | 32 | /// 33 | /// Time to live, after which NodeServer will dispose 34 | /// 35 | public TimeSpan TTL { get; set; } = TimeSpan.FromHours(1); 36 | 37 | /// 38 | /// Useful for single npm cache folder, default is true 39 | /// 40 | public bool UseFileLock { get; set; } = true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.NodePackageInstaller/PackagePath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace NeuroSpeech 5 | { 6 | public class PackagePath 7 | { 8 | 9 | public string PrivateNpmUrl => $"{Options.NPMRegistry}{Package}/-/{ID}-{Version}.tgz"; 10 | 11 | public readonly bool isPrivate; 12 | public readonly string Package; 13 | public readonly string Version; 14 | public readonly string Path; 15 | public readonly string TempRoot; 16 | 17 | // private readonly string npmUrlTemplate; 18 | 19 | public string ID => Package.Split('/').Last(); 20 | 21 | public string Tag => $"v{Version}"; 22 | 23 | public string TagFolder 24 | => $"{Options.TempFolder}\\npm\\{Package}\\{Tag}"; 25 | public PackageInstallerOptions Options { get; } 26 | 27 | 28 | public PackagePath( 29 | PackageInstallerOptions options, 30 | PackagePathSegments p, 31 | bool isPrivate) 32 | { 33 | this.Options = options; 34 | this.isPrivate = isPrivate; 35 | this.Package = p.Package; 36 | this.Version = p.Version; 37 | this.Path = p.Path; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /NodePackageService/NeuroSpeech.NodePackageInstaller/Tasks/FileTransactionTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace NeuroSpeech.Tasks 8 | { 9 | public class FileTransactionTask : IDisposable 10 | { 11 | readonly DirectoryInfo folder; 12 | readonly DirectoryInfo _workingFolder; 13 | 14 | public DirectoryInfo WorkingFolder => this._workingFolder; 15 | 16 | public FileTransactionTask(string folder, string root) 17 | { 18 | this.folder = new DirectoryInfo($"{root}\\{folder}"); 19 | this._workingFolder = new DirectoryInfo($"{root}\\tmp\\{Guid.NewGuid()}"); 20 | 21 | if (!this._workingFolder.Exists) 22 | { 23 | this._workingFolder.Create(); 24 | } 25 | } 26 | 27 | public void Dispose() 28 | { 29 | this._workingFolder.Delete(true); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService.sln1: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2026 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A43852DA-3E5C-409B-95FA-E76FD81FEB61}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitignore = .gitignore 9 | README.md = README.md 10 | EndProjectSection 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodeServerTest", "NodeServerTest\NodeServerTest.csproj", "{E6682D8C-5BB9-4924-89E2-95C6BE50125E}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NeuroSpeech.NodePackageService", "NodePackageService\NeuroSpeech.NodePackageService.csproj", "{D2466C9C-0259-4B80-AE9F-175C859BFDB4}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {E6682D8C-5BB9-4924-89E2-95C6BE50125E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {E6682D8C-5BB9-4924-89E2-95C6BE50125E}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {E6682D8C-5BB9-4924-89E2-95C6BE50125E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {E6682D8C-5BB9-4924-89E2-95C6BE50125E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {D2466C9C-0259-4B80-AE9F-175C859BFDB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {D2466C9C-0259-4B80-AE9F-175C859BFDB4}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {D2466C9C-0259-4B80-AE9F-175C859BFDB4}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {D2466C9C-0259-4B80-AE9F-175C859BFDB4}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {BDF96B3A-C998-4BF8-AEF7-DA13BE217C77} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/NeuroSpeech.NodePackageService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | Library 7 | NeuroSpeech.NodePackageService 8 | 1.0.1 9 | 10 | true 11 | Akash Kava 12 | NeuroSpeech Technologies Pvt Ltd 13 | NeuroSpeech NodePackageService 14 | true 15 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/NeuroSpeech.NodePackageService.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | IIS Express 5 | false 6 | 7 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/NodePackageService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using System; 6 | using System.Threading.Tasks; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.IO; 10 | using NeuroSpeech.Tasks; 11 | using Microsoft.Extensions.Logging; 12 | using System.Diagnostics; 13 | using NeuroSpeech.Internal; 14 | using Jering.Javascript.NodeJS; 15 | 16 | namespace NeuroSpeech 17 | { 18 | 19 | 20 | public class NodePackageService: PackageInstallerService 21 | { 22 | 23 | private NodePackageServiceOptions _Options; 24 | 25 | public NodePackageService( 26 | IServiceProvider services, 27 | NodePackageServiceOptions options, 28 | Func> versionProvider = null) 29 | : base(services, options, versionProvider) 30 | { 31 | this._Options = options; 32 | } 33 | 34 | public async Task GetInstalledPackageAsync(string path) 35 | { 36 | return await this.InstalledPackageAsync(path) as NodePackage; 37 | } 38 | 39 | protected override NodeInstalledPackage CreatePackage(PackagePath pp) 40 | { 41 | 42 | //var options = this._Options.NodeServicesOptions ?? new NodeJSProcessOptions() 43 | //{ 44 | // ProjectPath = pp.TagFolder, 45 | // NodeInstanceOutputLogger = services.GetService>() 46 | //}; 47 | 48 | ver options = services.Configure(); 49 | 50 | options.ProjectPath = pp.TagFolder; 51 | 52 | if (this.Options.EnvironmentVariables != null) 53 | { 54 | if (options.EnvironmentVariables == null) 55 | { 56 | options.EnvironmentVariables = new Dictionary(); 57 | } 58 | foreach (var item in this.Options.EnvironmentVariables) 59 | { 60 | options.EnvironmentVariables[item.Key] = item.Value; 61 | } 62 | } 63 | 64 | var s = services.GetRequiredService(); 65 | 66 | return new NodePackage { 67 | Path = pp, 68 | NodeServices = s 69 | }; 70 | } 71 | 72 | } 73 | 74 | public class NodePackage: NodeInstalledPackage 75 | { 76 | public INodeJSService NodeServices; 77 | 78 | public override void Dispose() 79 | { 80 | NodeServices?.Dispose(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/NodePackageServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Net.Http.Headers; 6 | using System.Threading.Tasks; 7 | 8 | namespace NeuroSpeech 9 | { 10 | public interface IVersionProvider 11 | { 12 | 13 | Task GetVersionAsync(PackagePathSegments path); 14 | 15 | } 16 | 17 | public static class NodePackageServiceExtensions 18 | { 19 | 20 | public static void AddNodePackageService(this IServiceCollection services, 21 | NodePackageServiceOptions options) 22 | { 23 | services.AddSingleton(sp => new NodePackageService(sp, options, null)); 24 | } 25 | 26 | public static void AddNodePackageService(this IServiceCollection services, 27 | NodePackageServiceOptions options) 28 | where T: IVersionProvider 29 | { 30 | services.AddSingleton(sp => new NodePackageService(sp, options, (s, path) => { 31 | var st = s.GetRequiredService(); 32 | return st.GetVersionAsync(path); 33 | })); 34 | } 35 | 36 | //public static IApplicationBuilder UseNpmDistribution( 37 | // this IApplicationBuilder app, 38 | // string route = "js-pkg/", 39 | // bool crossBrowser = false) 40 | //{ 41 | // app.Map(route, a => 42 | // { 43 | // a.Use(async (context, next) => 44 | // { 45 | 46 | // HttpRequest request = context.Request; 47 | // if (!request.Method.EqualsIgnoreCase("GET")) 48 | // { 49 | // await next(); 50 | // return; 51 | // } 52 | 53 | // PathString path = request.Path; 54 | 55 | // IHeaderDictionary headers = context.Response.Headers; 56 | // if (crossBrowser) 57 | // { 58 | // headers.Add("access-control-allow-origin", "*"); 59 | // headers.Add("access-control-expose-headers", "*"); 60 | // headers.Add("access-control-allow-methods", "*"); 61 | // headers.Add("access-control-allow-headers", "*"); 62 | // headers.Add("access-control-max-age", TimeSpan.FromDays(30).TotalSeconds.ToString()); 63 | // } 64 | 65 | // var nodeServer = context.RequestServices.GetService(); 66 | 67 | // string sp = path.Value.Substring(route.Length + 1); 68 | 69 | // var packageSegment = sp.ParseNPMPath(); 70 | 71 | // var package = await nodeServer.GetInstalledPackageAsync(sp); 72 | 73 | // string folder = package.Path.TagFolder; 74 | 75 | // string filePath = $"{folder}\\{packageSegment.Path}".Replace("/","\\"); 76 | 77 | // string host = context.Request.Query["host"]; 78 | 79 | // string contentType = MimeKit.MimeTypes.GetMimeType(filePath); 80 | 81 | // var ct = MediaTypeHeaderValue.Parse(contentType); 82 | // ct.CharSet = System.Text.Encoding.UTF8.EncodingName; 83 | // context.Response.Headers.SetCommaSeparatedValues("content-type", ct.ToString()); 84 | 85 | // // await context.Response.SendFileAsync(filePath, context.RequestAborted); 86 | 87 | // if (!string.IsNullOrWhiteSpace(host)) 88 | // { 89 | // string lang = context.Request.Query["lang"]; 90 | // if (string.IsNullOrWhiteSpace(lang)) 91 | // { 92 | // lang = "en-US"; 93 | // } 94 | // string designMode = context.Request.Query["designMode"]; 95 | // bool design = designMode.EqualsIgnoreCase("true"); 96 | // var startScript = $"UMD.lang = \"{lang}\";\r\n" + 97 | // $"UMD.hostView(\"{host}\",\"{packageSegment.Package}/{path}\", {(design? "true" : "false")})"; 98 | // await context.Response.WriteAsync(startScript); 99 | // } 100 | // }); 101 | 102 | // }); 103 | 104 | // return app; 105 | //} 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/NodeServerOptions.cs: -------------------------------------------------------------------------------- 1 | using Jering.Javascript.NodeJS; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace NeuroSpeech 7 | { 8 | public class NodePackageServiceOptions: PackageInstallerOptions 9 | { 10 | 11 | /// 12 | /// 13 | /// 14 | public NodeJSProcessOptions NodeServicesOptions { get; set; } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:62111/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "NodeServer": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:62112/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /NodePackageService/NodePackageService/Tasks/HtmlProcessor.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp; 2 | using AngleSharp.Parser.Html; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace NeuroSpeech.Tasks 9 | { 10 | public class HtmlProcessor 11 | { 12 | public static HtmlProcessor Instance = new HtmlProcessor(); 13 | 14 | internal string RedirectNPM(string text, string currentPackage, string cdn) 15 | { 16 | var parser = new HtmlParser(new Configuration().WithCss()); 17 | var document = parser.Parse(text); 18 | 19 | var all = document.All.Where(x => 20 | x.TagName.EqualsIgnoreCase("link") && x.HasAttribute("href")); 21 | 22 | foreach (var item in all) 23 | { 24 | UpdateAttribute(item, "href", currentPackage, cdn); 25 | } 26 | 27 | all = document.All.Where(x => 28 | x.TagName.EqualsIgnoreCase("script") && x.HasAttribute("src")); 29 | 30 | foreach (var item in all) 31 | { 32 | UpdateAttribute(item, "src", currentPackage, cdn); 33 | } 34 | 35 | all = document.All.Where(x => 36 | x.TagName.EqualsIgnoreCase("img") && x.HasAttribute("src")); 37 | 38 | foreach (var item in all) 39 | { 40 | UpdateAttribute(item, "src", currentPackage, cdn); 41 | } 42 | 43 | all = document.All.Where(x => 44 | x.Style?.Any(s => 45 | s.Name.EqualsIgnoreCase("background-image") 46 | || 47 | s.Name.EqualsIgnoreCase("list-style-image") 48 | ) ?? false); 49 | 50 | foreach (var item in all) 51 | { 52 | //UpdateAttribute(item, "src", currentPackage); 53 | var img = ExtractUrl(item.Style?.BackgroundImage); 54 | if (!string.IsNullOrWhiteSpace(img)) 55 | { 56 | img = UpdateAttributeValue(img, currentPackage, cdn); 57 | item.Style.BackgroundImage = $"url({img})"; 58 | } 59 | 60 | img = ExtractUrl(item.Style?.ListStyleImage); 61 | if (!string.IsNullOrWhiteSpace(img)) 62 | { 63 | img = UpdateAttributeValue(img, currentPackage, cdn); 64 | item.Style.ListStyleImage = $"url({img} )"; 65 | } 66 | } 67 | 68 | return document.DocumentElement.OuterHtml; 69 | } 70 | 71 | private string ExtractUrl(string attribute) 72 | { 73 | if (attribute == null) 74 | return null; 75 | attribute = attribute.Trim(); 76 | if (!attribute.StartsWith("url(", StringComparison.OrdinalIgnoreCase)) 77 | return null; 78 | attribute = attribute.Substring(4); 79 | attribute = attribute.Trim('(', ')', '"', '\''); 80 | 81 | return attribute; 82 | } 83 | 84 | private static string UpdateAttributeValue(string href, string currentPackage, string cdn) 85 | { 86 | int i = href.IndexOf("node_modules/", StringComparison.OrdinalIgnoreCase); 87 | if (i != -1) 88 | { 89 | href = $"//{cdn}/npm/package/{href.Substring(i + 13)}"; 90 | return href; 91 | } 92 | 93 | if (href.StartsWith("/")) 94 | return null; 95 | 96 | if (href.StartsWith("https://", StringComparison.OrdinalIgnoreCase) 97 | || href.StartsWith("http://", StringComparison.OrdinalIgnoreCase) 98 | || href.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) 99 | { 100 | return null; 101 | } 102 | 103 | href = href.TrimStart('.', '/'); 104 | href = $"https://{cdn}/npm/package/{currentPackage}/{href}"; 105 | return href; 106 | } 107 | 108 | private static void UpdateAttribute(AngleSharp.Dom.IElement item, string attribute, string currentPackage, string cdn) 109 | { 110 | 111 | string href = item.GetAttribute(attribute); 112 | href = UpdateAttributeValue(href, currentPackage, cdn); 113 | if (href != null) 114 | { 115 | item.SetAttribute(attribute, href); 116 | } 117 | 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /NodePackageService/NodeServerTest/InstallTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Caching; 3 | using NeuroSpeech; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace NodeServerTest 11 | { 12 | //public class MockCacheEntry: ICacheEntry 13 | //{ 14 | 15 | //} 16 | 17 | //public class MockCache : IMemoryCache 18 | //{ 19 | // public ICacheEntry CreateEntry(object key) 20 | // { 21 | // return null; 22 | // } 23 | 24 | // public void Dispose() 25 | // { 26 | 27 | // } 28 | 29 | // public void Remove(object key) 30 | // { 31 | 32 | // } 33 | 34 | // public bool TryGetValue(object key, out object value) 35 | // { 36 | // value = null; 37 | // return false; 38 | // } 39 | //} 40 | 41 | public class InstallTest 42 | { 43 | 44 | // public IServiceProvider sp { get; } 45 | // [Fact] 46 | public async Task InstallAsync() 47 | { 48 | var serviceProvider = new ServiceCollection() 49 | .BuildServiceProvider(); 50 | 51 | var server = new NodePackageService(serviceProvider, new NodePackageServiceOptions 52 | { 53 | TempFolder = "D:\\tempg\\" + Guid.NewGuid(), 54 | NPMRegistry = "https://proget-2018-10-29-ns.800casting.com/npm/NS-NPM/", 55 | PrivatePackages = new string[] { 56 | "@c8private/email-templates@1.0.14", 57 | "@c8private/lang@1.0.1" 58 | } 59 | }); 60 | 61 | var package = await server.GetInstalledPackageAsync("@c8private/email-templates@1.0.14"); 62 | Assert.NotNull(package); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /NodePackageService/NodeServerTest/NodeServerTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /NodePackageService/NodeServerTest/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using NeuroSpeech; 4 | 5 | namespace NodeServerTest 6 | { 7 | public class ParseNPMPackageTest 8 | { 9 | [Fact] 10 | public void Parse() 11 | { 12 | var (package, version, path) = "root".ParseNPMPath().Deconstruct; 13 | 14 | Assert.Equal("", version); 15 | Assert.Equal("root", package); 16 | Assert.Equal("", path); 17 | 18 | (package, version, path) = "@root/package".ParseNPMPath().Deconstruct; 19 | 20 | Assert.Equal("", version); 21 | Assert.Equal("@root/package", package); 22 | Assert.Equal("", path); 23 | 24 | } 25 | 26 | [Fact] 27 | public void ParseWithVersion() 28 | { 29 | var (package, version, path) = "root@1.1".ParseNPMPath().Deconstruct; 30 | 31 | Assert.Equal("1.1", version); 32 | Assert.Equal("root", package); 33 | Assert.Equal("", path); 34 | 35 | (package, version, path) = "@root/package@1.1".ParseNPMPath().Deconstruct; 36 | 37 | Assert.Equal("1.1", version); 38 | Assert.Equal("@root/package", package); 39 | Assert.Equal("", path); 40 | 41 | } 42 | } 43 | 44 | public class ParseNPMPathTest 45 | { 46 | [Fact] 47 | public void WithoutVersion() 48 | { 49 | 50 | var (package, version, path) = "root/path".ParseNPMPath().Deconstruct; 51 | 52 | Assert.Equal("", version); 53 | Assert.Equal("root", package); 54 | Assert.Equal("path", path); 55 | 56 | (package, version, path) = "root/path/1/2".ParseNPMPath().Deconstruct; 57 | 58 | Assert.Equal("", version); 59 | Assert.Equal("root", package); 60 | Assert.Equal("path/1/2", path); 61 | } 62 | 63 | [Fact] 64 | public void WithVersion() 65 | { 66 | 67 | var (package, version, path) = "root@1.1/path".ParseNPMPath().Deconstruct; 68 | 69 | Assert.Equal("1.1", version); 70 | Assert.Equal("root", package); 71 | Assert.Equal("path", path); 72 | 73 | (package, version, path) = "root@1.1/path/1/2".ParseNPMPath().Deconstruct; 74 | 75 | Assert.Equal("1.1", version); 76 | Assert.Equal("root", package); 77 | Assert.Equal("path/1/2", path); 78 | } 79 | } 80 | 81 | public class ParseScopedNPMPathTest 82 | { 83 | [Fact] 84 | public void WithoutVersion() 85 | { 86 | 87 | var (package, version, path) = "@root/package/path".ParseNPMPath().Deconstruct; 88 | 89 | Assert.Equal("", version); 90 | Assert.Equal("@root/package", package); 91 | Assert.Equal("path", path); 92 | 93 | (package, version, path) = "@root/package/path/1/2".ParseNPMPath().Deconstruct; 94 | 95 | Assert.Equal("", version); 96 | Assert.Equal("@root/package", package); 97 | Assert.Equal("path/1/2", path); 98 | } 99 | 100 | [Fact] 101 | public void WithVersion() 102 | { 103 | 104 | var (package, version, path) = "@root/package@1.1/path".ParseNPMPath().Deconstruct; 105 | 106 | Assert.Equal("1.1", version); 107 | Assert.Equal("@root/package", package); 108 | Assert.Equal("path", path); 109 | 110 | (package, version, path) = "@root/package@1.1/path/1/2".ParseNPMPath().Deconstruct; 111 | 112 | Assert.Equal("1.1", version); 113 | Assert.Equal("@root/package", package); 114 | Assert.Equal("path/1/2", path); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /NodePackageService/README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/rya0tf5dim3d1596?svg=true)](https://ci.appveyor.com/project/neurospeech/asp-net-core-node-server) [![NuGet](https://img.shields.io/nuget/v/NeuroSpeech.NodeServer.svg?label=NuGet)](https://www.nuget.org/packages/NeuroSpeech.NodeServer) 2 | # NodeServer for ASP.NET Core 3 | 4 | An extension of NodeServices to execute side by side versions of node packages. 5 | 6 | ## Installation 7 | 8 | `Install-Package NeuroSpeech.NodePackageService` 9 | 10 | Install and setup ProGet from https://inedo.com/proget and publish your private packages onto npm registry inside ProGet. Support for authenticated npm registry will come in future. 11 | 12 | ## Features 13 | 14 | 1. Side by side execution of different version of same package 15 | 2. Download and extract npm packages along with dependencies from private NPM repository such as ProGet 16 | 3. TempFolder can be configured on `D:` drive, recommended for Azure VMs. 17 | 18 | ## Security 19 | 20 | 1. NodePackageService does not execute `npm install` or `npm start` script. 21 | 2. TempFolder drive needs write access 22 | 3. Only packages listed in `PrivatePackages` will be downloaded and extracted, however dependencies will not be restricted to package whitelist. Your developers must be careful for not include them in package dependencies. 23 | 24 | ## Setup 25 | 26 | ```c# 27 | services.AddSingleton( 28 | sp => new NodePackageService(sp, 29 | new NodePackageServiceOptions 30 | { 31 | // This must be unique to avoid multiple process accessing same 32 | // folder conflicts 33 | TempFolder = "D:\\temp\\" + Guid.NewGuid(), 34 | // ProGet private registry url 35 | NPMRegistry = "https://private-proget.company.com/npm/PRIVATE", 36 | PrivatePackages = new string[] { 37 | "@company/package@1.0.1", 38 | "@company/core@1.0.1" 39 | } 40 | })); 41 | ``` 42 | 43 | ## Execution 44 | 45 | ```c# 46 | 47 | [HttpGet("template/{version}/{name}")] 48 | public async Task ProcessTemplate( 49 | [FromRoute] string name, 50 | [FromRoute] string version, 51 | [FromBody] EmailModel model, 52 | [FromService] NodePackageService packageService 53 | ) { 54 | 55 | // get node services from the installed package version 56 | // if version does not exist, it will download package 57 | // along with all its dependencies 58 | var package = await 59 | packageService.GetInstalledPackageASync($"@company/template@{version}"); 60 | 61 | return await package.NodeServices.InvokeExportAsync( 62 | "index.js", 63 | "default", 64 | new { 65 | fromName: "Sender", 66 | fromAddress: "senderEmail", 67 | body: model 68 | }); 69 | 70 | } 71 | 72 | ``` 73 | 74 | `GetInstalledPackageAsync` method will create a folder `{TempFolder}\npm\{package}\v{version}` and it will extract package from given npm registry. 75 | 76 | > It will not install npm package, as IIS Website may not have sufficient rights to execute `npm` command. So in order to make things simpler, NodeServer inspects package.json file and downloads all dependencies in `node_modules` folder. It does not execute any scripts. 77 | 78 | # Bundled Dependencies 79 | The best way to publish package would be to bundle all dependencies, so this Library will ignore package with bundled dependencies assuming package contains all necessary dependencies. 80 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/Controllers/NodeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | 11 | namespace SampleWebApp.Controllers 12 | { 13 | 14 | [Route("n-api/{*path}")] 15 | public class NodeController: Controller 16 | { 17 | 18 | [HttpGet] 19 | [HttpPost] 20 | [HttpPut] 21 | [HttpDelete] 22 | public async Task Run( 23 | [FromRoute] string path, 24 | [FromServices] NodeServer.NodeServer nodeServer) 25 | { 26 | 27 | path = $"@web-atoms/asp-net-core-node-server-test/{path}"; 28 | 29 | var p = await nodeServer.GetInstalledPackageAsync(path); 30 | string body = null; 31 | if (Request.ContentLength > 0) 32 | { 33 | using(var reader = new StreamReader(Request.Body, System.Text.Encoding.UTF8)) 34 | { 35 | body = await reader.ReadToEndAsync(); 36 | } 37 | } 38 | var q = new JObject(); 39 | foreach(var item in Request.Query) 40 | { 41 | string s = string.Join(" ",item.Value); 42 | q.Add(item.Key, JValue.CreateString(s)); 43 | } 44 | var result = await p.NodeServices.InvokeExportAsync( 45 | "dist/index", 46 | "default", 47 | new MethodRequest { 48 | Path = p.Path.Path, 49 | Method = Request.Method, 50 | Body = body, 51 | BodyType = Request.ContentType, 52 | Query = q 53 | }); 54 | return Content(result, "application/json"); 55 | } 56 | 57 | 58 | public class Result 59 | { 60 | [JsonProperty("success")] 61 | public bool Success { get; set; } 62 | 63 | [JsonProperty("content")] 64 | public string Content { get; set; } 65 | } 66 | 67 | public class MethodRequest 68 | { 69 | [JsonProperty("path")] 70 | public string Path { get; set; } 71 | 72 | [JsonProperty("method")] 73 | public string Method { get; set; } 74 | 75 | [JsonProperty("body")] 76 | public string Body { get; set; } 77 | 78 | [JsonProperty("bodyType")] 79 | public string BodyType { get; set; } 80 | 81 | [JsonProperty("query")] 82 | public JObject Query { get; set; } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace SampleWebApp.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ValuesController : ControllerBase 12 | { 13 | // GET api/values 14 | [HttpGet] 15 | public ActionResult> Get() 16 | { 17 | return new string[] { "value1", "value2" }; 18 | } 19 | 20 | // GET api/values/5 21 | [HttpGet("{id}")] 22 | public ActionResult Get(int id) 23 | { 24 | return "value"; 25 | } 26 | 27 | // POST api/values 28 | [HttpPost] 29 | public void Post([FromBody] string value) 30 | { 31 | } 32 | 33 | // PUT api/values/5 34 | [HttpPut("{id}")] 35 | public void Put(int id, [FromBody] string value) 36 | { 37 | } 38 | 39 | // DELETE api/values/5 40 | [HttpDelete("{id}")] 41 | public void Delete(int id) 42 | { 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace SampleWebApp 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .ConfigureLogging(lb => { 23 | lb.AddConsole(); 24 | }) 25 | .UseStartup(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55343", 7 | "sslPort": 0 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "n-api/test", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SampleWebApp": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "http://localhost:5000" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/SampleWebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using NodeServer; 13 | 14 | namespace SampleWebApp 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 29 | 30 | services.AddNodeServer(new NodeServerOptions { 31 | PrivateNPMUrlTemplate = "https://registry.npmjs.org/{package}/-/{id}-{version}.tgz", 32 | PrivatePackages = new string[] { 33 | "@web-atoms/asp-net-core-node-server-test@1.0.5" 34 | } 35 | }); 36 | 37 | } 38 | 39 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 40 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 41 | { 42 | if (env.IsDevelopment()) 43 | { 44 | app.UseDeveloperExceptionPage(); 45 | } 46 | 47 | app.UseMvc(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /NodePackageService/SampleWebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET Core Extensions and Helpers 2 | 3 | 1. Dependency Injection helper attributes 4 | 2. Useful String Extensions 5 | 3. Useful Dependency Injection Extensions 6 | 4. CSVArrayModel Provider 7 | 5. Useful Cache Extensions 8 | 9 | ## [Retro Core Fit](./RetroFit/README.md) 10 | Simple interface for creating REST API endpoints in c#. 11 | 12 | ## [Node Package Service](./NodePackageService/README.md) 13 | Copy of original NodeServices as it is deprecated, only for backword compatiblity. 14 | -------------------------------------------------------------------------------- /RetroFit/README.md: -------------------------------------------------------------------------------- 1 | # Retrofit Core 2 | 3 | 1. No build step 4 | 2. Dynamic Service Proxy generator 5 | 3. Support for Header as property 6 | 7 | # Example 8 | ```c# 9 | 10 | public interface IBackendService { 11 | 12 | // when set, it will always be sent with 13 | // every request 14 | [Header("access-key")] 15 | AccessKey { get; set; } 16 | 17 | [Get("/location/{ip}")] 18 | Task GetLocationInfoAsync([Path("ip")] string ip); 19 | 20 | [Post("/location/{ip}")] 21 | Task SaveLocationInfoAsync([Path("ip")] string ip, [Body] IPInfo info); 22 | 23 | [Get("/voice/{id}.mp3")] 24 | Task GetByteArrayAsync([Query("id")] string id); 25 | 26 | // Response Object with Header 27 | [Get("/projects")] 28 | Task> GetProjectsAsync(); 29 | 30 | 31 | // Retrieve http response for detailed response. 32 | // HttpResponseMessage is not disposed, it is responsibility of caller 33 | // to dispose the message (which will close open network streams) 34 | // This will not throw an error message if there was HTTP Error. 35 | [Get("/video/{id}.mp4")] 36 | Task GetRawResponseAsync([Query("id")] string id); 37 | 38 | } 39 | 40 | public class GitLabResponse: ApiResponse { 41 | 42 | [Header("x-total-pages")] 43 | public int TotalPages {get;set;} 44 | 45 | } 46 | 47 | ``` 48 | 49 | # Usage 50 | ```c# 51 | 52 | var client = RetroClient.Create( new Uri("base url...") , httpClient); 53 | 54 | ``` 55 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit.Tests/ApiImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using static RetroCoreFit.Tests.UnitTest1; 6 | 7 | namespace RetroCoreFit.Tests 8 | { 9 | //public class ApiImpl : ServiceInterface, IApi 10 | //{ 11 | // public string Authorize { get; set; } 12 | 13 | // public Task UpdateAsync(Product product, bool email = false) 14 | // { 15 | // return (Task)base.Invoke("Post","url", 16 | // new RestParameter { 17 | // Type = ParameterType.Body, 18 | // Value = product 19 | // }, 20 | // new RestParameter { 21 | // Name = "email", 22 | // Type = ParameterType.Query, 23 | // Value = email 24 | // }); 25 | // } 26 | //} 27 | } 28 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit.Tests/RetroCoreFit.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace RetroCoreFit.Tests 9 | { 10 | public class UnitTest1 11 | { 12 | [Fact] 13 | public async Task Test1Async() 14 | { 15 | 16 | var api = RetroClient.Create(new Uri("https://m.800casting.com"),new HttpClient(new TestHttpClient())); 17 | 18 | Assert.Null(api.Authorize); 19 | 20 | api.Authorize = "a"; 21 | 22 | Assert.Equal("a", api.Authorize); 23 | 24 | var r = await api.UpdateAsync(1,new Product { }, " all , ackava@gmail.com"); 25 | 26 | 27 | 28 | 29 | } 30 | 31 | public class TestHttpClient : HttpMessageHandler { 32 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 33 | { 34 | // return base.SendAsync(request, cancellationToken); 35 | 36 | return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK) 37 | { 38 | Content = new StringContent("{}") 39 | }); 40 | } 41 | } 42 | 43 | public class TestBaseService : BaseService { 44 | 45 | public TestBaseService() 46 | { 47 | this.BaseUrl = new Uri("https://m.800casting.com/"); 48 | } 49 | 50 | protected override Task InvokeAsync(HttpMethod method, string path, IEnumerable plist) 51 | { 52 | return base.InvokeAsync(method, path, plist); 53 | } 54 | 55 | } 56 | 57 | public interface IApi 58 | { 59 | 60 | [Header("Authorize")] 61 | string Authorize { get; set; } 62 | 63 | [Put("products/{id}/edit")] 64 | Task UpdateAsync( 65 | [Path("id")] long productId, 66 | [Body] Product product, 67 | [Query] string email = null); 68 | 69 | } 70 | 71 | public class Product { 72 | public string Name { get; set; } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/ApiResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Reflection; 6 | 7 | namespace RetroCoreFit 8 | { 9 | public interface IApiResponse 10 | { 11 | void Initialize(HttpResponseMessage response, object model); 12 | 13 | Type GetModelType(); 14 | } 15 | 16 | public class ApiResponse : IApiResponse 17 | { 18 | private static Dictionary _headerProperties = null; 19 | 20 | public Dictionary Headers { get; protected set; } 21 | 22 | public T Model { get; protected set; } 23 | 24 | public virtual void Initialize(HttpResponseMessage response, object model) 25 | { 26 | 27 | this.Headers = new Dictionary(); 28 | 29 | var headerProperties = _headerProperties ?? (_headerProperties = this.GetType().GetProperties().Select(x => new 30 | { 31 | Property = x, 32 | Header = x.GetCustomAttribute() 33 | }).Where(x => x.Header != null) 34 | .ToDictionary(x => x.Header.Name.ToLower(), x => x.Property)); 35 | 36 | foreach (var k in response.Headers) 37 | { 38 | string sv = string.Join("", k.Value); 39 | this.Headers[k.Key] = sv; 40 | if (headerProperties.TryGetValue(k.Key.ToLower(), out var p)) 41 | { 42 | object v = sv; 43 | if (p.PropertyType != typeof(string)) 44 | { 45 | v = p.PropertyType.ConvertFrom(v); 46 | } 47 | p.SetValue(this, v); 48 | } 49 | } 50 | 51 | this.Model = (T)model; 52 | } 53 | 54 | Type IApiResponse.GetModelType() 55 | { 56 | return typeof(T); 57 | } 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/HeaderAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace RetroCoreFit 5 | { 6 | 7 | public abstract class RestAttribute : Attribute { } 8 | 9 | public class NamedAttribute : RestAttribute { 10 | 11 | public NamedAttribute(string name) 12 | { 13 | this.Name = name; 14 | } 15 | 16 | public string Name { get; internal set; } 17 | 18 | } 19 | 20 | [System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter, Inherited = false, AllowMultiple = true)] 21 | public sealed class HeaderAttribute : NamedAttribute 22 | { 23 | public HeaderAttribute(string name) : base(name) 24 | { 25 | } 26 | } 27 | 28 | //[System.AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] 29 | //public sealed class BaseUrlAttribute : NamedAttribute { 30 | // public BaseUrlAttribute(string name) : base(name) 31 | // { 32 | // } 33 | //} 34 | 35 | public abstract class HttpMethodAttribute : NamedAttribute 36 | { 37 | public HttpMethodAttribute(HttpMethod method, string path) : base(path) 38 | { 39 | this.Method = method; 40 | } 41 | 42 | public HttpMethod Method { get; } 43 | 44 | } 45 | 46 | 47 | [System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] 48 | public sealed class PutAttribute : HttpMethodAttribute 49 | { 50 | public PutAttribute(string name) : base(HttpMethod.Put, name) 51 | { 52 | } 53 | } 54 | 55 | [System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] 56 | public sealed class PostAttribute : HttpMethodAttribute 57 | { 58 | public PostAttribute(string name) : base(HttpMethod.Post, name) 59 | { 60 | } 61 | } 62 | 63 | [System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] 64 | public sealed class GetAttribute : HttpMethodAttribute 65 | { 66 | public GetAttribute(string name) : base(HttpMethod.Get, name) 67 | { 68 | } 69 | } 70 | 71 | [System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] 72 | public sealed class DeleteAttribute : HttpMethodAttribute 73 | { 74 | public DeleteAttribute(string name) : base(HttpMethod.Delete, name) 75 | { 76 | } 77 | } 78 | 79 | [System.AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = true)] 80 | public sealed class BodyAttribute : RestAttribute { 81 | } 82 | 83 | 84 | public class ParamAttribute : RestAttribute { 85 | public ParamAttribute() 86 | { 87 | 88 | } 89 | 90 | public ParamAttribute(string name) 91 | { 92 | this.Name = name; 93 | } 94 | 95 | public string Name { get; set; } 96 | } 97 | 98 | 99 | [AttributeUsage(AttributeTargets.Parameter)] 100 | public sealed class QueryAttribute : ParamAttribute { 101 | public QueryAttribute() 102 | { 103 | 104 | } 105 | 106 | public QueryAttribute(string name):base(name) 107 | { 108 | 109 | } 110 | } 111 | 112 | 113 | [AttributeUsage(AttributeTargets.Parameter)] 114 | public sealed class PathAttribute : ParamAttribute 115 | { 116 | public PathAttribute() 117 | { 118 | 119 | } 120 | 121 | public PathAttribute(string name) : base(name) 122 | { 123 | 124 | } 125 | } 126 | 127 | [AttributeUsage(AttributeTargets.Parameter)] 128 | public sealed class CancelAttribute : ParamAttribute 129 | { 130 | public CancelAttribute() 131 | { 132 | 133 | } 134 | } 135 | 136 | [AttributeUsage(AttributeTargets.Parameter)] 137 | public sealed class CookieAttribute : ParamAttribute 138 | { 139 | public CookieAttribute() 140 | { 141 | 142 | } 143 | 144 | public CookieAttribute(string name) : base(name) 145 | { 146 | 147 | } 148 | } 149 | 150 | [AttributeUsage(AttributeTargets.Parameter)] 151 | public sealed class MultipartAttribute : ParamAttribute { 152 | public MultipartAttribute() 153 | { 154 | 155 | } 156 | 157 | public MultipartAttribute(string name): base(name) 158 | { 159 | 160 | } 161 | 162 | } 163 | 164 | [AttributeUsage(AttributeTargets.Parameter)] 165 | public sealed class MultipartFileAttribute : ParamAttribute 166 | { 167 | public MultipartFileAttribute() 168 | { 169 | 170 | } 171 | 172 | public MultipartFileAttribute(string name) : base(name) 173 | { 174 | 175 | } 176 | 177 | public string FileName { get; set; } 178 | } 179 | 180 | [AttributeUsage(AttributeTargets.Parameter)] 181 | public sealed class FormAttribute : ParamAttribute 182 | { 183 | public FormAttribute(string name) : base(name) 184 | { 185 | 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/RestCall.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace RetroCoreFit 5 | { 6 | public struct RestCall { 7 | public string Path; 8 | public HttpMethod Method; 9 | public RestAttribute[] Attributes; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/RestParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace RetroCoreFit 4 | { 5 | public struct RestParameter { 6 | 7 | public RestAttribute Type { get; set; } 8 | 9 | public object Value { get; set; } 10 | 11 | public static RestParameter Cancel(CancellationToken token) 12 | { 13 | return new RestParameter { 14 | Type = new CancelAttribute(), 15 | Value = token 16 | }; 17 | } 18 | 19 | public static RestParameter Query(string name, object v = null) 20 | { 21 | return new RestParameter { 22 | Type = new QueryAttribute(name), 23 | Value = v 24 | }; 25 | } 26 | 27 | public static RestParameter Form(string name, object v = null) 28 | { 29 | return new RestParameter 30 | { 31 | Type = new FormAttribute(name), 32 | Value = v 33 | }; 34 | } 35 | 36 | public static RestParameter Path(string name, object v = null) 37 | { 38 | return new RestParameter 39 | { 40 | Type = new PathAttribute(name), 41 | Value = v 42 | }; 43 | } 44 | public static RestParameter Cookie(string name, object v = null) 45 | { 46 | return new RestParameter 47 | { 48 | Type = new CookieAttribute(name), 49 | Value = v 50 | }; 51 | } 52 | 53 | public static RestParameter Body(object v) 54 | { 55 | return new RestParameter { 56 | Type = new BodyAttribute(), 57 | Value = v 58 | }; 59 | } 60 | 61 | public static RestParameter Header(string name, object v = null) 62 | { 63 | return new RestParameter 64 | { 65 | Type = new HeaderAttribute(name), 66 | Value = v 67 | }; 68 | } 69 | 70 | 71 | public static RestParameter Multipart(string name, object v = null) 72 | { 73 | return new RestParameter 74 | { 75 | Type = new MultipartAttribute(name), 76 | Value = v 77 | }; 78 | } 79 | 80 | public static RestParameter MultipartFile(string name, string fileName, object v = null) 81 | { 82 | return new RestParameter 83 | { 84 | Type = new MultipartFileAttribute(name) { 85 | FileName = fileName 86 | }, 87 | Value = v 88 | }; 89 | } 90 | } 91 | 92 | 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/RetroClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | 4 | namespace RetroCoreFit 5 | { 6 | 7 | public class RetroClient 8 | { 9 | 10 | public static T Create(Uri baseUrl, HttpClient client = null) 11 | where T:class 12 | where TBase: BaseService 13 | { 14 | return InterfaceBuilder.Instance.Build(baseUrl, client, typeof(TBase)); 15 | } 16 | 17 | public static T Create(Uri baseUrl, HttpClient client = null) 18 | where T:class 19 | { 20 | return InterfaceBuilder.Instance.Build(baseUrl, client, typeof(BaseService)); 21 | } 22 | 23 | } 24 | 25 | 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/RetroCoreFit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech.RetroCoreFit 6 | Akash Kava 7 | NeuroSpeech Technologies Pvt Ltd 8 | https://github.com/neurospeech/retro-core-fit 9 | https://github.com/neurospeech/retro-core-fit/blob/master/LICENSE 10 | https://github.com/neurospeech/retro-core-fit.git 11 | git 12 | 1.0.2 13 | true 14 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /RetroFit/RetroCoreFit/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace RetroCoreFit 6 | { 7 | internal static class TypeExtensions 8 | { 9 | 10 | public static object ConvertFrom(this Type type, object source) 11 | { 12 | var t = Nullable.GetUnderlyingType(type) ?? type; 13 | try 14 | { 15 | return Convert.ChangeType(source, t); 16 | } 17 | catch (Exception ex) 18 | { 19 | throw new TypeConversionException(source, $"Failed to convert {source.GetType().FullName} to {t.FullName}", ex); 20 | } 21 | 22 | } 23 | 24 | } 25 | 26 | public class TypeConversionException : Exception 27 | { 28 | public object Value { get; } 29 | 30 | public TypeConversionException(object value, string message, Exception ex) 31 | : base(message, ex) 32 | { 33 | this.Value = value; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /StringExtensions/CacheExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Memory; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Extensions.Caching.Memory 8 | { 9 | public static class AtomicMemoryCacheExtensions 10 | { 11 | 12 | private static Object lockObject = new object(); 13 | 14 | public static Task AtomicGetOrCreateAsync( 15 | this IMemoryCache cache, 16 | string key, 17 | Func> factory) 18 | { 19 | return cache.GetOrCreate>(key, (entry) => 20 | { 21 | lock (lockObject) 22 | { 23 | Func> fx = async (e2) => { 24 | try { 25 | return await factory(e2); 26 | } catch 27 | { 28 | // it is stale... 29 | cache.Remove(key); 30 | throw; 31 | } 32 | }; 33 | return cache.GetOrCreate>(key, (e1) => 34 | { 35 | return fx(e1); 36 | }); 37 | } 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StringExtensions/StringExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech.StringExtensions 6 | Akash Kava 7 | NeuroSpeech Technologies Pvt Ltd 8 | 1.0.1 9 | true 10 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /WKHtmlToXSharp/NeuroSpeech.WKHtmlToXSharp/NeuroSpeech.WKHtmlToXSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | NeuroSpeech.WKHtmlToXSharp 6 | Akash Kava 7 | NeuroSpeech Technologies Pvt Ltd 8 | https://github.com/neurospeech/asp-net-core-extensions 9 | https://github.com/neurospeech/asp-net-core-extensions/blob/master/LICENSE 10 | https://github.com/neurospeech/asp-net-core-extensions.git 11 | git 12 | 1.0.2 13 | true 14 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WKHtmlToXSharp/WKHtmlToXSharpTests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace WKHtmlToXSharpTests 6 | { 7 | [TestClass] 8 | public class UnitTest1 9 | { 10 | [TestMethod] 11 | public async Task TestMethod1() 12 | { 13 | NeuroSpeech.WKHtmlToXSharp.WKHtmlToX.TempFolder = Path.GetTempPath(); 14 | var b = await NeuroSpeech.WKHtmlToXSharp.WKHtmlToX.HtmlToPdfAsync("
t
", new NeuroSpeech.WKHtmlToXSharp.ConversionTask()); 15 | Assert.IsTrue(b.Length > 0); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /WKHtmlToXSharp/WKHtmlToXSharpTests/WKHtmlToXSharpTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------