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.
Related
I have an API with multiple endpoints. I'd like to add a property to all endpoint responses, without adding it to each endpoint response model individually.
Ex:
public class MyClass
{
public string MyProperty { get; set; } = "Hello";
}
public class MyOtherClass
{
public string MyOtherProperty { get; set; } = "World";
}
public class MyController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<MyClass>> EndpointOne(POSTData data)
{
// implementation omitted
}
[HttpPost]
public async Task<ActionResult<MyOtherClass>> EndpointTwo(POSTOtherData otherData)
{
// implementation omitted
}
}
Calling either endpoint returns a JSON representation of MyClass or MyOtherClass as appropriate - i.e.
{ "MyProperty":"Hello" } or { "MyOtherProperty":"World" }
I want to add a property, say a string ApiName, to all endpoints in the API, so that the result of the above code would be either (as appropriate)
{ "MyProperty":"Hello", "ApiName":"My awesome API" }
or
{ "MyOtherProperty":"World", "ApiName":"My awesome API" }
Is there a way to hook into the JSON-stringified result just before returning and add a top-level property like that? If so, I presume I'd have to wire it up in startup.cs, so I've been looking at app.UseEndpoints(...) methods, but haven't found anything that's worked so far. Either it's not added the property, or it's replaced the original result with the new property.
Thanks in advance!
Use Newtonsoft.Json in your net web api
Register a custom contract resolver in Startup.cs:
builder.Services.AddControllers()
.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = CustomContractResolver.Instance);
The implementation:
public class CustomContractResolver : DefaultContractResolver {
public static CustomContractResolver Instance { get; } = new CustomContractResolver();
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
// add new property
...
properties.Add(newProp);
return properties;
}}
See more Json.net Add property to every class containing of a certain type
You can add a base class with the shared property. Should work for both XML and JSON.
public class MyApiClass
{
public string ApiName => "MyAwesomeApi";
}
public class MyClass : MyApiClass
{
public string MyProperty { get; set; } = "Hello";
}
public class MyOtherClass : MyApiClass
{
public string MyOtherProperty { get; set; } = "World";
}
public class MyController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<MyClass>> EndpointOne(POSTData data)
{
// implementation omitted
}
[HttpPost]
public async Task<ActionResult<MyOtherClass>> EndpointTwo(POSTOtherData otherData)
{
// implementation omitted
}
}
My 0.02 cents says to implement an abstract base class.
Abstract class inheritance look similar to a standard inheritance.
public class MyClass:MyAbstractClass
{
[JsonPropertyName("Class Property")]
public string MyProperty { get; set; } = "Hello";
}
public class MyOtherClass:MyAbstractClass
{
[JsonPropertyName("Class Property")]
public string MyOtherProperty { get; set; } = "World";
}
However the abstract class will allow you to implement additional features in the event you need them in the future.
public abstract class MyAbstractClass{
[JsonPropertyName("API Name")]
public string ApiName{get;set;}="My Aweomse API";
//Just a thought if you want to keep track of the end point names
//while keeping your object names the same
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string EndPointName{
get{
return get_endpoint_name();
}}
private string get_endpoint_name(){
return this.GetType().Name;
}
//May as well make it easy to grab the JSON
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string As_JSON{
get {
return to_json();
}}
private string to_json(){
object _myObject = this;
string _out;
JsonSerializerOptions options =
new JsonSerializerOptions {
WriteIndented = true };
_out =
JsonSerializer.Serialize(_myObject, options);
return _out;
}
}
Probably should have implemented a generic return object, then you could just loop through the task results. I suppose you still can if you have the task return only the JSON string.
public static void run(){
Task<MyClass> _t0 = task0();
Task<MyOtherClass> _t1 = task1();
Task[] _tasks = new Task[]{_t0,_t1};
Task.WhenAll(_tasks).Wait();
Console.WriteLine(""
+$"{_t1.Result.ApiName}:\n"
+$"End Point: {_t1.Result.EndPointName}:\n"
+$"JSON:\n{_t1.Result.As_JSON}");
Console.WriteLine(""
+$"{_t0.Result.ApiName}:\n"
+$"End Point: {_t0.Result.EndPointName}:\n"
+$"JSON:\n{_t0.Result.As_JSON}");
}
private static Task<MyClass> task0(){
return Task.Run(()=>{
Console.WriteLine("Task 0 Doing Something");
return new MyClass();
});
}
private static Task<MyOtherClass> task1(){
return Task.Run(()=>{
Console.WriteLine("Task 1 Doing Something");
return new MyOtherClass();
});
}
And of course the aweosome...awesome:-) results:
Another thought is that you could implement your two different tasks as abstract methods, but that's a different conversation all together.
In addition to all of the great answers, I prefer to use Action Filter and ExpandoObject.
In Program File you should add your custom action Filter.
builder.Services.AddControllers(opt =>
{
opt.Filters.Add<ResponseHandler>();
});
and ResponseHandler acts like below:
public class ResponseHandler : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var propertyInfo in (context.Result as ObjectResult).Value.GetType().GetProperties())
{
var currentValue = propertyInfo.GetValue((context.Result as ObjectResult).Value);
expando.Add(propertyInfo.Name, currentValue);
}
dynamic result = expando as ExpandoObject;
result.ApiName = context.ActionDescriptor.RouteValues["action"].ToString();
context.Result = new ObjectResult(result);
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
I have a question about WCF and Channel Factory usage.
On Host :
[ServiceContract]
public interface IGetMessage
{
[OperationContract]
string ShowMessage(Sample p, string Username, string Password);
}
[DataContract]
public class Sample
{
[DataMember]
public string Name { get; set; }
}
public string ShowMessage(Sample p, string Username, string Password)
{
return p.Name.ToString() + " - " + "Correct"; //Error line
}
On Client :
[ServiceContract()]
public interface IGetMessage
{
[OperationContract()]
string ShowMessage(Sample p, string Username, string Password);
}
[DataContract()]
public class Sample
{
[DataMember]
public string Name { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
Sample p1 = new Sample();
p1.Name = "ALEX";
BasicHttpBinding myBinding = new BasicHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress("http://MYURL/GetMessage.svc");
using (var myChannelFactory = new ChannelFactory<IGetMessage>(myBinding, myEndpoint))
{
IGetMessage client = null;
try
{
client = myChannelFactory.CreateChannel();
MessageBox.Show(client.ShowMessage(p1, "abc","123"));
((ICommunicationObject)client).Close();
myChannelFactory.Close();
}
catch
{
(client as ICommunicationObject)?.Abort();
}
}
}
If I add on client as Service Reference, it works perfectly. I can get "ALEX - Correct" message.
When I test on WcfTestClient.exe, it works perfectly.
But, I have a problem when using on Winform with above codes.
When I check on WcfServer Trace Log and Message Log;
System.NullReferenceException - Object reference not set to an instance of an object. Line number:22
Line number 22:
p.Name.ToString() on the Host's GetMessage.cvs.cs file.
I think, there is no problem on host. Problem is Client side.
I'd like to ask you how I made a mistake on the client side?
Regards.
As mentioned in the comments, to solve the problem you only to do is adding the namespace property to ServiceContract and DataContract. be sure that the namespace is identical between the client and the server. the reason why we should do is an error occurred during serialization and deserialization, we must ensure that the service contract and data contract has a consistent namespace between the server and client.
Here is my example(Add this feature on both the server and client.)
[ServiceContract(Namespace ="http://mydomain")]
public interface IGetMessage
{
[OperationContract]
string ShowMessage(Sample p, string Username, string Password);
}
[DataContract(Namespace = "http://mydomain")]
public class Sample
{
[DataMember]
public string Name { get; set; }
}
Result.
Feel free to let me know if there is anything I can help with.
In my .NET 4.0 application I'm accessing application properties through the interface ISettings I prepared:
public interface ISettings
{
int Quota { get; }
string Property2 { get; }
// ...
int PropertyN { get; }
}
// code generated by Visual Studio
public sealed partial class Settings :
global::System.Configuration.ApplicationSettingsBase
{
// application properties generated from app.config
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("123")]
public int Quota {
get {
return ((int)(this["Quota"]));
}
}
// and so on...
}
// my code to apply the interface to the Settings class
public sealed partial class Settings : ISettings
{
}
In some scenarios I would like to override the values from the config file depending on the organization I'm processing the data for, in example I would like to increase the quota for some organization. Of course, I could create the method similar to:
public int GetQuotaByOrgId(int orgId);
and implement the logic there, but I would like to avoid passing the orgId among the code. The better solution for me would be to create a proxy class overriding only the values I want to change, something like:
public class OverridenSettings : ISettings
{
private ISettings instance;
private int orgId;
private int[] orgsWithBiggerQuota = {1, 2, 132, 6542};
public OverridenSettings(ISettings instance, int orgId)
{
this.instance = instance;
this.orgId = orgId;
}
public override int Quota
{
get
{
int quota = this.instance.Quota;
if (this.orgsWithBiggerQuota.Contains(this.orgId))
{
quota += 1000;
}
return quota;
}
}
// all other properties should be taken from the default instance
}
Is there an elegant way to generate such class without having to explicitely implement all the interface's members just to redirect them to the default instance?
You can use any of the frameworks out there to create a dynamic proxy of your Settings class.
For example using Unity I can create an object of a class(in your case Settings class) like this
ISettings settings = (ISettings)Intercept.NewInstance(typeof(Settings), new VirtualMethodInterceptor(), new IInterceptionBehavior[] { new OrganizationInterceptor(orgId)});
The OrganizationInterceptor has the ability to 'intercept' method calls(including property getters/setters) and could have an implementation like:
public class OrganizationInterceptor : IInterceptionBehavior
{
private int OrgId { get; set; }
private List<int> orgsWithBiggerQuota;
public OrganizationInterceptor(int orgId)
{
OrgId = orgId;
WillExecute = orgId > 0;
}
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
var ret = getNext()(input, getNext);
if (input.MethodBase.IsSpecialName && input.MethodBase.Name == "get_Quota" &&
this.orgsWithBiggerQuota.Contains(OrgId))
ret.ReturnValue = (int)ret.ReturnValue + 100;
return ret;
}
public bool WillExecute { get; set; }
}
I haven't ran this myself so you might need to debug it a bit(especially the Invoke method).
If you want to use the VirtualMethodInterceptor you need to declare your property virtual. There is also TransparentProxyInterceptor which doesnt require this but will create another object that will call into your object(2 objects in total vs 1 in the virtual case).
WCF Service INTERFACE:
[ServiceContract]
public interface ITest
{
[OperationContract]
int TestCall(GenericType<MyType> x);
[OperationContract]
int TestAnotherCall(GenericType<MyOtherType> x);
}
[DataContract(Name = "GenericType")]
[KnownType(typeof(List<MyType>))]
[KnownType(typeof(List<MyOtherType>))]
public class GenericType<T>
{
[DataMember]
public List<T> Data
{
get { return data; }
set { data = value; }
}
}
WCF Service IMPLEMENTATION:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Test : ITest
{
public int TestCall(GenericType<MyType> x)
{
return x.Data.Count;
}
public int TestAnotherCall(GenericType<MyOtherType> x)
{
return x.Data.Count;
}
}
CLIENT
List<MyType> list = from a in ctx.Table
select new MyType (a.Field1, a.Field2, a.Field3).ToList();
GenericType gt = new GenericType();
gt.Data = list;
using(WCFClient client = new WCFClient())
{
client.TestCall(gt);
client.Close();
}
ERROR:
The remote server returned an unexpected response: (400) Bad Request.
if I pass NULL to "gt.Data" ...it works fine.
NOTE:
When I put the mouse over the gt.Data ...the hint shows as MyType[]
Not sure if that's expected.
After some review, I noticed that the Client Service only knows about
the 1st [KnownType] stated, in my case the List.
No knowledge of List ....
Is that expected when you put various [KnownType] on the WCF Interface?
You need to decorate your generic with the KnownType() attribute
[DataContract(Name = "GenericType")]
[KnownType(typeof(MyType))]
public class GenericType<T>
{
[DataMember]
public List<T> Data
{
get { return data; }
set { data = value; }
}
}
Quick Working Example:
Service
[OperationContract]
GenericType<MyType> GetDataUsingDataContract(GenericType<MyType> composite);
public class Service1 : IService1
{
public GenericType<MyType> GetDataUsingDataContract(GenericType<MyType> composite)
{
composite.Data.First().Stuff = "Test";
return composite;
}
}
Model
[DataContract(Name = "GenericType")]
[KnownType(typeof (MyType))]
public class GenericType<T>
{
[DataMember]
public List<T> Data { get; set; }
}
public class MyType
{
public string Stuff { get; set; }
}
Client
var client = new Service1Client();
var genericType = new GenericType
{
Data = new[]
{
new MyType(),
}
};
var result = client.GetDataUsingDataContract(genericType);
client.Close();
Console.WriteLine(result.Data.First().Stuff);
Console.ReadLine();
this example was generated with adding a service reference and not using a shared assembly
You need to attribute GenericType with the KnownType attribute for each of the classes that it can contain.
For example:
[KnownType(typeof(List<MyType>)]
public class GenericType<T>
The problem could be that you're not providing a type for T in GenericType when you instantiate it. Try this:
GenericType<MyType> gt = new GenericType<MyType>();
Instead of
GenericType gt = new GenericType();
It's the same syntax as when you instantiate any other generic class. For example, to declare a list of strings, (List as in List<T>...see here) you'd say:
List<String> myStrings = new List<String>();
What are the correct Attribute tags necessary to create an interoperable WebService that uses Request/Response wrappers?
I created the interface and functions already (and I split up my service into 3 parts (Client.dll, Shared.dll, Server.dll):
Server.dll:
public sealed class CalcServer : ICalculator
{
public AddResponse Add(AddRequest request)
{
return new AddResponse(request.Value1 + request.Value2);
}
}
Shared.dll:
public interface ICalculator
{
AddResponse Add(AddRequest request);
}
public sealed class AddResponse
{
public int Value { get; private set; }
public AddResponse()
{
Init();
}
public AddResponse(int value)
{
Init(value);
}
private void Init(int value = 0)
{
Value = value;
}
}
public sealed class AddRequest
{
public int Value1 { get; private set; }
public int Value2 { get; private set; }
public AddResponse()
{
Init();
}
public AddResponse(int value1 = 0, int value2 = 0)
{
Init(value1, value2);
}
private void Init(int value1 = 0, int value2 = 0)
{
Value1 = value1;
Value2 = value2;
}
}
Client.dll:
public interface CalcChannel : ICalculator, IClientChannel
{
}
public sealed class CalcClient : ClientBase<ICalculator>, ICalculator
{
public CalcClient(String endpointConfigurationName) :
base(endpointConfigurationName)
{
}
pubic int DoAdd(int val1, int val2)
{
AddRequest request = new AddRequest(val1, val2);
AddResponse response = ((ICalculatr)this).Add(request);
return response.Value;
}
AddResponse ICalculator.Add(AddRequest request)
{
return Channel.Add(request);
}
}
I'm just not sure what the necessary attribute tags are to keep this as an interoperable web service. I've looked it up on google, but it seems that different sources use different tags. Some use [ServiceContract()] and some use [WebService()]. On top of that there are server-side tags such as [ServiceBehavior()] and I wonder if that's necessary or not.
You have to decide if you want older style webservices (asmx) or WCF. The former uses [WebService] and [WebMethod], the latter uses [DataContract], [ServiceContract], [OperationContract] etc. They are completely different. You need different code and project types to host them as a service. When you create new web service projects in Visual Studio, you always get sample codes, but the web is full of them, too.
There isn't really a decision. Never use the old [WebService] style services unless you have to. Simple.