DryIOC Container Contents - dryioc

StructureMap has a super-useful debug method on the Container class called WhatDoIHave()
It shows every type in the container along with its lifecycle, guid and a bunch of other info. It's useful for debugging.
There's some info here:
http://jeremydmiller.com/2014/02/18/structuremap-3-is-gonna-tell-you-whats-wrong-and-where-it-hurts/
Does DryIOC have an equivalent debug feature?

(I'm the creator of DryIoc).
You can use container.GetServiceRegistrations() to get registration infos as #fyodor-soikin suggested.
But at the latest version (2.0.0-rc3build339) I have added VerifyResolutions method that may help you to diagnose potential resolution problems, including missing registrations as well. Here the wiki explaining it.
Example from the wiki:
// Given following SUT
public class RequiredDependency {}
public class MyService { public MyService(RequiredDependency d) {} }
// Let's assume we forgot to register RequiredDependency
var container = new Container();
container.Register<MyService>();
// Find what's missing
var results = container.VerifyResolutions();
Assert.AreEqual(1, results.Length);
Verify result is array of ServiceRegistrationInfo and ContainerException KeyValuePairs. In this example the registration info will be:
MyNamespace.MyService registered as factory {ID=14, ImplType=MyNamespace.MyService}
And the exception will be:
DryIoc.ContainerException: Unable to resolve MyNamespace.RequiredDependency as parameter "d"
in MyNamespace.MyService.
Where no service registrations found
and number of Rules.FallbackContainers: 0
and number of Rules.UnknownServiceResolvers: 0
Update:
The functionality is available in latest stable DryIoc 2.0.0.

Related

Asp.Net Core Dependency Injection doesn't work, if it is started from unmanaged/native

First i try to explain the story.
I wanted to extend an C++/MFC application with REST-APIs, and decided to use for this purpose Asp.Net-Core 5 in a library by bridging it to unmanaged code with C++/CLI library. (having a separete ASP application is doubled expenditure, and needed to be rewritten all the logic in C#. in that regard RESTful-Server should be in same process implemented.)
Asp-Host is started in that way; C++/MFC -> C++/CLI -> ASP-Library (ASP referenced in CLI, CLI referenced in Native)
First problem was; by building the host Microsoft.Extensions.Hosting.Abstractions-Assembly could not be resolved. I found that, "My CLI Library".runtimeconfig.json has wrong framework reference and causes it not to be resolved. That means generated runtimeconfig.json is wrong. After every build it has to be manually with AspNetCore instead of NETCore corrected. (Done in PostBuildEvent)
Next problem; during the ASP-Host build, ASP could not resolve "My ASP Library.dll"-Assembly (in reflection). This problem solved by OnAssemblyResolve event by giving the right path. I'm not sure whether it is correct solution. Because AppDomain.BaseDirectory is an empty string, maybe it is the cause of it, that the library could not found.
In AssemblyResolve event;
Assembly::LoadFrom(assemblyFile)
Finally i could start the server, and it works. Then needed to use dependency injection, and my service could not be resolved from the controller.
Then i used my ASP-Library in another C#-project to test and it works...
i'm sure that it doesn't work if the entry point of process is in unmanaged code.
public interface IServerImpl
{
void OnFail(String msg);
}
public class CServerImpl : IServerImpl
{
public void OnFail(String msg)
{
}
}
... in Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IServerImpl, CServerImpl>();
services.AddControllers();
}
... Controller
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
private readonly IServerImpl _serverImpl;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IServerImpl serverImpl)
{
_logger = logger;
_serverImpl = serverImpl;
}
...
}
Is there any workaround to have it working? Are those problems bug in Asp.Net-Core or what am i doing wrong?
Thanks in advance
The problem has been solved. Assembly resolving event with Assembly.LoadFrom() causes my ASP-assembly to be loaded (or mapped) twice. Loading it in different assembly-load-context means that, doubled Types, static variables and so on. In that regard my registered IServerImpl service searched in the controller with another IServerImpl type. Type names are identical, but not the same.
For more information see technical challenges: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=net-5.0
Another issue on GitHub: https://github.com/dotnet/runtime/issues/39783
In assembly resolve event, i'm returning now the loaded assembly like that, instead of loading it again;
AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(asm => asm.FullName.StartsWith(args.Name.Split(',')[0] + ","))
still wellcome answers which clarifies "why my ASP-assembly not found from the framework?"

Acumatica - Problem with data displaying in custom field even though saved to database

I created a custom date field in the opportunity screen. The field shows up just as expected. I enter a date save the record, go out, go back in and the date is no longer there. However, when I query the database, the column is there and there is a data in the column. So, even though the date is being stored in the database it is not displaying it in the screen or when I added that value to the generic inquiry it is also blank. I am not a programmer but it seems like that should’ve been an easy thing to do. I found some references on the web of this type of problem but was hoping there was a more straightforward fix than what those appeared, as they were more programming than I am comfortable at this point.
I found many web pages explaining how to do this seemingly simple thing using the customization tools. Not sure what I'm missing.
I am running fairly recent version of 2019R1.
Any help would be appreciated!
Response from an Acumatica support person.. Evidently they changed some things with 2018R1. See comments/answers below from support. Image of the change I made in the customization tool is also below. After making this change the custom field worked as desired.
After reviewing it into more details, it sounds that it could be related to the new implementation of PXProjection on that DAC.
Unlike ver. 2017 R2, some DAC like the PX.Objects.CR.CROpportunity DAC were implemented as a regular Data Access Class:
[System.SerializableAttribute()]
[PXCacheName(Messages.Opportunity)]
[PXPrimaryGraph(typeof(OpportunityMaint))]
[CREmailContactsView(typeof(Select2<Contact,
LeftJoin<BAccount, On<BAccount.bAccountID, Equal<Contact.bAccountID>>>,
Where2<Where<Optional<CROpportunity.bAccountID>, IsNull, And<Contact.contactID, Equal<Optional<CROpportunity.contactID>>>>,
Or2<Where<Optional<CROpportunity.bAccountID>, IsNotNull, And<Contact.bAccountID, Equal<Optional<CROpportunity.bAccountID>>>>,
Or<Contact.contactType, Equal<ContactTypesAttribute.employee>>>>>))]
[PXEMailSource]//NOTE: for assignment map
public partial class CROpportunity : PX.Data.IBqlTable, IAssign, IPXSelectable
{
...
}
In version 2018 R1(and later) the PX.Objects.CR.CROpportunity DAC is a projection over the PX.Objects.CR.Standalone.CROpportunity and PX.Objects.CR.Standalone.CROpportunityRevision DACs:
[System.SerializableAttribute()]
[PXCacheName(Messages.Opportunity)]
[PXPrimaryGraph(typeof(OpportunityMaint))]
[CREmailContactsView(typeof(Select2<Contact,
LeftJoin<BAccount, On<BAccount.bAccountID, Equal<Contact.bAccountID>>>,
Where2<Where<Optional<CROpportunity.bAccountID>, IsNull, And<Contact.contactID, Equal<Optional<CROpportunity.contactID>>>>,
Or2<Where<Optional<CROpportunity.bAccountID>, IsNotNull, And<Contact.bAccountID, Equal<Optional<CROpportunity.bAccountID>>>>,
Or<Contact.contactType, Equal<ContactTypesAttribute.employee>>>>>))]
[PXEMailSource]//NOTE: for assignment map
[PXProjection(typeof(Select2<Standalone.CROpportunity,
InnerJoin<Standalone.CROpportunityRevision,
On<Standalone.CROpportunityRevision.opportunityID, Equal<Standalone.CROpportunity.opportunityID>,
And<Standalone.CROpportunityRevision.revisionID, Equal<Standalone.CROpportunity.defRevisionID>>>>>), Persistent = true)]
public partial class CROpportunity : IBqlTable, IAssign, IPXSelectable
{
...
}
Because of that change, it's now required to declare 2 extension classes, one for Standalone.CROpportunity (normal DAC) and the CROpportunity (PXProjection).
On the PXProjection DAC Extension, please remind to add BqlField to the correspondent field on the Standalone DAC, Ex.: BqlField = typeof(CROpportunityStandaloneExt.usrTest)
public class CROpportunityExt : PXCacheExtension<PX.Objects.CR.CROpportunity>
{
#region UsrTest
[PXDBDecimal(BqlField = typeof(CROpportunityStandaloneExt.usrTest))]
[PXUIField(DisplayName="Test Field")]
public virtual Decimal? UsrTest { get; set; }
public abstract class usrTest : IBqlField { }
#endregion
}
Please find more information on this article below:
Custom field on CROpportunity doesn't display saved value since upgrading from 6.10 or 2017R2 to 2018R1
change made in customization tool

Sitecore: Glass Mapper Code First

It is possible to automatically generate Sitecore templates just coding models? I'm using Sitecore 8.0 and I saw Glass Mapper Code First approach but I cant find more information about that.
Not sure why there isn't much info about it, but you can definitely model/code first!. I do it alot using the attribute configuration approach like so:
[SitecoreType(true, "{generated guid}")]
public class ExampleModel
{
[SitecoreField("{generated guid}", SitecoreFieldType.SingleLineText)]
public virtual string Title { get; set; }
}
Now how this works. The SitecoreType 'true' value for the first parameter indicates it may be used for codefirst. There is a GlassCodeFirstDataprovider which has an Initialize method, executed in Sitecore's Initialize pipeline. This method will collect all configurations marked for codefirst and create it in the sql dataprovider. The sections and fields are stored in memory. It also takes inheritance into account (base templates).
I think you first need to uncomment some code in the GlassMapperScCustom class you get when you install the project via Nuget. The PostLoad method contains the few lines that execute the Initialize method of each CodeFirstDataprovider.
var dbs = global::Sitecore.Configuration.Factory.GetDatabases();
foreach (var db in dbs)
{
var provider = db.GetDataProviders().FirstOrDefault(x => x is GlassDataProvider) as GlassDataProvider;
if (provider != null)
{
using (new SecurityDisabler())
{
provider.Initialise(db);
}
}
}
Furthermore I would advise to use code first on development only. You can create packages or serialize the templates as usual and deploy them to other environment so you dont need the dataprovider (and potential risks) there.
You can. But it's not going to be Glass related.
Code first is exactly what Sitecore.PathFinder is looking to achieve. There's not a lot of info publicly available on this yet however.
Get started here: https://github.com/JakobChristensen/Sitecore.Pathfinder

A descendant of TStyledPresentationProxy has not been registered for class

I have a custom grid control that inherits from TGrid called TFmGrid. This control was working fine in Rad Studio 10 Seattle Update One. I recently upgraded to 10.1 Berlin and started noticing this error message showing up on my TFmGrid controls both when I run the application and in the designer:
A descendant of TStyledPresentationProxy has not been registered for class TFmGrid. Maybe it is necessary to add the FMX.Grid.Style module to the uses section
The image below shows how the error message shows up on my grid controls:
I started by doing as the message suggests, and adding #include <FMX.Grid.Style.hpp> to the header file of my TFmGrid control, however this seems to have done nothing.
So as far as trying to register a decendant of TStyledPresentationProxy I am not exactly sure where to start. I found this documentation about a method which:
Attempts to register the presentation proxy class with the specified name or the specified combination of control class and control type.
So I assume I need to use this method or at least something similar, but I don't understand how I am supposed to go about calling this method.
But then that brings up the question of WHERE do I call this code?
My custom control has a method in its namespace called Register() which I believe was autogenerated by the IDE when the control was created:
namespace Fmgridu
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1] = {__classid(TFmGrid)};
RegisterComponents(L"Kalos FM Controls", classes, 0);
}
}
Do I need to call something in there to register a decendant of TStyledPresentationProxy? What is the proper way to go about this?
Just override virtual method DefinePresentationName in you TfmGrid and return name of presentation name for grid:
function TfmGrid.DefinePresentationName: string;
begin
Result := 'Grid-' + GetPresentationSuffix;
end;
Fm registers presentation by string name and uses class name for it, so if you create new component (based on existed) you automatically change classname, so system cannot find presentation for you. There are two solution:
Said that you will use presentation from TGrid (DefinePresentationName)
Register existed presentation for you class (look at the initialization section of FMX.Grid.Style.pas)
P.S. Year ago i wrote article about it in common eNew approach of development of FireMonkey control “Control – Model – Presentation”. Part 1 I hope it will help you
It's simple :
Just put "StyleBook" component to your form
I had the same issue with a test component I was developing.
Complementing Yaroslav Brovin's speech, I solved the problem by adding the class register in the initialization and finalization clauses at the end of the unit, like this:
initialization
TPresentationProxyFactory.Current.Register(<COMPONENT CLASSNAME HERE>, TControlType.Styled, TStyledPresentationProxy<TStyledGrid>);
finalization
TPresentationProxyFactory.Current.Unregister(<COMPONENT CLASSNAME HERE>, TControlType.Styled, TStyledPresentationProxy<TStyledGrid>);
In my case looks like this:
initialization
TPresentationProxyFactory.Current.Register(TSGrid, TControlType.Styled, TStyledPresentationProxy<TStyledGrid>);
finalization
TPresentationProxyFactory.Current.Unregister(TSGrid, TControlType.Styled, TStyledPresentationProxy<TStyledGrid>);
PS: Don't forget to declare the FMX.Presentation.Factory,
FMX.Presentation.Style and FMX.Grid.Style units in the uses clause

Getting ColdFusion-Called Web Service to Work with JavaLoader-Loaded Objects

Is it possible to use JavaLoader to get objects returned by CF-called web services, and JavaLoader-loaded objects to be the same classpath context? I mean, without a lot of difficulty?
// get a web service
ws = createObject("webservice", local.lms.wsurl);
// user created by coldfusion
user = ws.GenerateUserObject();
/* user status created by java loader.
** this api provider requires that you move the stubs
** (generated when hitting the wsdl from CF for the first time)
** to the classpath.
** this is one of the stubs/classes that gets called from that.
*/
UserStatus = javaLoader.create("com.geolearning.geonext.webservices.Status");
// set user status: classpath context clash
user.setStatus(UserStatus.Active);
Error:
Detail: Either there are no methods with the specified method name and
argument types or the setStatus method is overloaded with argument
types that ColdFusion cannot decipher reliably. ColdFusion found 0
methods that match the provided arguments. If this is a Java object
and you verified that the method exists, use the javacast function to
reduce ambiguity.
Message: The setStatus method was not found.
MethodName setStatus
Even though the call, on the surface, matches a method signature on user--setStatus(com.geolearning.geonext.webservices.Status)--the class is on a different classpath context. That's why I get the error above.
Jamie and I worked on this off-line and came up with a creative solution :)
(Apologies for the long answer, but I thought a bit of an explanation was warranted for those who find class loaders as confusing as I do. If you are not interested in the "why" aspect, feel free to jump to the end).
Issue:
The problem is definitely due to multiple class loaders/paths. Apparently CF web services use a dynamic URLClassLoader (just like the JavaLoader). That is how it can load the generated web service classes on-the-fly, even though those classes are not in the core CF "class path".
(Based on my limited understanding...) Class loaders follow a hierarchy. When multiple class loaders are involved, they must observe certain rules or they will not play well together. One of the rules is that child class loaders can only "see" objects loaded by an ancestor (parent, grandparent, etcetera). They cannot see classes loaded by a sibling.
If you examine the object created by the JavaLoader, and the other by createObject, they are indeed siblings ie both children of the CF bootstrap class loader. So the one will not recognize objects loaded by the other, which would explain why the setStatus call failed.
Given that a child can see objects loaded by a parent, the obvious solution is to change how the objects are constructed. Structure the calls so that one of the class loaders ends up as a parent of the other. Curiously that turned out to be trickier than it sounded. I could not find a way to make that happen, despite trying a number of combinations (including using the switchThreadContextClassLoader method).
Solution:
Finally I had a crazy thought: do not load any jars. Just use the web service's loader as the parentClassLoader. It already has everything it needs in its own individual "class path":
// display class path of web service class loader
dynamicLoader = webService.getClass().getClassLoader();
dynamicClassPath = dynamicLoader.getURLS();
WriteDump("CLASS PATH: "& dynamicClassPath[1].toString() );
The JavaLoader will automatically delegate calls for classes it cannot find to parentClassLoader - and bingo - everything works. No more more class loader conflict.
webService = createObject("webservice", webserviceURL, webserviceArgs);
javaLoader = createObject("component", "javaloader.JavaLoader").init(
loadPaths = [] // nothing
, parentClassLoader=webService.getClass().getClassLoader()
);
user = webService.GenerateUserObject();
userStatus = javaLoader.create("com.geolearning.geonext.webservices.Status");
user.setStatus(userStatus.Active);
WriteDump(var=user.getStatus(), label="SUCCESS: user.getStatus()");