I have a Source Generator, it looks like this:
public class Generator : ISourceGenerator
{
public Generator()
{
// Uncomment if you want to debug this
Debugger.Launch();
}
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new MySyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
var syntaxReceiver = (MySyntaxReceiver)context.SyntaxReceiver;
var responseMessageClasses = syntaxReceiver.ResponseMessageClasses;
// do stuff to responseMessageClasses
}
}
internal class MySyntaxReceiver : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> ResponseMessageClasses { get; private set; } = new List<ClassDeclarationSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax cds &&
cds.Identifier.ValueText.EndsWith("ResponseMessage", StringComparison.OrdinalIgnoreCase))
{
ResponseMessageClasses.Add(cds);
}
}
}
In the same project, I have a folder of simple classes of which the name all ends with "ResponseMessage". These classes only have simple types like string, int and bool as properties. They are in a different namespace than the SourceGenerator.
There's a second console app project that references my source generator as an Analyzer. When I build the project I can succesfully break into the SourceGenerator code.
The problem: the simple ResponseMessage classes never get seen by the SyntaxReceiver class I have. The OnVisitSyntaxNode method only hits the Program.cs file. What is going on?
By design, it's not possible to use a source generator in the assembly where it was defined. According to the design spec:
Generator implementations are defined in external assemblies passed to the compiler using the same -analyzer: option used for diagnostic analyzers.
...
Since generators are loaded from external assemblies, a generator cannot be used to build the assembly in which it is defined.
Related
I am creating sample project based on DDD.
I created SharedKernel project where I have my class for DomainEvents
public static class DomainEvents
{
public static IContainer Container { get; set; }
static DomainEvents()
{
Container = StructureMap.Container.For<GenericTypesScanning>();
}
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach (var handler in Container.GetAllInstances<IHandle<T>>())
{
handler.Handle(args);
}
}
}
and this is class GenericTypesScanning
public class GenericTypesScanning : Registry
{
public GenericTypesScanning()
{
Scan(scan =>
{
// 1. Declare which assemblies to scan
scan.Assembly("MyLib");
// 2. Built in registration conventions
scan.AddAllTypesOf(typeof(IHandle<>));
scan.WithDefaultConventions();
});
}
}
In MyLib project I have class AppointmentConfirmedEvent and handler for this event:
public class EmailConfirmationHandler: IHandle<AppointmentConfirmedEvent>
{
public void Handle(AppointmentConfirmedEvent appointmentConfirmedEvent)
{
// TBD
}
}
I have temporary rest api controller where I wanted to check if everything is correctly registered and I am doing this:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET: api/<ValuesController>
[HttpGet]
public IEnumerable<string> Get()
{
var appointmentConfirmedEvent = new AppointmentConfirmedEvent();
DomainEvents.Raise(appointmentConfirmedEvent);
return new string[] { "value1", "value2" };
}
}
but when DomainEvents.Raise is called the event is not handled because internal call Container.GetAllInstances<IHandle<T>>() returns empty array.
I did analogous example with Console app and there everything works fine. Any idea why it does not work in case of ASP.NET Core .NET 5 project?
-Jacek
The AddAllTypesOf() method does not work with open generics. See the ConnectImplementationsToTypesClosing() method in the StructureMap docs: http://structuremap.github.io/generics/
And just a reminder, StructureMap is no longer supported. Moreover, 2.6.4.1 was the "haunted" version of StructureMap that was admittedly buggy.
The first thing to do is to check out the type scanning diagnostics:
http://structuremap.github.io/diagnostics/type-scanning/.
The type scanning can be a little brittle if there are missing assemblies. The diagnostics might point out where things are going wrong. Also, try your WhatDoIHave() diagnostics too.
And also, just making sure that you know that StructureMap is no longer supported and has been replaced by Lamar:
https://jeremydmiller.com/2018/01/29/sunsetting-structuremap/
https://jasperfx.github.io/lamar
I have a DLL MyAssemblyOne.dll which only contains one class with static methods:
namespace MyAssemblyOne
{
public class MyClassOne
{
public static string MyStaticMethod()
{
...
}
}
}
All is good so far, the assembly MyAssemblyOne.dll is generated.
Now I have another DLL, MyAssemblyTwo.dll which has a dependency on MyAssemblyOne.dll and uses it like:
no using here;
namespace MyAssemblyTwo
{
public class MyClassFromAssemblyTwo
{
public string SomeRandomMethod()
{
...
var smth = MyAssemblyOne.MyClassOne.MyStaticMethod();
...
}
}
}
Now I create a Xamarin project with Linking set to Sdk Assemblies Only and Use Shared Runtime disabled(basically Release mode), and I add my two DLLs - MyAssemblyTwo.dll and MyAssemblyOne.dll. The app builds ok, but when I run it I get something like:
cannot find MyAssemblyOne.dll.
Please note that this works if the Linking option is set to None.
However, if I change MyAssemblyTwo usage of MyAssemblyOne to be:
using MyAssemblyOne;
namespace MyAssemblyTwo
{
public class MyClassFromAssemblyTwo
{
public string SomeRandomMethod()
{
...
var smth = MyClassOne.MyStaticMethod();
...
}
}
}
everything works fine even with the Linking set to Sdk Assemblies Only.
How does the linker work? Why if I have a using statement everything is fine, but if I use the assembly name directly in the code it breaks.
It is worth mentioning that MyAssemblyOne and MyAsseblyTwo are .netstandard20 projects.
I'm just taking my first baby steps in the MEF territory and wanted to do so using .net core 2.1.
Using VS 2017 (version 15.8.8) I've done a small Console App (.NET Core) with an interface
interface IMessageSender
{
void Send(string message);
}
and an implementation (in the same project)
[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine("EmailSender : " + message);
}
}
Finally I have a small compose method executed from my Main(string[] args)
[Import]
private void Compose()
{
var assembly_A = new[] { typeof(Program).GetTypeInfo().Assembly };
var config_A = new ContainerConfiguration().WithAssembly(assembly_A[0]);
var container_A = config_A.CreateContainer();
var msg_A = container_A.GetExport<IMessageSender>();
msg_A.Send("Hello");
}
It works as expected
However, if I add a new class library to my solution and move my implementation of Send(string) to the newly added project things do not work out.
namespace AnotherMefExtensionProjectNamespace
{
[Export(typeof(IMessageSender))]
public class EmailSenderExtended : IMessageSender
{
public void Send(string message)
{
Console.WriteLine("EmailSenderExtended : " + message);
}
}
}
The new Compose method
[Import]
public IMessageSender MessageSender { get; set; }
private void Compose()
{
var assembly_B = new[] { typeof(EmailSenderExtended).GetTypeInfo().Assembly };
var config_B = new ContainerConfiguration().WithAssembly(assembly_B[0]);
var container_B = config_B.CreateContainer();
var msg_B = container_B.GetExport<IMessageSender>();
msg_B.Send("Hello");
}
I've tried to compare the different configs and containers (_A versus _B in the examples) but can't understand what is different. I've even tried to extend the class ContainerConfiguration to load from a specified assembly and it works as long as the given file contains the Main method but fails if I use my "extended" .NET Core Class Library.
public static ContainerConfiguration WithChosenAssembly(this ContainerConfiguration configuration, string pathAndFile)
{
var context = AssemblyLoadContext.Default.LoadFromAssemblyPath(pathAndFile);
var ass_list = new List<Assembly>() { context };
configuration = configuration.WithAssemblies(ass_list, null);
return configuration;
}
I was under the impression that you extend your main application by developing a class library that basically implements the interfaces specified.
I seem to be unable to do this currently, but obviously I misunderstood something very basic.
If someone would care to put me on the right track or give me an alternative idea for "plug-in" development for .net core I would be very grateful.
King regards
Magnus
I realized that my test setup does not mimic any real world scenario and thus I brought my problems on myself.
Obviously I should have had three projects.
One project with only the interface definitions.
One "main" project where all my regular code exists.
One (or more) projects where my MEF implementations of the interfaces exist.
Reviewing my example and adhering to the obvious "design" above it all works exactly as it should.
Most StackOverflow users probably wouldn't make my blunder but for those that did, I hope the above helps. :-)
Been googling it for a while, but found nothing really good.
So, the thing is, in MVVM Cross guides our solution (for mobile platforms) is separated on, as example, HelloWorld.Core project and few platform specific projects, as example HelloWorld.Android.UI.
You can see full tutorial here https://www.mvvmcross.com/documentation/tipcalc-tutorial/the-tip-calc-tutorial
And, the thing is, in our .Core project we creating Services, ViewModels and we registering it in MVVM Cross provided IoC-container.
In example which provided by the MvvmCross documentation Service implementation is really simple, just one function like that
public class CalculationService: ICalculationService
{
public double TipAmount(double subTotal, int generosity)
{
return subTotal * ((double)generosity)/100.0;
}
}
so we just go ahead, registering it in our App.cs and then its fine to use in binded ViewModel
public class App: MvxApplication
{
public App()
{
Mvx.RegisterType<ICalculationService, CalculationService>();
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<HomeViewModel>());
}
}
View model code :
public class TipViewModel : MvxViewModel
{
readonly ICalculation _calculation;
public TipViewModel(ICalculation calculation)
{
_calculation = calculation;
}
// ...
double _tip;
public double Tip
{
get {
return _tip;
}
set
{
_tip = value;
RaisePropertyChanged(() => Tip);
}
}
void Recalculate()
{
Tip = _calculation.TipAmount(SubTotal, Generosity);
}
}
But the thing is, when I started playing around with that, I decided, to, let say, code some simple local storage data scanner to find some specific files.
Simple code example:
public class SimpleDataScanner : IBaseDataScanner
{
private string _basePath { get; set; }
private List<string> _scanResult { get; set; }
public SimpleDataScanner()
{
_basePath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
_scanResult = new List<string>();
}
public List<string> BasicDataScan(string startPath = null)
{
string startFolder = _basePath;
if (startPath.Length > 0 && startPath.Length != null)
{
startFolder = Path.Combine(_basePath, startPath);
}
try
{
TreeScan(startFolder);
}
catch (Exception)
{
throw;
}
return _scanResult;
}
private void TreeScan(string path)
{
foreach (var file in Directory.GetFiles(path))
{
if (Path.HasExtension("pdf"))
{
_scanResult.Add(file);
}
}
foreach (string dir in Directory.GetDirectories(path))
{
TreeScan(dir);
}
}
}
And, as I found out, the problems are:
1) I can't use Android specific functions and properties in PCL
2) I can't use reference in my PCL Core project to, lets say, some other Android class lib project
So, I can't create working Service which means I can't register it in IoC-container and pass to ViewModel.
Whats the way of doing such things?
Any tutorials? Or I getting whole idea wrong?
You need to implement the interface in your platform code, and register the concrete implementation against the interface in Setup.cs inside your platform project. So in your case, IDataScanner would exist inside your Core project, while SimpleDataScanner would live in the Android project.
Inside Setup.cs, you'd register the concrete implementation like this:
protected override void InitializeFirstChance()
{
base.InitializeFirstChance();
Mvx.RegisterSingleton<IDataScanner>(() => new SimpleDataScanner());
}
Essentially, if the interface requires platform-specific or full .NET framework code to implement, those per-platform implementations live in the platforms and are registered in the platform setup. If both the interface and the implementation are shared within the Core project, the dependency can be registered in App.cs.
This is my first AutoMapper project and may be obvious to some but the tutorials and examples are not clicking with me. I am trying to understand where and to a certain degree how to register(I think I want profiles) my maps for use. There are plenty of MVC examples saying to use the global asax and this makes sense but what is the equivalent in a library project?
In my sandbox I have a winform app and a core library. The winform app calls methods made available by the library and it is one of these library methods that makes use of automapper.
So for some background here is my map:
(and to be clear the mapping is in the SAME core library project)
public class Raw_Full_Map
{
public Raw_Full_Map()
{
Mapper.CreateMap<IEnumerable<RawData>, FullData>()
.ForMember(d => d.Acres, m => m.ResolveUsing(new RawLeadDataNameResolver("Acres")));
//this is clearly just a snip to show it's a basic map
}
}
This is the core library method being called: (note it is a static..which means I won't have a constructor...if this is the problem am I to understand then that AutoMapper can't be utilized by static helper classes...that doesn't make sense....so likely I'm just not doing it right.
public static class RawDataProcessing
{
public static FullData HTMLDataScrape(string htmlScrape)
{
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(htmlScrape);
var list = Recurse(doc.DocumentNode);
//HTML agility stuff that turns my html doc into a List<RawData> object
return Mapper.Map<FullData>(list);
}
My test harness calls it like this:
var _data = RawDataProcessing.HTMLDataScrape(rawHTML);
This of course errors because the map isn't "registered".
If I do this in the test harness:
var x = new RawData_FullData();
var _data = RawDataProcessing.HTMLDataScrape(rawHTML);
Then everything works as my map get's registered albeit I think in a really bogus way...but it does work.
So the question is how do I register my mapping in the core library project...so that ANY method can use it...there isn't really an equivalent global.asax in a dll is there?
Thank you for helping me connect the missing pieces.
Put it in the static constructor of either the source or the target type of the mapping.
public class FullData
{
static FullData()
{
Mapper.CreateMap<IEnumerable<RawData>, FullData>()
.ForMember(d => d.Acres, m => m.ResolveUsing(new RawLeadDataNameResolver("Acres")));
}
}
The static constructor will automatically get called the first time you try to use the type FullData for anything (for example a mapping).
You can use PreApplicationStartMethod for any class and it's method in your class library which will be referenced from your startup project if you want automatically to call this on startup. And then you can register all your mappings in that method. By the way, I suggest to use AddProfile for registering all mappings.
[assembly: PreApplicationStartMethod(typeof(MyClassLibrary.Startup), "Start")]
namespace MyClassLibrary
{
public class Startup
{
// Automatically will work on startup
public static void Start()
{
Mapper.Initialize(cfg =>
{
Assembly.GetExecutingAssembly().FindAllDerivedTypes<Profile>().ForEach(match =>
{
cfg.AddProfile(Activator.CreateInstance(match) as Profile);
});
});
}
}
}
You just need to create new classes which derived from Profile class and then override it's Configure() method:
...
public class FooMapperProfile:Profile
{
protected override void Configure()
{
Mapper.CreateMap<OtherFoo, Foo>()
.ForMember(...
... // so on
}
}
public class AnotherFooMapperProfile:Profile
{
protected override void Configure()
{
Mapper.CreateMap<OtherFoo, AnotherFoo>()
.ForMember(...
... // so on;
}
}
...
// and so on
Additional information:
If you have seen I have initialized all mappings with that code:
Mapper.Initialize(cfg =>
{
Assembly.GetExecutingAssembly().FindAllDerivedTypes<Profile>().ForEach(match =>
{
cfg.AddProfile(Activator.CreateInstance(match) as Profile);
});
});
It will automatically find all types derived from Profile and will add all profiles after createing their new instances.
Update1:
As #Scott Chamberlain commented, PreApplicationStartMethod only works for ASP.NET applications. This would not work with a desktop app. If you are working with Wpf, then you can use Application.OnStartup method. Or just call Start.Startup (); in load event.
Update2:
FindAllDerivedTypes extension method:
public static class AssemblyExtensions
{
public static List<Type> FindAllDerivedTypes<T>(this Assembly assembly)
{
var derivedType = typeof(T);
return assembly.GetTypes()
.Where(t => t != derivedType && derivedType.IsAssignableFrom(t))
.ToList();
}
}