I am trying to load an existing 3rd party plugin to CefSharp (WinForms).
Am I supposed to achieve this via the RequestContext.LoadExtension method?
I have tried the following
BrowserForm.cs
public BrowserForm()
{
InitializeComponent();
IRequestContext cont = new RequestContext(new CustomerRequestContextHandler());
cont.LoadExtension(#"C:\Users\xxxxx\AppData\Local\Google\Chrome\User Data\Default\Extensions\digojkgonhgmnohbapdfjllpnmjmdhpg\1.5.5_0", null, new ExtensionHandler());
browser = new ChromiumWebBrowser("",cont);
toolStripContainer.ContentPanel.Controls.Add(browser);
displayPlugins();
}
ExtensionHandler.cs
public void OnExtensionLoaded(IExtension extension)
{
}
This method fires suggesting that it is able to find and load the extension at that location.
But a subequent call to :-
List<WebPluginInfo> plugins = await Cef.GetPlugins();
Does not retrieve this plugin.
Can anyone tell me what I'm missing?
Related
I have a MainPage in PCL which then navigates to a Platform Specific
Login Page on LoginButton Click Event
The LoginPage is inherited from
platform specific PageRenderer as it needs platform specific
Authentication to Social Providers (Facebook, Google, Twitter,
Microsoft, etc.)
I am using Xamarin.Auth to do the authentication.
Inside the OnElementChanged event of the LoginPage, it instantiates
the OAuth2Authenticator object.
Upon successful instantiation (based
on provider and app details), it needs to call the UI of the specific
provider.
To do that, I call the auth.GetUI where auth is
Xamarin.Auth.OAuth2Authenticator object.
I have two questions:
In UWP, how do I navigate to the provider login UI? More specifically, what is the equivalent in UWP of the following code snippets in iOS and Android? In iOS, the following code is used:
PresentViewController(auth.GetUI(), true, null);
where auth is Xamarin.Auth.OAuth2Authenticator object.
In Android the following is used:
activity.StartActivity(auth.GetUI(activity));
I am looking for the equivalent code in UWP. Please bear in mind that these calls are made from the LoginPage which is derived from Platform specific PageRenderer
How do I navigate back to my MainPage (which is in PCL) upon successful authentication?
The code is based off of a sample from the following source:
http://www.c-sharpcorner.com/article/oauth-login-authenticating-with-identity-provider-in-xamarin-forms/
Here is my code for the LoginPage:
using System;
using Valufy;
using Xamarin.Forms.Platform.UWP;
using System.ComponentModel;
using Valufy.UWP;
using Valufy.AuthConfiguration;
using Xamarin.Forms;
[assembly: ExportRenderer(typeof(ProviderLoginPage), typeof(LoginRenderer))]
namespace Valufy.UWP
{
class LoginRenderer: PageRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
//Get and Assign ProviderName from ProviderLoginPage
ProviderLoginPage loginPage = (ProviderLoginPage)Element;
string providername = loginPage.ProviderName;
//Create OauthProviderSetting class with Oauth Implementation .Refer Step 6
OAuthProviderSetting oauth = new OAuthProviderSetting();
Xamarin.Auth.OAuth2Authenticator auth = oauth.LoginWithProvider(providername);
// After facebook,google and all identity provider login completed
auth.Completed += Auth_Completed;
Type page_type = auth.GetUI();
//////THIS IS WHERE I AM STUCK...HOW DO I GO TO THE PROVIDER AUTH UI ////////////
//this.Frame.Navigate(page_type, auth);
//parentPage.Navigation.PushModalAsync(auth.GetUI());
}
}
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
OAuthConfig.User = new UserDetails();
// Get and Save User Details
OAuthConfig.User.Token = e.Account.Properties["oauth_token"];
OAuthConfig.User.TokenSecret = e.Account.Properties["oauth_token_secret"];
OAuthConfig.User.TwitterId = e.Account.Properties["user_id"];
OAuthConfig.User.ScreenName = e.Account.Properties["screen_name"];
/////NOW, HOW GO I GO BACK TO THE CALLING PAGE IN PCL ///////////////////
}
else
{
// The user cancelled
/////NOW, HOW GO I GO BACK TO THE CALLING PAGE IN PCL ///////////////////
}
}
}
}
Here is the code to navigate to the provider login for UWP:
WindowsPage windowsPage = new WindowsPage();
_frame = windowsPage.Frame;
if (_frame == null)
{
_frame = new Windows.UI.Xaml.Controls.Frame
{
Language = global::Windows.Globalization.ApplicationLanguages.Languages[0]
};
windowsPage.Content = _frame;
SetNativeControl(windowsPage);
}
Type pageType = auth.GetUI();
_frame.Navigate(pageType, auth);
To navigate back to my page upon successful authentication, here is the code:
private async void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var request = new OAuth2Request("GET", new Uri("https://login.microsoftonline.com/common/oauth2/V2.0/token?oauth2_access_token=" + e.Account.Properties["access_token"]), null, e.Account);
try
{
string response = await MSGetUserInfo(e.Account);
}
catch (System.OperationCanceledException)
{
}
catch (Exception ex)
{
}
this.Element.Navigation.PushModalAsync(new MainPage());
}
else
{
// The user cancelled
}
}
1- In UWP, how do I navigate to the provider login UI
You need to create a UWP Page object, the same object you are will use to display in the renderer is the one you will use to do the navigation.
Type page_type = auth.GetUI();
page = new MyUWPLoginPage();
page.Frame.Navigate(page_type, auth);
2- How do I navigate back to my MainPage (which is in PCL) upon successful authentication?
There are many ways to do this, the easiest one is creating a public method in your ProviderLoginPage class and from the renderer classes call this method and pass-in the parameters.
public class ProviderLoginPage: ContentPage
{
......
public void AuthenticationCompleted(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
// Do your logic
}
}
In your Renderer using the Element:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
var xamElement = Element as ProviderLogin;
xamElement?.AuthenticationCompleted(sender, e);
}
Move all the logic to the PCL class so you don't have to repeat it on each renderer.
This should help.
I am creating a new project in Visual Studio using the SPA template. My goal is to have an Angular/SPA application that will "host"/contain some legacy applications that will eventually be modernized/migrated. So, I have an iframe on a page in my SPA app, and when a menu item is clicked, I want to load one of the legacy ASP.NET apps in that iframe (it has to be in an iframe, as the legacy site used them, and its architecture relies on them).
I am having trouble getting the routing right. The SPA template defines a DefaultRoute class like this (I changed RouteExistingFiles to true):
public class DefaultRoute : Route
{
public DefaultRoute()
: base("{*path}", new DefaultRouteHandler()) {
this.RouteExistingFiles = true;
}
}
and I have edited the RouteConfig.cs file to ignore "aspx" page requests, like this:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes) {
routes.Ignore("*.aspx");
routes.Add("Default", new DefaultRoute());
}
}
The default route handler that is defined, looks like this:
public class DefaultRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
// Use cases:
// ~/ -> ~/views/index.cshtml
// ~/about -> ~/views/about.cshtml or ~/views/about/index.cshtml
// ~/views/about -> ~/views/about.cshtml
// ~/xxx -> ~/views/404.cshtml
var filePath = requestContext.HttpContext.Request.AppRelativeCurrentExecutionFilePath;
if (filePath == "~/") {
filePath = "~/views/index.cshtml";
}
else {
if (!filePath.StartsWith("~/views/", StringComparison.OrdinalIgnoreCase)) {
filePath = filePath.Insert(2, "views/");
}
if (!filePath.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) {
filePath = filePath += ".cshtml";
}
}
var handler = WebPageHttpHandler.CreateFromVirtualPath(filePath); // returns NULL if .cshtml file wasn't found
if (handler == null) {
requestContext.RouteData.DataTokens.Add("templateUrl", "/views/404");
handler = WebPageHttpHandler.CreateFromVirtualPath("~/views/404.cshtml");
}
else {
requestContext.RouteData.DataTokens.Add("templateUrl", filePath.Substring(1, filePath.Length - 8));
}
return handler;
}
}
The directory structure is like this:
MySPA
SPA <- contains the SPA application (.net 4.5)
Legacy <- contains the legacy applications (.net 3.0)
In IIS, I have the legacy folder set as a virtual directory (subdirectory) within the SPA application.
How do I set up routing so that, when a menu item is clicked, and the request is sent (containing a url that has query string information) for an .aspx page, the request can be routed to the legacy application?
I have solved this issue. The issue was caused by several problems. But, the problem that most pertains to my information above was this...I needed to find the correct way to ignore the requests for the legacy aspx pages from within the routing code of the new site. So, in the RouteConfig.cs file, I placed this ignore statement as the first line of the RegisterRoute function:
routes.Ignore("{*allaspx}", new { allaspx = #".*\.aspx(/.*)?"});
That corrected the main issue, there were other minor issues that confused the situation but, I have identified what they are and I am working on those. So, ignoring the legacy urls in the routing functionality was the correct solution.
We have a need to be able to drop new projects/dlls into our main project and have the main project pick them up and be able to use them. It was decided to use AutoFac to handle this need. This way the main project would not need a direct reference to any of the other projects/dlls we want to use. Here is the global.asax:
var builder = new ContainerBuilder();
var directoryName = HttpContext.Current.Server.MapPath("~/bin/");
var cfg = new ModularityLoader(new ModularityConfig(Path.Combine(directoryName, #"Modules")), new Log4NetLogger(typeof(ModularityConfig)));
cfg.RegisterModulesFromCatalog(builder);
builder.RegisterType<AssembliesResolver>().AsImplementedInterfaces();
builder.RegisterType<AppsSecurityFeatureResolver>().AsImplementedInterfaces();
builder.RegisterType<FeaturesAutofacAuthoriztationFilter>()
.AsWebApiAuthorizationFilterFor<ApiController>().InstancePerApiRequest();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
builder.RegisterControllers(typeof(MvcApplication).Assembly);
Container = builder.Build();
We have post-build events on all projects that copy their dlls into the bin directory of our main project. All of the dlls are loaded in here with var cfg = new ModularityLoader.
See image:
builder.Build() calls the following class in any of the dlls in our project that implements Autofac.Module such as this one:
public class AutofacModuleConfig : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterApiControllers(typeof(AutofacModuleConfig).Assembly);
}
}
Here is where the trouble with SignalR comes in. I would like to put RouteTable.Routes.MapHubs() into AutofacModuleConfig, but SignalR 2.0.0+ no longer supports this. Instead, it wants you to create a Startup class like this:
[assembly: OwinStartup("SignalRConfig", typeof(KL.Apps.TestHarness.SignalR.Startup))]
namespace KL.Apps.TestHarness.SignalR
{
public static class Startup
{
public static void Configuration(IAppBuilder app)
{
app.MapSignalR("/signalr", new HubConfiguration());
}
}
}
Note: To get this to work I added the following to the web.config:
<add key="owin:appStartup" value="SignalRConfig" />
SignalR also requires Hub classes like this:
[HubName("BatchHub")]
public class BatchHub : Hub
{
public void RemoveBatchRow(Guid batchId)
{
Clients.All.RemoveBatchRow(batchId);
}
}
Having the Startup class and Hub classes in my main project works perfectly. However, because of the unique nature of AutoFac, none of my Hubs from my external dlls are getting loaded up. I was thinking to try to find a way to force AutofacModuleConfig.Load to accept RouteTable.Routes.MapHubs(), but adding this line breaks the code.
I was thinking to go back to an earlier version of SignalR so I can use RouteTable.Routes.MapHubs() but I would really like to use the most up to date version.
Any ideas?
Thanks in advance.
EDIT 1:
I found this: https://code.google.com/p/autofac/wiki/SignalRIntegration
It seems to be the answer the the problem. However, RegisterHubs does not exist off of the builder object even after installing the Autofac.SignalR NuGet package...
var builder = new ContainerBuilder();
builder.RegisterHubs(Assembly.GetExecutingAssembly());
EDIT 2:
So this is what my AutofacModuleConfig looks like now, but my hubs are still not showing up in javascript.
namespace OurName.IOC
{
public class AutofacModuleConfig : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
AutofacSignalRConfig.Load(builder);
builder.RegisterApiControllers(typeof(AutofacModuleConfig).Assembly);
}
}
}
namespace Autofac.Integration.SignalR
{
public static class AutofacSignalRConfig
{
public static void Load(ContainerBuilder builder)
{
builder.RegisterHubs(Assembly.GetExecutingAssembly());
}
}
}
Still not working though. In javascript I cannot see my hubs.
$(document).ready(function(){
var myHub = $.connection.BatchHub;
});
myHub is null.
EDIT 3:
Tried this last night. I removed AutofacSignalRConfig and this is how the main Global.asax looks now:
var builder = new ContainerBuilder();
var directoryName = HttpContext.Current.Server.MapPath("~/bin/");
var cfg = new ModularityLoader(new ModularityConfig(Path.Combine(directoryName, #"Modules")), new Log4NetLogger(typeof(ModularityConfig)));
cfg.RegisterModulesFromCatalog(builder);
builder.RegisterType<AssembliesResolver>().AsImplementedInterfaces();
builder.RegisterType<AppsSecurityFeatureResolver>().AsImplementedInterfaces();
builder.RegisterType<FeaturesAutofacAuthoriztationFilter>()
.AsWebApiAuthorizationFilterFor<ApiController>().InstancePerApiRequest();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
builder.RegisterControllers(typeof(MvcApplication).Assembly);
foreach (var assembly in cfg.LoadedAssemblies)
{
var hubs = builder.RegisterHubs(assembly);
}
Container = builder.Build();
GlobalHost.DependencyResolver = new Autofac.Integration.SignalR.AutofacDependencyResolver(Container);
Notice how I tried to take the output of cfg (which is a collection of all assemblies found in the bin folder) and then foreach through them in order to execute builder.RegisterHubs on each one. Still not picking up my hubs from any of those assemblies though.
Does there need to be a Startup class in each of my imported dlls too? If so, how would I go about calling it?
EDIT 4:
Tried this too with no success:
foreach (var assembly in cfg.LoadedAssemblies)
{
//var hubs = builder.RegisterHubs(assembly);
var a = assembly.GetExportedTypes().Where(x => x.BaseType == typeof (Hub));
foreach (var x in a)
{
builder.RegisterType(x);
}
}
See image for debug info:
EDIT 5:
Jim Bolla thank you for the input. Here is my first try at your suggestion.
public class AssemblyLocator : IAssemblyLocator
{
public IList<Assembly> GetAssemblies()
{
var directoryName = HttpContext.Current.Server.MapPath("~/bin/");
var cfg = new ModularityLoader(new ModularityConfig(Path.Combine(directoryName, #"Modules")), new Log4NetLogger(typeof(ModularityConfig)));
return cfg.LoadedAssemblies;
}
}
Also added this just above Container = builder.Build:
builder.RegisterType<AssemblyLocator>().As<IAssemblyLocator>().SingleInstance();
AssemblyLocator.GetAssemblies() is now getting called.
... Testing ...
EDIT 6:
Okay. So now when I throw a breakpoint into javascript, I can see $.connection.BatchHub which is great! However, when trying to call the hub method RemoveBatchRow on the server-side I get the following error:
"Using a Hub instance not created by the HubPipeline is unsupported."
Here is the stack trace:
at
Microsoft.AspNet.SignalR.Hubs.NullClientProxy.TryInvokeMember(InvokeMemberBinder
binder, Object[] args, Object& result) at CallSite.Target(Closure ,
CallSite , Object , Guid ) at
System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite
site, T0 arg0, T1 arg1) at
KL.Apps.WebIndex.Hubs.BatchHub.RemoveBatchRow(Guid batchId) in
d:\Source\Apps Framework
Modules\WebIndex\Main\Source\KL.Apps.WebIndex\Hubs\BatchHub.cs:line 20
at
KL.Apps.WebIndex.API.Batch.BatchLockController.TryLockBatchAsync(String
batchId) in d:\Source\Apps Framework
Modules\WebIndex\Main\Source\KL.Apps.WebIndex\API\Batch\BatchLockController.cs:line
30 at lambda_method(Closure , Object , Object[] ) at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.b__c(Object
instance, Object[] methodParameters) at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object
instance, Object[] arguments) at
System.Web.Http.Controllers.ReflectedHttpActionDescriptor.<>c__DisplayClass5.b__4()
at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1
func, CancellationToken cancellationToken)
I am calling the hub method from one of my remote dlls like this:
new BatchHub().RemoveBatchRow(gId);
You can see the definition for BatchHub above.
Any other ideas?
I have seen some of the questions/answers related to this topic here, however still I am not getting the suggestion which I want. So I am posting my question again here, and I would be thankful for your valuable time and answers.
I would like to create “Component, Page, SG, Publication, Folders “ via programmatically in SDL Tridion Content Manager, and later on, I would like to add programmatically created components in Page and attach CT,PT for that page, and finally would like to publish the page programmatically.
I have done these all the activities in SDL Tridion 2009 using TOM API (Interop DLL's), and I tried these activities in SDL Tridion 2011 using TOM.Net API. It was not working and later on I came to know that, TOM.Net API will not support these kinds of works and it is specifically only for Templates and Event System. And finally I came to know I have to go for Core services to do these kinds of stuffs.
My Questions:
When I create console application to create component programmatically using core service, what are the DLL’s I have to add as reference?
Earlier, I have created the exe and ran in the TCM server, the exe created all the stuffs, can I used the same approach using core services too? Will it work?
Is BC still available or Core Service replaced BC? (BC-Business Connector)
Can anyone send some code snippet to create Component/Page (complete class file will be helpful to understand better)
You will only need to reference Tridion.ContentManager.CoreService.Client.dll. You may want to reference Tridion.Common.dll to get access to some helpful classes such as TcmUri, but it is not needed.
You client program will make an explicit connection with the core service on a machine that you specify. If done properly, you can run the client both on the same machine as the Tridion Content Manager or on a different machine.
The Business Connector is still available, but has been superseded by the Core Service.
Have a look at these links:
Updating Components using the Core Service in SDL Tridion 2011
In SDL Tridion 2011, how can I process metadata on an item using the Core Service?
And the standard documentation on the topic connecting to the Core Service from .NET.
If you need more help with the code, I suggest you show us the code you've already written and explain what isn't working.
I will try to answer your questions:
You have to reference Tridion.ContentManager.CoreService.Client and add some stuff to app.config. It's described here
It will work from CM server, as well as from any other machine, provided it can access CoreService
CoreService is replacement for BC. BC is deprecated and will be dropped soon
You will get all the basic info from here.
This should be enough for you to start. If you will have specific problems - post them as a seperate questions.
From How can i use engine object in my console application
From a console application you should use the Core Service. I wrote a small example using the Core Service to search for items in the content manager.
Console.WriteLine("FullTextQuery:");
var fullTextQuery = Console.ReadLine();
if (String.IsNullOrWhiteSpace(fullTextQuery) || fullTextQuery.Equals(":q", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine("SearchIn IdRef:");
var searchInIdRef = Console.ReadLine();
var queryData = new SearchQueryData
{
FullTextQuery = fullTextQuery,
SearchIn = new LinkToIdentifiableObjectData
{
IdRef = searchInIdRef
}
};
var results = coreServiceClient.GetSearchResults(queryData);
results.ToList().ForEach(result => Console.WriteLine("{0} ({1})", result.Title, result.Id));
Add a reference to Tridion.ContentManager.CoreService.Client to your Visual Studio Project.
Code of the Core Service Client Provider:
public interface ICoreServiceProvider
{
CoreServiceClient GetCoreServiceClient();
}
public class CoreServiceDefaultProvider : ICoreServiceProvider
{
private CoreServiceClient _client;
public CoreServiceClient GetCoreServiceClient()
{
return _client ?? (_client = new CoreServiceClient());
}
}
And the client itself:
public class CoreServiceClient : IDisposable
{
public SessionAwareCoreServiceClient ProxyClient;
private const string DefaultEndpointName = "netTcp_2011";
public CoreServiceClient(string endPointName)
{
if(string.IsNullOrWhiteSpace(endPointName))
{
throw new ArgumentNullException("endPointName", "EndPointName is not specified.");
}
ProxyClient = new SessionAwareCoreServiceClient(endPointName);
}
public CoreServiceClient() : this(DefaultEndpointName) { }
public string GetApiVersionNumber()
{
return ProxyClient.GetApiVersion();
}
public IdentifiableObjectData[] GetSearchResults(SearchQueryData filter)
{
return ProxyClient.GetSearchResults(filter);
}
public IdentifiableObjectData Read(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
public ApplicationData ReadApplicationData(string subjectId, string applicationId)
{
return ProxyClient.ReadApplicationData(subjectId, applicationId);
}
public void Dispose()
{
if (ProxyClient.State == CommunicationState.Faulted)
{
ProxyClient.Abort();
}
else
{
ProxyClient.Close();
}
}
}
When you want to perform CRUD actions through the core service you can implement the following methods in the client:
public IdentifiableObjectData CreateItem(IdentifiableObjectData data)
{
data = ProxyClient.Create(data, new ReadOptions());
return data;
}
public IdentifiableObjectData UpdateItem(IdentifiableObjectData data)
{
data = ProxyClient.Update(data, new ReadOptions());
return data;
}
public IdentifiableObjectData ReadItem(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
To construct a data object of e.g. a Component you can implement a Component Builder class that implements a create method that does this for you:
public ComponentData Create(string folderUri, string title, string content)
{
var data = new ComponentData()
{
Id = "tcm:0-0-0",
Title = title,
Content = content,
LocationInfo = new LocationInfo()
};
data.LocationInfo.OrganizationalItem = new LinkToOrganizationalItemData
{
IdRef = folderUri
};
using (CoreServiceClient client = provider.GetCoreServiceClient())
{
data = (ComponentData)client.CreateItem(data);
}
return data;
}
Hope this gets you started.
My problem is NOT trying to simply verify whether a method was called or not. Instead, I have a method that works on a collection of objects and I want to verify that a method on all of the collection items is being called.
Use the example of a plug-in model where I have a plug-in manager that contains a collection of plug-in objects. Each plug-in subclasses the PlugIn abstract base class which exposes an abstract Initialize method. In my test I want to make sure that Initialize is called on every plug-in regardless of whether one of them throws an exception (just part of a larger test suite).
My initial approach was to create a collection of mocked plug-ins, then configure the class under test (PlugInManager) to use the mocked objects. I then perform the test by calling PlugInManager.DoWork() which should iterate through the collection, calling DoWork() on each item.
The full test codes is as follows:
[TestMethod()]
public void MyTest()
{
// ARRANGE
var testParameter = new Something();
var mockPlugIns = new Collection<Mock<PlugIn>>()
{
new Mock<PlugIn>(),
new Mock<PlugIn>(),
new Mock<PlugIn>()
};
var plugIns = new Collection<PlugIn>();
foreach (var plugIn in mockPlugIns)
plugIns.Add(plugIn.Object);
var testManager = new PlugInManager()
{
PlugIns = plugIns
};
// ACT
testManager.DoWork(testParameter);
// ASSERT
foreach (var mockPlugIn in mockPlugIns)
mockPlugIn.Verify(plugin => plugin.DoWork(testParameter), Times.Once());
// Also tried using It.IsAny<Something>()
}
public abstract class PlugIn
{
abstract void DoWork(Something something);
}
public sealed class PlugInManager
{
public IEnumerable<PlugIn> PlugIns { get; set; }
public void DoWork(Something something)
{
foreach (var plugIn in PlugIns)
plugIn.DoWork(something);
}
}
Unfortunately, Verify fails for every item.
I've stepped through the code and see that it actually is working correctly and the Initialize method is being called on every item. When, then, is Verify failing???
UPDATE #1
I've updated the post to show the entire test method in one block. I've also changed the method to require a parameter as is the case in my real code (now).
UPDATE #2
The error I receive when running the test is:
Moq.MockException:
Expected invocation on the mock once, but was 0 times: plugin => plugin.DoWork(It.IsAny<Something>())
No setups configured.
No invocations performed.
As mentioned, when I step through the unit test I see that each of the plugins are actually being called. For some reason, however, Moq doesn't seem to be registering it or recognizing it.
UPDATE #3
After playing with the test code more, I discovered that I could make the test pass with a simple change. The test passes if I replace the foreach loop in the middle of the method with the following:
plugIns.Add(mockPlugIns[0]);
plugIns.Add(mockPlugIns[1]);
plugIns.Add(mockPlugIns[2]);
I don't see how this is making a difference and would ultimately like to make the number of items dynamic so the tests aren't always testing the case when there are three, so using the foreach is really what I need.
Any ideas?
This is actually not true and upon further testing this morning, I find that everything is working fine with the original foreach loop. I have no idea what changed but I tried many different variations late last night and while the code I have this morning looks just like what is posted, for whatever reason, the test is now passing!?!?!?!?
This worked for me in LINQPad with Moq 4. The only thing I changed was adding the parentheses on Times.Once().
void Main()
{
var MockPlugIns = new Collection<Mock<PlugIn>>()
{
new Mock<PlugIn>(),
new Mock<PlugIn>(),
new Mock<PlugIn>()
};
var plugIns = new Collection<PlugIn>();
foreach (var mockPlugIn in MockPlugIns)
plugIns.Add(mockPlugIn.Object);
var testManager = new PlugInManager()
{
PlugIns = plugIns
};
testManager.Initialize();
foreach (var mockPlugIn in MockPlugIns)
mockPlugIn.Verify(plugin => plugin.Initialize(), Times.Once());
}
public abstract class PlugIn
{
public abstract void Initialize();
}
public class PlugInManager
{
public void Initialize()
{
foreach (var plugIn in PlugIns)
{
plugIn.Initialize();
}
}
public Collection<PlugIn> PlugIns { get; set; }
}
UPDATE
I ran your updated test code, and it passed given the following implementation:
public class PlugInManager
{
public void DoWork(Something s)
{
foreach (var plugIn in PlugIns)
{
plugIn.DoWork(s);
}
}
public Collection<PlugIn> PlugIns { get; set; }
}
It passed with or without the It.IsAny change you mentioned. One initial thought was that you might not have been passing the same instance of Something to the plug-ins, but It.IsAny would have resolved that.
In short, it appears that you are doing everything right in the tests. Perhaps the issue is in the actual implementation.
Please post your implementation of PlugInManager.DoWork and the exact error message you get when the test fails. Also, what version of Moq are you using?
UPDATE
I cut-and-pasted your code and tried it. I had to make one change: abstract void DoWork on abstract class PlugIn needs to be public. After making that change it compiles and the test passes. If I comment out the "ACT" portion of your test, it fails with the error message you saw (as I would expect).
Something is different in your project or environment. I'm running .NET 4 (not Mono) under Windows 64 with Moq 4.0. Everything you have posted is correct. I would suggest confirming that you're running the latest binary of Moq, checking your project references, and trying some very simple verification tests to insure that Moq is working.