├── .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 | [](https://ci.appveyor.com/project/neurospeech/asp-net-core-node-server) [](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 |
--------------------------------------------------------------------------------