locked
Issue with KnownTypes and resolving it at runtime. RRS feed

  • Question

  • I have a class
    [DataContract]
    public class GenericItem{
    [DataMember]
    public Object ObjectProperty { get; set; }
    [DataMember]
            public string ModelTypename { get; set; }

    }

     

    And ObjectProperty can keep different models inside it. i.e. Customer class type model where customer class has datacontract attribute and properties with DataMember.

     

    I get Models at runtime. so I have put this in web.config of service host: 
     <system.runtime.serialization>
        <dataContractSerializer>
          <declaredTypes>
            <add type="GenericItem,Common">
              <knownType type="Customer, Model" />
            </add>
          </declaredTypes>
        </dataContractSerializer>
      </system.runtime.serialization>

    I am gettting error :


    There was an error while trying to serialize parameter http://common.com/2010/10:ReadResult. The InnerException message was 'Type 'Model.Customer' with data contract name 'Customer:http://schemas.datacontract.org/2004/07/Model' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'.  Please see InnerException for more details.

    How to fix this so it understands and gets exact type.

    Monday, February 21, 2011 8:20 PM

Answers

  • The error message is saying that you need to add List<Model.Snippet> as a known type.
    • Marked as answer by matsha1 Wednesday, March 9, 2011 10:03 PM
    Wednesday, March 9, 2011 8:52 PM

All replies

  • The type attribute of the items listed in the <declaredTypes> section of the <system.runtime.serialization/dataContractSerializer> must match the exact name (assembly-qualified name) of the type in use. Your "known type" in this example is not called "Customer from the assembly Model", it's called "Model.Customer" (from an assembly which I don't know) - see the error message.

    To get the actual assembly-qualified name for the type, you can write it on the console:

    Console.WriteLine(typeof(GenericItem).AssemblyQualifiedName);
    Console.WriteLine(typeof(Customer).AssemblyQualifiedName);

    That's the value you need to put in config (actually, the assembly-qualified name has more information than you need; namely it has the type name, assembly name, assembly version, assembly culture and assembly public key token. You only need the fully-qualified name and the assembly name.

    Monday, February 21, 2011 11:07 PM
  • Right! I read Feb 2011 msdn issue-
    Known Types and the Generic Resolver. How to add the generic and type resolver to a config file in a WCF service hosted in IIS. This is nice solution but what if you just have .svc file for your service in IIS as a servicehost(not programmtically in code).

    Thursday, February 24, 2011 2:22 PM
  • You can't specify the resolver name directly in config. You have two options (which work even if your service is hosted in IIS):

    1) Use a custom factory, and add some logic on it, like in the example below (or with more details at http://blogs.msdn.com/b/carlosfigueira/archive/2007/12/26/modifying-code-only-settings-on-webhosted-services.aspx):

    <% @ServiceHost Service="MyTest.Service" Factory="MyTest.MyFactory" Language="C#" debug="true" %>
    
    using System;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Activation;
    using System.ServiceModel.Description;
    
    namespace MyTest
    {
      public class MyFactory : ServiceHostFactory
      {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
          return new MyServiceHost(serviceType, baseAddresses);
        }
      }
      public class MyServiceHost : ServiceHost
      {
        public MyServiceHost(Type serviceType, Uri[] baseAddresses) : base(serviceType, baseAddresses) { }
    
        protected override void InitializeRuntime()
        {
          foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
          {
            foreach (OperationDescription operation in endpoint.Contract.Operations)
            {
              DataContractSerializerOperationBehavior dcsob = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
              if (dcsob != null)
              {
                dcsob.DataContractResolver = new MyResolver();
              }
            }
          }
    
          base.InitializeRuntime();
        }
      }
    }
    
    

    2) Another option is to create an endpoint behavior with a custom config element, and then add a reference to your behavior in your web.config; your behavior would be responsible for setting the resolver to the DataContractSerializerOperationBehavior.

    • Marked as answer by matsha1 Thursday, February 24, 2011 5:39 PM
    • Unmarked as answer by matsha1 Monday, February 28, 2011 9:39 PM
    • Proposed as answer by Carlos Figueira Wednesday, March 9, 2011 9:18 PM
    Thursday, February 24, 2011 3:01 PM
  • Can you please open a new thread about this new issue? This isn't related to known types anymore, and it's better to have questions about specific answers in threads with an appropriate title, to help people searching for issues later.
    Thursday, February 24, 2011 4:43 PM
  • Thanks. !!
    Thursday, February 24, 2011 5:58 PM
  • Hi

    Where should I put my class MyResolver at the service side(IIS hosting).

    Thursday, February 24, 2011 10:17 PM
  • It can be defined in the same project as the service itself. I'd usually have it closer to the place where the data contracts which it resolves are defined.
    Thursday, February 24, 2011 10:24 PM
  • What to do if you have WCF service application running in say Dev server (not in IIS and also not using ServiceHost)..How to configure this Generic resolver for service and where to put the code for GenericResolver
    Monday, February 28, 2011 11:00 PM
  • By Dev server do you mean the Visual Studio development server? If so, the factory method works there as well.
    Monday, February 28, 2011 11:02 PM
  • Yes, Visual Studio development server. Channel factory is creating proxies and I can talk to services and db. But the problem is if take KnownType attribute out of GenericItem for Snippet, and put it config...Silverlight client can not serialize.
    Tuesday, March 1, 2011 3:20 AM
  • The config version of the known types apply only at the runtime for the process who onws the config file, so it won't be exposed in the service metadata (so the SL client generated by add service reference won't have the appropriate known type declaration).
    Tuesday, March 1, 2011 4:53 AM
  • I am generating SL client proxy using channelfactory. So what shud I do as knowntype will apply only to the process and SL won't get that. Is there a way to configure SL (curious??) and DatacotnractResolver won't work as discussed above due the same reason here.
    Tuesday, March 1, 2011 2:23 PM
  • In this case you'll need to use the other known type mechanisms (adding [ServiceKnownType] to the service contract, or adding [KnownType] to the data contracts).
    Tuesday, March 1, 2011 5:10 PM
  • [ServiceContract]
        [ServiceKnownType(typeof(Snippet))]
        public interface IServiceAsync1
        {......}

    Is there a way to dynamically add knowntype at runtime to this service contract. Would that work..Say if I discover dll for models..And if the IIS hosted service then doing this at start up would that work(SL should also be able to serialize/deserialize)?

    Tuesday, March 1, 2011 5:54 PM
  • Sure, as long as you can get a reference to the DLLs, you can use the same [ServiceKnownType(methodName, type)] which you can use in the desktop framework.

    Here's an example of a full end-to-end project which does that. My implementation of the GetKnownTypes method looks for all public classes in the executing assembly marked with [DataContract], but you can change it according to your needs.

    Common code (file which is referenced by both web and SL projects:

    namespace SLApp.Web
    {
      [ServiceContract(Name = "IService", Namespace = "")]
      [ServiceKnownType("GetKnownTypesForService", typeof(KnownTypesFinder))]
      public interface IService
      {
    #if !SILVERLIGHT
        [OperationContract]
        string SetItem(GenericItem item);
        [OperationContract]
        GenericItem GetItem(string value);
    #else
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginSetItem(GenericItem item, AsyncCallback callback, object state);
        string EndSetItem(IAsyncResult asyncResult);
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginGetItem(string value, AsyncCallback callback, object state);
        GenericItem EndGetItem(IAsyncResult asyncResult);
    #endif
      }
    
      [DataContract]
      public class GenericItem
      {
        [DataMember]
        public string ModelTypeName { get; set; }
        [DataMember]
        public object ObjectProperty { get; set; }
    
        public override string ToString()
        {
          return string.Format("GenericItem[model={0}, prop={1}]", ModelTypeName, ObjectProperty);
        }
      }
    
      [DataContract]
      public class Snippet
      {
        [DataMember]
        public string Name { get; set; }
    
        public override string ToString()
        {
          return string.Format("Snippet[name={0}]", Name);
        }
      }
    
      public class KnownTypesFinder
      {
        public static Type[] GetKnownTypesForService(ICustomAttributeProvider provider)
        {
          List<Type> result = new List<Type>();
          foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
          {
            if (type.IsPublic && type.IsClass && IsDataContract(type))
            {
              result.Add(type);
            }
          }
    
          return result.ToArray();
        }
    
        private static bool IsDataContract(Type type)
        {
          object[] dca = type.GetCustomAttributes(typeof(DataContractAttribute), false);
          return dca != null && dca.Length > 0;
        }
      }
    }
    

    Service implementation:

    namespace SLApp.Web
    {
      [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
      public class Service : IService
      {
        public string SetItem(GenericItem item)
        {
          return string.Format("From SetItem, item={0}", item);
        }
    
        public GenericItem GetItem(string value)
        {
          return new GenericItem { ModelTypeName = "Snippet", ObjectProperty = new Snippet { Name = value } };
        }
      }
    }
    

    Client XAML (MainPage.xaml):

      <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
          <RowDefinition Height="50"/>
          <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*"/>
          <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBox AcceptsReturn="True" Name="txtDebug" Margin="5" Grid.Row="1" Grid.ColumnSpan="2" />
        <Button Name="btnSendContract" Grid.Row="0" Grid.Column="0" Margin="5" Content="Send contract" Click="btnSendContract_Click" />
        <Button Name="btnReceiveContract" Grid.Row="0" Grid.Column="1" Margin="5" Content="Receive contract" Click="btnReceiveContract_Click" />
      </Grid>
    
    

    Client code (MainPage.xaml.cs):

    namespace SLApp
    {
      public partial class MainPage : UserControl
      {
        public MainPage()
        {
          InitializeComponent();
        }
    
        private Binding GetBinding()
        {
          return new CustomBinding(
            new BinaryMessageEncodingBindingElement(),
            new HttpTransportBindingElement());
        }
    
        private EndpointAddress GetAddress()
        {
          string baseAddress = Application.Current.Host.Source.ToString();
          baseAddress = baseAddress.Substring(0, baseAddress.LastIndexOf('/'));
          if (baseAddress.EndsWith("/bin", StringComparison.OrdinalIgnoreCase) || baseAddress.EndsWith("/clientbin", StringComparison.OrdinalIgnoreCase))
          {
            baseAddress = baseAddress.Substring(0, baseAddress.LastIndexOf('/'));
          }
    
          return new EndpointAddress(baseAddress + "/Service.svc");
        }
    
        private void btnSendContract_Click(object sender, RoutedEventArgs e)
        {
          try
          {
            ChannelFactory<SLApp.Web.IService> factory = new ChannelFactory<SLApp.Web.IService>(GetBinding(), GetAddress());
            SLApp.Web.IService proxy = factory.CreateChannel();
            SLApp.Web.GenericItem item = new SLApp.Web.GenericItem();
            item.ModelTypeName = "Model name";
            item.ObjectProperty = new SLApp.Web.Snippet { Name = "Snippet name" };
            proxy.BeginSetItem(item, delegate(IAsyncResult asyncResult)
            {
              try
              {
                string result = proxy.EndSetItem(asyncResult);
                this.AddToDebug("Result of sending item to server: {0}", result);
              }
              catch (Exception ex)
              {
                this.AddToDebug("Error sending item to server: {0}", ex);
              }
            }, null);
            this.AddToDebug("Called a method to send a data contract with known type to the server");
          }
          catch (Exception ex)
          {
            this.AddToDebug("Error calling method: {0}", ex);
          }
        }
    
        private void btnReceiveContract_Click(object sender, RoutedEventArgs e)
        {
          try
          {
            ChannelFactory<SLApp.Web.IService> factory = new ChannelFactory<SLApp.Web.IService>(GetBinding(), GetAddress());
            SLApp.Web.IService proxy = factory.CreateChannel();
            proxy.BeginGetItem("getItem", delegate(IAsyncResult asyncResult)
            {
              try
              {
                SLApp.Web.GenericItem result = proxy.EndGetItem(asyncResult);
                this.AddToDebug("Result of sending item to server: {0}", result);
              }
              catch (Exception ex)
              {
                this.AddToDebug("Error sending item to server: {0}", ex);
              }
            }, null);
            this.AddToDebug("Called a method to receive a data contract with known type to the server");
          }
          catch (Exception ex)
          {
            this.AddToDebug("Error calling method: {0}", ex);
          }
        }
    
        private void AddToDebug(string text, params object[] args)
        {
          if (args != null && args.Length > 0)
          {
            text = string.Format(text, args);
          }
    
          this.Dispatcher.BeginInvoke(() => this.txtDebug.Text = this.txtDebug.Text + text + Environment.NewLine);
        }
      }
    }
    
    

    The whole project can be downloaded at http://carlosfigueira.me/Projects/Post_6bd86eb5_808a_4119_8c18_dae0ffae94b8.zip.

    Tuesday, March 1, 2011 11:19 PM
  • If you can have a reference to an Assembly object (which is what I mean by getting a reference to the DLL), you can enumerate the types of that assembly. In the desktop/web project you can load DLLs dynamically at runtime, but on Silverlight I don't think it's possible.
    Wednesday, March 2, 2011 8:01 PM
  • So adding model dll as reference and have this dll available atdesign time. Right!
    Wednesday, March 2, 2011 8:15 PM
  • I don't know enough about MEF to answer that; can you open another thread (either here or in the MEF discussion forum)? This thread is already deviating from the original title :)
    Thursday, March 3, 2011 5:27 PM
  • Having the types in the same assembly of the service contract would guarantee that the assembly will always be there (after all, it's being executed at that moment).
    Thursday, March 3, 2011 10:07 PM
  • I don't think you can have compiler directives (#-statements, such as #if, #else, #endif) directly inside a .svc file. Since this file will be shared between SL and the web project, it needs to be a .cs file.

    Monday, March 7, 2011 5:34 PM
  • Hi As you have mentioned:

    2) Another option is to create an endpoint behavior with a custom config element, and then add a reference to your behavior in your web.config; your behavior would be responsible for setting the resolver to the DataContractSerializerOperationBehavior.

    Could you please provide an example for this. Also, DataContractSerializerOperationBehavior dcsob = operation.Behaviors.Find<DataContractSerializerOperationBehavior>(); if (dcsob != null) { dcsob.DataContractResolver = new MyResolver(); } Getting error: DataContractSerializerOperationBehavior does not contain definition DataContractResolver....

    thanks.

    • Edited by matsha1 Monday, March 7, 2011 6:21 PM
    Monday, March 7, 2011 5:59 PM
  • The thread at http://social.msdn.microsoft.com/Forums/en/wcf/thread/b8513225-2b84-4913-8017-125edd55e4be has an example of a custom behavior added in config.
    Monday, March 7, 2011 6:09 PM
  • thanks.

    Also, DataContractSerializerOperationBehavior dcsob = operation.Behaviors.Find<DataContractSerializerOperationBehavior>(); if (dcsob != null) { dcsob.DataContractResolver = new MyResolver(); }

     

    Getting error: DataContractSerializerOperationBehavior does not contain definition DataContractResolver....

    thanks.

    Monday, March 7, 2011 6:22 PM
  • The DataContractResolver property was adedd on .NET Framework 4 (http://msdn.microsoft.com/en-us/library/system.servicemodel.description.datacontractserializeroperationbehavior.datacontractresolver.aspx), so if you're using a previous version you won't be able to use it.
    Monday, March 7, 2011 6:24 PM
  • (#-statements, such as #if, #else, #endif

    This error ---It is gone after I restarted my service in IIS.

    Monday, March 7, 2011 6:24 PM
  • I already have it -.NET 4.0 installed. This code is in .svc file for IIS hosted service.. Does that have to do anything. Because other VS project shows it.
    Monday, March 7, 2011 6:34 PM
  • If that doesn't work on the .svc file, split it in two - leave the .svc only with the <%@ ServiceHost %> directive, and move the class definition to a "normal" .cs file. Intellisense doesn't work on code inside .svc files.

    Monday, March 7, 2011 10:42 PM
  • How service host gets to know and execute this cs file..

     

    Monday, March 7, 2011 10:52 PM
  • If it's built along with your web application, ASP.NET/WCF will find it - the type name in the Factory attribute will be used to search for it.
    Monday, March 7, 2011 11:01 PM
  • Ok.

     

    Regarding serialization...I am using Reflection to call Db and pass arguments. Method is returning ..obj which I pass in as MyObject inside genericItem..and then fasade layer converts that to List<T>.

    Wcfservice is giving this message. I have added KnnownType and ServiceKnownType for DataContract and servicecontract...

    There was an error while trying to serialize parameter http://tempuri.org/:ReadListResult. The InnerException message was 'Type 'System.Collections.Generic.List`1[[Model.Snippet, Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' with data contract name 'ArrayOfSnippet:http://schemas.datacontract.org/2004/07/Model' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'.  Please see InnerException for more details.

    Wednesday, March 9, 2011 8:49 PM
  • The error message is saying that you need to add List<Model.Snippet> as a known type.
    • Marked as answer by matsha1 Wednesday, March 9, 2011 10:03 PM
    Wednesday, March 9, 2011 8:52 PM
  • The type of the object is List<Snippet>, not Snippet, so you're simply trying to assign an incompatible type here.
    Wednesday, March 9, 2011 9:18 PM
  • list = result.MyObject

     

     

    Gives this error.

    Unable to cast object of type 'System.Collections.Generic.List`1[Model.Snippet]' to type Model.Snippet'.

    as List<T>;
    Wednesday, March 9, 2011 9:20 PM
  • As I said, result.MyObject is of type List<Snippet>. The type of "list" is Snippet. You can't assign one to the other.
    Wednesday, March 9, 2011 9:25 PM
  • Typo: I have this

               list = result.MyObject as List<T>;

    Wednesday, March 9, 2011 9:27 PM
  • Whole code:

      List<T> list =new List<T>();

         list = result.MyObject as List<T>;

    return list;

    Wednesday, March 9, 2011 9:29 PM
  • Insert those lines:

    Console.WriteLine(result.MyObject.GetType().FullName);
    Console.WriteLine(typeof(T).FullName);

    That will give you more information on the problem

    Wednesday, March 9, 2011 9:31 PM
  • Okay..Tnx...It's working. Bin/Relase had old copy of dll...
    Wednesday, March 9, 2011 9:35 PM
  • Please open a new thread for this new question. I'm already quite confused with all the different questions/answers in this one (already over 45 posts), it will be almost impossible for any other people to be able to get useful information from this thread.
    Wednesday, March 9, 2011 9:57 PM
  • I have posted a new thread could you please look at it. Tnx
    Wednesday, March 9, 2011 10:19 PM