I am consuming an external C# Web Service method which returns a simple calculation result object like this:
[Serializable]
public class CalculationResult
{
public string Name { get; set; }
public string Unit { get; set; }
public decimal? Value { get; set; }
}
When I add a Web Reference to this service in my ASP .NET project Visual Studio is kind enough to generate a matching class so I can easily consume and work with it.
I am using Castle Windsor and I may want to plug in other method of getting a calculation result object, so I want a common class CalculationResult (or ICalculationResult) in my solution which all my objects can work with, this will always match the object returned from the external Web Service 1:1.
Is there anyway I can tell my Web Service client to hydrate a particular class instead of its generated one? I would rather not do it manually:
foreach(var fromService in calcuationResultsFromService)
{
ICalculationResult calculationResult = new CalculationResult()
{
Name = fromService.Name
};
yield return calculationResult;
}
Edit: I am happy to use a Service Reference type instead of the older Web Reference.
You can use http://automapper.codeplex.com. Typically it is used to simplify domain objects to DTO.
First of all, your web service - why do you actually GENERATE the classes? use common shared types.
http://www.codeproject.com/KB/WCF/WCFCollectionTypeSharing.aspx
This is obviously not an option when consuming a public web service, but as you control both ends... that has many advantages.
Among them complete code control ;)
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'm trying to create a Dynamics365 extension that can be called from outside the Dynamics world, to fetch some data.
The first approach I was told to investigate is creating an "action" inside Dynamics - which entails creating a CodeActivity in C# code, that then gets added to the OData feeds provided by Dynamics - sounds quite compelling!
So I tried to set up a CodeActivity class to handle my task - getting a ProjectId (Guid) and Language as parameters from the caller, and then inside Dynamics, I will need to fetch a few entities, extract some information from them, and bundle everything up into a DTO class I've defined (which doesn't match any of the entities in the Dynamics world - so that's why I need a separate DTO class to hold just the data I need to provide) and return that from the code activity.
My class looks like this:
public class GetProject : System.Activities.CodeActivity
{
[RequiredArgument]
[Input("ProjectId")]
public InArgument<Guid> ProjectId { get; set; }
[Input("Language")]
public InArgument<string> Language { get; set; }
[Output("ProjectResponse")]
public OutArgument<WebPortalDto> Response { get; set; }
/// <summary>When implemented in a derived class, performs the execution of the activity.</summary>
/// <param name="context">The execution context under which the activity executes.</param>
protected override void Execute(CodeActivityContext context)
{
// TODO - implement logic
}
}
I was able to compile the assembly - but upon registration with the Plugin Registration tool, I first got an error that the datatype of InParameter #1 is not supported (I take it that's the ProjectId parameter, with the GUID type) - so ok, I changed that to string, no biggie.
But my bigger problem comes now: the datatype of the OutParameter #1 is also unsupported....... looking at all the other code activity classes in our project, I saw that they pretty much all returned an EntityReference - a reference to a built-in Dynamics entity.
But in my case, I really CANNOT do this. I must return this DTO class (which just holds a bunch of INT and STRING properties, mostly). How can I do that from a Dynamics365 plugin / code activity?
First of all keep in mind that calling an action from outside Dynamics will still require Dynamics authentication, they are not exposed to be consumed openly.
An alternative you can consider to create a webservice (hosted by you somewhere) or an Azure Function that will access the Dynamics data and return the output you want.
If you still want to proceed with an action and a custom workflow activity, you are limited to the types you can use, as you already found you can't use a custom object, the best choice in your case will be to return a string containing the JSON of your DTO.
Ps: Guid is not a valid type, to reference a specific record inside Dynamics you need to use EntityReference (that is a class with a Guid and the logical name of the entity)
I'm working on a Winforms project with sql server, splitted in several assemblies.
The first assembly Entities contains DTO like :
public class Attribution
{
public short UserId { get; set; }
public User User { get; set; }
}
public class User
{
public short Id { get; set; }
}
The second assembly Repository is accessing Sql Server database.
The third assembly Service is the link between previous.
There are other layers but this is not the point. I need of course DTO's everywhere in the app.
In sql server, Attribution.UserId and User.Id are the same datas, located in 2 separate tables, linked by Ìnner join.
Attribution.UserId must be public because I need access from Repository,Service, etc... But I don't need it in the "logical" part of the app, what I need is Attribution.User.
At this time I have a UserService class in which there is a GetUser() method and I call this method to get the user in my AttributionService.GetAttribution() method.
Is there a way to restrict access to Attribution.UserId property to Service assembly? Or is it a kind of "good practice violation" to query a User DTO in AttributionService class?
Many thanks for your recommandation.
`
One option would be to make the set of the property internal and the use the InternalsVisibleTo attribute to grant access to internals to the Repository assembly.
Another less technical and more logical option would be to make the setter private and let the only way for it to be modified be the classes constructor. That way, your repository can get users built, but nobody can modify the ID later.
As a last option you could create an interface that contains only what the non-repository classes should have access to and pass this around. I'm not a big fan because that means you have to cast it back to your concrete class in the repository and that basically means your repository is lying (saying it accepts an ISomething, but then throwing if the ISomething is not the exact, concrete Something it expects).
(Edited) -> I have a solution with different projects. It has a strange structure because I'm not an professional software engineer. The solution has three parts: one HMI project (WPF), bussiness project (class library type) and service project (WCF).
In the bussiness project, I do some process that has result which I save in a static class. This results are exposed in the screen by WPF project mentioned previously and now I need to transfer this data by one service placed in the WCF project.
The object is similar to:
[DataContract]
public class ObjectToTransfer
{
[DataMember]
public string ParameterOne = string.Empty;
[DataMember]
public string ParameterTwo = string.Empty;
}
In the other hand, the method that picks up the informtion from static class belonging to bussiness project is like this:
[ServiceBehavior]
public class Service: IService
{
public ObjectToTransfer SendObject()
{
return new ObjectToTransfer{
ParameterOne = BusinessProject.StaticClass.ResultOne,
ParameterTwo = BusinessProject.StaticClass.ResultTwo
};
}
Well, when I consume the service the result is a object with the parameters one and two empty. In additon, when I debug the solution with both projects run, the static class hasn't info in her atributes... It's like whether both projects run independently, in spite of both are under the same solutions..., It's like whether there was no relationship between them. Where is the mistake?
Thank you in advance!
The main thing you will need to do is change the DataContract class to a class with proper getters and setters.
[DataContract]
public class ObjectToTransfer
{
[DataMember]
public string ParameterOne { get; set; }
[DataMember]
public string ParameterTwo { get; set; }
}
The reason this is important is because of the way WCF works when it try to serialize the data from the server to the client. Essentially, the class on the server side schematically matches a generated class on the client. (They share the same class schema.) If there is no proper structure to the class, the data cannot get set. The members are basically read-only.
What you have in your class right now are public member variables, and they are set to always return string.Empty. WCF can't work with that, it is too strict. The server and the client need to be able to read and write to all DataMember properties, so you need the { get; set; }.
Don't forget to refresh your service reference after making this change.
If you need some good resources to learn WCF, check out these videos WCF Tutorial. They are a little dated, but all of the concepts still apply.
This might be late and I am new to WCF service, so I am sharing based on what I have done in my case to share data among different projects.
Background of my project:
I have a WCF service(host), a client and a server project. (A total of 3 different projects.) I will perform the necessary calculation in WCF service. Afterwards, I am suppose to share the result to the client and server. Therefore, there is this variable, variableA, that I have share among the 3 projects.
What I did:
In WCF service, I declare variableA and mark it static:
private **static** string variableA;
Only after I marked variableA as static, then I can see the same variableA across different projects.
I am not sure if this helps but this is what I did in my case.
I have tried a lot of solutions from the internet. But I am not able to resolve this problem.
I have tried solutions from these links below:
WCF Proxy Returning Array instead of List EVEN THOUGH Collection Type == Generic.List
WCF Returning Array instead of List EVEN THOUGH Collection Type == Generic.List
Why does WCF return myObject[] instead of List<T> like I was expecting?
But I am not able to resolve it.
I have 2 copies of the same wcf service source code "WCFServiceApplication". One is hosted on LAN on IIS7 and Other one is am running locally in my solution.
The one which is hosted on IIS7 is working fine and returning list of objects. But the one on my local solution. I am trying to reference locally in solution to consume "WCFServiceApplication" functions in one of my local project named "MyProjectLibrary.csproj".
But here i have the problem. It is not giving me the Generic List of my custom class objects.
Any help is appreciated.
Thanks
I am Returning the List of below class:
[DataContract(IsReference = true)]
[KnownType(typeof(Joc))]
[KnownType(typeof(Stakeholder))]
[XmlInclude(typeof(JocStakeholder))]
public class JocStakeholder
{
[DataMember]
public long ID { get; set; }
[DataMember]
public virtual Joc joc { get; set; }
[DataMember]
public virtual Stakeholder stakeholder { get; set; }
}
Joc and Stakeholder are 2 other classes. Actually, these are code first entities.
And My interface has this function:
[ServiceContract]
public interface IService
{
[OperationContract]
public IList<JocStakeholder> GetJocStakeHolders();
}
Change the Client's Service Reference Settings as follows
Right Click the Service Reference and Choose Configure Service Reference
Under the Data Type property settings, change the Collection Type value from Array to
System.Collections.Generic.List
This would give you the Generic List as a response
Specifies the list collection type for a WCF client. The default type is Array.
You can change it before adding service reference click Advance in Add Service Reference Dialog Box. Under Data Type beside Collection Type select in drownlist System.Collections.Generic.List.
I tried mostly all the method which i saw on the Stackoverflow.com and Internet. But i couldn't find one that could have done the job for me.
SO, finally, I found this article WCF the Right Wat by Miguel Castro and I implemented by services with the same architecture as the Miguel Sir is doing. The architecture resolved the problem.
But some programmers might not agree with the Architecture which is being used in that article because Model Classes project is being distributed to the clients as a DLL. Whereas in WCF usually, the Classes are exposed through the Service.