I am importing and exporting from the XPDL standard using C#.
I have written a hierarchy for classes using XPDL 2.1 and have debugged serialization with XmlSerializer. I need to implement XPDL 2.2.
The structure of the classes has changed. There are additional parameters as well. There was list of Artifacts which was child of the root class, but the newer version lost DataObject from all of Artifacts. The DataObjects moved from an additional List into WorkflowProcess.
I need to support XPDL 2.1 and XPDL 2.2. What is the best way to implement both of them?
I can see 3 ways:
Copy and paste existing hierarchy with needed fixes (it is awful!)
Create a base class and create two child classes. One for 2.1 and another for 2.2 (but will not it be too complex to maintain?)
Implement conditional serialization using an enum variable. The class will contain the super-set of 2.1 and 2.2 (This option seems overly complex)
Please let me know if there is a better approach.
Create two additional seperate projects in your solution. The first should contain classes to be parsed from XPDL 2.1.
The second one is for 2.2, it should include all cs files from first's project folder as links. Instead of managing them manually add these lines to your second project file:
<Compile Include="..\ParserXPDL21\Classes\**\*.cs">
<Link>Classes\file.cs</Link>
</Compile>
Remember to reload the second project each time you add or remove files from the first, otherwise Visual Studio will not compile it until you do.
For the second project declare a conditional constant in the project properties: XDPL22
Now you can modify the first project files like this to maintain two versions in the same file:
#if !XDPL22
namespace ParserXPDL21
#else
namespace ParserXPDL22
#endif
{
[Serializable]
public class Root
{
#if !XDPL22
public Artifact[] Artifacts { get; set; }
#endif
public int NormalProperty1 { get; set; }
public int NormalProperty2 { get; set; }
public int NormalProperty3 { get; set; }
}
}
After that you can reference these two projects from your main project and use the classes for the two different versions.
I know it doesn's seem to be a very elegant way but it helps when you have to make a lot of duplicated code.
You still need a way to check what version of XPDL you are going to read. May be you can just look at the file extension but if it's the same than you can just try to read 2.2 and if it throws an exception or the data is not correct think this is 2.1, or you can read XML manually with XmlReader and check before deserializing.
Related
I need to create code by the IIncrementalGenerator in at least two projects which are referring to the same library that references the SourceCodeGenerator project.
My solution, for further clarification:
MySolution
|->DesktopApp
| |->ref:Library
|
|->Library
| |->ref:SourceGenerator
|
|->WebApi
| |->ref:Library
|
|->SourceGenerator
In the library, I define Interfaces for all objects I use across my application, many properties I define in the interface are Ids which I mark by an attribute where I head the IType of the underlying object.
The attribute:
[System.AttributeUsage(System.AttributeTargets.Property)]
public class NavigationPropertyAttribute : System.Attribute
{
public System.Type NavigationPropertyType { get; }
public NavigationPropertyAttribute(System.Type type) => NavigationPropertyType = type;
}
This is how such an Interface looks like:
public interface IFoo
{
int Id { get; set; }
[NavigationProperty(typeof(IBar))]
int BarId { get; set; }
[NavigationProperty(typeof(IFoo))]
int ParentId { get; set; }
[NavigationProperty(typeof(IFoo))]
int ChildId { get; set; }
}
In both projects, one is a desktop application, the other is a Web API using EF Core, I have partial classes implementing these interfaces.
namespace IGTryout.Main;
public partial class Foo : IFoo
{
public int Id { get; set; }
public int BarId { get; set; }
public int ParentId { get; set; }
public int ChildId { get; set; }
}
what I need now, and where I'm struggling with, is using the IIncrementalGenerator to create a partial class with properties based on the Id, Name and the Type from the NavigationPropertyAttribute.
public partial class Foo
{
private Bar bar;
public Bar Bar => bar ??= GetValue<Bar>();
private Foo parent;
public Foo Parent => parent ??= GetValue<Foo>();
private Foo child;
public Foo Child => child ??= GetValue<Foo>();
}
(GetValue<T>() is an extension which gets the [CallerMemberName], resolves the matching Id via reflection and returns the object from cache by resolving it by the type and id)
In the Web API project, I'm also implementing the interfaces, yet, creating the NavigationProperties the common way
Foo { get; set; }
to fullfill EF Core's needs, also I'm creating the InversePropertyCollection in the related object for having the foreign keys set as needed by EF Core.
All of this is done, so I can use the interfaces as TransportObject with as little overhead as possible when sending them from the Web API to my desktop application and reverse.
Now to the problem I have:
to trigger all changes done to the interfaces, I reference my SourceCodeGenerator project from the library project, but by doing so, I can not find any opportunity to create the code inside the Web API assembly or the desktop app assembly.
I did solve this problem before by using the common SourceGenerator and referencing the SourceCodeGenerator project by both, the desktop app and the Web API, and accessing the interfaces inside my library project by calling the ReferencedAssemblySymbols of my compilations SourceModule and iterating over to the relevant project, since it is referenced by both, but this rather hacky solution is not possible, since would loose all the possibilities, which led me to switch to the IIncrementalGenerator in the first place.
Below is the answer to why this will not work with a Class Library project. however while tying up this answer I learned of the existence of Shared Projects which based on a cursory look at how they work, might enable the behavior of incremental generators on shared source code, see thoughts below.
The Problem
There are at 2 distinct reasons why what is asked in the question won't work with a Class Library project. One is to use source generator in a way they are not designed to work, and two, even if they worked as you wanted, the generated code would not behave as expected.
Source Generators hook to a compilation
By design, Source Generators hook to a compilation. The purpose of projects is to provide a distinct compilation unit. The use of solutions to group projects and order projects is a convivence, but does not cause the individual projects to merge into one compilation. Once a class library project is visible to the downstream projects, it is not longer a bunch of source code, it is just a dll.
Partial Classes in Different Projects
The example is making a partial class with the partials in three different projects, so even if you got the source generator to make the files in the correct project you would not end up with what you wanted, which is a WebApp version of Foo and and Desktop version of Foo. Instead you would end up with 3 versions of Foo the Library version with the Id Properties, the WebApp version with the auto properties for the navigations, and the Desktop version with the GetValue call. with both the desktop and webapp version missing the Id Properties. see this question/answer
Shared Projects
Can Shared Projects help here? Maybe. The intent of shared projects is to shared source code, not to be a compilation unit, so a change to the source should trigger the incremental generator in any project referencing that shared source, since that source is now part of that project's compilation.
You will want to be aware of the differences between a class library, and a shared project, and how that can impact your program, as now that code is being compiled multiple times in different contexts, so you can actually end up with completely different functionality from the same source. (i.e. features like global usings could cause namespace resolution to cause a Type to match by name to one in a completely different assembly, e.g. you have a property Document {get;set;} which in one project resolves to say AutoCAD.Document, but in another resolves to Microsoft.CodeAnalysis.Document.)
I want to add this class as setting's type:
using System.Collections.Generic;
using System.Configuration;
namespace MY_PROJECT.SUB_PROJECT
{
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public class Configs: List<ConfigData>
{
Configs(int capacity): base(capacity) { }
public string GroupName { get; set; }
}
}
So what I did:
Select Browse... in the type dropbox:
I cannot see the MY_PROJECT namespace anywhere:
So I typed the full type manually:
The result is an error:
Type 'MY_PROJECT.SUB_PROJECT.Configs' is not defined.
I also tried SUB_PROJECT.Configs and Configs alone. Nothing helped. Why does my class not show in the browser?
In order to pull something in as a reference you need to have it compiled as a dll file. In Visual Studio they refer to this as a "Library" which is really just a class without a main function. Other option is to just leave it in the same namespace and pull the class into whatever else your working on.
I just had this issue and it was due to an Inconsistent accessibility error. Make sure that any 'required' type/field is globally accessible (public?). For the OP's case, making the constructor public solves the issue:
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public class Configs: List<ConfigData>
{
public Configs(int capacity): base(capacity) { }
// ^^
public string GroupName { get; set; }
}
In my case this was the problem:
internal struct NativeType
{
//...
}
[SettingsSerializeAs(SettingsSerializeAs.Xml)]
public class NativeTypeWrapper
{
public NativeType type; // This will not work because NativeType
// is less accessible than NativeTypeWrapper...
}
I had this problem as well. I was doing exactly the same: creating a custom class to use in Application Settings. In my case, I followed the steps outlined in this very informative article:
http://www.blackwasp.co.uk/CustomAppSettings.aspx
I should note the article is written for C#, and I painstakingly converted it to VB until it worked. I had to solve the Type...is not defined error the hard way: relentlessly experimenting until I got it to work.
I will describe my first solution, one which was not mentioned in the article, probably because it's for C# instead of VB, and that is: put the custom class or classes each in their own .vb files. For instance: Employee.vb and Room.vb. This is the only way I could make it work perfectly with no errors. After doing this and rebuilding the solution, I was then able to add my custom class as an Application Setting, but of course only by manually typing the full name TestProject.Employee in the Select a Type dialog.
However, following the article I linked above, if I put all the class definitions in the Module1.vb file with Sub Main(), the Select a Type dialog cannot find them, and I receive the Type...is not defined error.
And the cause of this error seems to be shortcomings in the code & design of the Applications Settings system and Settings page of the Project Properties dialog. I say this because of the solution I found: I hacked my classes into the settings the hard way.
What I mean by that is I initially created the setting with the name DefaultEmployee and type of String. Then I used the Find In Files dialog to find all instances of DefaultEmployee and replaced the appropriate instances of String with TestProject.Employee.
The files I made replacements in are: App.config, Settings.Designer.vb, and Settings.settings.
And it worked..! Sort of. I should say the code ran fine and it did what was expected. But...the Application Settings dialog didn't like it. After I made the changes, there are various errors from the Project Properties/Settings system every time I opened it. But as I said, it still works.
Thus...my only conclusion is the coding of the Settings system is not designed to handle this situation, and if you wish to have the most reliable & error-free experience, it's best to put each of the custom classes in their own .vb class file.
On the other hand, if you wish to become very adventurous, you could create your own Applications Settings system, as the author of this article did. I have not read all of this yet, but scanning through it seems very interesting:
https://weblog.west-wind.com/posts/2012/dec/28/building-a-better-net-application-configuration-class-revisited
I am quite new to fitnesse, I really like the ideas. But how do you do it in real life?
I have a solution that contains several dll projects in visual studio.
The projects use each other.
It's basically WPF projects so it should be relatively easy to make Fitnesse a new view that uses the viewmodels.
I think the solution here is to make a project for fitnesse, a.proj. that project will link to other assemblies that we make. lets say that b is where our business logic is and c, contains some lower level logic. In b I have my business class.
namespace b {
public class SomeBusinesslogic {
public C:SomeValue something;
public bool DoSomething(C:SomeOtherValue value1,C:Somevalue value2){
... somelogic ....
}
}
}
namespace c {
public class SomeValue{
public int a;
public int b;
}
public class SomeOtherValue{
public float c;
public string textd;
}
}
in my fitnesse wiki page how would I write the paths to include a.dll that is my fitness wrapping. and b.dll that is under test. And the c.dll that is also called through b.dll.
!path ..\xxx\bin\c.dll
!path ..\xxx\bin\b.dll
!path ..\xxx\bin\a.dll
or is there any smarter way of doing this?
thanks
See if this helps you get started http://www.asoftwarecraft.com/2011/07/starting-fitnesse-project-with-fitsharp.html
Try to use one path variable with comma-separated list.
Also there are different behaviours. FitSharp runner requires the list of the namespaces or classes which are defined in the configuration (see the project above). NetRunner plugin requires the dlls list in the path only. Then it will find all classes inherited from the BaseTestContainer class and add them to the functions containers list. Then it will union all these function to the one list and will use this list for test execution.
And important note for the configuration file: for the fitSharp you have to show the configuration file directly. For the NetRunner the configuration file will be used from the first library availble. So, for example you have this path variable:
!path a.dll, b.dll, c.dll, d.dll
b.dll and c.dll contains configuration, e.g. there is two existing files: b.dll.config and c.dll.config. And there are any configuration for the a.dll and d.dll. Then the b.dll.config will be used for the test domain.
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
It sounds strange but this is exactly what I want because I am using a data structure named "Project" which gets serialized to a save file. I would like to be able to de-serialize an older version of a save file with deprecated fields in tact, but then re-serialize it using only the currently used fields. The problem is that I want to get rid of those deprecated fields when re-serializing the structure in order to minimize file size. Is it possible to mark a field as "de-serializable only"?
Edit:
Thanks for the ideas! I decided to build mostly off of NickLarsen's suggestions and create an old version of the project structure with all depreciated fields in a separate namespace. The difference is that I decided to perform the upgrade upon deserialization. This is great because I can do it all in one line (hopefully you can get the gist what I'm doing here):
Project myProject = new Project((Depreciated.Project)myFormatter.Deserialize(myStream));
The constructor simply returns a new instance of the fresh minimal data structure based on the old bloated one.
Second Edit:
I decided to follow the advice of bebop instead and create new classes for each project version with the oldest version including all depreciated and new fields. Then the constructor of each project upgrades to the next version getting rid of depreciated fields along the way. Here is an illustrative example of converting from version 1.0.0 -> 1.0.5 -> current.
Project myProject = new Project(new Project105((Project100)myFormatter.Deserialize(myStream)));
One key to this is to forced the deserialized file as well as any fields into the older versions of the classes by using a SerializationBinder.
could you not create a new version of your data structure class each time the structure changes, and have the constructor for the new class take an instance of the previous class, and populate itself from there. To load the newest class you try and create the earliest class from the serialised file until one succeeds, and then pass that into the constructor of the next class in the chain repeatedly until you get the latest version of the data structure then you can save that.
Having a new class for each change in format would avoid having to change any existing code when the data structure changed, and your app could be ignorant of the fact that the save file was some older version. It would allow you to load from any previous version, not just the last one.
This sort of thing implemented by a chain of responsibility can make it easy to slot in a new format with minimal changes to your existing code.
Whilst not a textbook chain of responsibility you could implement with something like this:
(NOTE: untested code)
public interface IProductFactory<T> where T : class
{
T CreateProduct(string filename);
T DeserializeInstance(string filename);
}
public abstract class ProductFactoryBase<T> : IProductFactory<T> where T : class
{
public abstract T CreateProduct(string filename);
public T DeserializeInstance(string filename)
{
var myFormatter = new BinaryFormatter();
using (FileStream stream = File.Open(filename, FileMode.Open))
{
return myFormatter.Deserialize(stream) as T;
}
}
}
public class ProductV1Factory : ProductFactoryBase<ProductV1>
{
public override ProductV1 CreateProduct(string filename)
{
return DeserializeInstance(filename);
}
}
public class ProductV2Factory : ProductFactoryBase<ProductV2>
{
ProductV1Factory successor = new ProductV1Factory();
public override ProductV2 CreateProduct(string filename)
{
var product = DeserializeInstance(filename);
if (product==null)
{
product = new ProductV2(successor.CreateProduct(filename));
}
return product;
}
}
public class ProductV2
{
public ProductV2(ProductV1 product)
{
//construct from V1 information
}
}
public class ProductV1
{
}
this has the advantage that when you want to add ProductV3 you only need to change the class you are using in your app to be a ProductV3 type, which you need to do anyway, then you change your loading code so that it uses a ProductV3Factory, which is basically the same as a ProductV2Factory, but it uses a ProductV2Factory as the successor. You don't need to change any existing classes. you could probably refactor this a bit to get the commanality of CreateProduct into a base class, but it gets the idea across.
You have a couple options for this.
First you could create a version of your class (maybe in a different namespace) for the old format, and one for the new format. In the old class, overload the serialize function to throw an error, or convert itself to the new class and serialize that.
Second, you could just write your own serializer, which would be a bit more involved. There are plenty of resources which can help you though.
As far as I know .NET is very careful to only serialise things it can deserialise and vice versa.
I think what you are searching for is the OptionalFieldAttribute.
There isn't an attribute defined to explicitly support that but one possbility may be to define custom serialization using either ISerializable or by defining a custom serializer class for your type that takes no action when serializing