WCF and ServiceKnownType with many assemblies - wcf

WCF Question. I'm having trouble with multiple assemblies and inheritances and data contracts.
Senario: data contracts binaries are shared
Common.dll
[DataContract]
public abstract class Command
{
[DataMember]
public Guid Id { get; set; }
public Command(Guid id)
{
Id = id;
}
}
assembly1.dll
[DataContract]
public class DeleteStuff : Command
{
public DeleteStuff(Guid id)
: base(id) { }
[DataMember]
public int StuffToDeleteID { get; set; }
}
assembly2.dll
[DataContract]
public class DeleteSomeOtherStuff : Command
{
public DeleteSomeOtherStuff(Guid id)
: base(id) { }
[DataMember]
public int SomeOtherID { get; set; }
}
Service Contract
[ServiceContract]
[ServiceKnownType("GetKnownTypes", typeof(DerivedType))]
public partial interface ICommandsServiceContract
{
[OperationContract]
void Execute(IEnumerable<Command> command);
}
DerivedType class, method GetKnownTypes
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
//Works!*!*! but hard-coded, wont work in big picture since i dont know all the classes
//return new List<Type> { typeof(DeleteSomeOtherStuff), typeof(DeleteStuff) };
//DOESNT WORK!!
Type type = typeof(Command);
IEnumerable<Type> types = AppDomain.CurrentDomain.GetAssemblies().ToList()
.SelectMany(a => a.GetTypes()
.Where(t => type.IsAssignableFrom(t)));
IEnumerable<Type> j = types.ToArray();
return j;
}
If I put a break point on return j; above, when the service first runs it has the correct assembly types that inherit from Command. Then the client spins up and as soon as I send a DeleteSomeOtherStuff to the service it explodes with an error on the SelectMany clause.
Server Error in '/' Application.
Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
Source Error:
Line 25: var type = typeof(Command);
Line 26: var types = AppDomain.CurrentDomain.GetAssemblies().ToList()
Line 27: .SelectMany(a => a.GetTypes())
Line 28: .Where(t => type.IsAssignableFrom(t));
Line 29:
Line 27 is marked as the error.
I tried throwing the array list in a static variable to cache it when the service first runs and then it would be available when its called by a consumer but that had the same error.
I'm using a basic channel factory from the client.
Ideas?? I can't limit to a single assembly.
Thanks!!

In your classes that inherit from Command, add the [KnownType] attribute. For example,
[DataContract]
[KnownType(typeof(Command))]
public class DeleteStuff
{
}

Related

Exception when adding new method in a working Service using gRPC-Web and protobuf-net

I am getting the following exception:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: The type initializer for 'DefaultProxyCache1' threw an exception. System.TypeInitializationException: The type initializer for 'DefaultProxyCache1' threw an exception. ---> System.ArgumentException: Invalid generic arguments
Parameter name: typeArguments
at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.MakeGenericMethod_impl(System.Reflection.RuntimeMethodInfo,System.Type[])
at System.Reflection.RuntimeMethodInfo.MakeGenericMethod (System.Type[] methodInstantiation) <0x342def8 + 0x000d6> in :0
at ProtoBuf.Grpc.Internal.ContractOperation.TryGetClientHelper () [0x0001b] in //src/protobuf-net.Grpc/Internal/ContractOperation.cs:291
at ProtoBuf.Grpc.Internal.ProxyEmitter.EmitFactory[TService] (ProtoBuf.Grpc.Configuration.BinderConfiguration binderConfig) [0x00477] in //src/protobuf-net.Grpc/Internal/ProxyEmitter.cs:238
at ProtoBuf.Grpc.Internal.ProxyEmitter.CreateFactory[TService] (ProtoBuf.Grpc.Configuration.BinderConfiguration binderConfig) [0x0006d] in //src/protobuf-net.Grpc/Internal/ProxyEmitter.cs:123
at ProtoBuf.Grpc.Configuration.ClientFactory+DefaultProxyCache`1[TService]..cctor () [0x00000] in //src/protobuf-net.Grpc/Configuration/ClientFactory.cs:81
My project uses gRPC-Web, Blazor web assembly and protobuf-net
This is my service contract:
[ServiceContract(Name = "Services.Customer")]
public interface ICustomerService
{
ValueTask<Customer> CreateCustomer(Customer customerDTO);
ValueTask<CustomerResultSet> GetCustomers();
}
The implementation is:
public class CustomerService : ICustomerService
{
private readonly CustomerUseCases customerLogic;
public CustomerService(CustomerUseCases customerLogic)
{
this.customerLogic = customerLogic;
}
public async ValueTask<Customer> CreateCustomer(Customer customerDTO)
{
var result = await customerLogic.CreateCustomer(customerDTO);
return customerDTO;
}
public async ValueTask<CustomerResultSet> GetCustomers()
{
CustomerResultSet result = new CustomerResultSet { Customers = await customerLogic.GetCustomer() };
return result;
}
}
As for the Datacontracts:
[DataContract]
public class CustomerResultSet
{
[DataMember(Order = 1)]
public IEnumerable<Customer> Customers { get; set; }
}
And,
[DataContract]
public partial class Customer
{
[DataMember(Order = 1)]
public int CustomerId { get; set; }
[DataMember(Order = 2)]
public string CustomerName { get; set; }
}
Before I was returning a List of customers in the service but I realize I needed a class to model the message in order to protobuf-net be able to serialize that is why CustomerResultSet. Still, it is not working.
Any help much appreciated
That is... odd. I can't repro it here, so I'm guessing it is something specific to Blazor. I've checked what the code does in the "regular" frameworks, and at least for me it seems to do the right things - using UnaryValueTaskAsync<Customer, Customer>() and UnaryValueTaskAsync<Empty, CustomerResultSet>(), which is what I would expect. I've improved the exception handling in that code path, to at least give us a clue what it is trying to do, so my suggestion is:
update to protobuf-net.Grpc version >= 1.0.119 (I'll get it deployed as soon as CI finishes)
retry, and let me know exactly what it says now
Alternatively, if you have a minimal repro including the blazor bits on, say, a GitHub repo, I can happily take a look there.
(tip: I try to keep an eye on both Stack Overflow and GitHub, but GitHub is probably more appropriate for this kind of question - I'd happily say that this is a bug, so: https://github.com/protobuf-net/protobuf-net.Grpc/issues)
I was having a similar problem.
System.InvalidOperationException: Error obtaining client-helper 'UnaryValueTaskAsync' (from: 'System.Guid', to: 'Test.DTO.OpResult'): Invalid generic arguments
My entire ServiceContract on that service stopped working. This happened after I added
ValueTask ChangeCompany(Guid companyGuid);
I changed it to
ValueTask ChangeCompany(string companyGuid);
And that got it working again. The error was a bit confusing since i wasn't using ChangeCompany, but like a said, not calls were working.

Cannot create a DbSet for 'Model' because this type is not included in the model for the context

I do a Generic and using DI
so I create a empty class
public class DBRepo
{
}
and my model class to inheriting class DBRepo
public partial class UserAccount : DBRepo
{
public int Id { get; set; }
public string Account { get; set; }
public string Pwd { get; set; }
}
then this is a Interface to do CRUD
public interface IDBAction<TEntity> where TEntity : class,new()
{
void UpdateData(TEntity _entity);
void GetAllData(TEntity _entity);
}
public class DBService<TEntity> : IDBAction<TEntity> where TEntity : class,new()
{
private readonly CoreContext _db;
public DBService(CoreContext _db)
{
this._db = _db;
}
public void UpdateData(TEntity _entity)
{
this._db.Set<TEntity>().UpdateRange(_entity);
this._db.SaveChanges();
}
public void GetAllData(TEntity _entity)
{
var x = this._db.Set<TEntity>().Select(o => o).ToList();
}
}
And I Dependency Injection Service Provider in constructor
this.DBProvider = new ServiceCollection()
.AddScoped<IDBAction<DBRepo>, DBService<DBRepo>>()
.AddScoped<DBContext>()
.AddDbContext<CoreContext>(options => options.UseSqlServer(ConnectionString))
.BuildServiceProvider();
last step I Get Services
DBProvider.GetService<IDBAction<DBRepo>>().GetAllData(new UserAccount());
I will get a error message same with title
or I change to
DBProvider.GetService<IDBAction<UserAccount>>().GetAllData(new UserAccount());
I'll get other message
Object reference not set to an instance of an object.'
but the void UpdateData() is can work,
so how to fix GetAllData() problem?
The error simply is because the class you're using here UserAccount has apparently not been added to your context, CoreContext. There should be a property there like:
public DbSet<UserAccount> UserAccounts { get; set; }
Regardless of whether you end up using the generic Set<T> accessor, you still must defined a DbSet for the entity on your context.
That said, you should absolutely not be creating your own service collection inside your repo. Register your context and your repo with the main service collection in Startup.cs and then simply inject your repo where you need it. The DI framework will take care of instantiating it with your context, as long as you have a constructor that takes your context (which you seem to).
And that said, you should ditch the repo entirely. It still requires a dependency on Entity Framework and doesn't do anything but proxy to Entity Framework methods. This is just an extra thing you have to maintain and test with no added benefit.

Return Entity Framework objects over WCF

We have a problem concerning Entity Framework objects and sending them through WCF.
We have a database, and Entity Framework created classes from that database, a 'Wallet' class in this particular situation.
We try to transfer a Wallet using this code:
public Wallet getWallet()
{
Wallet w = new Wallet();
w.name = "myname";
w.walletID = 123;
return w;
}
We need to transfer that Wallet class, but it won't work, we always encounter the same exception:
"An error occurred while receiving the HTTP response to localhost:8860/ComplementaryCoins.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details."
We searched on the internet, and there is a possibility that the error is due to the need of serialization of Entity Framework-objects.
We have absolutely no idea if this could be the case, and if this is the case, how to solve it.
Our DataContract looks like this (very simple):
[DataContract]
public partial class Wallet
{
[DataMember]
public int getwalletID { get { return walletID; } }
[DataMember]
public string getname { get { return name; } }
}
Does anyone ever encountered this problem?
EDIT: Our Entity Framework created class looks like this:
namespace ComplementaryCoins
{
using System;
using System.Collections.Generic;
public partial class Wallet
{
public Wallet()
{
this.Transaction = new HashSet<Transaction>();
this.Transaction1 = new HashSet<Transaction>();
this.User_Wallet = new HashSet<User_Wallet>();
this.Wallet_Item = new HashSet<Wallet_Item>();
}
public int walletID { get; set; }
public string name { get; set; }
public virtual ICollection<Transaction> Transaction { get; set; }
public virtual ICollection<Transaction> Transaction1 { get; set; }
public virtual ICollection<User_Wallet> User_Wallet { get; set; }
public virtual ICollection<Wallet_Item> Wallet_Item { get; set; }
}
}
Thanks for helping us.
I had the same problem some time ago and the solution for this was:
The entity framework was returning a serialized class instead of normal class.
eg. Wallet_asfawfklnaewfklawlfkawlfjlwfejlkef instead of Wallet
To solve that you can add this code:
base.Configuration.ProxyCreationEnabled = false;
in your Context file.
Since the context file is auto generated you can add it in the Context.tt
In the Context.tt file it can be added around lines 55-65:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
base.Configuration.ProxyCreationEnabled = false;
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
Try specifying a setter for the properties, something like this :
[DataContract]
public partial class Wallet
{
[DataMember]
public int getwalletID { get { return walletID; } set { } }
[DataMember]
public string getname { get { return name; } set { } }
}
If it still doesn't work, you may consider creating an intermediate POCO class for this purpose, and use mapper library like AutoMapper or ValueInjecter to transfer the data from the EF objects.
The POCO class should have same properties as your EF class :
[DataContract]
public class WalletDTO
{
[DataMember]
public int walletID { get; set; }
[DataMember]
public string name { get; set; }
}
And modify your method to return this class instead :
public WalletDTO getWallet()
{
Wallet w = new Wallet(); // or get it from db using EF
var dto = new WalletDTO();
//assuming we are using ValueInjecter, this code below will transfer all matched properties from w to dto
dto.InjectFrom(w);
return dto;
}
Are you trying to recieve a IEnumerable<Wallets>? If - yes, please modify your server class that returns the IEnumerable by adding .ToArray() method

OData / WCF Data Service not working with complex type

I'm brand new to OData and WCF data services so this might be an easy problem. I'm using VS Web Developer Express 2010 where I have a very simple WCF Data Service hosted in a console app. It's returning an IQuerable collection of a simple 'Study' class from a repository (located in a separated dll project), which in turn retrieves 'Study' classes from a db project in another dll (so 3 projects in the solution).
I also have an 'Experiment' class in the db project and there can be multiple Experiments in a Study. When I exclude the Experiment class from the Study everything works and I get data coming back. The problem happens when I add a List collection to the Study class, then I get a runtime error when I try to run the service. In Firebug the error is '500 Internal Server Error', and the message in the browser is 'Request Error. The server encountered an error processing the request. See server logs for more details.'
I have IIS 7 and I also just installed IIS 7.5 but again it's brand new to me, so I can't figure out where the service is hosted or where to view the server / web logs. There are only IIS 7 logs visible in 'C:\inetpub\logs\LogFiles\W3SVC1'. The VS web server (Cassini) doesn't start when I run the app, so this suggests it's being hosted in IIS 7.5 (?).
So
- how do I return child classes / complex objects?
- how do I know where my service is hosted and where can I find the server logs?
Here's the host app:
using MyStudyRepository;
using MyStudyDB;
namespace MyStudyService
{
public class Program
{
public static void Main(string[] args)
{
string serviceAddress = "http://localhost:998";
Uri[] uriArray = { new Uri(serviceAddress) };
Type serviceType = typeof(StudyDataService);
using (var host = new DataServiceHost(serviceType,uriArray))
{
host.Open();
Console.WriteLine("Press any key to stop service");
Console.ReadKey();
}
}
}
public class StudyDataService : DataService<StudyRepository>
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
}
}
}
Here's the repository:
using MyStudyDB;
namespace MyStudyRepository
{
public class StudyRepository
{
List<Study> _List = new List<Study>();
//Add constructor to populate myStudies list on creation of class
public StudyRepository()
{
for (int i = 1; i < 5; i++)
{
Study myStudy = new Study() { ID = i, StudyOwnerId = i, StudyName = "Study" + i.ToString() /*, Experiments = null */ };
_List.Add(myStudy);
}
}
public IQueryable<Study> Studies
{
get
{
return _List.AsQueryable<Study>();
}
}
}
}
And here's the DB:
namespace MyStudyDB
{
public class Study
{
public int ID { get; set;}
public int StudyOwnerId { get; set; }
public string StudyName { get; set; }
//public List<Experiment> Experiments { get; set; }
}
public class Experiment
{
public int ID { get; set; }
public string Name { get; set; }
public int StudyId { get; set; }
}
}
To debug the WCF Data Service please refer to this blog post: http://blogs.msdn.com/b/phaniraj/archive/2008/06/18/debugging-ado-net-data-services.aspx
As to why the collection of Experiment doesn't work, there are two reasons:
The Experiment class is not recognized as an entity type because there's no entity set for it. (Entity set is the IQueryable property on your repository class, which you don't have). As a result the Experiment class is only recognized as a complex type.
The currently released version of WCF Data Services doesn't support MultiValues, MultiValue is effectively a collection of primitive or complex types.
So you have two way to "fix" this. Either make sure that Experiment is in fact an entity, by adding IQueryable property on your repository class.
Or use the latest CTP (http://blogs.msdn.com/b/astoriateam/archive/2011/06/30/announcing-wcf-data-services-june-2011-ctp-for-net4-amp-sl4.aspx) which does support MultiValues.
Thanks! And I guess it is missing the DataServiceKey attribute on the class as follows:
[DataServiceKey("ID")]
public class Study
{
.....
}

Silverlight 2, Cannot Update Service Ref with New Service

In Silverlight 2, I am attempting to add a new service which will return an object containing two lists from the WCF Service to the Silverlight app on the client. The svc and interface file already contain two contracts which work and are being used. After adding the new service, I click on the "Update Service Reference" option in the Silverlight app and receive the error:
There was an error downloading "http://localhost:3005/CMS.svc" ...
Metadata contains a reference that cannot be resolved "http://localhost:3005/CMS.svc" ...The client and service bindings may be mismatched...
Even though the web service project rebuilds without error, I think there must be something wrong with the way I have defined the service in the web service project, because when I remove the new service, the remaining two services are updated OK, and if I add a new service which I know is OK, the service reference will update OK. So I don t think it is a problem of endpoints, or the port number, etc.
The new service is supposed to return an object which contains two lists.
Here is the code:
In the interface file:
namespace CMSSilverlight.Web
{
// NOTE: If you change the interface name "ICMS" here, you must also update the reference to "ICMS" in Web.config.
[ServiceContract]
public interface ICMS
{
[OperationContract]
POCollection GetPOCollection(String s);
}
[DataContract]
public class POCollection
{
[DataMember]
public IList<Employee> em;
[DataMember]
public IList<School> sc;
}
public class Employee
{
public string EmpID { get; set; }
public string EmpName { get; set; }
public Employee(string empID, string empName)
{
this.EmpID = empID;
this.EmpName = empName;
}
}
public class School
{
public string SchID { get; set; }
public string SchName { get; set; }
public School(string schID, string schName)
{
this.SchID = schID;
this.SchName = schName;
}
}
}
In the service file:
namespace CMSSilverlight.Web
{
{
public POCollection GetPOCollection(String sParam)
{
IList<Employee> empList = new List<Employee>();
for (int i = 0; i < 5; i++)
{
empList.Add(new Employee(i.ToString(), i.ToString() + " Emp Name"));
}
IList<School> schList = new List<School>();
for (int i = 0; i < 5; i++)
{
schList.Add(new School(i.ToString(), i.ToString() + " Sch Name"));
}
POCollection po = new POCollection()
{
em = empList,
sc = schList
};
return po;
}
}
}
James,
Many thanks - I should have though of that. At any rate below was the error. It was just a matter of adding the [DataContractAttribute] attribute to the Employee and School classes, and everything worked fine. This is a frustrating learning process, but it is nice when a solution is revealed.
An ExceptionDetail, likely created by `IncludeExceptionDetailInFaults=true`, whose value is:
System.InvalidOperationException: An exception was thrown in a call to a WSDL export extension: System.ServiceModel.Description.DataContractSerializerOperationBehavior
contract: http://tempuri.org/:ICMS ----> System.Runtime.Serialization.InvalidDataContractException: Type 'CMSSilverlight.Web.Employee' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. See the Microsoft .NET Framework documentation for other supported types.