I have a very basic WCF service which has a method named SaveSchoolName(string SchoolName) which basically returns as boolean value of True if the operation is good.
.
I added a service reference to my client application and is consuming the service as follows:
MyService.WebServicesClient svc = new MyService.WebServicesClient();
bool dataSaved = false;
dataSaved = svc.SaveSchoolName("School Name");
if(dataSaved){
// do something.
}
else{
// log not saved.
}
I want to know how do I determine the Http Status Code (200 - OK) for the WCF Service call. I have tried to search but none seems to provide any detailed info on how I would be able to get the response headers from invoking the method.
You need to create a client message inspector for this.
Check the below code out ... to make it work just add the inspector to your client. BTW, obviously this only works for HTTP :)
public class HttpStatusCodeMessageInspector : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
{
var httpResponseProperty = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
Console.WriteLine($"Response status is {(int)httpResponseProperty.StatusCode}");
}
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
To extend #Jorge's answer, I started by implementing the IClientMessageInspector and IEndpointBehavior from the example here. I replaced the contents of IClientMessageInspector.AfterReceiveReply with the code provided by Jorge:
public void AfterReceiveReply(ref Message reply, object correlationState)
{
if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
{
var httpResponseProperty = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
Console.WriteLine($"Response status is {(int)httpResponseProperty.StatusCode}");
}
}
Now, you can choose to continue inheriting BehaviorExtensionElement and add the behavior to your client configuration file. If you take this approach you'll be done.
However, since my client configuration was created programmatically, I needed a few additional steps here.
In the docs you can see that an Endpoint Behavior can be added by using the ServiceEndpoint.Behaviors.Add method. But how do we get access to the ServiceEndpoint object?
This can be done by first creating your service client object, and then using its Endpoint property like so:
''' VB.NET
'' Creating the binding obj
Dim objMyBinding As New System.ServiceModel.WSHttpBinding()
objMyBinding.Name = "YOUR_BINDING_NAME"
'' Other details of binding added here ''
'' Creating the endpoint address obj
Dim objMyEndpoint As System.ServiceModel.EndpointAddress
objMyEndpoint = New System.ServiceModel.EndpointAddress(New Uri(ENDPOINT_ADDR_HERE))
'' Creating the Service obj
Dim objWebServices As CalculatorService = New CalculatorService(objMyBinding, objMyEndpoint)
'' Finally adding the behavior to the Service Endpoint (CustomEndpointBehavior is implementation of IEndpointBehavior)
objWebServices.Endpoint.EndpointBehaviors.Add(New CustomEndpointBehavior)
/// C#.NET
// Creating the binding obj
System.ServiceModel.WSHttpBinding objMyBinding = New System.ServiceModel.WSHttpBinding();
objMyBinding.Name = "YOUR_BINDING_NAME";
// Other details of binding added here //
// Creating the endpoint address obj
System.ServiceModel.EndpointAddress objMyEndpoint;
objMyEndpoint = New System.ServiceModel.EndpointAddress(New Uri(ENDPOINT_ADDR_HERE));
// Creating the Service obj
CalculatorService objWebServices = New CalculatorService(objMyBinding, objMyEndpoint);
// Finally adding the behavior to the Service Endpoint (CustomEndpointBehavior is implementation of IEndpointBehavior)
objWebServices.Endpoint.EndpointBehaviors.Add(New CustomEndpointBehavior());
Related
Background
I have a web api server (asp.net core v2.1) that serve some basic operation, like managing entities on the server. This is the interface:
[HttpPost]
[Route("create")]
public async Task<ActionResult<NewEntityResponse>> Create(CreateEntityModel model)
{
// 1) Validate the request.
// 2) Create a new row on the database
// 3) Return the new entity in response.
}
The user running this REST method in this way:
POST https://example.com/create
Content-Type: application/json
{
"firstName": "Michael",
"lastName": "Jorden"
}
And getting response like this:
Status 200
{
"id": "123456" // The newly created entity id
}
The Problem
When sending thousands of requests like this, at some point it will fail because of network connections. When connection fails, it can leads us into two different situations:
The network call was ended on the way to the server - In this case, the server don't know about this request. Therefore, the entity wasn't created. The user just have to send the same message again.
The network call was sent from the server to back to the client but never rich the destination - In this case the request was fulfill completely, but the client don't aware for this. The expected solution is to send the same request again. In this case, it will create the same entity twice - and this is the problem.
The Requested Solution
I want to create an generic solution for web-api that "remmeber" which commands it already done. if he got same request twice, it's return HTTP status code Conflict.
Where I got so far
I thought to add the client an option to add a unique id to the request, in this way:
POST https://example.com/create?call-id=XXX
Add to my server a new filter that check if the key XXX is already fulfill. If yes, return Conflict. Otherwise - continue.
Add another server filter that checks the response of the method and marking it as "completed" for further checks.
The problem with this solution on concurrency calls. If my method takes 5 seconds to be returned and the client sent the same message again after 1 second - it will create two entities with same data.
The Questions:
Do you think that this is good approach to solve this problem?
Do you familiar with ready to use solutions that doing this?
How to solve my "concurrency" problem?
Any other tips will be great!
thanks.
Isnt the easiest solution to make the REST action idempotent?
I mean by that: the call should check if the resource already exists and either create a new resource if it doesnt or return the existing if it does?
OK, I just figure it up how to make it right. So, I implemented it by myself and share it with you.
In order to sync all requests between different servers, I used Redis as cache service. If you have only one server, you can use Dictionary<string, string> instead.
This filter do:
Before processing the request - add a new empty value key to Redis.
After the server processed the request - store the server response in Redis. This data will be used when the user will ask again for same request.
public class ConflictsFilter : ActionFilterAttribute
{
const string CONFLICT_KEY_NAME = "conflict-checker";
static readonly TimeSpan EXPIRE_AFTER = TimeSpan.FromMinutes(30);
private static bool ShouldCheck(ActionDescriptor actionDescription, IQueryCollection queries)
{
return queries.ContainsKey(CONFLICT_KEY_NAME);
}
private string BuildKey(string uid, string requestId)
{
return $"{uid}_{requestId}";
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (ShouldCheck(context.ActionDescriptor, context.HttpContext.Request.Query))
{
using (var client = RedisConnectionPool.ConnectionPool.GetClient())
{
string key = BuildKey(context.HttpContext.User.GetId(), context.HttpContext.Request.Query[CONFLICT_KEY_NAME]);
string existing = client.Get<string>(key);
if (existing != null)
{
var conflict = new ContentResult();
conflict.Content = existing;
conflict.ContentType = "application/json";
conflict.StatusCode = 409;
context.Result = conflict;
return;
}
else
{
client.Set(key, string.Empty, EXPIRE_AFTER);
}
}
}
base.OnActionExecuting(context);
}
public override void OnResultExecuted(ResultExecutedContext context)
{
base.OnResultExecuted(context);
if (ShouldCheck(context.ActionDescriptor, context.HttpContext.Request.Query) && context.HttpContext.Response.StatusCode == 200)
{
string key = BuildKey(context.HttpContext.User.GetId(), context.HttpContext.Request.Query[CONFLICT_KEY_NAME]);
using (var client = RedisConnectionPool.ConnectionPool.GetClient())
{
var responseBody = string.Empty;
if (context.Result is ObjectResult)
{
ObjectResult result = context.Result as ObjectResult;
responseBody = JsonConvert.SerializeObject(result.Value);
}
if (responseBody != string.Empty)
client.Set(key, responseBody, EXPIRE_AFTER);
}
}
}
}
The code is executed only if the query ?conflict-checker=XXX is exists.
This code is provide you under MIT license.
Enjoy the ride :)
I d'like to add custom properties to metrics taken by Application Insights to each request of my app. For example, I want to add the user login and the tenant code, such as I can segment/group the metrics in the Azure portal.
The relevant doc page seems to be this one : Set default property values
But the example is for an event (i.e. gameTelemetry.TrackEvent("WinGame");), not for an HTTP request :
var context = new TelemetryContext();
context.Properties["Game"] = currentGame.Name;
var gameTelemetry = new TelemetryClient(context);
gameTelemetry.TrackEvent("WinGame");
My questions :
What is the relevant code for a request, as I have no specific code at this time (it seems to be automatically managed by the App Insights SDK) : Is just creating a TelemetryContext sufficient ? Should I create also a TelemetryClient and if so, should I link it to the current request ? How ?
Where should I put this code ? Is it ok in the Application_BeginRequest method of global.asax ?
It looks like adding new properties to existing request is possible using ITelemetryInitializer as mentioned here.
I created sample class as given below and added new property called "LoggedInUser" to request telemetry.
public class CustomTelemetry : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
requestTelemetry.Properties.Add("LoggedInUserName", "DummyUser");
}
}
Register this class at application start event.
Example below is carved out of sample MVC application I created
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
TelemetryConfiguration.Active.TelemetryInitializers
.Add(new CustomTelemetry());
}
}
Now you can see custom property "LoggedInUserName" is displayed under Custom group of request properties. (please refer screen grab below)
Appinsight with custom property
Related to the first question "how to add custom event to my request / what is the relevant code to a request", I think the main confusion here is related to the naming.
The first thing that we need to point out is that there are different kinds of information that we can capture with Application Insights:
Custom Event
Request
Exception
Trace
Page View
Dependency
Once we know this, we can say that TrackEvent is related to "Custom Events", as TrackRequest is related to Requests.
When we want to save a request, what we need to do is the following:
var request = new RequestTelemetry();
var client = new TelemetryClient();
request.Name = "My Request";
client.TrackRequest(request);
So let's imagine that your user login and tenant code both are strings. We could make a new request just to log this information using the following code:
public void LogUserNameAndTenant(string userName, string tenantCode)
{
var request = new RequestTelemetry();
request.Name = "My Request";
request.Context.Properties["User Name"] = userName;
request.Context.Properties["Tenant Code"] = tenantCode;
var client = new TelemetryClient();
client.TrackRequest(request);
}
Doing just a TelemetryContext will not be enough, because we need a way to send the information, and that's where the TelemetryClient gets in place.
I hope it helps.
You can use the static HttpContext.Current's Items dictionary as a short term (near stateless) storage space to deliver your custom property values into the default telemetry handler with a custom ITelemetryInitializer
Implement handler
class AppInsightCustomProps : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
// Is this a TrackRequest() ?
if (requestTelemetry == null) return;
var httpCtx = HttpContext.Current;
if (httpCtx != null)
{
var customPropVal = (string)httpCtx.Items["PerRequestMyCustomProp"];
if (!string.IsNullOrWhiteSpace(customPropVal))
{
requestTelemetry.Properties["MyCustomProp"] = customPropVal;
}
}
}
}
Hook it in. Put this inside Application_Start in global.asax.cs
TelemetryConfiguration.Active.TelemetryInitializers.Add(new AppInsightCustomProps());
Program the desired custom property, anywhere in your request pipeline have something like
if (HttpContext.Current != null)
{
HttpContext.Current.Items["PerRequestMyCustomProp"] = myCustomPropValue;
}
As mentioned by Alan, you could implement the IContextInitializer interface to add custom properties to ALL telemetry sent by Application Insights. However, I would also suggest looking into the ITelemtryInitializer interface. It is very similar to the context initializer, but it is called for every piece of telemetry sent rather than only at the creation of a telemetry client. This seems more useful to me for logging property values that might change over the lifetime of your app such as user and tenant related info like you had mentioned.
I hope that helps you out. Here is a blog post with an example of using the ITelemetryInitializer.
In that doc, scroll down a few lines to where it talks about creating an implementation of IContextInitializer. You can call that in any method that will be called before telemetry starts rolling.
Your custom properties will be added to all events, exceptions, metrics, requests, everything.
I am using Ninject.Extensions.Wcf in a WCF service, and also have the Ninject.Web.Common package in my project.
In the NinjectWebCommon class this is how I bind my dependency
kernel.Bind<ISomething>().To<ConcreteSomething>();
straightforward binding, and everything works..
Now, I want to do the resolution based on some members in the message request body, so I create a provider
kernel.Bind<ISomething>().ToProvider<SomethingProvider>();
And SomethingProvider tries to read the request message like this
public class SomethingProvider : Provider<ISomething>
{
protected override ISomething CreateInstance(IContext context)
{
var message = OperationContext.Current.RequestContext.RequestMessage;
//message.State is Read here, so CreateBufferedCopy throws an InvalidOperationException.
var buffer = message.CreateBufferedCopy(int.MaxValue);
message = buffer.CreateMessage();
var messageCopy = buffer.CreateMessage();
var body = messageCopy.GetBody<string>();
//Parse body, check some members and return one of the "Somethings"
return new AnotherConcreteSomething();
}
}
This throws an invalid operation exception. The RequestMessage in the operationcontext is already in read state, so I cant read it again.
I read that RequestMessage.ToString() is not always reliable since it truncates the body. And, I will have to get the body from the whole SOAP message.
Is there any other way I can do this? Is this approach even correct?
I'm building a web api using WCF web api preview 6, currently I'm stuck with a little problem. I would like to have an operation handler to inject an IPrincipal to the operation in order to determine which user is making the request. I already have that Operation Handler and is already configured. But I noticed that when I decorate the operation with the WebInvoke attribute and simultaneously the operation receives an IPrincipal and other domain object, the system throws an exception telling me:
The HttpOperationHandlerFactory is unable to determine the input parameter that should be associated with the request message content for service operation 'NameOfTheOperation'. If the operation does not expect content in the request message use the HTTP GET method with the operation. Otherwise, ensure that one input parameter either has it's IsContentParameter property set to 'True' or is a type that is assignable to one of the following: HttpContent, ObjectContent1, HttpRequestMessage or HttpRequestMessage1.
I do not know what is happening here. To give you some background I'll post some of my code to let you know how am I doing things.
The operation:
[WebInvoke(UriTemplate = "", Method = "POST")]
[Authorization(Roles = "")]
public HttpResponseMessage<dto.Diagnostic> RegisterDiagnostic(dto.Diagnostic diagnostic, IPrincipal principal)
{
......
}
WCF web api knows when to inject the IPrincipal because I decorate the operation with a custom Authorization attribute.
The configuration in the Global file:
var config = new WebApiConfiguration() {EnableTestClient = true};
config.RegisterOAuthHanlder(); //this is an extension method
routes.SetDefaultHttpConfiguration(config);
routes.MapServiceRoute<MeasurementResource>("Measurement");
routes.MapServiceRoute<DiagnosticResource>("Diagnostic");
Then the RegisterOAuthHandler method adds an operation handler to the operation if it's been decorated with the custom authorization attibute. this is how it looks:
public static WebApiConfiguration RegisterOAuthHanlder(this WebApiConfiguration conf)
{
conf.AddRequestHandlers((coll, ep, desc) =>
{
var authorizeAttribute = desc.Attributes.OfType<AuthorizationAttribute>().FirstOrDefault();
if (authorizeAttribute != null)
{
coll.Add(new OAuthOperationHandler(authorizeAttribute));
}
});
return conf;
}
public static WebApiConfiguration AddRequestHandlers(
this WebApiConfiguration conf,
Action<Collection<HttpOperationHandler>, ServiceEndpoint, HttpOperationDescription> requestHandlerDelegate)
{
var old = conf.RequestHandlers;
conf.RequestHandlers = old == null ? requestHandlerDelegate : (coll, ep, desc) =>
{
old(coll, ep, desc);
};
return conf;
}
Can somebody help me with this? Thank you in advanced!
Try wrapping your Diagnostic param in ObjectContent i.e. ObjectContent<Diagnostic>. Then you will use the ReadAs() method to pull out the object.
It should work.
I just started with the WCF REST Starter Kit.
I created a simple service that return an array of an object.
Using the browser, everything works fine but when I use a WCF client, I get an ArgumentException.
I'm not using IIS and here is the code:
The contract:
[ServiceContract]
public interface IGiftService {
[WebGet(UriTemplate="gifts")]
[OperationContract]
List<Gift> GetGifts();
}
public class GiftService : IGiftService {
public List<Gift> GetGifts() {
return new List<Gift>() {
new Gift() { Name = "1", Price = 1.0 },
new Gift() { Name = "2", Price = 1.0 },
new Gift() { Name = "3", Price = 1.0 }
};
}
}
[DataContract]
public class Gift {
[DataMember]
public string Name { get; set; }
[DataMember]
public double Price { get; set; }
}
To start the service:
WebServiceHost2 host = new WebServiceHost2(
typeof(GiftService),
true,
new Uri("http://localhost:8099/tserverservice"));
host.Open();
Console.WriteLine("Running");
Console.ReadLine();
host.Close();
To start the client:
WebChannelFactory<IGiftService> factory = new WebChannelFactory<IGiftService>(
new Uri("http://localhost:8099/tserverservice"));
IGiftService service = factory.CreateChannel();
List<Gift> list = service.GetGifts();
Console.WriteLine("-> " + list.Count);
foreach (var item in list) {
Console.WriteLine("-> " + item.Name);
}
The server and the client are in the same solution and I'm using the same interface in both (to describe the service contract).
The exception says: "A property with the name 'UriTemplateMatchResults' already exists." and that is the stack trace:
Class firing the exception -> Microsoft.ServiceModel.Web.WrappedOperationSelector
Stack trace:
at System.ServiceModel.Channels.MessageProperties.UpdateProperty(String name, Object value, Boolean mustNotExist)
at System.ServiceModel.Channels.MessageProperties.Add(String name, Object property)
at System.ServiceModel.Dispatcher.WebHttpDispatchOperationSelector.SelectOperation(Message& message, Boolean& uriMatched)
at System.ServiceModel.Dispatcher.WebHttpDispatchOperationSelector.SelectOperation(Message& message)
at Microsoft.ServiceModel.Web.WrappedOperationSelector.SelectOperation(Message& message) in C:\Program Files\WCF REST Starter Kit\Microsoft.ServiceModel.Web\WrappedOperationSelector.cs:line 42
at Microsoft.VisualStudio.Diagnostics.ServiceModelSink.ServiceMethodResolver.GetOperation()
at Microsoft.VisualStudio.Diagnostics.ServiceModelSink.ServiceMethodResolver..ctor(ContractDescription contract, DispatchRuntime runtime, Message request, InstanceContext instanceContext)
What am I doing wrong?
UPDATE: I disabled the help page and the service is working now. Is it a bug?
host.EnableAutomaticHelpPage = false;
Thank you!
André Carlucci
Had the same problem, disabled the help page and it fixed it. The exception was being thrown if some REST urls were called in a sequence very quickly. It was fine when waiting between the calls.
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new WebServiceHost2(serviceType, true, baseAddresses) {EnableAutomaticHelpPage = false};
}
I had the same probem, but I wanted to see the help page so disabling it wasn't a solution for me. I found out that URITemplating in the WCF REST Toolkit is causing those problem, when it sees that it already have this template in the template tables. Basically you will only need a template when the URL to your method is different according to the requested data, after all, that's what the templates are for. I had the same URITemplates for my POST operations, so the URLs did not differ between separate queries which causes this error. then I found out that I actually didn't need any templating at all, at least for the POST operations, moreover youvannot make a POST query though the URL if your method requires a complex object to be passed as a parameter. So I removed the URITemplate named parameter from the WebInvoke attribute in the service interface, I think that solved the problem. Of course if you make GET queries to the server and rely on URITemplating you'll still have to either put up with or leave away the help page.
In my case, the problem only occurred when accessing the endpoint using a WCF channel with Visual Studio debugger integration enabled.
I worked around the issue by adding some code to remove the VS behaviour from the ChannelFactory:
var vsBehaviour = channelFactory.Endpoint.EndpointBehaviors
.FirstOrDefault(i =>
i.GetType().Namespace == "Microsoft.VisualStudio.Diagnostics.ServiceModelSink");
if (vsBehaviour != null)
{
channelFactory.Endpoint.Behaviors.Remove(vsBehaviour);
}
Apparently, there are other ways to disable WCF Visual Studio debugger integration, but they seem to be system-wide, while this solution is local.