I am creating an application framework that can be shared between .Net Core 1.2, .Net Core 2.0 and .NET Framework 4.6+. So I choose the target framework of my project as .NET Standard. In my framework project I have factory class that is used to send a text message or email. On application startup, each application configures the factory with the supported services as singleton instance with DI framework.
public class MyFactory : IMyFactory
{
private ILogger _logger;
private IDictionary<string,IMyService> _container = new Dictionary<string,IMyService>();
// this method is called on application startup to configure supported services
public void Configure(string[] keys, ILogger logger)
{
foreach(var key in keys)
{
if(key == "text")
{
container.Add(key,new TextMessageService());
}
if(key == "email")
{
container.Add(key,new EmailService());
}
}
}
//application call this method to send message
public bool SendMessage(string key, string message)
{
// we don't want application to stop when publish fail, so we add try-catch and log the message
var flag = false;
try
{
var service= container[key];
service.Publish(message);
flag = true;
}
class(Exception ex)
{
_logger.Error(ex);
}
return flag;
}
}
Issue: the publish method could fail for any reason. And in such case I don't want application to stop, instead the framework should log the error message. My problem is since the framework can be shared between different .NET frameworks, I don't know which type of ILooger I should be using that can be shared between .NET Core and .NET Full.
I am trying to avoid creating my own ILogger interface. Also currently all applications are using Serilog but in future that could change.
Can Microsoft.Extensions.Logging be used here?
Yes it can. If you have to support netcore 1, then
Install-Package Microsoft.Extensions.Logging -Version 1.1.2
would work:
https://www.nuget.org/packages/Microsoft.Extensions.Logging/1.1.2
tells that it requires netstandard1.1
https://github.com/dotnet/standard/blob/master/docs/versions.md tells you
that all of your three target platforms implement netstandard1.1
But better still your reusable component only need rely on the Abstractions:
Install-Package Microsoft.Extensions.Logging.Abstractions -Version 1.1.2
Your component only need reference ILogger, and the applications that use your component are not tied to using the MS extensions logger. In particular, if you look at the dependencies for
Serilog.Extensions.Logging -Version 2.0.2
At https://www.nuget.org/packages/Serilog.Extensions.Logging/ , you can see that it depends on Abstractions but doesn't depend on the MS Extensions logger.
Serilog does depend on netstandard1.3. But again the netstandard version page tells you all your targets support it.
The applications using your framework can then carry on using Serilog, so long as they know how to wrap a serilogger as a MS.Extensions ILogger. the SerilogLoggerProvider in Serilog.Extensions.Logging does the trick:
var msFrameworkLogger= new SerilogLoggerProvider(myExistingSerilog).CreateLogger("name");
Related
We have a software product that is currently released that is a .NET Framework 4.7.2 application (the "legacy" app). The legacy client-server implementation is built on System.Runtime.Remoting, which is not supported in .NET 5 and later, so the .NET 5 implementation is gRPC.
It is necessary to instantiate each of the two COM servers in turn because the legacy and the .NET 5 COM servers can only connect to the comm (not COM) server application that implements the same communications framework, which are System.Runtime.Remoting and gRPC, respectively.
The COM servers are used by third party applications to interface with the comm server application, so I am currently working on creating a static class that returns the interface from the COM server that can connect to the currently running instance of the comm server.
I have a .NET 5 WPF implementation of the product almost complete, but I've hit a roadblock in that, I am unable to register the .NET COM server.
I found these two articles:
Exposing .NET Core Components to COM
GitHub Issue
I have now been able to:
Create a Type Library
I found a comment from #SimonMourier suggesting copying the .NET 5 COM server code into a .NET Framework project and use RegAsm to export the type library to be used in the .NET 5 project. The type library was added to the .NET 5 COM server project folder and "" was added to an ItemGroup in the .csproj file per the first referenced article.
Register the .NET 5 COM server
This required using the "dotnet publish -r win-x64 -c Debug" command in the project folder from the Visual Studio Developer Command Line. I was then able to use regsvr32 to register the WinCalRemoting.comhost.dll in the "bin\Debug\net5.0\win-x64\publish" project directory.
Create an Instance of the COM Class
After registering the COM server, I am now able to create an instance of the COM class, but haven't been successful at getting the interface from it:
public static IWinCalClient LoadCompatibleRemotingClient(bool useClientEventWindow, string serverName, int serverPort, bool connectToServer = true)
{
UseClientEventWindow = useClientEventWindow;
WinCalServerName = serverName;
WinCalServerPort = serverPort;
ClassIdList = new Guid[]
{
LegacyWinCalClientClsId, // CAN'T GET INTERFACE FROM THIS COM SERVER
//WinCal5ClientClsId // THE .NET 5 COM SERVER WORKS
};
if (RemotingClassObject != null)
{
UnloadClient();
}
foreach (Guid clsId in ClassIdList)
{
try
{
RemotingClassObject = Activator.CreateInstance(Type.GetTypeFromCLSID(clsId, true));
}
catch (Exception e)
{
continue;
}
if (RemotingClassObject != null)
{
RemotingInterface = (IWinCalClient)RemotingClassObject;
if (RemotingInterface == null)
{
UnloadClient();
continue;
}
if (CanClientConnect(_RemotingInterface, connectToServer))
{
break;
}
}
if (Marshal.IsComObject(RemotingClassObject))
{
Marshal.FinalReleaseComObject(RemotingClassObject);
}
RemotingClassObject = null;
}
return RemotingInterface;
}
Update on the exception
After correcting the "bitness" of the test COM Client application that #SimonMourier clued me to, I am able to get the interface from the .NET 5 COM server. I have updated the code from the method.
HOWEVER, I'm now struggling with getting the interface from the .NET Framework COM server in the same way I get it from the .NET 5 COM server. I successfully register it using RegAsm.exe, but I get the following exception:
System.InvalidCastException: 'Unable to cast object of type 'CMI.WinCalRemoting.cWinCalClient' to type 'CMI.WinCalRemoting.IWinCalClient'.'.
I've done an exhaustive search to try to find out how to fix the .NET Framework COM project so that it can be used in the same way that the .NET 5 COM server is used so that it doesn't matter whether the COM client is a .NET Framework or a .NET Core assembly.
I added a .NET Framework COM server project to the shared directory below to replicate what I'm seeing. With the .NET Framework COM server.
I also switched the test application to be 32-bit to replicate how our sister application will be using the COM servers.
All of the projects are located here:
.NET 5 COM Interop
Unable to Add .NET Framework COM Type Library Reference
For a .NET Framework client assembly, I've attempted to add a reference to the .NET Framework COM server that was registered with regasm.exe, but that fails with the following message:
I'm trying to create non-static functions in my Azure Function projet in .NET 5 (VS 2022) and the Startup Configure method is not being called.
Here's my start up class
[assembly: FunctionsStartup(typeof(AuthenticationGateway.Functions.Startup))]
namespace AuthenticationGateway.Functions
{
class Startup : FunctionsStartup //public or not, still does not get called.
{
public override void Configure(IFunctionsHostBuilder builder)
{
//break point here never gets hit...
}
}
}
And here's the function in question:
namespace AuthenticationGateway.Functions
{
public class CreationConnection
{
private AuthenticationGatewayContext Context { get; set; }
public CreationConnection(AuthenticationGatewayContext context)
{
Context = context;
}
[Function("CreationConnection")]
public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req,
FunctionContext executionContext)
{
var response = req.CreateResponse(HttpStatusCode.OK);
return response;
}
}
}
I've tried commenting all of the code in Configure just in case it was a problem with it, not working either. Also tried marking the startup class as public too, no go.
Here are the dependencies for the projet in question
They are not the default dependencies the projet has when creating an Azure Function projet but as I tried other solutions to fix the issue, it lead me to plug those in.
Here's what the console is saying when starting the project:
Azure Functions Core Tools Core Tools Version: 3.0.3904 Commit
hash: c345f7140a8f968c5dbc621f8a8374d8e3234206 (64-bit) Function
Runtime Version: 3.3.1.0
Anything I missed ?
EDIT: I have reverted to the following dependencies as the previous ones made it so no functions would be found in the project.
On this page here it says those following dependencies have to be installed:
Microsoft.Azure.Functions.Extensions
Microsoft.NET.Sdk.Functions package version 1.0.28 or later
Microsoft.Extensions.DependencyInjection (currently, only version 3.x and earlier supported)
I have done so, except the last one because it is incompatible with .NET 5 it seems. Also, the project is now unbuildable:
error MSB4062: The "GenerateFunctionMetadata" task could not be loaded from the assembly
I have tried to reproduce the same issue which you got by following the below steps:
Created the Azure Functions (Stack: .Net5-v3) in VS2022.
Before adding Microsoft.Net.Sdk.functions to the project, it was built successfully.
After adding Microsoft.Net.Sdk.functions to the project, it has run to the same issue MSB4062 error as below:
By referencing these SO Thread1 and Thread2, removing Microsoft.Net.Sdk.functions will solve the compile issue.
I've upgraded my .NET (not .NET Core) WebJob from V2 (which was working fine) to V3. I'm having trouble getting it to run. I just want the webjob to call a function I've written according to this CRON schedule: "0 0 8,10,12,14,16,18,20 * * *". The website it's running with is .NET also, not .NET Core.
How do I do this? I just want a simple working .NET code sample. I've seen this question New Azure WebJob Project - JobHostConfiguration/RunAndBlock missing after NuGet updates and this example https://github.com/Azure/azure-webjobs-sdk/blob/00686a5ae3b31ca1c70b477c1ca828e4aa754340/sample/SampleHost/Program.cs and this documentation https://learn.microsoft.com/en-us/azure/app-service/webjobs-sdk-how-to#triggers but none of it is helpful.
Actually the use of .Net webjob or .Net Core webjob are almost same, cause the 3.0 sdk targets .NET standard 2.0. I test with Microsoft.Azure.WebJobs -version 3.0.4 and Microsoft.Azure.WebJobs.Extensions -version 3.0.1, i think your TimerTrigger doesn't work cause you lost call the AddTimers extension methods. You could find the description here:Binding types.
Other package I use:
Microsoft.Extensions.Logging -version 2.2.0
Microsoft.Extensions.Logging.Console -version 2.2.0
This is my main method:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
namespace ConsoleApp20
{
class Program
{
static void Main(string[] args)
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddTimers();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
var host = builder.Build();
using (host)
{
host.Run();
}
}
}
}
This is my Functions.cs:
public static void Run([TimerTrigger("0 0 8,10,12,14,16,18,20 * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
}
And use a appsettings.json(Don't forget set the Copy to Output Directory to Copy always) to configure the storage connection string.
Here is the result:
Scheduled .NET WebJob V3 example
No, it is not possible.
WebJob 3.x only support for .NET Core. Here is the article.
Here is a SO thread about Webjob 3.x for .net core to do some settings.
As stated in official document, I am trying to implement UseOwin in the Startup.cs.I am trying to use/port IAppBuilder (Microsoft.Owin.Builder.AppBuilder) inside IApplicationBuilder (Microsoft.AspNetCore.Builder.IApplicationBuilder). I had legacy code written using IAppBuilder running fine on .Net Framework 4.5.
I have seen couple of examples about using IAppBuilder in IAplicationBuilder e.g. example 1 example 2. These attempts were about .netcore 1.1 and not .net core 2.0. May be this is the reason i am unable to port.
Please share your thoughts whether i am trying to achieve something not possible at the moment in .net core 2.0 or there is some error in my code.
Note:
I am using dotnetcore 2.0 with Visual Studio 2017
Error
I am getting following error.
return owinAppBuilder.Build,
Task>>(); TypeLoadException: Could not load type
'System.Security.Cryptography.DpapiDataProtector' from assembly
'System.Security, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a'.
My attempt
app.UseOwin(setup => setup(next =>
{
var owinAppBuilder = new AppBuilder();
var aspNetCoreLifetime =
(IApplicationLifetime)app.ApplicationServices.GetService(typeof(IApplicationLifetime));
new AppProperties(owinAppBuilder.Properties)
{
OnAppDisposing = aspNetCoreLifetime?.ApplicationStopping ?? CancellationToken.None,
DefaultApp = next,
AppName = "test"
};
// Only required if CORS is used, configure it as you wish
var corsPolicy = new System.Web.Cors.CorsPolicy
{
AllowAnyHeader = true,
AllowAnyMethod = true,
AllowAnyOrigin = true,
SupportsCredentials = true
};
//corsPolicy.GetType()
// .GetProperty(nameof(corsPolicy.ExposedHeaders))
// .SetValue(corsPolicy, tusdotnet.Helpers.CorsHelper.GetExposedHeaders());
owinAppBuilder.UseCors(new Microsoft.Owin.Cors.CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(corsPolicy)
}
});
PublicClientId = "self";
OAuthAuthorizationServerOptions OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new Microsoft.Owin.PathString("/Login"),
Provider = new MyServiceProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(60),
AllowInsecureHttp = true,
RefreshTokenProvider = new MyRefreshTokenProvider(),
};
owinAppBuilder.UseOAuthBearerTokens(OAuthOptions);
//owinAppBuilder.UseTus(context => new DefaultTusConfiguration
//{
// // Excluded for brevity, use the same configuration as you would normally do
//});
return owinAppBuilder.Build<Func<IDictionary<string, object>, Task>>();
}));
Microsoft.Owin and related packages do not have targets for .NET Core, no for .NET Standard. All they have is dlls targeting full .NET. You can reference such libraries from your project targeting .NET Core, but they are not guaranteed to work, as you see yourself, because API (set of classes\methods\signatures) of full .NET and .NET Core are different. Visual Studio even will show a warning when you are doing that, for example:
Package 'Microsoft.Owin 3.1.0' was restored using
'.NETFramework,Version=v4.6.1' instead of the project target framework
'.NETCoreApp,Version=v2.0'. This package may not be fully compatible
with your project.
There is Microsoft.AspNetCore.Owin package and you can use OWIN middleware in .NET Core app as your first link describes, but almost all it provides is UseOwin extension method. There is no AppBuilder type there and so on, and there are no Microsoft.AspNetCore.Owin.Cors packages or similar. So you have to either implement all that yourself (no reason to, because you can use the same functionality provided by asp.net core framework) or wait for OWIN packages that target .NET Standard\Core and do that (didn't check, maybe they even exist already).
So, your code uses packages which are indeed not compatible with your target framework, as exception you have at runtime shows. So another answer (for some reason downvoted) is technically correct.
If you still want to use those packages reliably - you need to target full .NET Framework and not .NET Core. To do that, open your .csproj file and change
<TargetFramework>netcoreapp2.0</TargetFramework>
To some .NET framework version that supports .NET Standard 2.0, for example:
<TargetFramework>net47</TargetFramework>
Then go to nuget package manager and, if you have microsoft.aspnetcore.all package (or other packages targeting .NET Core) - uninstall it, you don't need it anyway. Then install Microsoft.AspNetCore package and all other asp.net core packages you need (if not installed already). Rebuild, run and it will work just fine.
That works because all (most?) AspNetCore packages target .NET Standard, not .NET Core, and you can use them in projects targeting full .NET Framework.
Note that by doing that you have asp.net Core project, but not on .NET Core, with all consequences that come from that (cannot run with dotnet run, on linux need to run with mono, and so on).
The Microsoft.Owin components will not work on dotnet core 2.0, they only work on .NET 4.5+
We're having a weird problem when using Castle.Windsor to instantiate an SqlConnection using a typed factory:
The registration looks like this:
container.Register(Component.For<IDbConnectionFactory>().AsFactory().LifestyleTransient());
container.Register(Component.For<IDbConnection>().ImplementedBy<SqlConnection>()
.LifestyleTransient()
.DependsOn(Dependency.OnValue<string>
(ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString)));
And the IDbConnectionFactory:
public interface IDbConnectionFactory
{
IDbConnection Create();
void Release();
}
Now, when I try to access a new connection using this code:
using (var connection = _connectionFactory.Create())
{
}
I get an exception:
An unhandled exception of type
'Castle.MicroKernel.ComponentActivator.ComponentActivatorException' occurred
in Castle.Windsor.dll
Additional information: Error setting property SqlConnection.AccessToken in component
System.Data.SqlClient.SqlConnection. See inner exception for more information.
If you don't want Windsor to set this property you can do it by either decorating it
with DoNotWireAttribute or via registration API.
Alternatively consider making the setter non-public.
The problem with this Exception is that the type SqlConnection in System.Data for .NET 4.5.1 does not contain the property AccessToken whereas the one in .NET 4.6 does. In other words, if I try to manually do
var connection = new SqlConnection("connectionstring");
connection.AccessToken = "";
I get a build-error if the project is configured for .NET 4.5.1, but a runtime error on setting the AccessToken if it's configured for .NET 4.6.
Any idea why Castle.Windsor attempts to create a v4.6 SqlConnection instead of a .NET 4.5.1?
Workaround/Hack
I can get around the problem by telling Castle to ignore the property, but this seems like a hack. Doing this requires me to add it to the PropertiesIgnore in the registration:
container.Register(Component.For<IDbConnection>().ImplementedBy<SqlConnection>()
.PropertiesIgnore(info => info.Name.Equals("AccessToken"))
.LifestyleTransient()
.DependsOn(Dependency.OnValue<string>
(ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString)));
All .NET versions since 4.5 are in place updates
as you can see here.
This means that once you have installed .NET 4.6 you will always get the .NET 4.6 version of SqlConnection regardless of how you instantiate it.
When building your application in Visual Studio you build against a specific version of the .NET framework typically located in a folder under:
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework
This means that when building msbuild can check that you are not using something which isn't available in the framework version you are targeting.
However when you run your 64 bit application it will use the assemblies typically located in C:\Windows\Microsoft.NET\Framework64\v4.0.30319
This is the same folder for all versions from .NET 4.0 through .NET 4.6, this is what in place upgrade means.
So when you execute your application on your developement environment that has .NET 4.6 installed, you will always get the .NET 4.6 version (at least unless you do something special to load other versions of the assemblies).
Castle Windsor will try to set properties with public setter and it will use reflection to find the properties which means that it will find the .NET 4.6 properties on a .NET 4.6 machine, even if you are building against 4.5.1.
The reason it fails when it tries to set the AccessToken is most likely because your connection string is not compatible with setting AccessToken.
If you check the source code of the AccessToken setter you will see that it will throw an exception if you try to set it for a incompatible connection string, even if you only try to set the AccessToken to the empty string.
As you don't need to inject any dependencies into the SqlConnection object you may as well create it simply using the new operator and then you avoid the problem caused by Windsors attempts to inject the properties of the connection.
Using this registration should work:
container.Register(Component.For<IDbConnection>().ImplementedBy<SqlConnection>()
.LifestyleTransient()
.UsingFactoryMethod(() => new SqlConnection
(ConfigurationManager.ConnectionStrings["DbConnectionString"].ConnectionString)));