I have a windows service that references an assembly which contains the following class.
[Serializable]
public class User
{
public User(string userName)
{
UserName = userName;
}
public string UserName { get; private set; }
}
This service has a method to allow us to create a User:
public User CreateUser(User user)
{
//Create and return user
}
Through remoting we are able to call this method in the service that takes in User object from our application. The application references the same assembly that the service does for the User class but it forces a specific 1.0.0.0 version.
CreateUser(User user);
We would like to change the existing assembly that is referenced from each project to add a new property "Phone" to the User class.
[Serializable]
public class User
{
public User(string userName)
{
UserName = userName;
}
public string UserName { get; private set; }
**public string Phone { get; set; }**
}
We will increment the assembly version from 1.0.0.0 to 2.0.0.0. The windows service will have a reference to the 2.0.0.0 version of the assembly in the GAC. Because some of our clients are slow at upgrading and they force specific versions of the assembly or copy it local they will still be referencing the 1.0.0.0 version.
With this change to the service assembly reference the marshaller that deserializes/serializes the object from the service to the application throws a serialization error. "The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter..."
The change we made should be a non breaking change since we just added additional feature. Is there a way to allow the existing application continue and use the 1.0.0.0 version of the assembly and serialize the 2.0.0.0 without the new property down to them without some convoluted conversion on the service?
UPDATE:
We are using the following to connect over to our service
marshaller = ChannelFactory<T>.CreateChannel(new NetTcpBinding() { MaxReceivedMessageSize = MaxResponseSize }, new EndpointAddress(new Uri(new Uri(servers[serverIndex]), serviceName)));
Inner exception:
http://tempuri.org/:CreateUserResult. The InnerException message was ''EndElement' 'CreateUserResult' from namespace 'http://tempuri.org/' is not expected. Expecting element '_someOtherField'.'. Please see InnerException for more details
...Next inner is null
As I suspected, here's the problem;
NetTcpBinding
(from your edit). Basically, WCF includes some nice friendly contract-based support, and some less-than-friendly type-based support.
If at all possible, I would suggest the simplest option here is to not use NetTcp as it by default uses NetDataContractSerializer (type-based), so will have versioning issues. Alternatively, you can use oter serializers on the transport - for example DataContractSerializer of protobuf-net. However, changing this would itself be a breaking change.
I gather it is possible to use a custom binder with NetDataContractSerializer - see Problem deserializing with NetDataContractSerializer after refactoring code . If you can get that working it should preserve the API, but it should not be underestimated; I think that will be a maintenance burden, in all honesty. I'd rather cut my losses, break the API once and switch to a contract-based serializer.
Maybe the OptionalField attribute works for you, depending on the formatter, see http://msdn.microsoft.com/en-us/library/system.runtime.serialization.optionalfieldattribute.aspx.
Related
In my application I can register different datasources by name. These data-sources each have a few string properties required, along with a set of other dependencies, but are otherwise the same, so take a few different standard implementations.
To construct instances of each datasource when requested, I create a binding to an instance of a Provider<T> which is initialized with the information required to access that data-source. The provider looks something like the below:
public class StandardListProvider<T> : Provider<IListExecutor<T>>
where T : new()
{
public string Name { get; set; }
public string ListMethod { get; set; }
public StandardListProvider(string name, string listMethod)
{
Name = name;
ListMethod = listMethod;
}
protected override IListExecutor<T> CreateInstance(IContext context)
{
var connector = (IInternalConnector)context.Kernel.GetService(typeof(IInternalConnector));
return new StandardListExecutor<T>(connector, Name)
{
ListMethodName = ListMethod
};
}
}
The problem is with resolving dependencies of the StandardListExecutor<T> like IInternalConnector. Obviously I can construct them manually, or request them from context.Kernel as I am in my example (and suggested by Ninject Providers -> Get another dependency inside the provider), but this results in a request with no Target information, which is not ideal if we want to perform contextual bindings for the dependencies of StandardListExecutor.
I've tried playing with context.Request.CreateChild(...), but this appears to require reflection on every activation to create a ParameterTarget. There doesn't appear to be much information about this in the Ninject docs either.
My question is: What is the correct way to resolve/request dependencies, or other services like this from within the activation process of an existing binding?
Edit
The requests themselves are made via the Ninject.Mvc hookups into the System.Web.Mvc controller activation process.
You should be able to use the extension Ninject.Extensions.ContextPreservation. Specifically the extension method IContext.ContextPreservingGet(...):
var connector = context.ContextPreservingGet<IInternalConnector>();
However, personally I think that creating specific settings types is the better choice - because it's the simpler idea.
I have a WCF Host with something like this:
[ServiceContract]
public interface IMountToOs
{
[OperationContract]
char GetMountDriveLetter();
[OperationContract]
MyTestClass MyTest();
}
public class MyTestClass
{
public string A { get; set; }
public string B { get; set; }
}
Client
private IMountToOs _proxy;
public IMountToOs Proxy
{
get
{
if (_proxy == null)
{
NetTcpBinding binding = new NetTcpBinding();
binding.MaxReceivedMessageSize = 2147483647;
binding.OpenTimeout = TimeSpan.FromMilliseconds(50000);
EndpointAddress address = new EndpointAddress("net.tcp://localhost:1234/MountToOsHost");
//_proxy = new MountToOsClient(binding, address);
ChannelFactory<IMountToOs> factory = new ChannelFactory<IMountToOs>(binding);
_proxy = factory.CreateChannel(address);
}
return _proxy;
}
}
While I can access
MessageBox.Show("Okay - " + Proxy.GetMountDriveLetter());
I can't call this method:
MessageBox.Show("Okay - " + Proxy.MyTest().A);
The complete extension is not working. But only while using it in an extension. Even if I insert a Messagebox in the first line of the extension it is not hit. I don't know why. It seems to run a pre-check and find the call of the custom class which is refused or so...
If I use a winform or so there is no problem.
.net 3.5
curious is that I have a break-point and a message of the hosts side. So I see that the method is not called
Update
now I moved the wcf-call in the Load Method of the extension and get a exception:
System.MissingMethodException: method not found:
"Contracts.Interfaces.MyTestClass
Contracts.Interfaces.IMountToOs.MyTest()".
My winform test and this extension use the same interface so that the method should known from both. no contract or so is outdated
According to what I found here and in the comments of the post: "For creating dynamic service proxy using client channel factory method, you will need datacontracts of the service. If you don't have datacontracts but you have the service URL, then you could use reflection to create proxy at runtime and call the service method."
Seems that the MyTestClass type is not known on the client side, so I think you could use reflection, or share the class between the client and server or much more simple, use the datacontract attribute.
Also, found something on MSDN that says something like this:
"When to use a proxy?
We create proxy using svcutil.exe. The output of this tool gives a proxy class and makes corresponding changes to the application configuration file. If you have a service that you know is going to be used by several applications or is generic enough to be used in several places, you'll want to continue using the generated proxy classes. We use proxy in WCF to be able to share the service contract and entities with the client. Proxies have several restrictions like they need to have gets and sets , contructors can't be exposed , methods other than the service contract cannot be exposed, repetition of code, everytime that we add/modify a service contract/data contract/message contract we need to re-generate the proxy for the client.
When to use ChannelFactory
The other option is using the ChannelFactory class to construct a channel between the client and the service without the need of a proxy . In some cases, you may have a service that is tightly bound to the client application. In such a case, it makes sense to reference the Interface DLL directly and use ChannelFactory to call your methods using that. One significant advantage of the ChannelFactory route is that it gives you access to methods that wouldn't otherwise be available if you used svcutil.exe..
When to use a ChannelFactory vs Proxy class?
A DLL is helpful if the client code is under you control and you'd like to share more than just the service contract with the client -- such as some utility methods associated with entities and make the client & the service code more tightly bound. If you know that your entities will not change much and the client code is less, then a DLL would work better than a proxy. If the client to your service is external to the system, such as API, it makes sense to use a proxy, because it makes sharing the contract easier by giving a code file rather than a DLL."
We cant see the class
MountToOsClient: IMountToOs
So we can only assume it is ok.
[DataContract] // Missing
public class MyTestClass
{
[DataMember] // Missing
public string A { get; set; }
[DataMember] // Missing
public string B { get; set; }
}
MountToOsClient can not expose Mytestclass without these attributes.
My goal is to load an external class in a running application environment (like a plugin model). Creating an instances of the class in an running environment is not the problem (the classes using an Interface). The problem is to get the class which must be available from a central WCF services.
Is it possible to transport an class or assembly to the client by using WCF?
Something like this:
[ServiceContract]
public interface ISourceData
{
[OperationContract]
xxx GetClassData { get; set; } // <-- here to get data the class to app can create an instances of this
}
I hope that you understand my situation. Thanks.
First of all, the attribute in your sample above must be OperationContract, not DataContract. The DataContract attribute is for the class that you want to return in GetClassData.
The problem in your situation is that on the client side the class itself is not replicated when you add the service reference, but a stub is generated for the properties that you define in your DataContract. So you get the data, but not the logic.
You could now create an assembly which defines the data classes to be exchanged and add them to both the service and the client, but as I understand your question, you want to dynamically load assemblies in the service and send these "implementations" to the client without the client actually having access to the DLL that implements the class. This may not be possible in an easy way.
EDIT
Re-reading your question I now understand that you do not want to "transfer an instance", but you want to transfer the class definition. One way would be to actually transfer the source code for the class and try to use Reflection.Emit to create a dynamic assembly. A sample of this can be found here.
Yes , you can .
and also you must to define the type of your class like ↓
[ServiceKnownType(typeof(xxx))]
public interface IService
I think you need the assembly on the client so you need to transfer the dll containing the assembly to the client, then have the client save it in a plugins directory for the app and have the app and load it from there.
Although I image that this is going to be a permissions nightmare to get the app to be able to use the dlls downloaded from the service.
You would mark up the classes used in your interface like this:
[ServiceContract]
public interface ISourceData
{
[OperationContract]
MyClass GetClassData();
}
[DataContract]
public class MyClass
{
[DataMember]
public string MyMember1 {get; set;} // included in transport
public int MyMember2 {get; set;} // not included
}
I'm running two instances of VS2010 on my local machine. One instance is running my Web Service (written in C#). The other instance is running my MVC web app (also C#). The MVC web app has a reference to the web service. I can successfully invoke web service methods from within the MVC app.
In my web service is a PageNavigation class:
// PageNavigation.cs
using System;
using System.Collections.Generic;
using System.Text;
public class PageNavigation
{
public string Page_Number { get; set; }
public string Page_Count { get; set; }
public PageNavigation()
{
Page_Number = "1";
Page_Count = "2";
}
}
By default, this should return an object with auto-implemented properties when I call the class constructor:
WebService.PageNavigation pageNavigation = new WebService.PageNavigation();
This works when constructing a PageNavigation object elsewhere in the web service.
pageNavigation.Page_Number
"1"
pageNavigation.Page_Count
"2"
However, running the same line of code on the MVC isn't giving the same result; the object's properties are all null values.
pageNavigation.Page_Number
null
pageNavigation.Page_Count
null
Is this the expected behavior? Is there a way to populate the properties with default values as intended? If more information is needed please let me know and I will update the question.
The service reference only sees the schema of your object, not business logic; in your case, your service reference just created a shell data type in the MVC application. When you create a service reference, it's actually creating another type with the same property names and types as the type defined in the service.
For your particular scenario (simply providing default property values and not more general business logic), you should be able to apply the [System.ComponentModel.DefaultValue] attribute to your properties in order for the class generator to recognize that these properties should be populated with a default value.
Incidentally, if the service reference were reusing existing types (if you had this type in a common library that was referenced both by the service and the application, for example), then your business logic would be intact.
An alternative would be to implement a factory pattern, whereby you call a function on the web service that instantiates (and optionally populates) the data object, then returns it to the client.
Yes, this is expected behaviour. The MVC site is not actually using your PageNavigation class. It is a simple copy (generated when you add the web service reference) containing of all the properties, but none of the methods, including the constructor.
You could work around this by refactoring your service so the entities are in a separate assembly and then you can reuse this assembly on the client as an option when you generate the proxy.
If you insist on using the same types between client and service, then on the "Advanced" tab of the "Add Service Reference" dialog, you can choose to reuse the types in your server assembly.
I would move that class out of the service and into a class library project referenced by the service and by the client.
And I wouldn't do this for such a small reason as default values. this violates SOA by coupling the service and the client. It will obviously not work for clients which are not running .NET.
What serializer are you using to deserialize the response from the server? Some of them (like the DataContractSerializer for example) do not call the default constructor.
The solution that you should use if you are in fact using DataContractSerializer is to use the OnDeserialized attribute like this:
using System.Runtime.Serialization;
public class PageNavigation
{
public string Page_Number { get; set; }
public string Page_Count { get; set; }
public PageNavigation()
{
Init();
}
[OnDeserialize]
void Init()
{
Page_Number = "1";
Page_Count = "2";
}
}
A problem with "Add Service Reference", and actually with SvcUtil over all its features.
In order to reproduce you just need to add an OperationContract with argument or returning the following class :
[XmlSchemaProvider("MySchema")]
public class MyStructure : IXmlSerializable
{
private XmlElement e;
private static void Func(object o, ValidationEventArgs args)
{
}
public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
//xs.XmlResolver = new XmlUrlResolver();
XmlSchema s = XmlSchema.Read(new XmlTextReader(new StringReader("<?xml version=\"1.0\"?><xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"><xs:complexType name=\"MyStructure\"><xs:sequence><xs:any /></xs:sequence></xs:complexType></xs:schema>")), null);
xs.Add(s);
return new XmlQualifiedName("MyStructure");
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
XmlDocument doc = new XmlDocument();
e = (XmlElement)doc.ReadNode(reader);
}
public void WriteXml(XmlWriter writer)
{
e.WriteTo(writer);
}
#endregion
}
The result is that when you use AddWebReference or AddSerivceReference without a reference to the class library containing the MyStructure type, everything will be fine ad you will get an xmlElement representation at the auto created proxy.
However, when you have a reference you will get the following warning :
================
Warning 1 Custom tool warning: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter
Error: Referenced type 'ServiceLibrary.MyStructure, ServiceLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' with data contract name 'MyStructure' in namespace '' cannot be used since it does not match imported DataContract. Need to exclude this type from referenced types.
XPath to Error Source: //wsdl:definitions[#targetNamespace='http://tempuri.org/']/wsdl:portType[#name='IService1'] \Projects\WCFSample\WCFExample\TestAddReference\Service References\ServiceReference1\Reference.svcmap 1 1 TestAddReference
======================
And no proxy will be generated for you.
Now, the internet is full with descriptions of this when you have a generic DataContract, and/or using IsReference attribute.
This is a much serious problem, since any non-typed data will do this problem.
Could not find any way to solve the problem. What if I want to know the type at the client side, by sharing the class library of the contracts ?
This type of exception generally means there is at least one difference in the type contracts generated by the service as compared to the referenced types (as the message indicates!). But it may not be obvious at first glance, as I found out. Make sure all nested and referenced types are up to date with the server. In my case, nested types were updated on the server. I thought I had updated by locally referenced assembly (and the shared reference types) but I missed some. It took close examination to find the culprit.
See additional information in this question
I have a suggestion:
I had similar errors, including:
the .svcmap file cannot be found. It may have been moved or deleted. To generate a new .svcmap file, delete the service reference and add it again.
And at that point, no way to delete the service reference unless I close VS2010 and open it again.
The situation is: my WCF service is running, I programmatically added a Description.ServiceMetadataBehavior at an HTTP address that I define.
In VS2010, I try to add a service reference at the HTTP address, I see my service, I add the reference, and voila, errors and warning.
The problem: my HTTP address is containing some key words that WCF doesn't like. Specifically the word COM (it breaks with LPT too).
So my solution: modify my HTTP address not to have the word COM. It worked for me.
If the service is hosted over HTTPS, go into the server's IIS Manager. Under "SSL Settings" for the site, make sure "Require SSL" is checked, and check the Client Certificates radio button for "Accept".