Implementing a client-side cache using WCF, REST and standard HTTP headers - c#

I have a Perl based REST service and I'm using C# and WCF to make a client to talk to the service. I have a few expensive calls and would like to construct a caching system. I need the ability to check and see if newer versions of the cached data exist on the server. I had the idea to use the standard "If-Modified-Since" request header and "304 Not Modified" response status code, however I'm having trouble catching the exception that is thrown on the response.
My client class derives from ClientBase<>. Here is the method that I use to call a service method:
private T RunMethod<T>(ReqHeaderType reqHeaders, ResHeaderType resHeaders, Func<T> meth)
{
//Get request and response headers
var reqProp = GetReqHeaders(reqHeaders);
var resProp = GetResHeaders(resHeaders);
using (var scope = new OperationContextScope(this.InnerChannel))
{
//Set headers
OperationContext
.Current
.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = reqProp;
OperationContext
.Current
.OutgoingMessageProperties[HttpResponseMessageProperty.Name] = resProp;
//Return the result of the call
return meth();
}
}
The exception occurs when the call back, which runs the service method, is executed. Is there a way to catch the exception and check if it is a "Not Modified" response?

In my opinion, you really only want to use WCF channels on the client if you are using non-web WCF bindings on the server.
In your case you are not even using .Net on the server so I think WCF is going to cause you a whole lot of pain.
I suggest you simply use the HttpWebRequest and HttpWebResponse classes in System.Net. If you do that you can also take advantage of the built in caching that is provided by WinINet cache. If you set the caching policy in the client Http client you will get all the caching behaviour you need for free.

Related

How to do DI for non-request bound code in ASP.NET Core?

We're all being taught to use Dependency-Injection for coding in ASP.NET Core applications, but all of the examples I've seen so far that related to the retrieval of services via DI relate to situations where the method that has the service reference injected is strictly bound to a specific HTTP request (HttpContext) (e.g. MVC controllers, Routing delegates).
Service location is warned against as an anti-pattern, but I'm not sure on how to obtain a proper service (e.g. DbContext) reference via DI in code that is not bound a specific HTTP request, e.g. code that has to respond to messages arriving over a websocket.
Although the websocket itself is set-up initially with a specific HTTP request, messages will get responses over potentially a long lifetime of the websocket (as long as the user web session lasts). The server should not reserve/waste a DbContext/DB connection over this entire lifetime (this would result in exhaustion quickly), but rather obtain a DB connection temporarily when a message arrives and requires a response; discarding the DbContext/connection immediately afterwards - while the original HTTP request that set-up the websocket in the very beginning of the user-session technically is still there.
I haven't been able to find anything else but using:
httpContext.RequestServices.GetService(typeof(<MyNeededDbContext>)
This way I use the initial httpContext (obtained via DI when the websocket was set up), but at multiple times after that whenever a websocket message needs a response I can request a transient service object (a DbContext in this example), that may be recycled or pooled after the message response is complete, but while the original httpContext is very much still alive.
Anyone aware of a better approach?
You can create a new service scope to manage the lifetime of services yourself;
IServiceProvider provider = ...;
using (var scope = provider.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyNeededDbContext>();
...
}

Checking if URL exists - HTTP Request always returns an exception

There are numerous posts on how to check if a URL is valid. All of them feature basically the same code, which seems to work for everyone - not for me though and I don't get why.
public static bool ifURLexists(string url)
{
try
{
var request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "HEAD";
//response ALWAYS throws an exception
using (var response = (HttpWebResponse)request.GetResponse())
{
return response.StatusCode == HttpStatusCode.OK;
}
}
catch
{
return false;
}
}
I have tested the method with parameters such as "http://www.nonexistingwebsiteblabla.com" and "http://www.google.com". No matter if I insert an existing or a non existing URL, I get a WebException at this line:
using (var response = (HttpWebResponse)request.GetResponse())
Why could it be not working?
Check the status WebException.Status
This will let you know what specific web exception has occured.
Update: Try change the request.Method = "HEAD";
to GET and try.
Try with a unavailable (404) url, compare the status. Check whether anything is blocking your request.
This is how i manage in my code, i am handling using only ftp specific status.'CommStatus' is an ENUM with error codes which is available in whole application.
catch (WebException ex)
{
FtpWebResponse response = (FtpWebResponse)ex.Response;
switch(response.StatusCode)
{
case FtpStatusCode.ActionNotTakenFileUnavailable:
return CommStatus.PathNotFound;
case FtpStatusCode.NotLoggedIn:
return CommStatus.AuthenticationError;
default: return CommStatus.UnhandledException;
}
}
Below are the available Status of WebException.
CacheEntryNotFound
This API supports the product infrastructure and is not intended to be used directly from your code. The specified cache entry was not found.
ConnectFailure
This API supports the product infrastructure and is not intended to be used directly from your code. The remote service point could not be contacted at the transport level.
ConnectionClosed
This API supports the product infrastructure and is not intended to be used directly from your code. The connection was prematurely closed.
KeepAliveFailure
This API supports the product infrastructure and is not intended to be used directly from your code. The connection for a request that specifies the Keep-alive header was closed unexpectedly.
MessageLengthLimitExceeded
This API supports the product infrastructure and is not intended to be used directly from your code. A message was received that exceeded the specified limit when sending a request or receiving a response from the server.
NameResolutionFailure
This API supports the product infrastructure and is not intended to be used directly from your code. The name resolver service could not resolve the host name.
Pending
This API supports the product infrastructure and is not intended to be used directly from your code. An internal asynchronous request is pending.
PipelineFailure
This API supports the product infrastructure and is not intended to be used directly from your code. The request was a piplined request and the connection was closed before the response was received.
ProtocolError
This API supports the product infrastructure and is not intended to be used directly from your code. The response received from the server was complete but indicated a protocol-level error. For example, an HTTP protocol error such as 401 Access Denied would use this status.
ProxyNameResolutionFailure
This API supports the product infrastructure and is not intended to be used directly from your code. The name resolver service could not resolve the proxy host name.
ReceiveFailure
This API supports the product infrastructure and is not intended to be used directly from your code. A complete response was not received from the remote server.
RequestCanceled
This API supports the product infrastructure and is not intended to be used directly from your code. The request was canceled, the WebRequest.Abort method was called, or an unclassifiable error occurred. This is the default value for Status.
RequestProhibitedByCachePolicy
This API supports the product infrastructure and is not intended to be used directly from your code. The request was not permitted by the cache policy. In general, this occurs when a request is not cacheable and the effective policy prohibits sending the request to the server. You might receive this status if a request method implies the presence of a request body, a request method requires direct interaction with the server, or a request contains a conditional header.
RequestProhibitedByProxy
This API supports the product infrastructure and is not intended to be used directly from your code. This request was not permitted by the proxy.
SecureChannelFailure
This API supports the product infrastructure and is not intended to be used directly from your code. An error occurred while establishing a connection using SSL.
SendFailure
This API supports the product infrastructure and is not intended to be used directly from your code. A complete request could not be sent to the remote server.
ServerProtocolViolation
This API supports the product infrastructure and is not intended to be used directly from your code. The server response was not a valid HTTP response.
Success
This API supports the product infrastructure and is not intended to be used directly from your code. No error was encountered.
Timeout
This API supports the product infrastructure and is not intended to be used directly from your code. No response was received during the time-out period for a request.
TrustFailure
This API supports the product infrastructure and is not intended to be used directly from your code. A server certificate could not be validated.
UnknownError
This API supports the product infrastructure and is not intended to be used directly from your code. An exception of unknown type has occurred.
More details here: https://msdn.microsoft.com/en-us/library/system.net.webexceptionstatus(v=vs.110).aspx
Also you can use this option.
IPHostEntry ipHost = Dns.GetHostEntry(url);

c# mvc reroute request to different server

I have a web application which is a mesh of a few different servers and 1 server is the front-end server which handles all request external incoming requests.
So some of these request will have to be passed along to different servers and ideally the only thing I want to change is the host and Uri fields of these request. Is there a way to map an entire incoming request to a new outgoing request and just change a few fields?
I tried something like this:
// some controller
public HttpResponseMessage get()
{
return this.Request.Rewrite("192.168.10.13/api/action");
}
//extension method Rewrite
public static HttpResponseMessage Rewrite(this HttpRequestMessage requestIn, string Uri) {
HttpClient httpClient = new HttpClient(new HttpClientHandler());
HttpRequestMessage requestOut = new HttpRequestMessage(requestIn.Method, Uri);
requestOut.Content = requestIn.Content;
var headerCollection = requestIn.Headers.ToDictionary(x => x.Key, y => y.Value);
foreach (var i in headerCollection)
{
requestOut.Headers.Add(i.Key, i.Value);
}
return httpClient.SendAsync(requestOut).Result;
}
The issue I am having is that this has a whole slew of issues. If the request is a get Content shouldn't be set. THe headers are incorrect since it also copies things like host which shouldn't be touched afterwards etc.
Is there an easier way to do something like this?
I had to do this in C# code for a Silverlight solution once. It was not pretty.
What you're wanting is called reverse proxying and application request routing.
First, reverse proxy solutions... they're relatively simple.
Here's Scott Forsyth and Carlos Aguilar Mares guides for creating a reverse proxy using web.config under IIS.
Here's a module some dude named Paul Johnston wrote if you don't like the normal solution. All of these focus on IIS.
Non-IIS reverse proxies are more common for load balancing. Typically they're Apache based or proprietary hardware. They vary from free to expensive as balls. Forgive the slang.
To maintain consistency for the client's perspective you may need more than just a reverse proxy configuration. So before you go down the pure reverse proxy route... there's some considerations.
The servers likely need to share Machine Keys to synchronize view state and other stuff, and share the Session Store too.
If that's not consistent enough, you may want to implement session stickiness through Application Request Routing (look for Server Affinity), such that a given session cookie (or IP address, or maybe have it generate a token cookie) maps the user to the same server on every request.
I also wrote a simple but powerful reverse proxy for asp.net / web api. It does exactly what you need.
You can find it here:
https://github.com/SharpTools/SharpReverseProxy
Just add to your project via nuget and you're good to go. You can even modify on the fly the request, the response, or deny a forwarding due to authentication failure.
Take a look at the source code, it's really easy to implement :)

Extending WCF Compact / Custom Headers

I'm developing an app which will be deployed across various platform including Windows Phone. Because of this, I only have access to the WCF Compact / Portable classes.
I need to be able to catch every outgoing request and incoming response in order to apped headers to the request, and read the headers from the response.
When extending standard WCF I am able to achieve this using a custom behaviour, however in WCF compact this is not supported, so, I am able to use the following code to append headers to a specific request:
CalculatorServiceClient client = new CalculatorServiceClient();
using(new OperationContextScope(client.InnerChannel))
{
// We will use a custom class called UserInfo to be passed in as a MessageHeader
UserInfo userInfo = new UserInfo();
userInfo.FirstName = "John";
userInfo.LastName = "Doe";
userInfo.Age = 30;
// Add a SOAP Header to an outgoing request
MessageHeader aMessageHeader = MessageHeader.CreateHeader("UserInfo", "http://tempuri.org", userInfo);
OperationContext.Current.OutgoingMessageHeaders.Add(aMessageHeader);
}
However, I'm not able to catch the response headers in this example. I'm also worried that this isn't thread safe (where multiple requests could be happening at the same time). And, finally I'd like to implement this functionality in a way that is transparent to the developer - so that they don't need to do anything special their requests. I think I should be able to achieve it using something along the lines of a IClientMessageFormatter, however I'm at a loss as to how to implement this in WCF compact.
Any help would be greatly appreciated.
Thanks
David

Is single Request object instance in REST client okay?

I'm writing a simple REST client for a C# WinForm application. I use RestSharp to make sending requests and getting responses easier. I have a few questions regarding how I should design my client.
The user interacts with the client only once. He pushes a Button and the client is instantiated and passed to private methods to do some logic in the background. It accesses objects from the server and synchronizes them with objects in the user's internal database.
The point is that the client's methods are accessed by private methods called following the user's single action in the GUI. He does not have any control over which of the client's methods are called, and in which order.
So my questions are:
Can I ask the server for a token only once when I instantiate my client, and then store it in the client instance for future reference in the client's following requests? The token is a hash of the username and password, so it should not change over time. Of course, once I create a new instance of the client, it will again ask the server for a token.
Is it okay to keep a single Request object instance in my client? I can then set request header only once and all the methods that access the API will only need to change the request's resource URL and HTTP method. It would reduce repetitiveness in my code.
For example:
public PriceListItem[] GetPriceListItems()
{
string requestUrl = Resources.PriceListItemsUrl;
var request = new RestRequest(requestUrl, Method.GET);
request.AddHeader("SecureToken", _token);
var response = Client.Execute(request) as RestResponse;
JObject jObject = JObject.Parse(response.Content);
var priceListItems = jObject["Data"].ToObject<PriceListItem[]>();
return priceListItems;
}
I have quite a few methods for utilizing different resource URLs, but all have the same header. If I keep only one Request instance in my client I can set the header only once. Is this approach okay? I would like to avoid any delegates and events.
You have to use ParameterType.HttpHeader parameter:
request.AddParameter("Authorization", "data", ParameterType.HttpHeader);
It's perfectly normal to save auth token on client, as long it's encrypted and have expired time on it.
You can improve it with implement session on your REST API, so you just need check if the auth token is still valid or not, and do the authentication if it's not valid.
Clearly you need to manage the way you request to the REST API, I Recommend you to use IDisposable Pattern for this manner, you can utilize some lazy implementation or Singelton.

Categories