I'm working on a WCF service and trying to cleanup code. Issue I'm running into is I have a base data type I want to send as results from the service to the client; however in the service code itself it will mostly make use of classes derived on the base with additional properties for processing. The client has no reason to know about these at all.
Right now I can get this working but only if I define the derived classes in the shared library. I do not want them in there as they are specific solely to the service.
Below is example to show the problem. All three files are in separate projects in the same solution.
Common.IPersonService.cs
using System.ServiceModel;
using System.Runtime.Serialization;
namespace Common
{
[ServiceContract]
public interface IPersonService
{
[OperationContract(Name = "GetPersonById")]
Person GetPersonById(int id);
}
[DataContract(Name = "Person")]
public class Person
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
}
}
WcfClient.Program.cs
using System;
using System.ServiceModel;
using Common;
namespace WcfClient
{
class Program
{
static void Main(string[] args)
{
var binding = new NetTcpBinding();
var endpoint = new EndpointAddress("net.tcp://localhost:8001/WcfTest/");
var factory = new ChannelFactory<IPersonService>(binding, endpoint);
IPersonService service = null;
try
{
service = factory.CreateChannel();
Person result = service.GetPersonById(5);
Console.WriteLine(result.Name);
((ICommunicationObject)service).Close();
Console.ReadLine();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
}
}
WcfService.Program.cs
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Description;
using Common;
namespace WcfService
{
[DataContract(Name = "Person")]
public class Contact : Person
{
public string Address { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (ServiceHost serviceHost = new ServiceHost(typeof(PersonService)))
{
serviceHost.Open();
Console.WriteLine("Service started");
Console.ReadLine();
}
}
}
public class PersonService : IPersonService
{
private Dictionary<int, Contact> _testData = new Dictionary<int, Contact>();
public PersonService()
{
Random rnd = new Random();
for (int i = 0; i < 100; i++)
{
_testData.Add(i + 1, new Contact()
{
Id = i + 1,
Name = Guid.NewGuid().ToString(),
Address = Guid.NewGuid().ToString()
});
}
}
public static void Configure(ServiceConfiguration config)
{
config.AddServiceEndpoint(typeof(IPersonService), new NetTcpBinding(), "net.tcp://localhost:8001/WcfTest/");
config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
}
public Person GetPersonById(int id)
{
return _testData[id];
}
public Person GetValueByKey(string key)
{
return null;
}
}
}
The exception received is the following:
The socket connection was aborted. This could be caused by an error
processing your message or a receive timeout being exceeded by the
remote host, or an underlying network resource issue. Local socket
timeout was '00:00:59.9780000'.
Now if I move the Contact class from the WcfService project and put it in the Common project it will work. As said though I'd prefer not to muddy a common library with items specific to the service implementation.
Thanks!
Related
I'm having a bit of trouble with using Azure storage. I have an implementation at the moment which is fine but I want to expand it so that I am able to use multiple storage accounts/containers in one solution. I can't get my head around how to do that and still allow for dependency injection. I also need to be able to pass in settings which define the connection string and container name
This is how I'm doing it at the moment:
builder.Services.AddSingleton<IAzureStorageClient, AzureStorageClient>();
builder.Services.Configure<AzureStorageSettings>(configuration.GetSection("AzureStorageSettings"));
and then in the constructor
public AzureStorageClient(IOptions<AzureStorageSettings> options)
{
var azureStorageSettings = options.Value;
var cloudStorageAccount = GetCloudStorageAccount(azureStorageSettings.ConnectionString);
_blobClient = cloudStorageAccount.CreateCloudBlobClient();
_blobContainer = GetBlobContainer(azureStorageSettings.ContainerName);
}
I've read a lot of similar posts which mention using named registrations but I am using the built in IoC container and it doesn't allow for that. I've also seen posts saying to use a factory which looks good but I am hoping to package this logic and share it among different solutions and the factory solution would require a lot of configuration which I would like to avoid to make the package easy to consume.
Update:
I made the settings an interface to force it to be implemented each time it is required and I used the generic T to pass that into my storage client as follows:
public sealed class AzureStorageClient<T> : IAzureStorageClient<T> where T : class, IAzureStorageSettings, new()
public AzureStorageClient(IOptions<T> options)
{
var azureStorageSettings = options.Value;
var cloudStorageAccount = GetCloudStorageAccount(azureStorageSettings.ConnectionString);
_blobClient = cloudStorageAccount.CreateCloudBlobClient();
_blobContainer = GetBlobContainer(azureStorageSettings.ContainerName);
}
Then it could be injected like this:
builder.Services.AddSingleton<IAzureStorageClient<SpecificStorageSettings>, AzureStorageClient<SpecificStorageSettings>>();
Just an example, you can use settings like this:
Settings.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace UseDifferentSettings
{
public abstract class Settings
{
public abstract string connectingstring {
get;
}
public abstract string containername {
get;
}
public abstract string blobname {
get;
}
}
}
Setting1.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace UseDifferentSettings
{
class Setting1 : Settings
{
string _connectingstring = "DefaultEndpointsProtocol=https;AccountName=xxx;EndpointSuffix=core.windows.net";
string _containername = "video1";
string _blobname = "test.txt";
public override string connectingstring
{
get { return _connectingstring; }
}
public override string containername
{
get { return _containername; }
}
public override string blobname
{
get { return _blobname; }
}
}
}
Setting2.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace UseDifferentSettings
{
class Setting2 : Settings
{
private string _connectingstring = "DefaultEndpointsProtocol=https;AccountName=xxx;EndpointSuffix=core.windows.net";
private string _containername = "test";
private string _blobname = "test.txt";
public override string connectingstring
{
get { return _connectingstring; }
}
public override string containername
{
get { return _containername; }
}
public override string blobname
{
get { return _blobname; }
}
}
}
UploadToStorage.cs
using Azure.Storage.Blobs;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace UseDifferentSettings
{
public class UploadToStorage
{
Settings setting;
public UploadToStorage(Settings setting) {
this.setting = setting;
}
public void GoUpload() {
string connectingstring = setting.connectingstring;
string containername = setting.containername;
string blobname = setting.blobname;
string filecontent = "This is my test file content";
byte[] array = Encoding.ASCII.GetBytes(filecontent);
MemoryStream filestream = new MemoryStream(array);
BlobServiceClient blobServiceClient = new BlobServiceClient(connectingstring);
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containername);
BlobClient blobClient = containerClient.GetBlobClient(blobname);
blobClient.Upload(filestream);
}
}
}
Program.cs(The main method class)
using System;
namespace UseDifferentSettings
{
class Program
{
static void Main(string[] args)
{
Settings setting1 = new Setting1();
Settings setting2 = new Setting2();
UploadToStorage uploadtostorage = new UploadToStorage(setting1);
uploadtostorage.GoUpload();
Console.WriteLine("Hello World!");
}
}
}
With WCF, assume the following service interface:
[ServiceContract]
public interface IForDataTypeA
{
[OperationContract]
List<DataTypeA> GetValues();
}
[DataContract]
public class DataTypeA { }
This is how it's defined on the server side (service host).
Now, someone - with no access to the server's source code - tries to use this WCF service. He defines the service interface himself but accidentally changes the name of the data type to DataTypeB:
[ServiceContract]
public interface IForDataTypeA
{
[OperationContract]
List<DataTypeB> GetValues();
}
[DataContract]
public class DataTypeB { }
When he now calls GetValues() (via ChannelFactory<IForDataTypeA>) the returned list will always be empty but no exception is thrown.
Is there a way to make WCF throw an exception when the elements in the list can't be deserialized (instead of returning an empty list)?
Here's the complete code to reproduce the problem:
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace TestProj
{
[DataContract]
public class DataTypeA { }
[DataContract]
public class DataTypeB { }
[ServiceContract]
public interface IForDataTypeA
{
[OperationContract]
List<DataTypeA> GetValues();
}
[ServiceContract(Name = "IForDataTypeA")]
public interface IForDataTypeB
{
[OperationContract]
List<DataTypeB> GetValues();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class ServiceImpl : IForDataTypeA
{
public List<DataTypeA> GetValues()
{
return new List<DataTypeA>
{
new DataTypeA(),
new DataTypeA(),
};
}
}
class Program
{
static void Main()
{
var serviceHost = new ServiceHost(new ServiceImpl());
var binding = new NetTcpBinding();
var endpointUrl = "net.tcp://127.0.0.1:5555/MyService";
serviceHost.AddServiceEndpoint(typeof(IForDataTypeA), binding, endpointUrl);
serviceHost.Open();
var channelFactory = new ChannelFactory<IForDataTypeB>(binding, endpointUrl);
var channel = channelFactory.CreateChannel();
List<DataTypeB> values = channel.GetValues();
// values will always be empty
}
}
}
Imagine a WCF service running on IIS. It has one method which returns one type:
namespace TheServer
{
[ServiceContract]
public interface IServerSideInterface
{
[OperationContract]
ServerSideResultType CreateParentData(ServerSideParameterType input);
}
}
However, on the client I wish to have:
namespace TheClient
{
[ServiceContract]
public interface IClientSideInterface
{
[OperationContract]
ClientSideResultType CreateParentData(ClientSideParameterType input);
}
}
It's actually slightly more complicated as I want it to be asynchronous, but one step at a time.
I wish to use a ChannelFactory to communicate from the client to the server.
It is here I am stuck.
The next bit of code uses the type names I'm using in my sample.
...
private readonly IClientWcfServiceChannel _client;
public ChanFacWcfServiceMainPageViewModel()
{
var f = new ChannelFactory<IClientWcfServiceChannel>(new BasicHttpBinding(),
new EndpointAddress("http://localhost:50001/WcfService.svc"));
_client = f.CreateChannel();
FireCommand = new RelayCommand(Execute);
}
private void Callback(IAsyncResult ar)
{
var result2 = _client.EndCreateParentData(ar);
//var result = ((IClientWcfService)ar.AsyncState).EndCreateParentData(ar);
Result = result2.ToString();
}
private void Execute()
{
_client.BeginCreateParentData(ClientWcfServiceStartUpMode.StartUpLater, Callback, SynchronizationContext.Current);
}
This gives me a "not found" exception in the Callback method.
How do I map from the server type to the client type? They are essentially identical except for the names. On the server everything starts "Server" and on the client, the types were copy and pasted and renamed with "Client" at the start. The namespace also.
I do not want to use a shared type in a library common to both projects and I do not want to use svcUtil or "Add service reference" to create proxies (although I have to poke at their code).
Additional info:
VS 2012 + 4.5.
Ok, so I've got it to work. There were a number of issues mixing together to make it more complicated than I expected. The end result uses a Silverlight client. Silverlight has some specific restrictions in that you have to use async calls for services. It throws an exception if you try to access a synchronous called service.
EDIT: Added some DataMember attributes.
So on the server I annotated the interfaces and classes:
[ServiceContract(Name = "MyServiceClass", Namespace = "Ian.Server")]
public interface IServerWcfService
{
[OperationContract]
ServerWcfServiceParentData CreateParentData(ServerWcfServiceStartUpMode mode);
}
[DataContract(Name = "ServiceChildData", Namespace = "Ian.Server")]
public class ServerWcfServiceParentData
{
[DataMember]
public IEnumerable<ServerWcfServiceChildData> Children { get; private set; }
}
[DataContract(Name = "ServiceChildData", Namespace = "Ian.Server")]
public class ServerWcfServiceChildData
{
[DataMember]
public string ChildName { get; set; }
[DataMember]
public ServerWcfServiceChildData NestedChild { get; set; }
[DataMember]
public string Text { get; set; }
}
[DataContract(Name = "ServiceStartUpMode", Namespace = "Ian.Server")]
public enum ServerWcfServiceStartUpMode
{
[EnumMember(Value = "None")]
None,
[EnumMember(Value = "StartUpNow")]
StartUpNow,
[EnumMember(Value = "StartUpLater")]
StartUpLater
}
On the client I created the same classes but with my new names and similar annotations:
[ServiceContract(Name = "MyServiceClass", Namespace = "Ian.Server")]
public interface IClientWcfService
{
[OperationContract(AsyncPattern = true, Action = "Ian.Server/MyServiceClass/CreateParentData",
ReplyAction = "Ian.Server/MyServiceClass/CreateParentDataResponse")]
IAsyncResult BeginCreateParentData(ClientWcfServiceStartUpMode mode, AsyncCallback callback, object asyncState);
ClientWcfServiceParentData EndCreateParentData(IAsyncResult result);
}
public interface IClientWcfServiceChannel : IClientWcfService, IClientChannel
{
}
[DataContract(Name = "ServiceChildData", Namespace = "Ian.Server")]
public class ClientWcfServiceParentData
{
[DataMember]
public IEnumerable<ClientWcfServiceChildData> Children { get; set; }
}
[DataContract(Name = "ServiceChildData", Namespace = "Ian.Server")]
public class ClientWcfServiceChildData
{
[DataMember]
public string ChildName { get; set; }
[DataMember]
public ClientWcfServiceChildData NestedChild { get; set; }
[DataMember]
public string Text { get; set; }
}
[DataContract(Name = "ServiceStartUpMode", Namespace = "Ian.Server")]
public enum ClientWcfServiceStartUpMode
{
[EnumMember(Value = "None")]
None,
[EnumMember(Value = "StartUpNow")]
StartUpNow,
[EnumMember(Value = "StartUpLater")]
StartUpLater
}
Notice the async changes for the service contract. We have a Begin and End pair with the async flag set. Only the Begin has an OperationContract attribute.
Also I set the Action and ReplyAction to the values I found in the wsdl.
I have a viewmodel in my Silverlight App, the important parts for calling the service are here:
private string _result;
private readonly IClientWcfServiceChannel _client;
public ChanFacWcfServiceMainPageViewModel()
{
var f = new ChannelFactory<IClientWcfServiceChannel>(new BasicHttpBinding(),
new EndpointAddress("http://localhost:50001/WcfService.svc"));
_client = f.CreateChannel();
FireCommand = new RelayCommand(Execute);
}
private void Callback(IAsyncResult ar)
{
var context = ar.AsyncState as SynchronizationContext;
if (context == null)
{
throw new Exception("wtf");
}
var result2 = _client.EndCreateParentData(ar);
context.Post(o => { Result = result2.ToString(); }, null);
}
private void Execute()
{
_client.BeginCreateParentData(ClientWcfServiceStartUpMode.StartUpLater, Callback, SynchronizationContext.Current);
}
It's quite scrappy still, passing the SyncContext around and so on, but it does work.
For this example it just returns the type name to the view, which is pointless but proves it has a) returned something and b) that the type is the type I expected.
Importantly there is no shared code. Nothing exists in a common Portable library for example.
I am working on a project using windows Azure and within an worker role i want to use a webservice to submit some information to it.My question is: Can i use the webservice without adding a service reference to it? or to add it in a certain way that when i publish my project in azure i don't need to change the service reference?
You can connect directly using a channel factory. Here's a sample base repository class, which you would override, where T is your Service Contract, such as IMyService,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Microsoft.WindowsAzure.ServiceRuntime;
namespace pageengine.clients
{
public class RepositoryBase<T> : IDisposable
{
#region Channel
protected String roleName;
protected String serviceName;
protected String endpointName;
protected String protocol = #"http";
protected EndpointAddress _endpointAddress;
protected BasicHttpBinding httpBinding;
protected NetTcpBinding tcpBinding;
protected IChannelFactory channelFactory;
protected T client;
protected virtual AddressHeader[] addressHeaders
{
get
{
return null;
}
}
protected virtual EndpointAddress endpointAddress
{
get
{
if (_endpointAddress == null)
{
var endpoints = RoleEnvironment.Roles[roleName].Instances.Select(i => i.InstanceEndpoints[endpointName]).ToArray();
var endpointIP = endpoints.FirstOrDefault().IPEndpoint;
if(addressHeaders != null)
{
_endpointAddress = new EndpointAddress(new Uri(String.Format("{1}://{0}/{2}", endpointIP, protocol, serviceName)), addressHeaders);
}
else
{
_endpointAddress = new EndpointAddress(String.Format("{1}://{0}/{2}", endpointIP, protocol, serviceName));
}
}
return _endpointAddress;
}
}
protected virtual Binding binding
{
get
{
switch (protocol)
{
case "tcp.ip":
if (tcpBinding == null) tcpBinding = new NetTcpBinding();
return tcpBinding;
default:
//http
if (httpBinding == null) httpBinding = new BasicHttpBinding();
return httpBinding;
}
}
}
public virtual T Client
{
get
{
if (this.client == null)
{
this.channelFactory = new ChannelFactory<T>(binding, endpointAddress);
this.client = ((ChannelFactory<T>)channelFactory).CreateChannel();
((IContextChannel)client).OperationTimeout = TimeSpan.FromMinutes(2);
var scope = new OperationContextScope(((IContextChannel)client));
addCustomMessageHeaders(scope);
}
return this.client;
}
}
protected virtual void addCustomMessageHeaders(OperationContextScope operationContextScope)
{
// Overidden
}
#endregion
#region CTOR
public RepositoryBase()
{
}
#endregion
#region IDisposable Members
public virtual void Dispose()
{
if (client != null)
{
try
{
((ICommunicationObject)client).Close();
}
catch
{
try
{
((ICommunicationObject)client).Abort();
}
catch { } // Die quietly.
}
}
if (channelFactory != null)
{
try
{
channelFactory.Close();
}
catch
{
try
{
channelFactory.Abort();
}
catch { } // Die quietly.
}
channelFactory = null;
}
_endpointAddress = null;
httpBinding = null;
tcpBinding = null;
}
#endregion
}
}
You are then discovering the endpoint, which will work both in the published and emulated environments. A class extending this base might look like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using pageengine.services.entities.account;
using pageengine.services;
namespace pageengine.clients.accounts
{
public class AccountsRepository : RepositoryBase<IAccounts>, IDisposable
{
#region CTOR
public AccountsRepository()
{
this.roleName = "EntitiesRole"; // Name of the role my service is on
this.endpointName = "HttpInternal"; // Name of the endpoint configured on that role. Can be internal or input, tcp or http.
this.serviceName = "Accounts.svc"; // Name of my service.
}
#endregion
}
}
Calls to your service (in this case from an MVC controller action) are then in the form of:
public ActionResult ListAccounts()
{
using (var accountsRepository = new AccountsRepository())
{
return View("ListAccounts", accountsRepository.Client.ListAccounts());
}
}
This is my first WCF Server:
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
namespace Myns.MBClient
{
[ServiceContract]
public interface IManagementConsole
{
[OperationContract]
ConsoleData GetData(int strategyId);
}
[ServiceContract]
public class ConsoleData
{
private int currentIndicator;
[OperationContract]
public double GetCurrentIndicator()
{
return currentIndicator;
}
public void SetCurrentIndicator(int currentIndicator)
{
this.currentIndicator = currentIndicator;
}
}
class ManagementConsole : IManagementConsole
{
public ConsoleData GetData(int strategyId)
{
ConsoleData data = new ConsoleData();
data.SetCurrentIndicator(33);
return data;
}
}
}
In client I just call pipeProxy.GetData(0).GetCurrentIndicator()
Why program prints 0 while it supposed to print 33?
Client code (which I think has no problems):
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using Commons;
using myns.MBClient;
namespace myns.MBClientConsole
{
class Program
{
static void Main(string[] args)
{
ChannelFactory<IManagementConsole> pipeFactory =
new ChannelFactory<IManagementConsole>(
new NetNamedPipeBinding(),
new EndpointAddress(
"net.pipe://localhost/PipeMBClientManagementConsole"));
IManagementConsole pipeProxy =
pipeFactory.CreateChannel();
while (true)
{
string str = Console.ReadLine();
Console.WriteLine("pipe: " +
pipeProxy.GetData(0).GetCurrentIndicator());
}
}
}
}
If you create your own complex type to use with WCF you have to add a DataContract attribute instead of a ServiceContract, and you should use fields/properties that are decorated with DataMember. And do yourself a favor and use plain DTOs (DataTransferObjects - Objects with only fields/properties but no behavior):
[DataContract]
public class ConsoleData
{
[DataMember]
public int CurrentIndicator {get;set;}
}
You can find more on this here