Client hooks in OData client
Applies To: # OData client v7 supported OData Client V7
OData Client provides several ways to allow developers to hook into the client request and response. It gives developers the opportunity to inspect, adjust or replace some request or response.
This doc will give you several real world examples to explain all these kinds of methods in OData Client.
DataServiceContext
provided three events to let developers to hook up to.
public event EventHandler<BuildingRequestEventArgs> BuildingRequest;
This event is fired before a request message object is built, giving the handler the opportunity to inspect, adjust and/or replace some request information before the message is built. This event is always used to modify the outgoing Url of the request, alter request headers or change the http method.
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/V4/TripPinServiceRW/"));
dataServiceContext.BuildingRequest += (sender, eventArgs)=>
{
eventArgs.RequestUri = new Uri("https://services.odata.org/V4/(S(ghojd5jj5d33cwotkyfwn431))/TripPinServiceRW/People");
};
dataServiceContext.People.Execute();
Developers can also change the HttpMethod of the request.
dataServiceContext.BuildingRequest += (sender, eventArgs) =>
{
eventArgs.Method = "PUT";
};
public event EventHandler<ReceivingResponseEventArgs> ReceivingResponse;
This event is fired when a response is received by the client. It is fired for both top level responses and each operation or query within a batch response.
For a non-batch response:
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/V4/TripPinServiceRW/"));
dataServiceContext.ReceivingResponse += (sender, eventArgs) =>
{
Console.WriteLine(eventArgs.ResponseMessage.GetHeader("OData-Version"));
};
dataServiceContext.People.First();
For a batch request for query, the ReceivingResponse
will firstly be fired when the client receives the top level response. Then, the event will be fired when the client enumerates the inner QueryOperationResponse
. ReceivingResponse
is fired as many times as the responses are enumerated. So about the following code, before the client executes foreach
, the code will only print the Content-Type
for the top-level request. The last several lines of following code enumerate each of the QueryOperationResponse
. ReceivingResponse
will be fired accordingly.
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(irl1k2jt4e4bscxuk30bpgji))/TripPinServiceRW/"));
dataServiceContext.ReceivingResponse += (sender, eventArgs) =>
{
Console.WriteLine(eventArgs.ResponseMessage.GetHeader("Content-Type"));
};
var responses = dataServiceContext.ExecuteBatch(dataServiceContext.People, dataServiceContext.Airlines);
// Enumerate the response will fire the ReceivingResponse for each of the inner query
foreach (QueryOperationResponse response in responses)
{
}
But for a batch request for changes, ReceivingResponse
will be fired for both top level response and inner response even though the client doesn't enumerate the response. So the following code will print
200
204
204
200 is the response status code of the top level message. the other two 204 status codes are of the inner responses.
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(irl1k2jt4e4bscxuk30bpgji))/TripPinServiceRW/"));
var p1 = dataServiceContext.People.First();
var p2 = dataServiceContext.People.Skip(1).First();
dataServiceContext.ReceivingResponse += (sender, eventArgs) =>
{
Console.WriteLine(eventArgs.ResponseMessage.StatusCode);
};
p1.FirstName = "aa";
p2.FirstName = "bb";
dataServiceContext.UpdateObject(p1);
dataServiceContext.UpdateObject(p2);
dataServiceContext.SaveChanges(Microsoft.OData.Client.SaveChangesOptions.BatchWithSingleChangeset);
public event EventHandler<SendingRequest2EventArgs> SendingRequest2;
This event is fired before a request is sent to the server, giving the handler the opportunity to inspect, adjust and/or replace the WebRequest object used to perform the request.
The most common use of this event is to set the headers of the request. You can set the header for response payload format, or the authentication information like token or cert name. You also can use this event to set preferences, If-Match headers.
The code below will add the odata.include-annotations
preference in the request header to enable getting instance annotations.
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(ghojd5jj5d33cwotkyfwn431))/TripPinServiceRW/"));
dataServiceContext.SendingRequest2 += (sender, eventArgs) =>
{
eventArgs.RequestMessage.SetHeader("Prefer", "odata.include-annotations=\"*\"");
};
dataServiceContext.People.Execute();
You can also use this event to check other information in the request message.
DataServiceContext
defines a Configurations
property of DataServiceClientConfigurations
which contains a RequestPipeline
and ResponsePipeline
. These two pipelines provide several hooks to developers to hook into the client request or response.
OnMessageCreating
is a property of the RequestPipeline
.
public Func<DataServiceClientRequestMessageArgs, DataServiceClientRequestMessage> OnMessageCreating
Developers can use this function to customize the request message.
The following code provides a sample which overrides the GetResponse()
method in user-defined request message which fakes a response message. We define a client request message which inherits HttpWebRequestMessage
. HttpWebRequestMessage
is a sub class of DataServiceClientRequestMessage
public class CustomizedRequestMessage : HttpWebRequestMessage
{
public string Response { get; set; }
public Dictionary<string, string> CustomizedHeaders { get; set; }
public CustomizedRequestMessage(DataServiceClientRequestMessageArgs args)
: base(args)
{
}
public CustomizedRequestMessage(DataServiceClientRequestMessageArgs args, string response, Dictionary<string, string> headers)
: base(args)
{
this.Response = response;
this.CustomizedHeaders = headers;
}
public override IODataResponseMessage GetResponse()
{
return new HttpWebResponseMessage(
this.CustomizedHeaders,
200,
() =>
{
byte[] byteArray = Encoding.UTF8.GetBytes(this.Response);
return new MemoryStream(byteArray);
});
}
}
Then, developers can replace the default client message with CustomizedClientRequestMessage
by using the following code. Then, if the client sends a request after this setting, it will automatically return the fake response message.
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(irl1k2jt4e4bscxuk30bpgji))/TripPinServiceRW/"));
string response = "..." //set the response
dataServiceContext.Configurations.RequestPipeline.OnMessageCreating =
(args) =>
{
return new CustomizedRequestMessage(
args,
response,
new Dictionary<string, string>()
{
{"Content-Type", "application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8"},
{"Preference-Applied", "odata.include-annotations=\"*\""}
});
};
dataServiceContext.PeoplePlus.ByKey("Jason").GetValue();
OnEntryStarting
is a method of the RequestPipeline
.
public DataServiceClientRequestPipelineConfiguration OnEntryStarting(Action<WritingEntryArgs> action)
Developers can use this function to control the information of an ODataResource
to be serialized.
The following code provides a sample to add properties to an ODataResource
.
public static void AddProperties(this ODataResource entry, params ODataProperty[] newProperties)
{
var odataProps = entry.Properties as List<ODataProperty>;
if (odataProps == null)
{
odataProps = new List<ODataProperty>(entry.Properties);
}
odataProps.AddRange(newProperties);
entry.Properties = odataProps;
}
Then, to add new properties in the ODataResource
, developers can call AddProperties
in OnEntryStarting
.
DefaultContainer dataServiceContext = new DefaultContainer(new Uri("https://services.odata.org/v4/(S(ghojd5jj5d33cwotkyfwn431))/TripPinServiceRW/"));
dataServiceContext.Configurations.RequestPipeline.OnEntryStarting(
arg =>
{
arg.Entry.AddProperties(new ODataProperty
{
Name = "NewProperty",
Value = "new property"
});
});
var person = dataServiceContext.People.ByKey("russellwhyte").GetValue();
dataServiceContext.UpdateObject(person);
dataServiceContext.SaveChanges();
These two configurations provide more other client hooks in request pipeline and response pipeline.
Please refer to this link for details.