logging and dotenv refactoring

This commit is contained in:
Spyros Garyfallos 2018-09-25 13:13:52 -07:00
Родитель 132abc0100
Коммит c6ed1b190d
15 изменённых файлов: 230 добавлений и 937 удалений

Просмотреть файл

@ -2,14 +2,16 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>TypeEdge.Host</id>
<version>0.3.6</version>
<version>0.3.9</version>
<authors>paloukari</authors>
<owners>paloukari</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Package Description</description>
<dependencies>
<group targetFramework=".NETCoreApp2.1">
<dependency id="TypeEdge" version="0.3.6" exclude="Build,Analyzers" />
<dependency id="TypeEdge" version="0.3.9" exclude="Build,Analyzers" />
<dependency id="Docker.DotNet" version="3.125.2.0" exclude="Build,Analyzers" />
<dependency id="Serilog.Extensions.Logging" version="1.4.0" exclude="Build,Analyzers" />
<dependency id="Serilog.Extensions.Logging" version="1.4.0" exclude="Build,Analyzers" />
<dependency id="Serilog.Sinks.Console" version="2.1.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.DependencyInjection" version="2.1.1" exclude="Build,Analyzers" />
@ -61,8 +63,8 @@
</contentFiles>
</metadata>
<files>
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.dll" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.dll" />
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.pdb" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Docker.pdb" />
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.dll" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.dll" />
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.pdb" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Agent.Core.pdb" />
<file src="bin\Debug\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Hub.Amqp.dll" target="lib\netcoreapp2.1\Microsoft.Azure.Devices.Edge.Hub.Amqp.dll" />

Просмотреть файл

@ -116,7 +116,7 @@ namespace TypeEdge.Host.Docker
await (await _dockerFactory.RemoveAsync(_moduleWithIdentity.Module)).ExecuteAsync(cancellationToken);
}
}
catch (Exception ex)
catch (Exception)
{
Console.WriteLine($"{_moduleWithIdentity.Module.Name} not found");
}

Просмотреть файл

@ -3,7 +3,7 @@
<PropertyGroup>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<PackageId>TypeEdge.Proxy</PackageId>
<Version>0.3.6</Version>
<Version>0.3.9</Version>
<Authors>paloukari</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>

Просмотреть файл

@ -33,6 +33,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edg
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Storage", "IoT.Edge\edge-util\src\Microsoft.Azure.Devices.Edge.Storage\Microsoft.Azure.Devices.Edge.Storage.csproj", "{101A6115-688F-474A-AC5F-051FA6232349}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Devices.Edge.Agent.Docker", "IoT.Edge\edge-agent\src\Microsoft.Azure.Devices.Edge.Agent.Docker\Microsoft.Azure.Devices.Edge.Agent.Docker.csproj", "{05407166-867F-4321-A3B2-0E5D56726C2B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
CodeCoverage|Any CPU = CodeCoverage|Any CPU
@ -153,6 +155,14 @@ Global
{101A6115-688F-474A-AC5F-051FA6232349}.Release|Any CPU.Build.0 = Release|Any CPU
{101A6115-688F-474A-AC5F-051FA6232349}.TemplateDevelopment|Any CPU.ActiveCfg = CodeCoverage|Any CPU
{101A6115-688F-474A-AC5F-051FA6232349}.TemplateDevelopment|Any CPU.Build.0 = CodeCoverage|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.CodeCoverage|Any CPU.ActiveCfg = CodeCoverage|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.CodeCoverage|Any CPU.Build.0 = CodeCoverage|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.Release|Any CPU.Build.0 = Release|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.TemplateDevelopment|Any CPU.ActiveCfg = CodeCoverage|Any CPU
{05407166-867F-4321-A3B2-0E5D56726C2B}.TemplateDevelopment|Any CPU.Build.0 = CodeCoverage|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -169,6 +179,7 @@ Global
{A7DEAB53-6DCB-4287-8DED-C0358BCB4EBB} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
{ED0444A3-03A3-4B84-854F-CC6BC53DEBB8} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
{101A6115-688F-474A-AC5F-051FA6232349} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
{05407166-867F-4321-A3B2-0E5D56726C2B} = {ABC622C1-4D24-48CF-8B0D-CA63C27CB444}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {114E5850-267D-4B44-ACD5-91ACA382DDBC}

Просмотреть файл

@ -3,57 +3,30 @@ using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
//copied from https://github.com/frozzare/dotnet-dotenv
namespace TypeEdge.DovEnv
{
public class Dotenv
{
public static readonly string DefaultPath = "./.env";
public static readonly string DefaultPath = ".env";
private readonly Dictionary<string, string> _variables = new Dictionary<string, string>();
public static Dotenv Load(string path)
public static Dotenv Read(Stream input)
{
if (String.IsNullOrEmpty(path))
{
path = DefaultPath;
}
if (!File.Exists(path))
{
throw new Exception("The .env file don't exists in the current directory");
}
var content = File.ReadAllText(path);
return new Dotenv(content);
}
public static Dotenv Load(Stream input)
{
string dotEnvContent;
// Ensure resources are cleaned up after the read...
using (var reader = new StreamReader(input))
{
dotEnvContent = reader.ReadToEnd();
return new Dotenv(reader.ReadToEnd());
}
return new Dotenv(dotEnvContent);
}
public Dictionary<string, string> GetVariables()
public Dictionary<string, string> GetData()
{
// Return a copy so caller cannot modify internal state.
return CloneVariables();
return _variables.DeepClone();
}
private Dictionary<string, string> CloneVariables()
{
var clone = new Dictionary<string, string>(_variables.Count, _variables.Comparer);
foreach (var item in _variables)
{
clone.Add(item.Key, item.Value);
}
return clone;
}
protected Dotenv(string content)
{

Просмотреть файл

@ -1,23 +1,20 @@
using System.IO;

using System.IO;
using Microsoft.Extensions.Configuration;
namespace TypeEdge.DovEnv
{
public class DotenvConfigurationProvider : FileConfigurationProvider
{
/// <summary>
/// Initializes a new instance.
/// </summary>
public DotenvConfigurationProvider(DotenvConfigurationSource source) : base(source)
{
}
/// <summary>
/// Loads the dotenv variables.
/// </summary>
public override void Load(Stream stream)
{
Data = Dotenv.Load(stream).GetVariables();
Data = Dotenv.Read(stream).GetData();
}
}
}
}

Просмотреть файл

@ -1,120 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using PhysicalFileProvider = TypeEdge.DovEnv.FileProvider.PhysicalFileProvider;
namespace TypeEdge.DovEnv
{
public static class DotenvExtension
{
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder)
{
return AddDotenvFile(builder, null, string.Empty, false, false);
}
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path)
{
return AddDotenvFile(builder, null, path, false, false);
}
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional)
{
return AddDotenvFile(builder, null, path, optional, false);
}
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
{
return AddDotenvFile(builder, null, path, optional, reloadOnChange);
}
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange)
{
// Bail if builder is null.
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
// Use the default value if value is empty.
if (string.IsNullOrEmpty(path))
{
path = Dotenv.DefaultPath;
path = path.Replace("./", "");
}
#if NET451
var basePath1 = AppDomain.CurrentDomain.GetData("APP_CONTEXT_BASE_DIRECTORY") as string
?? AppDomain.CurrentDomain.BaseDirectory
?? string.Empty;
#else
var basePath1 = AppContext.BaseDirectory ?? string.Empty;
#endif
var basePaths = new List<string>() { basePath1 };
// Since we shouldn't relay on asp.net core hosting package
// and then we can't get content root path and because of
// https://github.com/aspnet/FileSystem/issues/232
// we have to create two different paths that we can try to read the dotenv file from.
if (basePath1.Contains("/bin/"))
{
var basePath2 = basePath1.Split(new string[] { "bin" }, StringSplitOptions.None)[0];
basePaths.Add(basePath2.TrimEnd('/'));
}
if (provider == null)
{
//--
// The below is still needed for .NET Core 1.x
//--
var fileExists = path.StartsWith('/') && File.Exists(path);
if (!fileExists)
foreach (var basePath in basePaths)
{
var testPath = string.Join("/", new string[] { basePath, path });
//Console.WriteLine($"DotEnv: checking for {testPath}");
if (!File.Exists(testPath))
continue;
fileExists = true;
path = testPath;
break;
}
if (!fileExists && !optional)
{
throw new Exception($"The .env configuration file '{path}' was not found");
}
if (Path.IsPathRooted(path))
{
// Real PhysicalFileProvider has a bug that don't allow dot files:
// https://github.com/aspnet/FileSystem/issues/232
provider = new PhysicalFileProvider(Path.GetDirectoryName(path));
path = Path.GetFileName(path);
}
}
else
{
//--
// For .NET Core 2.0 and above, the PhysicalFileProvider has ways to deal
// with hidden files.
// See the change: https://github.com/aspnet/FileSystem/pull/280/files
// This also allowed MockFileProvider to be plugged in for unit testing.
//--
if (!provider.GetFileInfo(path).Exists)
{
throw new Exception($"The configuration file {path} could not be found.");
}
}
var source = new DotenvConfigurationSource
{
Path = path,
Optional = optional,
FileProvider = provider,
ReloadOnChange = reloadOnChange
};
builder.Add(source);
return builder;
}
}
}

Просмотреть файл

@ -1,128 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Microsoft.Extensions.Primitives;
namespace TypeEdge.DovEnv.FileProvider
{
public class CompositeChangeToken : IChangeToken
{
private static readonly Action<object> _onChangeDelegate = OnChange;
private readonly object _callbackLock = new object();
private CancellationTokenSource _cancellationTokenSource;
private bool _registeredCallbackProxy;
private List<IDisposable> _disposables;
/// <summary>
/// Creates a new instance of <see cref="CompositeChangeToken"/>.
/// </summary>
/// <param name="changeTokens">The list of <see cref="IChangeToken"/> to compose.</param>
public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens)
{
ChangeTokens = changeTokens ?? throw new ArgumentNullException(nameof(changeTokens));
for (var i = 0; i < ChangeTokens.Count; i++)
{
if (ChangeTokens[i].ActiveChangeCallbacks)
{
ActiveChangeCallbacks = true;
break;
}
}
}
/// <summary>
/// Returns the list of <see cref="IChangeToken"/> which compose the current <see cref="CompositeChangeToken"/>.
/// </summary>
public IReadOnlyList<IChangeToken> ChangeTokens { get; }
/// <inheritdoc />
public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
EnsureCallbacksInitialized();
return _cancellationTokenSource.Token.Register(callback, state);
}
/// <inheritdoc />
public bool HasChanged
{
get
{
if (_cancellationTokenSource != null && _cancellationTokenSource.Token.IsCancellationRequested)
{
return true;
}
for (var i = 0; i < ChangeTokens.Count; i++)
{
if (ChangeTokens[i].HasChanged)
{
OnChange(this);
return true;
}
}
return false;
}
}
/// <inheritdoc />
public bool ActiveChangeCallbacks { get; }
private void EnsureCallbacksInitialized()
{
if (_registeredCallbackProxy)
{
return;
}
lock (_callbackLock)
{
if (_registeredCallbackProxy)
{
return;
}
_cancellationTokenSource = new CancellationTokenSource();
_disposables = new List<IDisposable>();
for (var i = 0; i < ChangeTokens.Count; i++)
{
if (ChangeTokens[i].ActiveChangeCallbacks)
{
var disposable = ChangeTokens[i].RegisterChangeCallback(_onChangeDelegate, this);
_disposables.Add(disposable);
}
}
_registeredCallbackProxy = true;
}
}
private static void OnChange(object state)
{
var compositeChangeTokenState = (CompositeChangeToken)state;
if (compositeChangeTokenState._cancellationTokenSource == null)
{
return;
}
lock (compositeChangeTokenState._callbackLock)
{
try
{
compositeChangeTokenState._cancellationTokenSource.Cancel();
}
catch
{
}
}
var disposables = compositeChangeTokenState._disposables;
Debug.Assert(disposables != null);
for (var i = 0; i < disposables.Count; i++)
{
disposables[i].Dispose();
}
}
}
}

Просмотреть файл

@ -1,263 +0,0 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Internal;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.Primitives;
using PhysicalFilesWatcher = TypeEdge.DovEnv.FileProvider.PhysicalFilesWatcher;
namespace TypeEdge.DovEnv.FileProvider
{
public sealed class PhysicalFileProvider : IFileProvider, IDisposable
{
private const string PollingEnvironmentKey = "DOTNET_USE_POLLING_FILE_WATCHER";
private static readonly char[] _invalidFileNameChars = Path.GetInvalidFileNameChars()
.Where(c => c != Path.DirectorySeparatorChar && c != Path.AltDirectorySeparatorChar).ToArray();
private static readonly char[] _invalidFilterChars = _invalidFileNameChars
.Where(c => c != '*' && c != '|' && c != '?').ToArray();
private static readonly char[] _pathSeparators = new[]
{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};
private readonly PhysicalFilesWatcher _filesWatcher;
/// <summary>
/// Initializes a new instance of a PhysicalFileProvider at the given root directory.
/// </summary>
/// <param name="root">The root directory. This should be an absolute path.</param>
public PhysicalFileProvider(string root)
: this(root, CreateFileWatcher(root))
{
}
internal PhysicalFileProvider(string root, PhysicalFilesWatcher physicalFilesWatcher)
{
if (!Path.IsPathRooted(root))
{
throw new ArgumentException("The path must be absolute.", nameof(root));
}
var fullRoot = Path.GetFullPath(root);
// When we do matches in GetFullPath, we want to only match full directory names.
Root = EnsureTrailingSlash(fullRoot);
if (!Directory.Exists(Root))
{
throw new DirectoryNotFoundException(Root);
}
_filesWatcher = physicalFilesWatcher;
}
private static PhysicalFilesWatcher CreateFileWatcher(string root)
{
var environmentValue = Environment.GetEnvironmentVariable(PollingEnvironmentKey);
var pollForChanges = string.Equals(environmentValue, "1", StringComparison.Ordinal) ||
string.Equals(environmentValue, "true", StringComparison.OrdinalIgnoreCase);
root = EnsureTrailingSlash(Path.GetFullPath(root));
return new PhysicalFilesWatcher(root, new FileSystemWatcher(root), pollForChanges);
}
/// <summary>
/// Disposes the provider. Change tokens may not trigger after the provider is disposed.
/// </summary>
public void Dispose()
{
_filesWatcher.Dispose();
}
/// <summary>
/// The root directory for this instance.
/// </summary>
public string Root { get; }
private string GetFullPath(string path)
{
if (PathNavigatesAboveRoot(path))
{
return null;
}
string fullPath;
try
{
fullPath = Path.GetFullPath(Path.Combine(Root, path));
}
catch
{
return null;
}
if (!IsUnderneathRoot(fullPath))
{
return null;
}
return fullPath;
}
private bool PathNavigatesAboveRoot(string path)
{
var tokenizer = new StringTokenizer(path, _pathSeparators);
var depth = 0;
foreach (var segment in tokenizer)
{
if (segment.Equals(".", StringComparison.OrdinalIgnoreCase) || segment.Equals("", StringComparison.OrdinalIgnoreCase))
{
continue;
}
else if (segment.Equals("..", StringComparison.OrdinalIgnoreCase))
{
depth--;
if (depth == -1)
{
return true;
}
}
else
{
depth++;
}
}
return false;
}
private bool IsUnderneathRoot(string fullPath)
{
return fullPath.StartsWith(Root, StringComparison.OrdinalIgnoreCase);
}
private static string EnsureTrailingSlash(string path)
{
if (!string.IsNullOrEmpty(path) &&
path[path.Length - 1] != Path.DirectorySeparatorChar)
{
return path + Path.DirectorySeparatorChar;
}
return path;
}
private static bool HasInvalidPathChars(string path)
{
return path.IndexOfAny(_invalidFileNameChars) != -1;
}
private static bool HasInvalidFilterChars(string path)
{
return path.IndexOfAny(_invalidFilterChars) != -1;
}
/// <summary>
/// Locate a file at the given path by directly mapping path segments to physical directories.
/// </summary>
/// <param name="subpath">A path under the root directory</param>
/// <returns>The file information. Caller must check Exists property. </returns>
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath) || HasInvalidPathChars(subpath))
{
return new NotFoundFileInfo(subpath);
}
// Relative paths starting with leading slashes are okay
subpath = subpath.TrimStart(_pathSeparators);
// Absolute paths not permitted.
if (Path.IsPathRooted(subpath))
{
return new NotFoundFileInfo(subpath);
}
var fullPath = GetFullPath(subpath);
if (fullPath == null)
{
return new NotFoundFileInfo(subpath);
}
return new PhysicalFileInfo(new FileInfo(fullPath));
}
/// <summary>
/// Enumerate a directory at the given path, if any.
/// </summary>
/// <param name="subpath">A path under the root directory. Leading slashes are ignored.</param>
/// <returns>
/// Contents of the directory. Caller must check Exists property. <see cref="NotFoundDirectoryContents" /> if
/// <paramref name="subpath" /> is absolute, if the directory does not exist, or <paramref name="subpath" /> has invalid
/// characters.
/// </returns>
public IDirectoryContents GetDirectoryContents(string subpath)
{
try
{
if (subpath == null || HasInvalidPathChars(subpath))
{
return NotFoundDirectoryContents.Singleton;
}
// Relative paths starting with leading slashes are okay
subpath = subpath.TrimStart(_pathSeparators);
// Absolute paths not permitted.
if (Path.IsPathRooted(subpath))
{
return NotFoundDirectoryContents.Singleton;
}
var fullPath = GetFullPath(subpath);
if (fullPath == null || !Directory.Exists(fullPath))
{
return NotFoundDirectoryContents.Singleton;
}
return new PhysicalDirectoryContents(fullPath);
}
catch (DirectoryNotFoundException)
{
}
catch (IOException)
{
}
return NotFoundDirectoryContents.Singleton;
}
/// <summary>
/// <para>Creates a <see cref="IChangeToken" /> for the specified <paramref name="filter" />.</para>
/// <para>Globbing patterns are interpreted by <seealso cref="Microsoft.Extensions.FileSystemGlobbing.Matcher" />.</para>
/// </summary>
/// <param name="filter">
/// Filter string used to determine what files or folders to monitor. Example: **/*.cs, *.*,
/// subFolder/**/*.cshtml.
/// </param>
/// <returns>
/// An <see cref="IChangeToken" /> that is notified when a file matching <paramref name="filter" /> is added,
/// modified or deleted. Returns a <see cref="NullChangeToken" /> if <paramref name="filter" /> has invalid filter
/// characters or if <paramref name="filter" /> is an absolute path or outside the root directory specified in the
/// constructor <seealso cref="PhysicalFileProvider(string)" />.
/// </returns>
public IChangeToken Watch(string filter)
{
if (filter == null || HasInvalidFilterChars(filter))
{
return NullChangeToken.Singleton;
}
// Relative paths starting with leading slashes are okay
filter = filter.TrimStart(_pathSeparators);
// Absolute paths and paths traversing above root not permitted.
if (Path.IsPathRooted(filter) || PathNavigatesAboveRoot(filter))
{
return NullChangeToken.Singleton;
}
return _filesWatcher.CreateFileChangeToken(filter);
}
}
}

Просмотреть файл

@ -1,333 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders.Physical;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Primitives;
namespace TypeEdge.DovEnv.FileProvider
{
public class PhysicalFilesWatcher : IDisposable
{
private readonly ConcurrentDictionary<string, ChangeTokenInfo> _filePathTokenLookup =
new ConcurrentDictionary<string, ChangeTokenInfo>(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, ChangeTokenInfo> _wildcardTokenLookup =
new ConcurrentDictionary<string, ChangeTokenInfo>(StringComparer.OrdinalIgnoreCase);
private readonly FileSystemWatcher _fileWatcher;
private readonly object _fileWatcherLock = new object();
private readonly string _root;
private readonly bool _pollForChanges;
/// <summary>
/// Initializes an instance of <see cref="PhysicalFilesWatcher" /> that watches files in <paramref name="root" />.
/// Wraps an instance of <see cref="System.IO.FileSystemWatcher" />
/// </summary>
/// <param name="root">Root directory for the watcher</param>
/// <param name="fileSystemWatcher">The wrapped watcher that is watching <paramref name="root" /></param>
/// <param name="pollForChanges">
/// True when the watcher should use polling to trigger instances of
/// <see cref="IChangeToken" /> created by <see cref="CreateFileChangeToken(string)" />
/// </param>
public PhysicalFilesWatcher(
string root,
FileSystemWatcher fileSystemWatcher,
bool pollForChanges)
{
_root = root;
_fileWatcher = fileSystemWatcher;
_fileWatcher.IncludeSubdirectories = true;
_fileWatcher.Created += OnChanged;
_fileWatcher.Changed += OnChanged;
_fileWatcher.Renamed += OnRenamed;
_fileWatcher.Deleted += OnChanged;
_fileWatcher.Error += OnError;
_pollForChanges = pollForChanges;
}
/// <summary>
/// <para>
/// Creates an instance of <see cref="IChangeToken" /> for all files and directories that match the
/// <paramref name="filter" />
/// </para>
/// <para>
/// Globbing patterns are relative to the root directory given in the constructor
/// <seealso cref="PhysicalFilesWatcher(string, FileSystemWatcher, bool)" />. Globbing patterns
/// are interpreted by <seealso cref="Microsoft.Extensions.FileSystemGlobbing.Matcher" />.
/// </para>
/// </summary>
/// <param name="filter">A globbing pattern for files and directories to watch</param>
/// <returns>A change token for all files that match the filter</returns>
/// <exception cref="ArgumentNullException">When <paramref name="filter" /> is null</exception>
public IChangeToken CreateFileChangeToken(string filter)
{
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
filter = NormalizePath(filter);
var changeToken = GetOrAddChangeToken(filter);
TryEnableFileSystemWatcher();
return changeToken;
}
private IChangeToken GetOrAddChangeToken(string pattern)
{
IChangeToken changeToken;
var isWildCard = pattern.IndexOf('*') != -1;
if (isWildCard || IsDirectoryPath(pattern))
{
changeToken = GetOrAddWildcardChangeToken(pattern);
}
else
{
changeToken = GetOrAddFilePathChangeToken(pattern);
}
return changeToken;
}
private IChangeToken GetOrAddFilePathChangeToken(string filePath)
{
ChangeTokenInfo tokenInfo;
if (!_filePathTokenLookup.TryGetValue(filePath, out tokenInfo))
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationChangeToken = new CancellationChangeToken(cancellationTokenSource.Token);
tokenInfo = new ChangeTokenInfo(cancellationTokenSource, cancellationChangeToken);
tokenInfo = _filePathTokenLookup.GetOrAdd(filePath, tokenInfo);
}
IChangeToken changeToken = tokenInfo.ChangeToken;
if (_pollForChanges)
{
// The expiry of CancellationChangeToken is controlled by this type and consequently we can cache it.
// PollingFileChangeToken on the other hand manages its own lifetime and consequently we cannot cache it.
changeToken = new CompositeChangeToken(
new[]
{
changeToken,
new PollingFileChangeToken(new FileInfo(filePath))
});
}
return changeToken;
}
private IChangeToken GetOrAddWildcardChangeToken(string pattern)
{
ChangeTokenInfo tokenInfo;
if (!_wildcardTokenLookup.TryGetValue(pattern, out tokenInfo))
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationChangeToken = new CancellationChangeToken(cancellationTokenSource.Token);
var matcher = new Matcher(StringComparison.OrdinalIgnoreCase);
matcher.AddInclude(pattern);
tokenInfo = new ChangeTokenInfo(cancellationTokenSource, cancellationChangeToken, matcher);
tokenInfo = _wildcardTokenLookup.GetOrAdd(pattern, tokenInfo);
}
IChangeToken changeToken = tokenInfo.ChangeToken;
if (_pollForChanges)
{
// The expiry of CancellationChangeToken is controlled by this type and consequently we can cache it.
// PollingFileChangeToken on the other hand manages its own lifetime and consequently we cannot cache it.
changeToken = new CompositeChangeToken(
new[]
{
changeToken,
new PollingWildCardChangeToken(_root, pattern)
});
}
return changeToken;
}
/// <summary>
/// Disposes the file watcher
/// </summary>
public void Dispose()
{
_fileWatcher.Dispose();
}
private void OnRenamed(object sender, RenamedEventArgs e)
{
// For a file name change or a directory's name change notify registered tokens.
OnFileSystemEntryChange(e.OldFullPath);
OnFileSystemEntryChange(e.FullPath);
if (Directory.Exists(e.FullPath))
{
try
{
// If the renamed entity is a directory then notify tokens for every sub item.
foreach (
var newLocation in
Directory.EnumerateFileSystemEntries(e.FullPath, "*", SearchOption.AllDirectories))
{
// Calculated previous path of this moved item.
var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1));
OnFileSystemEntryChange(oldLocation);
OnFileSystemEntryChange(newLocation);
}
}
catch (Exception ex) when (
ex is IOException ||
ex is SecurityException ||
ex is DirectoryNotFoundException ||
ex is UnauthorizedAccessException)
{
// Swallow the exception.
}
}
}
private void OnChanged(object sender, FileSystemEventArgs e)
{
OnFileSystemEntryChange(e.FullPath);
}
private void OnError(object sender, ErrorEventArgs e)
{
// Notify all cache entries on error.
foreach (var path in _filePathTokenLookup.Keys)
{
ReportChangeForMatchedEntries(path);
}
}
private void OnFileSystemEntryChange(string fullPath)
{
try
{
var relativePath = fullPath.Substring(_root.Length);
ReportChangeForMatchedEntries(relativePath);
}
catch (Exception ex) when (
ex is IOException ||
ex is SecurityException ||
ex is UnauthorizedAccessException)
{
// Swallow the exception.
}
}
private void ReportChangeForMatchedEntries(string path)
{
path = NormalizePath(path);
var matched = false;
ChangeTokenInfo matchInfo;
if (_filePathTokenLookup.TryRemove(path, out matchInfo))
{
CancelToken(matchInfo);
matched = true;
}
foreach (var wildCardEntry in _wildcardTokenLookup)
{
var matchResult = wildCardEntry.Value.Matcher.Match(path);
if (matchResult.HasMatches &&
_wildcardTokenLookup.TryRemove(wildCardEntry.Key, out matchInfo))
{
CancelToken(matchInfo);
matched = true;
}
}
if (matched)
{
TryDisableFileSystemWatcher();
}
}
private void TryDisableFileSystemWatcher()
{
lock (_fileWatcherLock)
{
if (_filePathTokenLookup.IsEmpty &&
_wildcardTokenLookup.IsEmpty &&
_fileWatcher.EnableRaisingEvents)
{
// Perf: Turn off the file monitoring if no files to monitor.
_fileWatcher.EnableRaisingEvents = false;
}
}
}
private void TryEnableFileSystemWatcher()
{
lock (_fileWatcherLock)
{
if ((!_filePathTokenLookup.IsEmpty || !_wildcardTokenLookup.IsEmpty) &&
!_fileWatcher.EnableRaisingEvents)
{
// Perf: Turn off the file monitoring if no files to monitor.
_fileWatcher.EnableRaisingEvents = true;
}
}
}
private static string NormalizePath(string filter) => filter = filter.Replace('\\', '/');
private static bool IsDirectoryPath(string path)
{
return path.Length > 0 &&
(path[path.Length - 1] == Path.DirectorySeparatorChar ||
path[path.Length - 1] == Path.AltDirectorySeparatorChar);
}
private static void CancelToken(ChangeTokenInfo matchInfo)
{
if (matchInfo.TokenSource.IsCancellationRequested)
{
return;
}
Task.Run(() =>
{
try
{
matchInfo.TokenSource.Cancel();
}
catch
{
}
});
}
private struct ChangeTokenInfo
{
public ChangeTokenInfo(
CancellationTokenSource tokenSource,
CancellationChangeToken changeToken)
: this(tokenSource, changeToken, matcher: null)
{
}
public ChangeTokenInfo(
CancellationTokenSource tokenSource,
CancellationChangeToken changeToken,
Matcher matcher)
{
TokenSource = tokenSource;
ChangeToken = changeToken;
Matcher = matcher;
}
public CancellationTokenSource TokenSource { get; }
public CancellationChangeToken ChangeToken { get; }
public Matcher Matcher { get; }
}
}
}

Просмотреть файл

@ -1,16 +1,98 @@
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TypeEdge.DovEnv;
namespace TypeEdge
{
public static class Extensions
{
public static IConfigurationBuilder AddDotenv(this IConfigurationBuilder builder)
{
return AddDotenv(builder, null, string.Empty, false, false);
}
public static IConfigurationBuilder AddDotenv(this IConfigurationBuilder builder, string path)
{
return AddDotenv(builder, null, path, false, false);
}
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional)
{
return AddDotenv(builder, null, path, optional, false);
}
public static IConfigurationBuilder AddDotenvFile(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange)
{
return AddDotenv(builder, null, path, optional, reloadOnChange);
}
public static IConfigurationBuilder AddDotenv(this IConfigurationBuilder configurationBuilder, IFileProvider provider, string filePath, bool optional, bool reloadOnChange)
{
if (configurationBuilder == null)
throw new ArgumentNullException(nameof(configurationBuilder));
if (string.IsNullOrEmpty(filePath))
filePath = Dotenv.DefaultPath;
var lookUpPaths = new List<string>() { AppContext.BaseDirectory, "" };
if (provider == null)
{
bool exists = false;
foreach (var lookUpPath in lookUpPaths)
{
var fullPath = Path.Join(lookUpPath, filePath);
if (File.Exists(fullPath))
{
exists = true;
filePath = fullPath;
break;
}
}
if (!exists && !optional)
throw new Exception($"Could not locate {filePath}");
}
else
if (!provider.GetFileInfo(filePath).Exists)
throw new Exception($"Could not locate {filePath}");
if (Path.IsPathRooted(filePath))
{
provider = new PhysicalFileProvider(Path.GetDirectoryName(filePath), Microsoft.Extensions.FileProviders.Physical.ExclusionFilters.None);
filePath = Path.GetFileName(filePath);
}
var source = new DotenvConfigurationSource
{
Path = filePath,
Optional = optional,
FileProvider = provider,
ReloadOnChange = reloadOnChange
};
configurationBuilder.Add(source);
return configurationBuilder;
}
public static Dictionary<TKey, TValue> DeepClone<TKey, TValue>
(this Dictionary<TKey, TValue> original) where TValue : ICloneable
{
Dictionary<TKey, TValue> ret = new Dictionary<TKey, TValue>(original.Count,
original.Comparer);
foreach (KeyValuePair<TKey, TValue> entry in original)
{
ret.Add(entry.Key, (TValue)entry.Value.Clone());
}
return ret;
}
public static Task WhenCanceled(this CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
@ -42,4 +124,4 @@ namespace TypeEdge
return obj;
}
}
}
}

68
TypeEdge/Logger.cs Normal file
Просмотреть файл

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
using Serilog.Core;
namespace TypeEdge
{
//this code has been copied from the IoT Edge Runtime
public static class Logger
{
public const string RuntimeLogLevelEnvKey = "RuntimeLogLevel";
static readonly Dictionary<string, LogEventLevel> LogLevelDictionary = new Dictionary<string, LogEventLevel>(StringComparer.OrdinalIgnoreCase)
{
{"verbose", LogEventLevel.Verbose},
{"debug", LogEventLevel.Debug},
{"info", LogEventLevel.Information},
{"information", LogEventLevel.Information},
{"warning", LogEventLevel.Warning},
{"error", LogEventLevel.Error},
{"fatal", LogEventLevel.Fatal}
};
static LogEventLevel logLevel = LogEventLevel.Information;
public static void SetLogLevel(string level)
{
logLevel = LogLevelDictionary.TryGetValue(level.ToLower(), out LogEventLevel value) ? value : LogEventLevel.Information;
}
public static LogEventLevel GetLogLevel() => logLevel;
static readonly Lazy<ILoggerFactory> LoggerLazy = new Lazy<ILoggerFactory>(() => GetLoggerFactory(), true);
public static ILoggerFactory Factory => LoggerLazy.Value;
static ILoggerFactory GetLoggerFactory()
{
var levelSwitch = new LoggingLevelSwitch();
levelSwitch.MinimumLevel = logLevel;
Serilog.Core.Logger loggerConfig = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:1}] - {Message}{NewLine}{Exception}"
)
.CreateLogger();
if (levelSwitch.MinimumLevel <= LogEventLevel.Debug)
{
// Overwrite with richer content if less then debug
loggerConfig = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{SourceContext:1}] - {Message}{NewLine}{Exception}"
)
.CreateLogger();
}
ILoggerFactory factory = new LoggerFactory()
.AddSerilog(loggerConfig);
return factory;
}
}
}

Просмотреть файл

@ -23,6 +23,7 @@ using Autofac;
using Castle.DynamicProxy;
using TypeEdge.Proxy;
using Newtonsoft.Json.Linq;
using Microsoft.Extensions.Logging;
[assembly: InternalsVisibleTo("TypeEdge.Host")]
[assembly: InternalsVisibleTo("TypeEdge.Proxy")]
@ -35,10 +36,11 @@ namespace TypeEdge.Modules
private readonly Dictionary<string, MethodCallback> _methodSubscriptions;
private readonly Dictionary<string, SubscriptionCallback> _routeSubscriptions;
private readonly Dictionary<string, SubscriptionCallback> _twinSubscriptions;
private string _connectionString;
private readonly ILogger _logger;
private ModuleClient _ioTHubModuleClient;
private ITransportSettings[] _transportSettings;
#endregion
protected TypeModule()
@ -52,6 +54,8 @@ namespace TypeEdge.Modules
DefaultTwin = new TwinCollection();
Routes = new List<string>();
_logger = Logger.Factory.CreateLogger(GetType());
InstantiateProperties();
}
@ -156,10 +160,10 @@ namespace TypeEdge.Modules
RegisterMethods();
// Open a connection to the Edge runtime
_ioTHubModuleClient = ModuleClient.CreateFromConnectionString(_connectionString, _transportSettings);
_ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(_transportSettings);
await _ioTHubModuleClient.OpenAsync();
//Console.WriteLine($"{Name}:IoT Hub module client initialized.");
_logger?.LogInformation($"IoT Hub module client initialized.");
// Register callback to be called when a message is received by the module
foreach (var subscription in _routeSubscriptions)
@ -167,7 +171,7 @@ namespace TypeEdge.Modules
await _ioTHubModuleClient.SetInputMessageHandlerAsync(subscription.Key, MessageHandler,
subscription.Value);
//Console.WriteLine($"{Name}:MessageHandler set for {subscription.Key}");
_logger?.LogInformation($"MessageHandler set for {subscription.Key}");
}
// Register callback to be called when a twin update is received by the module
@ -176,25 +180,25 @@ namespace TypeEdge.Modules
foreach (var subscription in _methodSubscriptions)
{
await _ioTHubModuleClient.SetMethodHandlerAsync(subscription.Key, MethodCallback, subscription.Value);
//Console.WriteLine($"{Name}:MethodCallback set for{subscription.Key}");
_logger?.LogInformation($"MethodCallback set for{subscription.Key}");
}
//Console.WriteLine($"{Name}:Running RunAsync..");
_logger?.LogInformation($"Running RunAsync..");
return await RunAsync(cancellationToken);
}
internal virtual InitializationResult _Init(IConfigurationRoot configuration, IContainer container)
{
//Console.WriteLine($"{Name}:InternalConfigure called");
_connectionString = configuration.GetValue<string>($"{Constants.EdgeHubConnectionStringKey}");
if (string.IsNullOrEmpty(_connectionString))
throw new ArgumentException($"Missing {Constants.EdgeHubConnectionStringKey} in configuration for {Name}");
_logger?.LogInformation($"InternalConfigure called");
var connectionString = configuration.GetValue<string>($"{Constants.EdgeHubConnectionStringKey}");
if (string.IsNullOrEmpty(connectionString))
_logger?.LogWarning($"Missing {Constants.EdgeHubConnectionStringKey} variable.");
// Cert verification is not yet fully functional when using Windows OS for the container
var bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (!bypassCertVerification)
InstallCert();
//var bypassCertVerification = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
//if (!bypassCertVerification)
// InstallCert();
var settings = new AmqpTransportSettings(TransportType.Amqp_Tcp_Only);
if (true) //bypassCertVerification)
@ -208,7 +212,7 @@ namespace TypeEdge.Modules
internal async Task<PublishResult> PublishMessageAsync<T>(string outputName, T message)
where T : IEdgeMessage
{
//Console.WriteLine($"{Name}:PublishMessageAsync called");
_logger?.LogInformation($"PublishMessageAsync called");
var edgeMessage = new Message(message.GetBytes());
@ -219,7 +223,7 @@ namespace TypeEdge.Modules
await _ioTHubModuleClient.SendEventAsync(outputName, edgeMessage);
var messageString = Encoding.UTF8.GetString(message.GetBytes());
//Console.WriteLine($">>>>>>{Name}: message: Body: [{messageString}]");
_logger?.LogInformation($"Message: Body: [{messageString}]");
return PublishResult.Ok;
}
@ -228,7 +232,7 @@ namespace TypeEdge.Modules
Func<T, Task<MessageResult>> handler)
where T : IEdgeMessage
{
//Console.WriteLine($"{Name}:SubscribeRoute called");
_logger?.LogInformation($"SubscribeRoute called");
if (outRoute != "$downstream")
Routes.Add($"FROM {outRoute} INTO {inRoute}");
@ -241,7 +245,7 @@ namespace TypeEdge.Modules
internal void SubscribeRoute(string outName, string outRoute, string inName, string inRoute)
{
//Console.WriteLine($"{Name}:SubscribeRoute called");
_logger?.LogInformation($"SubscribeRoute called");
if (outRoute != "$downstream")
Routes.Add($"FROM {outRoute} INTO {inRoute}");
@ -249,7 +253,7 @@ namespace TypeEdge.Modules
internal void SubscribeTwin<T>(string name, Func<T, Task<TwinResult>> handler) where T : TypeTwin
{
//Console.WriteLine($"{Name}:SubscribeTwin called");
_logger?.LogInformation($"SubscribeTwin called");
_twinSubscriptions[name] = new SubscriptionCallback(name, handler, typeof(T));
}
@ -257,7 +261,7 @@ namespace TypeEdge.Modules
internal async Task ReportTwinAsync<T>(string name, T twin)
where T : TypeTwin
{
//Console.WriteLine($"{Name}:ReportTwinAsync called");
_logger?.LogInformation($"ReportTwinAsync called");
await _ioTHubModuleClient.UpdateReportedPropertiesAsync(twin.GetReportedProperties());
}
@ -292,7 +296,7 @@ namespace TypeEdge.Modules
}
catch (Exception ex)
{
Console.WriteLine($"{Name}:ERROR:{ex}");
_logger?.LogError(ex, "Error in GetReferenceData.");
}
return null;
}
@ -311,7 +315,7 @@ namespace TypeEdge.Modules
}
catch (Exception ex)
{
Console.WriteLine($"{Name}:ERROR:{ex}");
_logger?.LogError(ex, "Error in SetReferenceData.");
}
return false;
}
@ -327,7 +331,7 @@ namespace TypeEdge.Modules
}
catch (Exception ex)
{
Console.WriteLine($"{Name}:ERROR:{ex}");
_logger?.LogError(ex, "Error in DeleteReferenceData.");
}
return false;
}
@ -354,7 +358,7 @@ namespace TypeEdge.Modules
private Task<MethodResponse> MethodCallback(MethodRequest methodRequest, object userContext)
{
//Console.WriteLine($"{Name}:MethodCallback called");
_logger?.LogInformation($"MethodCallback called");
if (!(userContext is MethodCallback callback))
throw new InvalidOperationException($"{Name}:UserContext doesn't contain a valid SubscriptionCallback");
@ -373,8 +377,7 @@ namespace TypeEdge.Modules
}
catch (Exception ex)
{
Console.WriteLine($"{Name}:ERROR:{ex}");
_logger?.LogError(ex, "Error in MethodCallback.");
// Acknowlege the direct method call with a 400 error message
var result = "{\"result\":\"" + ex.Message + "\"}";
return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 400));
@ -383,18 +386,17 @@ namespace TypeEdge.Modules
private async Task<MessageResponse> MessageHandler(Message message, object userContext)
{
//Console.WriteLine($"{Name}:MessageHandler called");
_logger?.LogInformation($"MessageHandler called");
if (!(userContext is SubscriptionCallback callback))
throw new InvalidOperationException("UserContext doesn't contain a valid SubscriptionCallback");
var messageBytes = message.GetBytes();
var messageString = Encoding.UTF8.GetString(messageBytes);
//Console.WriteLine($"<<<<<<{Name}:message: Body: [{messageString}]");
if (!(Activator.CreateInstance(callback.Type) is IEdgeMessage input))
{
Console.WriteLine($"{Name}:Abandoned Message");
_logger?.LogWarning("Abandoned Message");
return MessageResponse.Abandoned;
}
@ -408,13 +410,13 @@ namespace TypeEdge.Modules
private async Task PropertyHandler(TwinCollection desiredProperties, object userContext)
{
//Console.WriteLine($"{Name}:PropertyHandler called");
_logger?.LogInformation($"PropertyHandler called");
if (!(userContext is Dictionary<string, SubscriptionCallback> callbacks))
throw new InvalidOperationException("UserContext doesn't contain a valid SubscriptionCallback");
//Console.WriteLine($"{Name}:Desired property change:");
//Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
_logger?.LogInformation($"Desired property change:");
_logger?.LogInformation(JsonConvert.SerializeObject(desiredProperties));
foreach (var callback in callbacks)
if (desiredProperties.Contains($"___{callback.Key}"))
@ -432,21 +434,21 @@ namespace TypeEdge.Modules
if (string.IsNullOrWhiteSpace(certPath))
{
// We cannot proceed further without a proper cert file
Console.WriteLine($"Missing path to certificate collection file: {certPath}");
_logger?.LogWarning($"Missing path to certificate collection file: {certPath}");
throw new InvalidOperationException("Missing path to certificate file.");
}
if (!File.Exists(certPath))
{
// We cannot proceed further without a proper cert file
Console.WriteLine($"Missing path to certificate collection file: {certPath}");
_logger?.LogWarning($"Missing path to certificate collection file: {certPath}");
throw new InvalidOperationException("Missing certificate file.");
}
var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Add(new X509Certificate2(X509Certificate.CreateFromCertFile(certPath)));
//Console.WriteLine($"{Name}:Added Cert: " + certPath);
_logger?.LogInformation($"Added Cert: " + certPath);
store.Close();
}
@ -480,7 +482,7 @@ namespace TypeEdge.Modules
private void RegisterMethods()
{
//Console.WriteLine($"{Name}:RegisterMethods called");
_logger?.LogInformation($"RegisterMethods called");
var interfaceType = GetType().GetProxyInterface();
if (interfaceType == null)

Просмотреть файл

@ -84,7 +84,7 @@ namespace TypeEdge
.AddJsonFile($"{moduleName}Settings.json", true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.AddDotenvFile(fileName)
.AddDotenv(fileName)
.Build();
File.Delete(fileName);
break;

Просмотреть файл

@ -3,7 +3,7 @@
<PropertyGroup>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<PackageId>TypeEdge</PackageId>
<Version>0.3.6</Version>
<Version>0.3.9</Version>
<Authors>paloukari</Authors>
<PackageOutputPath>../../TypeEdgeNuGets</PackageOutputPath>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
@ -27,12 +27,14 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
<PackageReference Include="Serilog" Version="2.7.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>
</Project>