At first I have a Multi-platform Project where I created a Class which transfers Data and my problem is that as example if I click a Button a method in this class should be called but I cannot reach the method.
This is my project structure:
The red part is where the Data Handler is located
the green part from where I get the clicked event and call the method.
I'll hope someone can help me with this problem!
As Jason mentioned in comment, you can not reach code from platform specific just like that, because you are not referencing platform specific projects, and there is something called DependencyService (which Jason also mentioned) and that will help you to solve this "issue" that you have.
This is how you can use DependencyService, inside your shared code project, create one interface in my case that will be:
public interface IDataHandler
{
string GetSomeStringValue();
}
Go to your iOS or other platform specific project and create new class DataHandler.cs (which you already have). And it should implement this interface that we created. Something like this:
[assembly: Dependency(typeof(DataHandler))]
namespace provisioning.ios
{
public class DataHandler: IDataHandler
{
public DataHandler()
{
}
public string GetSomeStringValue()
{
return "Some string value or whatever";
}
}
}
After that when you want to reach this method you will use DepedencyService inside of your shared code project like this:
private void SomeMethod()
{
string fromSpecificProject = DependencyService.Get<IDataHandler>().GetSomeStringValue();
}
If you want or need you can use this to pass some values to platform specific project and to return the data like I did it this mini example.
Note that implementations must be provided for each platform project
in your solution. Platform projects without implementations will fail
at runtime!
Strongly recommend you to take a look at official docs here.
Also I made this mini blogpost about usage of Dependency Service in Xamarin.Forms apps you can find it here.
Related
I'm having a problem compiling a cross platform MAUI library project implementing a partial class platform dependent. My project structure looks like this:
Project
Core
MyClass
Design
IMyInterface
Platform
Android
MyClass
Windows
MyClass
And my code looks like this:
Project\Design\IMyInterface.cs
namespace Core
{
puclic interface IMyInterface
{
string SomeString { get; set; }
event EventHandler<int> OnSomethingHappend;
int SomeMethod();
}
}
Project\Core\MyClass.cs
namespace Core
{
puclic partial class MyClass : IMyInterface
{
public event EventHandler<int> OnSomethingHappend;
}
}
Project\Platform\*\MyClass.cs
namespace Core
{
puclic partial class MyClass
{
string SomeString { get; set; }
int SomeMethod()
{
// Do some platform dependent stuff
}
}
}
As you can see I want my class to implement my interface, but have the relevant parts in the platform specific files in Platform\* . I made sure that they are placed in the same namespace. I also made sure to select a target platform for compilation.
At this point visual studio should build it using the partial class for the selected platform. However I get an error saying that MyClass doesn't implement my interface. If I copy the platform code into the Core folder there is no problem. I guess it's some kind of small setting that's missing here, but I can't find anything. For some reason it seems to ignore the platform folder when looking for the partial classes. All I can find is the general "partial class implementing interface" stuff. I didn't find something related to a Maui SingleProject.
Edit: As asked, here is a minimal github repo. On my pc it creates the error.
On the core partial, inform the compiler that the missing members will be provided by a partial elsewhere:
public partial class MyClass : IMyInterface
{
public event EventHandler<int> OnSomethingHappend;
public partial string SomeString { get; set; }
public partial int SomeMethod();
}
I have found the solution, and what causes the error is actually very simple.
On the side of your RUN button you can choose what framework you want to compile to. In a normal MAUI app, you would choose e.g. android or windows here to decide what platform you are working/debugging on.
As it turns out, this is not all you need to keep in mind when working with partial classes in your platform folders, there are two parts to take into account.
First part is intellisense. It does not show errors based on what platform you select using the RUN button drop-down menu. Below your open file tabs are some drop-down menus showing the structure of the file. you can use them to move to a specific element (property, method, etc) of a class/interface etc implemented in that file. The left one lets you select the target framework that intellisense will use for marking code errors (see image).
I don't remember seeing the left one before MAUI, it might have been added specifically for single project. If you open e.g. a standard net6 library it will hace nothing to chose as there are no other targets. But I can't really say because I never paid any attention to them before.
The second part is the actual compilation. The answer to that is rather simple as well. Visual Studio dowsn't build the platform you select using the RUN button drop-down menu for building, only for running. It will build all platforms it seems.
You need a partial class with a default implementation (e.g. throw new NotImplementedException(); for everything) for every platform.
My project was new, and I had just started on the windows part. I expected it to compile only that part because I chose windows as target platform. But it wants to compile for all platforms. So you have to either add that default until you get to that platform or remove it from the project target frameworks until then.
I have a dll named ExpensiveAndLargeObfuscatedFoo.dll.
Lets says it defines a type named ExpensiveAndLargeObfuscatedFooSubClass.
It's been compiled for .NET.
Are there any tools (free, paid, whatever) that will generate c# or vb class files that will do nothing but wrap around everything defined in this expensive dll? That way I can add functionality, fix bugs (that CorpFUBAR won't fix), add logging, etc?
Literally, I want output that looks like this
namespace easytoread {
public class SubClass {
private ExpensiveAndLargeObfuscatedFoo.SubClass _originalSubClass;
public SubClass() {
this._originalSubClass = new ExpensiveAndLargeObfuscatedFoo.SubClass ();
}
public string StupidBuggyMethod(string param1,int param2) {
return _originalSubClass.StupidBuggyMethod(param1, param2);
}
}
}
It would have to handle custom return types as well as primitives
namespace easytoread {
public class SubFooClass {
private ExpensiveAndLargeObfuscatedFoo.SubFooClass _originalSubFooClass;
public SubFooClass() {
this._originalSubFooClass= new ExpensiveAndLargeObfuscatedFoo.SubFooClass ();
}
private SubFooClass(ExpensiveAndLargeObfuscatedFoo.SubFooClass orig) {
this._originalSubFooClass = orig;
}
public SubFooClass StupidBuggyMethod(string param1,int param2) {
return new SubFooClass(_originalSubFooClass.StupidBuggyMethod(param1, param2));
}
}
}
And so on and so forth for every single defined class.
Basically, poor mans dynamic proxy? (yay, Castle Project is awesome!)
We'd also like to rename some of our wrapper classes, but the tool doesn't need to do that.
Without renaming, we'd be able to replace the old assembly with our new generated one, change using statements and continue on like nothing happened (except the bugs were fixed!)
It just needs to examine the dll and do code generation. the generated code can even be VB.NET, or ironpython, or anything CLR.
This is a slippery slope and I'm not happy that I ended up here, but this seems to be the way to go. I looked at the Castle Project, but unless I'm mistaken that won't work for two reasons: 1) I can't rename anything (don't ask), 2) none of the assemblies methods are declared virtual or even overridable. Even if they were, there's hundreds of types I'd have to override manually, which doesn't sound fun.
ReSharper can do much of the work for you.
You will need to declare a basic class:
namespace easytoread {
public class SubClass {
private ExpensiveAndLargeObfuscatedFoo.SubClass _originalSubClass;
}
}
Then, choose ReSharper > Edit > Generate Code (Alt+Ins), select "Delegating Members", select all, and let it generate the code.
It won't wrap return values with custom classes (it will return the original type), so that would still have to be added manually.
It seems the best answer is "There is no such tool". So, I'll be taking a stab at writing my own later as an off-hours project. If I ever get something useful working I'll github it and update here.
UPDATE
Visual Studio 2012 Fakes seem to be promising. http://msdn.microsoft.com/en-us/library/tfs/hh549175(v=vs.110).aspx - we've moved on but I might try creating a fake and then dropping it in as a replacement dll sometime in the future
If you have access to the source code, rename and fix in the source
code.
If you don't have access (and you can do it legally) use some
tool like Reflector or dotPeek to get the source code and then,
goto to the first point.
I have to be able to connect to two different versions of the an API (1.4 and 1.5), lets call it the Foo API. And my code that connects to the API and processes the results is substantially duplicated - the only difference is the data types returned from the two APIs. How can I refactor this to remove duplication?
In Foo14Connector.cs (my own class that calls the 1.4 API)
public class Foo14Connector
{
public void GetAllCustomers()
{
var _foo = new Foo14WebReference.FooService();
Foo14WebReference.customerEntity[] customers = _foo.getCustomerList;
foreach (Foo14WebReference.customerEntity customer in customers)
{
GetSingleCustomer(customer);
}
}
public void GetSingleCustomer(Foo14WebReference.customerEntity customer)
{
var id = customer.foo_id;
// etc
}
}
And in the almost exact duplicate class Foo15Connector.cs (my own class that calls the 1.5 API)
public class Foo15Connector
{
public void GetAllCustomers()
{
var _foo = new Foo15WebReference.FooService();
Foo15WebReference.customerEntity[] customers = _foo.getCustomerList;
foreach (Foo15WebReference.customerEntity customer in customers)
{
GetSingleCustomer(customer);
}
}
public void GetSingleCustomer(Foo15WebReference.customerEntity customer)
{
var id = customer.foo_id;
// etc
}
}
Note that I have to have two different connectors because one single method call (out of hundreds) on the API has a new parameter in 1.5.
Both classes Foo14WebReference.customerEntity and Foo15WebReference.customerEntity have identical properties.
If the connectors are in different projects, this is an easy situation to solve:
Add a new class file, call it ConnectorCommon and copy all of the common code, but with the namespaces removed. Make this class a partial class and rename the class (not the file) to something like Connector.
You will need to add a link to this to each project.
Next, remove all of the code from your current connector classes, rename the class (not necessarily the file) to the same as the partial class, and add a using statement that references the namespace.
This should get what you are looking for.
So, when you are done you will have:
File ConnectorCommon:
public partial class Connector
{
public void GetAllCustomers()
{
var _foo = new FooService();
customerEntity[] customers = _foo.getCustomerList;
foreach (customerEntity customer in customers)
{
GetSingleCustomer(customer);
}
}
public void GetSingleCustomer(customerEntity customer)
{
var id = customer.foo_id;
// etc
}
}
File Magento15Connector
using Foo15WebReference;
partial class Connector
{
}
File Magento14Connector
using Foo14WebReference;
partial class Connector
{
}
Update
This process can be a little confusing at first.
To clarify, you are sharing source code in a common file between two projects.
The actual classes are the specific classes with the namespaces in each project. You use the partial keyword to cause the common file to be combined with the actual project file (i.e. Magneto14) in each project to create the full class within that project at compile time.
The trickiest part is adding the common file to both projects.
To do this, select the Add Existing Item... menu in the second project, navigate to the common file and click the right-arrow next to the Add button.
From the dropdown menu, select Add as link. This will add a reference to the file to the second project. The source code will be included in both projects and any changes to the common file will be automatically available in both projects.
Update 2
I sometimes forget how easy VB makes tasks like this, since that is my ordinary programming environment.
In order to make this work in C#, there is one more trick that has to be employed: Conditional compilation symbols. It makes the start of the common code a little more verbose than I would like, but it still ensures that you can work with a single set of common code.
To employ this trick, add a conditional compilation symbol to each project (ensure that it is set for All Configurations). For example, in the Magento14 project, add Ver14 and in the Magento15 project add Ver15.
Then in the common file, replace the namespace with a structure similar to the following:
#if Ver14
using Magneto14;
namespace Magento14Project
#elif Ver15
using Magneto15;
namespace Magento15Project
#endif
This will ensure that the proper namespace and usings are included based on the project the common code is being compiled into.
Note that all common using statements should be retained in the common file (i.e., enough to get it to compile).
If the FooConnectors are not sealed and you are in control to create new instances, then you can derive your own connectors and implement interfaces at the same time. In c# you can implement members by simply inheriting them from a base class!
public IFooConnector {
void GetAllCustomers();
}
public MyFoo14Connector : Foo14Connector, IFooConnector
{
// No need to put any code in here!
}
and then
IFooConnector connector = new MyFoo14Connector();
connector.GetAllCustomers();
You should introduce an interface that is common to both of the implementations. If the projects are written in the same language and are in different projects, you can introduce a common project that both projects reference. You are then making a move towards having dependencies only on your interface which should allow you to swap in different implementations behind the scenes somewhere using inversion of control (google, dependency injection or service locator or factory pattern).
Difficulties for you could be:
1) Public static methods in the implementations are not able to be exposed staticly via an interface
2) Potentially have code in one implementation class ie Foo14Connector or Foo15Connector that doesnt make sense to put into a generic interface
I'm writing a simple plugin based program. I have an interface IPlugin which has some methods and functions, and a List<Plugin> in my main program. For the sake of simplicity, lets say its defined like this:
public interface IPlugin
{
public void OnKeyPressed(char key);
}
Everytime a key is pressed, I loop through the Plugin list, and call OnKeyPressed(c) on each of them.
I can create a class like so, and add it to the list...
public class PrintPlugin
{
public void OnKeyPressed(char key)
{
Console.WriteLine(c);
}
}
And then whenever you press a key, its printed out. But I want to be able to load plugins from DLL files. This link was helpful, but it doesn't explain how to have the classes in the DLL implement my IPlugin interface... How can I do that? I really don't want to have to copy the IPlugin.cs file every time I want to make a plugin...
If I am understanding you correctly...
Create 3 Projects:
Project 1: Your main program (the one with List in it)
Project 2: the project with your interface
public interface IPlugin
{
public void OnKeyPressed(char key);
}
Project 3: A sample Plugin
public class PrintPlugin : IPlugin
{
public void OnKeyPressed(char key)
{
Console.WriteLine(c);
}
}
Then Add project 2 as a reference to both project 1 and 3.
This way you share the interface with both your main project and any of your plugins.
I have used this on a couple of projects and it has served me well.
You may want to look into the Managed Extensibility Framework as well. It provide a complete API for writing plugin based programs and covers a lot of concerns such as security if you're ever going to plan to make the plugin API available to third parties.
If you need to load user defined plugins, you should search for new DLLs when the application starts (or any other action). This can be done by using:
1) AppDomain.CurrentDomain.GetAssemblies() method returns the list of loaded assemblies in the current AppDomain
2) Search all DLLs in a folder where plugins should be positioned and check if a certain assembly is in the list. If not, use the Assembly.Load method to load this assembly, find the IPlugin class in it and finally add it to the List object.
This is sort of a continuation of one of my earlier posts, which involves the resolving of modules in my WPF application. This question is specifically related to the effect of interdependencies of modules and the method of constructing those modules (i.e. via MEF or through new) on MEF's ability to resolve relationships.
I have tried two approaches:
left approach: the App implements IError
right approach: the App has a member that implements IError
Left approach
My code behind looked like this (just the MEF-related stuff):
// app.cs
[Export(typeof(IError))]
public partial class Window1 : Window, IError
{
[Import]
public CandyCo.Shared.LibraryInterfaces.IPlugin Plugin { get; set; }
[Import]
public CandyCo.Shared.LibraryInterfaces.ICandySettings Settings { get; set; }
private ICandySettings Settings;
public Window1()
{
// I create the preferences here with new, instead of using MEF. I wonder
// if that's my whole problem? If I use MEF, and want to have parameters
// going to the constructor, then do I have to [Export] a POCO (i.e. string)?
Settings = new CandySettings( "Settings", #"c:\settings.xml");
var catalog = new DirectoryCatalog( ".");
var container = new CompositionContainer( catalog);
try {
container.ComposeParts( this);
} catch( CompositionException ex) {
foreach( CompositionError e in ex.Errors) {
string description = e.Description;
string details = e.Exception.Message;
}
throw;
}
}
}
// plugin.cs
[Export(typeof(IPlugin))]
public class Plugin : IPlugin
{
[Import]
public CandyCo.Shared.LibraryInterfaces.ICandySettings CandySettings { get; set; }
[Import]
public CandyCo.Shared.LibraryInterfaces.IError ErrorInterface { get; set; }
[ImportingConstructor]
public Plugin( ICandySettings candy_settings, IError error_interface)
{
CandySettings = candy_settings;
ErrorInterface = error_interface;
}
}
// candysettings.cs
[Export(typeof(ICandySettings))]
public class CandySettings : ICandySettings
{
...
}
Right-side approach
Basically the same as the left-side approach, except that I created a class that inherits from IError in the same assembly as Window1. I then used an [Import] to try to get MEF to resolve that for me.
Can anyone explain how the two ways I have approached MEF here are flawed? I have been in the dark for so long that instead of reading about MEF and trying different suggestions, I've added MEF to my solution and am stepping into the code. The part where it looks like it fails is when it calls partManager.GetSavedImport(). For some reason, the importCache is null, which I don't understand. All the way up to this point, it's been looking at the part (Window1) and trying to resolve two imported interfaces -- IError and IPlugin. I would have expected it to enter code that looks at other assemblies in the same executable folder, and then check it for exports so that it knows how to resolve the imports...
I had found a mistake in my code, and when I fixed it, the MEF exception changed, and was also more useful. It clearly pointed out that it couldn't find a CandySettings default constructor! And digging more, I found a good post from Glenn Block that discusses this. So I need to finish reading it and see if his workaround will do the trick or not. I would still appreciate more answers, since there's no telling if the workaround is the right thing to do or not.
This post really helped. I hadn't seen this information before, but it totally did the trick for me.
http://mindinthewater.blogspot.com/2010/01/using-mef-with-classes-which-take.html
Basically, my problem was that I needed to pass values to the constructor. All of my past tests involved passing interfaces to other shared libraries, but in my case, I just wanted to pass a couple of strings. I obviously didn't want to try to wrap these strings in an interface just to pass POCOs.
My first attempt in getting around this inconvenience was to do the best I could with the default constructor. I then left it up to fate that a developer would remember to call the Init() method. This was bad for obvious reasons, but I wanted to try it out anyway. In the end, it just didn't work -- the problem here is that MEF wants to resolve imports and exports, but my Init() method wouldn't get called until after composing the parts... so any other dependents of that particular library would end up with a not-truly-initialized instance of the library since Init() won't get called until later.
Anyhow, this trick of importing strings for the constructor parameters worked like a charm.
It would help if you would include the error message that you are getting.
However, if you go with the left approach, I think putting a PartNotDiscoverableAttribute on your Window1 class may fix the problem.
The issue is that the DirectoryCatalog is going to include the assembly that includes Window1, so there is going to be an IError export available from the catalog (and MEF would create an instance of Window1 if you requested that export's value). When you add the Window1 you created via ComposeParts, you are trying to add another IError export to the container. Since your plugin is only requesting a single IError export, it won't work when there is more than one available. Adding the PartNotDiscoverableAttribute on the Window1 class will prevent it from being included in the catalog.