Recently I have observed the following interesting scenario in one of the application I'm developing using .NET 3.5. In this particualr application I have a singletion object which I access as a static variable. I exepected that the .NET run time should initializes this singleton object at the very first time I access it, but it seems this is not the case. .NET runtime initialize it way before I access this particualr object. Following is some peudo code,
if(!initSingleton)
//Do some work without using the singletion class.
else
//Do some work using the singletion class.
Even at runtime my code only executes the code in side the if statement .NET runtime still initializes the singleton object. In some of the application runs I don't need to access this pariticualr object at all!
Also I don't see this behavior with debug builds. Seems this has something to do with optimized builds (release builds).
Is this is the expected behavior of the .NET runtime?
Update:
Following is the actual code,
private void InitServiceClient(NetworkCredential credentials, bool https)
{
string uri = currentCrawlingWebUrl;
if (!uri.EndsWith("/"))
uri = string.Concat(uri, "/");
uri = string.Concat(uri, siteDataEndPointSuffix);
siteDataService = new SiteData.SiteDataSoapClient();
siteDataService.Endpoint.Address = new EndpointAddress(uri);
if (credentials != null)
{
siteDataService.ClientCredentials.Windows.ClientCredential = credentials;
}
else if (MOSSStateHandler.Instance.UserName.Length > 0 && MOSSStateHandler.Instance.Password.Length > 0)
{
siteDataService.ClientCredentials.Windows.ClientCredential.UserName = MOSSStateHandler.Instance.UserName;
siteDataService.ClientCredentials.Windows.ClientCredential.Password = MOSSStateHandler.Instance.Password;
siteDataService.ClientCredentials.Windows.ClientCredential.Domain = MOSSStateHandler.Instance.Domain;
}
BasicHttpBinding httpBinding = (BasicHttpBinding)siteDataService.Endpoint.Binding;
httpBinding.Security.Mode = (https ? BasicHttpSecurityMode.Transport : BasicHttpSecurityMode.TransportCredentialOnly);
string authmode = MOSSConnectorConfiguration.Instance.Config.GetString(ConfigConstants.SHAREPOINT_AUTH_PROVIDER, "ntlm");
if (authmode.Equals("ntlm", StringComparison.OrdinalIgnoreCase))
httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
else if (authmode.Equals("kerberos", StringComparison.OrdinalIgnoreCase))
httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
else
throw new Exception(string.Format("Not supported"));
}
Even though my application doesn't execute the code in side else if block the class MOSSStateHandler get initialized.
I suggest you read Jon Skeet's article about Singleton pattern in C#, and its appendix about lazy-loading etc. You'll get better understanding about the implementation issues.
From C# language spec:
The static constructor for a class executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:
An instance of the class is created.
Any of the static members of the class are referenced.
Related
I am writing some integration tests for my web API, which means that it has to be running during the execution of the tests. Is there any way to run it with an in-memory database instead of a real one based on SQL Server?
Also, I need to run a few instances at a time, so I need somehow to change the base address of each of them to be unique. For example, I could append to the base URL these instance IDs, that are mentioned in the code below.
Here is the code which I am using to run a new instance for my tests:
public static class WebApiHelper
{
private const string ExecutableFileExtension = "exe";
private static readonly Dictionary<Guid, Process> _instances = new();
public static void EnsureIsRunning(Assembly? assembly, Guid instanceId)
{
if (assembly is null)
throw new ArgumentNullException(nameof(assembly));
var executableFullName = Path.ChangeExtension(
assembly.Location, ExecutableFileExtension);
_instances.Add(instanceId, Process.Start(executableFullName));
}
public static void EnsureIsNotRunning(Guid instaceId)
=> _instances[instaceId].Kill();
}
Talking in general, is this a good way to create test instances, or maybe I am missing something? Asking this, because maybe there is another 'legal' way to achieve my goal.
Okay, so in the end, I came up with this super easy and obvious solution.
As was mentioned in the comments - using the in-memory database is not the best way to test, because relational features are not supported if using MS SQL.
So I decided to go another way.
Step 1: Overwrite the connection strings.
In my case, that was easy since I have a static IConfiguration instance and was need just to overwrite the connection string within that instance.
The method looks as follows:
private const string ConnectionStringsSectionName = "ConnectionStrings";
private const string TestConnectionStringFormat = "{0}_Test";
private static bool _connectionStringsOverwitten;
private static void OverwriteConnectionStrings()
{
if (_connectionStringsOverwitten)
return;
var connectionStrings = MyStaticConfigurationContainer.Configuration
.AsEnumerable()
.Where(entry => entry.Key.StartsWith(ConnectionStringsSectionName)
&& entry.Value is not null);
foreach (var connectionString in connectionStrings)
{
var builder = new SqlConnectionStringBuilder(connectionString.Value);
builder.InitialCatalog = string.Format(TestConnectionStringFormat,
builder.InitialCatalog);
MyStaticConfigurationContainer.Configuration[connectionString.Key] = builder.ConnectionString;
}
_connectionStringsOverwitten = true;
}
Of course, you would need to handle the database creation and deletion before and after running the tests, otherwise - your test DBs may become a mess.
Step 2: Simply run your web API instance within a separate thread.
In my case, I am using the NUnit test framework, which means I just need to implement the web API setup logic within the fixture. Basically, the process would be more or less the same for every testing framework.
The code looks as follows:
[SetUpFixture]
public class WebApiSetupFixture
{
private const string WebApiThreadName = "WebApi";
[OneTimeSetUp]
public void SetUp() => new Thread(RunWebApi)
{
Name = WebApiThreadName
}.Start();
private static void RunWebApi()
=> Program.Main(Array.Empty<string>());
// 'Program' - your main web app class with entry point.
}
Note: The code inside Program.Main(); will also look for connection strings in the MyStaticConfigurationContainer.Configuration which was changed in the previous step.
And that's it! Hope this could help somebody else :)
We're trying to use the REST API of Administration Service to manage the Configuration Manager
(What is the administration service in Configuration Manager?)
We have successfully queried entities of different types and executed some custom static methods (i.e. MoveMembers Method on SMS_ObjectContainerItem). It's all mostly blind shots as there is barely any documentation, but those basic functionalities seem to work fine.
Now we have hit the wall, trying to add collection rules to a SMS_Collection (existing or new). This was normally done calling the AddMembershipRule on the instance itself, that was previously fetched by e.g. WqlConnectionManager or some other proxy. However, this is clearly a no-go on a plain object fetched from the REST service.
We have tried to use the wmi OData service (by a generated proxy) as it clearly offers similar functionality, but this ends up with a "Not supported exception":
var savedCollection = Proxy.SMS_Collection.Where(c => c.CollectionID == result.CollectionID).FirstOrDefault();
savedCollection.AddMembershipRule(inclusionRule);
Proxy.UpdateObject(savedCollection);
Proxy.SaveChanges(); //EXCEPTION
I have tried running POST request in numerous ways, using urls like:
SMS_Collection.AddMembershipRule?CollectionID=DV000037 -> 404
SMS_Collection/DV000037/AddMembershipRule -> 404
SMS_Collection.DV000037.AddMembershipRule -> 404
SMS_Collection/DV000037.AddMembershipRule -> treated it as post to SMS_Collection/DV000037, and therefore triggers an update
or just
SMS_Collection.AddMembershipRule with collectionID as param
As for the request body I've used (or just the AddCollectionMembershipRuleRequestRule):
public class AddCollectionMembershipRuleRequest
{
public AddCollectionMembershipRuleRequestRule CollectionRule { get; set; }
}
public class AddCollectionMembershipRuleRequestRule
{
public string RuleName { get; set; }
public string IncludeCollectionID { get; set; }
}
I have also tried to Post an existing or new collection, with CollectionRules prefilled, but this ends up with an exception complaining about IncludeCollectionID not being part of CollectionRule (base class) - looks like validation being too strict and not dealing well with the inheritance.
var collectionRequest = new ECMCollectionCreationRequest()
{
Name = collectionName,
CollectionType = 2,
RefreshType = 4,
LimitToCollectionID = DefaultLimitingCollectionID,
CollectionRules = new List<SMS_CollectionRule>()
{
new SMS_CollectionRuleIncludeCollection()
{
RuleName = "MembershipRule",
IncludeCollectionID = "DV100020"
}
}
};
Stil, no luck with any of those. Do you have any idea if such a scenario (modification of CollectionRules) is even supported with the Rest /OData service? If so, what would be the right way to achieve so?
It looks like this part is simply not supported at the moment. Looking at the code it seems that the service is not interpreting the arguments properly and therefore causing validation issues.
However, the same can be achieved, in a bit less clean and structured way, using ManagementScope and ManagementObject
var scope = new ManagementScope(siteAddress);
scope.Connect();
using (ManagementObject collection = new ManagementObject(scope, new ManagementPath($"SMS_Collection.CollectionID='{collectionID}'"), new ObjectGetOptions()))
{
if (collection == null)
throw new Exception($"Unable to find collection with ID '{collectionID}'");
collection.Get();
using (ManagementBaseObject inParams = collection.GetMethodParameters("AddMembershipRule"))
using (ManagementClass ruleClass = new ManagementClass(scope, new ManagementPath("SMS_CollectionRuleDirect"), new ObjectGetOptions()))
using (ManagementObject rule = ruleClass.CreateInstance())
{
rule["ResourceClassName"] = "SMS_R_System";
rule["ResourceID"] = ecmResourceID;
rule["RuleName"] = machineName;
inParams["collectionRule"] = rule;
collection.InvokeMethod("AddMembershipRule", inParams, null);
}
}
One can add and remove all the other rule types in similar way.
Another alternative is of course to use PowerShell. Sill, I hope that with one of the next iterations of the Administration Service, support of those methods will be added.
Similarly, there seems to be no way to add/remove application or package and import/export them, using the admin services or even in the way mentioned above.
$Rule="{'collectionRule':{`"#odata.type`": `"#AdminService.SMS_CollectionRuleDirect`", `"ResourceClassName`": `"SMS_R_System`", `"ResourceID`": $DeviceID,`"RuleName`": `"$MachineName`"}}"
$RuleCreated = (Invoke-RestMethod -Method Post -Uri "https://$($CMProvider)/AdminService/wmi/SMS_Collection('$CollectionID')/AdminService.AddMembershipRule" -Body $Rule -ContentType 'application/json' -Credential $Cred)
We were using cefsharp version 57.0 in our application and it was working all fine. But now due to some limitations we need to update cefsharp version to 75.1.143 and suddenly our working code broke. One change was to change LegacyJavascriptBindingEnabled to true and secondly this line is breaking:
browser.RegisterJsObject("objectForCallingServerSideMethods", this);
giving error System.ArgumentException: Registering of .Net framework built in types is not supported, create your own Object and proxy the calls if you need to access a window/Form/Control. Parameter name: value.
It was all working fine. I don't know how to deal with this. Sample code is here which was working before update.
Cef.Initialize(new CefSettings() { IgnoreCertificateErrors = true, PersistSessionCookies = true, CachePath = LocalFolderPath + "/cache" });
CefSharpSettings.ShutdownOnExit = true;
CefSharpSettings.LegacyJavascriptBindingEnabled = true; // added now for version 75.1.143
browser = new ChromiumWebBrowser("url goes here");
browser.DownloadHandler = new DownloadHandler();
browser.Width = 0;
browser.Height = 0;
browser.MenuHandler = new CustomContextHandler();
this.Controls.Add(browser);
browser.LoadingStateChanged += Browser_LoadingStateChanged;
browser.RegisterJsObject("objectForCallingServerSideMethods", this); // this line is throwing exception now. screenshot is attached.
I have sort out its solution. I was passing winform object as 'this' and in version 75.1.143 they have changed the implementation now we cannot use Window/Form/Control. I have created another class named as "BoundObject" and passed its object and it worked as expected.
public class BoundObject
{
// some implementation
// all methods from JS will be catched in this class. Before this (in version 57) we were receiving these methods from JS on windows form.
}
browser.RegisterJsObject("objectForCallingServerSideMethods", new BoundObject());
Below I have some code that that I cannot Unit test because it tries to read settings from IIS7 and unfortunately our nightly build machine does not have IIS7. The only thing I can think of is to pass the ServerManager into the method, but then again in the caller I will have a ServerManager that will make that method unable to be unit tested. We use MOQ for our Mock library.
public ISection GetCurrentSettings(string location, Action<string> status)
{
#region Sanity Checks
if (string.IsNullOrEmpty(location))
{
throw new ArgumentNullException("location");
}
if (status == null)
{
throw new ArgumentNullException("status");
}
#endregion
ISection section = null;
_logger.Debug(string.Format("Retrieving current IIS settings for app at {0}.", location));
status("Getting current IIS settings.");
using (ServerManager manager = new ServerManager())
{
var data = (from site in manager.Sites
from app in site.Applications
from vdir in app.VirtualDirectories
where vdir.PhysicalPath.Equals(location, StringComparison.CurrentCultureIgnoreCase)
select new {Website = site, App = app}).SingleOrDefault();
if (data == null)
{
_logger.Debug(string.Format("Could not find an application at {0} in IIS. Going to load the defaults instead.", location));
//ToDo possibly load defaults
}
else
{
_logger.Debug(string.Format("Application found in IIS with website: {0} and a path of {1}", data.Website.Name, data.App.Path));
int port =
data.Website.Bindings.Where(b => b.EndPoint != null).Select(b => b.EndPoint.Port).Single();
section = new IISSection
{
ApplicationPoolName = data.App.ApplicationPoolName,
VirtualDirectoryAlias = data.App.Path,
WebsiteName = data.Website.Name,
WebsiteRoot = data.App.VirtualDirectories[0].PhysicalPath,
Port = port.ToString(CultureInfo.InvariantCulture),
WillApply = true,
AnonymousUser = _userService.GetUserByType(UserType.Anonymous)
};
}
return section;
}
Without rewriting your code fully, the general idea would be to pass in an ISettingReader* (implemented as IisSettingReader), which would expose methods that would get the data you need from IIS. Then, you can stub in the ISettingReader to return what you need, by passing ISettingReader into the method/class
*Or, IServerManager as it seems to be the current name, but I am not sure if that is IIS specific
UPDATE
To be more specific, as Darin Dimitrov elaborated, you need to pull all of the dependencies outside of the method and pass them in via parameter/constructor/property injection. This will require a rewrite of the code as it stands in its current state.
If not (and I do suggest a rewrite), then you can use something like TypeMock, which supposedly can fake the dependencies INSIDE a class, but I have not used this myself and only know what I have read on it.
Use Moq.
This will allow you to create a mocked version of ISettings rather than having to create a real one. It has the added advantage of allowing you to specify your own functionality as well.
i am using the arcGIS api to make a plugin for arcFM, when i try to run this code
Type t = Type.GetTypeFromProgID("esriFramework.AppRef");
System.Object obj = Activator.CreateInstance(t);
pApp = obj as IApplication;
i get
System.Runtime.InteropServices.COMException(0x8000FFFF): Creating an instance of the component with CLSID {Appref CLSID HERE} from the IClassFactory faileddue to the following error: 8000ffff
Thanks
This was impossible i needed to be using arcMap not ArcFM
In the AppRef CoClass documentation, it says:
Note you can only use the AppRef
object if your code is running inside
one of the ArcGIS application
processes.
Forum posts seem to confirm that this is the same error which is seen when this constraint has been violated:
From http://forums.esri.com/Thread.asp?c=93&f=1729&t=217861:
It is my understanding that there is
indeed no way to access the
IApplication instance from a
geoprocessing script.
In theory, if your task is purely
geoprocessing, you should be able to
do it all without accessing the
IApplication object.
It looks like the OP of the above forum post was able to get around their problem by "using IToolboxWorkspace and accessing directely the Esri-toolboxes". This was her code:
public IGPTool GetTool(string _sToolName, string _sToolboxName)
{
IWorkspaceFactory pGPTFact;
IToolboxWorkspace pToolboxWorkspace;
IGPToolbox pGPToolbox;
IGPTool pGPTool;
pGPTFact = new ToolboxWorkspaceFactoryClass();
pToolboxWorkspace = pGPTFact.OpenFromFile(
ArcGISInstallFolder + #"\ArcToolbox\Toolboxes", 0) as IToolboxWorkspace;
pGPToolbox = pToolboxWorkspace.OpenToolbox(_sToolboxName);
pGPTool = pGPToolbox.OpenTool(_sToolName);
return pGPTool;
}
private string ArcGISInstallFolder
{
get
{
if (string.IsNullOrEmpty(this.m_sArcGISInstallFolder))
{
Microsoft.Win32.RegistryKey regkey;
regkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
#"Software\ESRI\ArcGIS", false);
this.m_sArcGISInstallFolder = regkey.GetValue("InstallDir") as String;
}
return this.m_sArcGISInstallFolder;
}
}
Perhaps you can accomplish your goal either without the AppRef object or by running your script from inside the application.