WCF REST Proxy using ChannelFactory - c#

I'm trying to create a proxy WCF service over a WCF service using ChannelFactory.
I have defined the following interface for the WCF service to call. It has a generic GetResource method that writes directly into HttpContext the response byte[] and sets it's content type based on the type of the resource.
[ServiceContract]
public interface ITest
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "getResource/?resourceKey={resourceKey}")]
void GetResource(string resourceKey);
}
Proxy implementation for GetResource:
public void GetResource(string resourceKey)
{
var factory = new ChannelFactory<ITest>(GetCustomBinding(), new EndpointAddress('https://test.com/mytest.svc'));
channel.Endpoint.Behaviors.Add(new WebHttpBehavior());
var channel = factory.CreateChannel();
using (var scope = new OperationContextScope((IContextChannel)channel))
{
channel.GetResource(resourceKey);
}
}
private static Binding GetCustomBinding()
{
var binding = new WebHttpBinding(WebHttpSecurityMode.Transport)
{
MaxReceivedMessageSize = 20000000,
MaxBufferSize = 20000000,
MaxBufferPoolSize = 20000000,
CloseTimeout = TimeSpan.FromMinutes(1)
};
binding.ContentTypeMapper = new JsonMapper();
return binding;
}
private class JsonMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Json;
}
}
The problem that I have is that when I'm calling the proxy at this endpoint, proxy.svc/GetResource?resourceKey="text.css" it returns the right content type but no actual content, ContentLength=0.
How should I handle Response Body forwarding? How do I read the response body? I tried using WebOperationContext.Current.IncomingResponse but it doesn't give me the response body as well.

I think you are attempting to use WCF in a way that it is not intended. A void method will return nothing. In theory, you can intercept this by adding an inspection behaviour, but not even sure that would work.
I have (potentially) similar code which loads file from a database and returns them, but that will return the content.
var file = ServiceUtilities.FileManager.GetFile(id);
if (file != null)
{
var fcr = new FileContentResult(file.Content, file.MimeType);
return fcr;
}
Perhaps you should revisit your intended approach.

Related

WCF client Configure method not automatically being called

I am configuring a WCF client service programmatically, this is because we are executing libraries in a third-party sandbox where we have no control of the AppDomains configuration file.
The problem is that the standard 'public static void Configure(ServiceConfiguration config)' isn't being called automatically, this results in the client not being configured.
Here's the client with the static Configure method:
namespace OpenIt.service
{
public class CustomerClient : ClientBase<ICustomerClient>, ICustomerClient
{
public static void Configure(ServiceConfiguration config)
{
WebHttpBinding binding = new WebHttpBinding
{
TransferMode = TransferMode.Streamed
};
var serviceEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(ICustomerClient)), binding, new EndpointAddress(new Uri("http://localhost:3245")));
serviceEndpoint.Behaviors.Add(new WebHttpBehavior
{
DefaultOutgoingRequestFormat = System.ServiceModel.Web.WebMessageFormat.Json
});
config.AddServiceEndpoint(serviceEndpoint);
config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });
}
public GetCustomerResponse GetCustomer(GetCustomerRequest request)
{
using (new OperationContextScope((IContextChannel)Channel))
{
GetCustomerResponse response = Channel.GetCustomer(request);
return response;
}
}
}
}
Here's the interface:
namespace OpenI
{
[ServiceContract]
public interface ICustomerClient
{
[OperationContract]
[WebInvoke(
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
Method = "POST",
UriTemplate = "rest/v6/customerMan/customer/GetCustomer")]
GetCustomerResponse GetCustomer(GetCustomerRequest request);
}
}
Here's the initialization and call:
private bool Testit()
{
//Here it fails due to no config
var client = new CustomerClient();
GetCustomerResponse response = client.GetCustomer(new GetCustomerRequest { CustomerNumber = "001" });
}
Does anyone have any idea why the 'public static void Configure(ServiceConfiguration config)' method isn't automatically being called? Am I missing something where 'CustomerClient()' is initialized under 'Testit()'.
The webservice is fine, tested with postman.
This method is used to configure WCF services, not clients.
Strong hint is that the parameter is of type ServiceConfiguration.
See the documentation for details.
To configure a client, you can use the methods (and constructors) on ClientBase and/or app.config settings (docs).

How to get a simple stream of string using ServiceStack Grpc?

fighting with the ServiceStack library since a while to get a basic "stream" of string to work in C#.
In short, I'm trying to replicate the basic example from "native" gRPC.
Proto buf
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (stream HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
Server
public override async Task SayHello(HelloRequest request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
foreach (var x in Enumerable.Range(1, 10))
{
await responseStream.WriteAsync(new HelloReply
{
Message = $"Hello {request.Name} {x}"
});
await Task.Delay(200);
}
}
Client
var replies = client.SayHello(new HelloRequest { Name = "Laurent" });
await foreach (var reply in replies.ResponseStream.ReadAllAsync())
{
Console.WriteLine(reply.Message);
}
Then with the ServiceStack library, I'm not able to get the server piece done correctly. I always get a message telling me my function 'SayHello' isn't defined.
Let me know, thx !
ServiceStack gRPC implementation adopts a code-first implementation where your existing ServiceStack Services can be called from gRPC endpoints.
So instead of manually authoring a .proto file you would instead create Services using standard Request / Response DTOs and Service implementation for normal Request/Reply gRPC Services.
For Server Stream gRPC Services you would need to implement IStreamService interface in addition to inheriting from ServiceStack's Service base class.
An example of this is covered in Implementing Server Stream Services in the docs:
public class StreamFileService : Service, IStreamService<StreamFiles,FileContent>
{
public async IAsyncEnumerable<FileContent> Stream(StreamFiles request,
CancellationToken cancel = default)
{
var i = 0;
var paths = request.Paths ?? TypeConstants.EmptyStringList;
while (!cancel.IsCancellationRequested)
{
var file = VirtualFileSources.GetFile(paths[i]);
var bytes = file?.GetBytesContentsAsBytes();
var to = file != null
? new FileContent {
Name = file.Name,
Type = MimeTypes.GetMimeType(file.Extension),
Body = bytes,
Length = bytes.Length,
}
: new FileContent {
Name = paths[i],
ResponseStatus = new ResponseStatus {
ErrorCode = nameof(HttpStatusCode.NotFound),
Message = "File does not exist",
}
};
yield return to;
if (++i >= paths.Count)
yield break;
}
}
}
You would also need to register your Stream Service implementation in RegisterServices:
Plugins.Add(new GrpcFeature(App) {
RegisterServices = {
typeof(StreamFileService)
}
});
If you're using the smart C# generic gRPC Service Client you can avoid .proto descriptors and protoc generated classes entirely as you can reuse the Server DTOs in your ServiceModel project to enable an end-to-end API without code-gen:
var request = new StreamFiles {
Paths = new List<string> {
"/js/ss-utils.js",
"/js/hot-loader.js",
"/js/not-exists.js",
"/js/hot-fileloader.js",
}
};
var files = new List<FileContent>();
await foreach (var file in client.StreamAsync(request))
{
files.Add(file);
}
An alternative to sharing your ServiceModel.dll you can use C# Add ServiceStack Reference to generate your C# DTOs on the client.
For protoc generated clients you can use the x dotnet tool to Generate protoc Dart gRPC Client
$ x proto-dart https://todoworld.servicestack.net -out lib
Where you can use the serverStreamFiles API stubs to invoke the server stream Service:
var stream = client.serverStreamFiles(StreamFiles()..paths.addAll([
'/js/ss-utils.js',
'/js/hot-loader.js',
'/js/hot-fileloader.js',
]));
await for (var file in stream) {
var text = utf8.decode(file.body);
print('FILE ${file.name} (${file.length}): ${text.substring(0, text.length < 50 ? text.length : 50)} ...');
}
The todo-world/clients repo contains a number of gRPC test examples in different langauges.
In the end, here's what I end up doing as a simple POC of a stream, if this can help anyone else. No more proto-file either !
Client Program.cs :
private async static Task GetBotStream()
{
var res = client.StreamAsync(new BotStreamRequest { });
await foreach (var textReceived in res)
{
Console.WriteLine(textReceived.Result);
}
}
Client dtos.cs
[DataContract]
public partial class BotStreamRequest : IReturn<BotStreamReply>
{
}
[DataContract]
public partial class BotStreamReply
{
[DataMember(Order = 1)]
public virtual string Result { get; set; }
[DataMember(Order = 2)]
public virtual ResponseStatus ResponseStatus { get; set; }
}
Server Program.cs
public async IAsyncEnumerable<BotStreamReply> Stream(BotStreamRequest request, [EnumeratorCancellation]CancellationToken cancel = default)
{
foreach (var x in Enumerable.Range(1, 10))
{
yield return new BotStreamReply { Result = $"My stream {x}" };
}
}

How to call a web service with .ashx extention in C# MVC?

Want to call a web service with .aspx extension. It cannot add to web references.Then I used webclient and return string. But then I do not Know how to use it. This is my code.
WebClient client = new WebClient();
detail.Title = client.DownloadString("https://somename/cruiseproducts.ashx");
return downloadedString;
It depends on what is returned by you service
for example
you ashx service should look like this
public class TestHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable
{
get
{
return false;
}
}
}
on your side client code should look like this
WebClient client = new WebClient();
var testResult = client.DownloadString("http://localhost:19238/TestHandler.ashx");
return testResult;
Noitice that service is returning "plain/text" type
if service is returning xml type then u can simply get string firstly and then parse like this
//consider that result is in testResult variable
var xml = new XmlDocument();
//your xml tree
xml.LoadXml(testResult);

Wcf sends xml content type instead of json,

have WCF server and WCF client. Below is the client code:
[GeneratedCode("System.ServiceModel", "3.0.0.0")]
[ServiceContract(ConfigurationName = "IMyService")]
public interface IMyService
{
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, RequestFormat = WebMessageFormat.Json, UriTemplate = "DoSmth")]
[OperationContract(Action = "http://tempuri.org/IConfigService/SetSettings")]
OrigamiHttpResponse<List<ErrorRecords>> DoSmth(int param);
}
public class MyService: BaseClient<IMyService>
{
public ConfigServiceClient(AuthResult authObject) : base(authObject, null)
{
}
public OrigamiHttpResponse<List<ErrorRecords>> DoSmth(int param)
{
return Proxy.DoSmth(param);
}
}
public abstract class BaseClient<T> : BaseClient where T : class
{
protected T Proxy { get; private set; }
protected BaseClient(AuthResult authObject, IConfig config)
: base(authObject, config)
{
var factory = new ChannelFactory<T>("*");
if (factory.Endpoint == null || factory.Endpoint.Address == null)
throw new Exception("WCF Endpoint is not configured");
if (factory.Endpoint.Address.Uri.Scheme.ToLower() == "https")
HttpAccess.PrepareSslRequests();
if (_authObject != null)
{
var cb = new CookieBehavior(_authObject);
factory.Endpoint.Behaviors.Add(cb);
}
Proxy = factory.CreateChannel();
}
}
When I call method DoSmth() from console application, content type is json. But my architecture is that I am calling proxy method and then proxy server acts as client for wcf server and calls wcf method that is my DoSmth(). In this case content type is xml and i can't change it. May be the issue is in operation context, because it is one call from another. Could anyone help please?
This is caused by the fact that your WCF client (Proxy) is running in the operation context on the service method (which contains information about the incoming request), and that overrides the context which should be used by the outgoing request. To fix this you need to create a new operation context scope when doing the call, so that it will use the appropriate property from the WebInvoke/WebGet attributes:
public OrigamiHttpResponse<List<ErrorRecords>> DoSmth(int param)
{
using (new OperationContextScope((IContextChannel)Proxy))
{
return Proxy.DoSmth(param);
}
}

Where do I set the CookieContainer on a Service Reference?

When adding WebService Reference to an ASMX Service on a .NET 2.0 project for example,
var objService = new NameSpace.groupservices();
there exists,
objService.CookieContainer = new System.Net.CookieContainer();
When adding ServiceReference to an ASMX Service on a .NET 4.0 project for example,
var objService = new NameSpace.groupservicesSoapClient();
there isn't any CookieContainer property for objService
A similar question was asked here with no positive solution.
Could someone please guide where to find the property?
In contrast to ASMX Web Services that are tied to HTTP transport, WCF allows for various transport protocols to be used. Therefore, not all protocol-specific options (such as Cookies for HTTP transport) are available in a WCF service reference.
You can, however, add a message inspector that inspects the messages that are sent between client and server. This article describes a way to send cookies to the server.
I've extended the sample to use a CookieContainer. Also, the following code shows how to evaluate the Set-Cookie header sent by the server to add the new cookies to the container. Please note that the sample shows a basic outline, but might need extension or some more validation. However, in a simple scenario it worked.
The following snippet shows a test method of a WCF service that is hosted on IIS and integrated in the ASP.NET framework. It basically echoes the cookies sent to the server in a string and adds two new ones:
public string GetData(int value)
{
var reply = string.Join(", ",
from x in HttpContext.Current.Request.Cookies.AllKeys
select x + "=" + HttpContext.Current.Request.Cookies[x].Value);
HttpContext.Current.Response.Cookies.Add(new HttpCookie("Test", "Test123"));
HttpContext.Current.Response.Cookies.Add(new HttpCookie("Test2", "Test1234"));
return reply;
}
The following test program creates a CookieContainer for the cookies, adds a demo cookie and registers a new behavior for the endpoint of the service:
class Program
{
static void Main(string[] args)
{
var cookieCont = new CookieContainer();
using(var svc = new TestServiceReference.TestServiceClient())
{
cookieCont.Add(svc.Endpoint.Address.Uri, new Cookie("TestClientCookie", "Cookie Value 123"));
var behave = new CookieBehavior(cookieCont);
svc.Endpoint.EndpointBehaviors.Add(behave);
var data = svc.GetData(123);
Console.WriteLine(data);
Console.WriteLine("---");
foreach (Cookie x in cookieCont.GetCookies(svc.Endpoint.Address.Uri))
Console.WriteLine(x.Name + "=" + x.Value);
}
Console.ReadLine();
}
}
The behavior serves the purpose of adding a custom message inspector and handing over the CookieContainer:
public class CookieBehavior : IEndpointBehavior
{
private CookieContainer cookieCont;
public CookieBehavior(CookieContainer cookieCont)
{
this.cookieCont = cookieCont;
}
public void AddBindingParameters(ServiceEndpoint serviceEndpoint,
System.ServiceModel.Channels
.BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint,
System.ServiceModel.Dispatcher.ClientRuntime behavior)
{
behavior.MessageInspectors.Add(new CookieMessageInspector(cookieCont));
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint,
System.ServiceModel.Dispatcher
.EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint serviceEndpoint) { }
}
The message inspector both adds cookies when a request is sent to the server in the BeforeSendRequest method and retrieves the cookies that should be updated in the AfterReceiveReply method. Note that the correlationState returned by BeforeSendRequest is used to retrieve the Uri in the AfterReceiveReply:
public class CookieMessageInspector : IClientMessageInspector
{
private CookieContainer cookieCont;
public CookieMessageInspector(CookieContainer cookieCont)
{
this.cookieCont = cookieCont;
}
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
object obj;
if (reply.Properties.TryGetValue(HttpResponseMessageProperty.Name, out obj))
{
HttpResponseMessageProperty httpResponseMsg = obj as HttpResponseMessageProperty;
if (!string.IsNullOrEmpty(httpResponseMsg.Headers["Set-Cookie"]))
{
cookieCont.SetCookies((Uri)correlationState, httpResponseMsg.Headers["Set-Cookie"]);
}
}
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel)
{
object obj;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out obj))
{
HttpRequestMessageProperty httpRequestMsg = obj as HttpRequestMessageProperty;
SetRequestCookies(channel, httpRequestMsg);
}
else
{
var httpRequestMsg = new HttpRequestMessageProperty();
SetRequestCookies(channel, httpRequestMsg);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMsg);
}
return channel.RemoteAddress.Uri;
}
private void SetRequestCookies(System.ServiceModel.IClientChannel channel, HttpRequestMessageProperty httpRequestMessage)
{
httpRequestMessage.Headers["Cookie"] = cookieCont.GetCookieHeader(channel.RemoteAddress.Uri);
}
}
Open your app.config file and add allowCookies="true" to the binding.
Something like this:
<binding allowCookies="true" />
Found the solution in here:
http://msdn.microsoft.com/en-us/library/bb628649.aspx
It turns out I need a web reference instead of a service reference

Categories