I'm developing a Web API RESTful service on the Azure platform.
I thought that the default client-side caching behavior would be to cache GET requests (since GET is idempotent and all).
To my surprise when I deployed the service to Azure all responses were sent with a Cache-Control: private header or other cache-disallowing header.
I tried the solution suggested in this question, it did work locally in IIS but did not work once we deployed to Azure. I could not find anything in the documentation about this ability which I thought was very basic in a RESTful service, I really hope that I'm missing something obvious, in MVC it was very easy.
tl;dr
We need to cache GET requests on the client side when using Azure and Web API.
I don't believe Azure is doing anything to you in this respect. It's a matter of you needing to specify exactly what caching properties you want for your resource(s).
With WebAPI you can control what caching properties your response has via the CacheControlHeaderValue which is accessible via the myHttpResponseMessage.Headers.CacheControl property.
Assuming you had a controller action like this:
public Foo Get(int id)
{
Foo myFoo = LoadSomeFooById(id);
return myFoo;
}
You'll need to do something like this to take control of the caching explicitly:
public HttpResponseMessage Get(int id)
{
Foo myFoo = LoadSomeFooById(id);
HttpResponseMessage myHttpResponseMessage = this.Request.CreateResponse(HttpStatusCode.OK, myFoo)
CacheControlHeaderValue cacheControlHeaderValue = new CacheControlHeaderValue();
cacheControlHeaderValue.Public = true;
cacheControlHeaderValue.MaxAge = TimeSpan.FromMinutes(30);
myHttpResponseMessage.Headers.CacheControl = cacheControlHeaderValue;
return myHttpResponseMessage;
}
Many of the other properties related to caching that you'd expect are also available on the CacheControlHeaderValue class, this is just the most basic example.
Also, bear in mind my example is extremely brute force/simplistic in that all the caching behavior/logic is right there in the action method. A much cleaner implementation might be to have an ActionFilterAttribute which contains all the caching logic based on attribute settings and applies it to the HttpResponseMessage. Then you could revert to the more model centric action method signature because you would, in this case, no longer need access to the HttpResponseMessage anymore at that level. As usual, many ways to skin the cat and you have to determine which works best for your specific problem domain.
Take a look at this http://forums.asp.net/post/4939481.aspx it implements caching as an attribute that modifies the HTTP response.
Disclaimer: I haven't tried it.
I would recommend this https://github.com/filipw/AspNetWebApi-OutputCache
Simple, quick and has various options to cache.
Hope that helps
Related
How can I generate absolute links to other resources in my RESTful API app when the app is meant to be accessed via a reverse proxy that publishes just the paths under /api?
My app is an API with a common layout of routes like /api, /swagger and /health. It is published on my employer's API management under a path of the form /business-area/api-name/v1. Calling the API both directly and through the API gateway overall works: calling https://api-gateway.company.com/business-area/api-name/v1/some-resource results in internal call to https://my-app.company.com/api/some-resource.
The issue is that the links in my app's responses point directly to the backend app (https://my-app.company.com/api/another-resource), not the the API gateway (https://api-gateway.company.com/business-area/api-name/v1/another-resource). They are generated using IUrlHelper.
I solved the domain by the ForwardedHeadersMiddleware and adding the X-Forwarded-Host by a policy on the API management. Sadly, we are allowed to use just extremely simple policies, so if we published the API using multiple gateways, the current solution would generate link to just a single one. But that is an issue to be solved somewhen later; now it works OK.
I could not get the path to work well. I tried changing the paths using a middleware as hinted in the ASP.NET Core behind proxy docs:
app.Use((context, next) =>
{
context.Request.PathBase = "/business-area/api-name/v1";
if (context.Request.Path.StartsWithSegments("/api", out var remainder))
{
context.Request.Path = remainder;
}
return next();
});
When I insert this middleware high in the pipeline, it breaks the routing, but if I insert it low enough, the routing works OK and only link generation is affected. But it seems that only PathBase change really affects link generation as the /api is still in the generated URI. I can see that the Path of the request object is really changed, though, so it is probably just that link generation uses the routing info directly, without passing through my middleware, which makes sense, but it rules out the middleware solution.
Is wrapping the standard IUrlHelper in my own implementation and postprocessing the URLs it returns a good way to go? I don't know how to go about that. I use the IUrlHelper from the ControllerBase.Url property and debugger tells it is actually an instance of Microsoft.AspNetCore.Mvc.Routing.EndpointRoutingUrlHelper. Doing the wrapping in every action seems wrong (repetitive, error-prone).
Changing the routing so that /api moves to the root is my last resort option as it mixes up the namespaces: technical endpoints like /health and /swagger would live among the actual resources of the API. Is there a reasonable way to avoid that while keeping the links working? This all seems like a pretty standard problem and I am surprised I cannot find how to solve it.
We use .NET 5 and we will migrate to .NET 6 as soon as it is out, if that makes any difference.
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 :)
I have a POST IHttpActionResult method in my API controller that uses the [FromBody] attribute.
[Route("gameworld/generate")]
public IHttpActionResult PostNewWorld([FromBody] WorldData json)
Normally, when I want to test my controllers locally, I just goto the URL.
However, I can't do that with this method because I get this error:
The requested resource does not support http method 'GET'.
Is there a way for my local environment to see that this is a POST event?
Thanks!
First of all, ASP.NET Web Api is smart enough to deserialize your json into an object, so unless you really expect a string value in the body you can just put your type in there.
[Route("gameworld/generate")]
public IHttpActionResult PostNewWorld([FromBody] WorldData newWorld)
You can either test it manually with tools like Postman or Swagger, but manual tests are usually done once and then forgotten. This opens up for regression bugs where you make a change in the future, forget to retest the endpoint and break the application using the api.
Therefor, you should write unit tests to keep checking your code and prevent regression bugs. I've been using MyTested.WebApi on several projects and you can test both the routing and the actual calls. For example:
MyWebApi
.Server()
.Working()
.WithHttpRequestMessage(req => req
.WithRequestUri("api/bugs")
.WithMethod(HttpMethod.Post)
.WithContent(myJsonString)
.ShouldReturnHttpResponseMessage()
.WithStatusCode(HttpStatusCode.Created)
.WithResponseModelOfType<BugReport>()
.Passing(r => r != null);
There's a lot of things to be tested, so be sure to read the docs.
What is the best practice when you need to authenticate specific OperationContracts, while using the default MembershipProvider for security (FormsAuthentication).
I guess that doing Membership.ValidateUser and Membership.GetUser just won't cut it when using WebServices, right?
In other words: How can I verify that a user is allowed to use specific methods in the webservice (that the user is authenticated/"logged on")?
Yeah--you can't really use FormsAuthentication in this case. But there is excellent infrastructure available in WCF for managing role-based access to individual methods: http://msdn.microsoft.com/en-us/magazine/cc948343.aspx
I have been known to over-engineer things, so when I use WCF in my web applications, I wrap the service in my web app. This way my web app calls the abstraction.
Now, what you can do is apply your code access security (CAS) on the wrapper.
Example code might look like this (tons of details omitted for brevity)
internal class ServiceWrapper
{
Service Svc;
public ServiceWrapper()
{
Svc = ServiceClient();
}
[System.Security.Permissions.PrincipalPermission(System.Security.Permissions.SecurityAction.Demand, Role = "HelloWorld")]
public string HelloWorld()
{
return Svc.HelloWorld();
}
}
In a perfect world, we would want CAS to be a bit more dry (don't repeat yourself), meaning handled in the WCF as you suggest. But this might be a good middle of the road if know you can lock down your WCF app and control who calls it :-)
That would help you simplify getting the ball rolling...
Good luck!
I am very new to web service stuff so please be kind.
I have written a simple POJO class, and deployed it on an axis2 server:
public class Database {
private Project project;
public void login(){
project = new Project();
project.setDescription("Hello there");
project.setName("To me");
}
public Project getProject(){
return project;
}
}
I call the service from a c# client:
localhost.Database db = new WindowsFormsApplication1.localhost.Database();
db.login();
localhost.getProjectResponse pr = new WindowsFormsApplication1.localhost.getProjectResponse();
pr = db.getProject();
When I debug the response is null.
At the java end, when I call getProject, the project object is null.
What's happening?
How do I preserve the state of project between service calls?
For most toolkits, web services are stateless by default. I think axis is no different.
If you want to maintain state between calls then you will need to enable sessions. An example on how to maintain sessions in axis can be found at:
http://kickjava.com/src/test/session/TestSimpleSession.java.htm
On the .NET side you will need to assign a CookieContainer to your request to store the session identifier. See HOW TO: Use CookieContainer to Maintain a State in Web Services for more information.
I think your code would look something like this:
localhost.Database db = new WindowsFormsApplication1.localhost.Database();
// Assign the CookieContainer to the proxy class.
db.CookieContainer = new System.Net.CookieContainer();
db.login();
localhost.getProjectResponse pr = new WindowsFormsApplication1.localhost.getProjectResponse();
pr.CookieContainer = db.CookieContainer;
pr = db.getProject();
I think that should let you do what you want -- but I wouldn't recommend it.
Designing service interfaces is a bit different than designing object oriented interfaces. Service interfaces typically eschew the use of state and instead require the consumer to provide all of the relevant information in the request.
From Service-Oriented Architecture:
Services should be independent,
self-contained requests, which do not
require information or state from one
request to another when implemented.
I would definitely recommend reading that article and perhaps revisiting your design.
I'm not sure why #shivaspk left a comment instead of writing an answer, it is quite correct: web service calls (not just axis calls) are meant to be stateless, so although the project object gets created by
db.login();
when you call
db.getProject();
It is being called on a different instance of your Database class that was created by Axis to service the second call.
There is no really good answer to your question, except for you to rethink what you are trying to do. If you need some kind of authentication (via login), then that authentication needs to be part of every web service call.