.Net 5 Custom Response Cache - webapi

Our old .Net framework APIs utilized the Strathweb.CacheOutput.WebApi2 which took care of server-side caching and set the appropriate client side headers in order to implement a form of output caching.
In that scheme, during project startup the following was injected to create a cache key. There is more custom code for our implementation that is appended to the key but this is enough to start the conversation. This method used the RedisOutputCache.
public class CacheKeyWithHeadersGenerator : DefaultCacheKeyGenerator
{
public override string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType,
bool excludeQueryString = false)
{
const string itemSeparator = "-";
const string paramSeparator = "=";
var cacheKey = base.MakeCacheKey(context, mediaType, excludeQueryString);
var requestHeader = context.Request.GetHeaders();
cacheKey += $"{itemSeparator}isdr{paramSeparator}{requestHeader.IsDR}";
Similar functionality is needed as we migrate to .Net 5, but I would like to know my options as there does not appear to be much in that covers this. In order for this to work, the rule that the presence of an Authorization header must be overridden.
Can anyone point me in the right direction? Should I be using Distributed Caching instead?
Edit 1
This question may be a bit hard to understand. I am attempting to cache at the client side using a custom cache key to contain the userid and other information contained in the Authorization header (as Strathweb did) in .Net 5 web api. In other words, data would be both client cached and server cached by utilizing a custom cache key in addition to what is sent via the query.

Related

.NET Core 6 + Blazor: How to call server while only passing data in the uri

My question is about how to do the following with .NET Core 6 Blazor. If I knew how to simply make Blazor send some URI to the server, I would just do so, but I don't. I have googled for this quite extensively, but not found an answer.
I want to modify a single property of a record stored in a table. The server should load the data record with the given id, increment or decrement a counter stored in it, and save the record. There is no need to send the data to the client, have it modify the data, and have it send the modified data back to the server. Basically I am trying to model kind of RPC with Blazor.
So I want to make a call to my .NET Core 6 Blazor server, only passing a parameter in the URI (the ID of the database record to be modified).
Is there a method I could call that doesn't require a content parameter like PostAsync() and PutAsync() do? Right now I am doing this:
public static async Task<bool> AddReference (Guid ID)
{
HttpResponseMessage response =
await httpClient.PostAsync(Routes.WatermarkApi + $"AddReference/{ID}", null);
return (response.StatusCode == System.Net.HttpStatusCode.OK);
}
passing null for the content parameter, but this looks a little awkward to me.

Service Fabric Web API Versioning issue

I'm working on a service fabric project with multiple stateless services. When i try to add versioning as in the code below
[Authorize]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class SessionController : Controller
{
...
}
it's not working when calling the service later using Postman or using some client winforms app i made just to call this service. And when i say it's not working i mean it's not looking for a specific version i placed in the controller.
e.g.
I'm calling http://localhost:1234/api/v1.0/session/set-session and as you can see in my controller i only have version 2.0. Now my API gets hit this way or another no matter what version number i put in.
I added code to the Startup.cs
services.AddApiVersioning(options => {
options.DefaultApiVersion = new ApiVersion(2, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});
Specific API call looks like this:
[HttpPost]
[Route("set-session")]
public async Task<IActionResult> SetSession([FromBody] SessionModel model)
{ ... }
Can anyone tell me what am i missing or maybe api versioning is not supported in service fabric at all?
Thanks.
Does your solution work locally? Based on what I see, I would suspect - no. This should have nothing to do with Service Fabric at all.
Issue 1
I see that your base class inherits from Controller, which is allowed, but is usually ControllerBase. No concern there, just FYI. The crux of the problem is likely that your controller has not applied the [ApiController] attribute. API Versioning defines IApiControllerSpecification and IApiControllerFilter, which is used to filter which controllers should be considered an API. This is important for developers building applications that have the UI and API parts mixed. A controller is a controller in ASP.NET Core and it was difficult to distinguish these two in the early days. There is now a built-in IApiControllerSpecification that considers any controller with [ApiController] applied to be an API. This can be changed, replaced, or completely disabled using ApiVersioningOptions.UseApiBehavior = false.
If your library/application is only APIs, you can decorate all controllers at once using:
[assembly: ApiController]
Since your controller is not currently being considered an API, all requests matching the route are being directed there. The value 1.0 is being considered an arbitrary string rather than an API version. This is why it matches at all instead of HTTP 400. I suspect you must only have one API and it is defined as 2.0; otherwise, I would expect an AmbiguousActionException.
Issue 2
Your example shows that you are trying to version by URL segment, but you've configured the options to only consider the header x-api-version. This option should be configured with one of the following:
URL Segment (only)
options.ApiVersionReader = new UrlSegmentApiVersionReader();
URL Segment and Header
// registration order is irrelevant
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"));
Default (Query String and URL Segment)
// NOTE: this is the configuration
// options.ApiVersionReader = ApiVersionReader.Combine(
// new QueryStringApiVersionReader(),
// new UrlSegmentApiVersionReader());
Side Note
As defined, using the URL segment and header versioning methodologies don't make sense. You have a single route which requires an API version. A client will always have to include the API version in every request so there is no point to also supporting a header.
If you define 2 routes, then it makes sense:
[Route("api/[controller]")] // match by header
[Route("api/v{version:apiVersion}/[controller]")] // match by url segment
Versioning by URL segment, while common, is the least RESTful. It violates the Uniform Interface constraint. This issue demonstrates yet another problem with that approach. Query string, header, media type, or any combination thereof will all work with the single route template of: [Route("api/[controller]")]
Observation 1
You have configured options.AssumeDefaultVersionWhenUnspecified = true. This will have no effect when versioning by URL segment. It is impossible to provide a default value of route parameter in the middle of a template. The same would be true for api/value/{id}/subvalues if {id} is not specified.
This option will have an effect if you:
Add a second route template that doesn't have the API version parameter
You update your versioning strategy to not use a URL segment
It should be noted that is a highly abused feature. It is meant to grandfather in existing services that didn't previously have explicit versioning because adding it will break existing clients. You should be cognizant of that if that isn't your use case.

Why does GraphQL .Net HttpConext not have the correct user session?

I am using GraphQL .net to respond to graphql queries on the backend of an Asp.net Core website. All the cookies seem to be passed with the requests but for some reason my graphql.net requests do not have the proper user session set on the HttpContext. The ClaimPrincipal is mostly empty via graphql.net while my Asp.net Core WebApi/Mvc style endpoints have the correct principal with user id even though both GraphQl.Net requests and non-graphql.net requests are happening at the same time.
I checked the payload and all the same cookies are passed in both requests. So it makes me wonder why are my regular WebApi endpoints able to (auto-magically) get the claims principal and why can't the graph.net endpoints do the same. As far as I know from previous usages of GraphQl.net I wasn't aware that any special session code had to be added (other than passing the user from the HttpContext to graphQL.net).
I've been reading through GraphQL.Net and Asp.net core source code and docs, but so far I haven't found any obvious offenses or leads.
What might cause some issue like this? what are some common causes?
Should I just try to figure out how to manually read in the cookie to Asp.net core and pull the principal?
Perhaps I'm missing a special header value? I don't think the headers are weird but I haven't done a side by side comparison between the headers in graphql.net and asp.net core requests.
In this snippet is where I first detect a problem. If I put a breakpoint here then the claimsprinical isn't correctly set for the current user session. And also later when I access the HttpContext the user session is not correct for graphql.net requests.
public static GraphQLUserContext InitializeFromContext(HttpContext httpContext)
{
return new GraphQLUserContext
{
User = httpContext.User,
};
}
Here's part of the Graphql.net configuration:
services.AddGraphQL((options, provider) =>
{
options.EnableMetrics = _env.IsDevelopment();
var logger = provider.GetRequiredService<ILogger<WebDependencyInjectionConfig>>();
options.UnhandledExceptionDelegate = ctx => logger.LogError("{Error} occurred", ctx.OriginalException.Message);
})
.AddErrorInfoProvider(opt =>
{
opt.ExposeExceptionStackTrace = _env.IsDevelopment();
opt.ExposeCodes = _env.IsDevelopment();
opt.ExposeCode = _env.IsDevelopment();
opt.ExposeData = _env.IsDevelopment();
opt.ExposeExtensions = _env.IsDevelopment();
})
.AddSystemTextJson()
.AddUserContextBuilder(GraphQLUserContext.InitializeFromContext)
.AddGraphTypes(typeof(PrimarySchema), ServiceLifetime.Scoped);
I'll gladly provide any requested configuration if anyone wants it, but there is a lot of possible code it touches. Thanks!
What does your Configure method look like? Is your app.UseAuthentication() before your GraphQL middleware configuration?
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseGraphQL<MySchema>();
}
https://github.com/dotnet/aspnetcore/blob/790c4dc2cf59e16e6144f7790328d563ca310533/src/Security/samples/Cookies/Startup.cs#L45-L66

How do I apply the OutputCache attribute on a method in a vNext project?

What is the correct way of using the following in a vNext application on an async method:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
I see it is part of System.Web.Caching, but the only place I could add that would be in the aspnet50 -> frameworkAssemblies section of my project.json file, which is incorrect.
ASP.NET Core 1.1/2.0 Answer
Add the response caching middleware like so:
public void Configure(IApplicationBuilder application)
{
application
.UseResponseCaching()
.UseMvc();
}
This middleware caches content based on the caching HTTP headers you set in your response. You can take a look at the rest of the answer to see how to use ResponseCache.
ASP.NET Core 1.0 Answer
Use the new ResponseCache attribute instead. ResponseCache is not a direct replacement of OutputCache as it only controls client and proxy caching using the Cache-Control HTTP header.
If you want to use server side caching, see this StackOverflow question discussing how to use IMemoryCache or IDistributedCache.
// Add this to your controller action.
[ResponseCache(Duration = 3600)]
Here is an example using the new cache profiles:
// Add this to your controller action.
[ResponseCache(CacheProfile="Cache1Hour")]
// Add this in Startup.cs
services.AddMvc(options =>
{
options.CacheProfiles.Add(
new CacheProfile()
{
Name = "Cache1Hour",
Duration = 3600,
VaryByHeader = "Accept"
});
});
Gotchas
The response caching middleware stops working in a variety of situations which you can learn more about in the docs. Two common ones you will probably hit are that it stops working if it sees an Authorization or Set-Cookie HTTP header.
Bonus Comment
In ASP.NET 4.6, we could represent cache profiles in the web.config and change the settings without recompiling the code. For more information about how you can move your cache profiles to the new appsettings.json, rather than hard coding it in Startup.cs see this question.
Update
As AndersNS was kind to point out, it will be available in RC1 most likely: https://github.com/aspnet/Mvc/issues/536.
To put it simply there's no OutputCache or equivalent in ASP.NET 5 currently.
However, please note that OutputCache is just an attribute with minimal logic that talks to a cache provider. You can easily implement your own such attribute, using Memory Cache for example. Or you can use third party solutions.
I am sure that when ASP.NET 5 will ship there will be plenty of solutions out on the market. And I'm quite sure that we will have an official OutputCache equivalent too.
Here's the basic MemoryCache usage in case someone finds it useful
MemoryCache cache = MemoryCache.Default;
string cacheName = "MyCache";
if (cache.Contains(cacheName) == false || cache[cacheName] == null)
{
var data = ... get data
cache.Set(cacheName, data, new CacheItemPolicy() { SlidingExpiration = DateTime.Now.AddDays(1).TimeOfDay });
}
return cache[cacheName];

.NET Recaptcha https

We've started using the ASP.NET recaptcha control and it works fine. but one of the requirements we have is that all outbound traffic goes over Https.
I know that recaptcha supports https, but It's not clear how to configure (or even if it is configurable) when using the ASP.NET plugin option.
has anyone got any experience of this?
I'll expand a little on what I've found so far....
The Recaptcha package contains 3 public classes
RecaptchaControl,
RecaptchaValidator
and
RecaptchaResponse
RecaptchaControl is an Asp.NET control, the recaptcha specific methods on there seem to be concerning themes/look and feel.
An instance of the Validator has a RemoteIP field (which I presume would represent the verification server), but I can't a way of binding that to the control.
RecaptchaResponse seems to more or less represent an enum with possible responses (valid/invalid/failed to connect).
looks like the Recaptcha control intelligently selects https if the request was https.
I'm presuming it does the same for the validation, but its not clear from source code
http://code.google.com/p/recaptcha/source/browse/trunk/recaptcha-plugins/dotnet/library/
private const string VerifyUrl = "http://www.google.com/recaptcha/api/verify";
private const string RECAPTCHA_SECURE_HOST = "https://api-secure.recaptcha.net";
private const string RECAPTCHA_HOST = "http://api.recaptcha.net";
--------------------------------SNIP------------------------------------
/// <summary>
/// This function generates challenge URL.
/// </summary>
private string GenerateChallengeUrl(bool noScript)
{
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.Append(Context.Request.IsSecureConnection || this.overrideSecureMode ? RECAPTCHA_SECURE_HOST : RECAPTCHA_HOST);
urlBuilder.Append(noScript ? "/noscript?" : "/challenge?");
urlBuilder.AppendFormat("k={0}", this.PublicKey);
if (this.recaptchaResponse != null && this.recaptchaResponse.ErrorCode != string.Empty)
{
urlBuilder.AppendFormat("&error={0}", this.recaptchaResponse.ErrorCode);
}
return urlBuilder.ToString();
}
If you check out http://recaptcha.net/apidocs/captcha/client.html it says:
"In order to avoid getting browser
warnings, if you use reCAPTCHA on an
SSL site, you should replace
http://api.recaptcha.net with
https://api-secure.recaptcha.net."
So clearly recaptcha supports HTTPS submissions. Does the ASP.NET control have any properties you can configure the outbound URL? At worst you might need to use Reflector to examine the code and see how it's built.
The .NET library does not require any configuration to work on HTTPS environment. It will derive from the current HttpContext whether the request is made from HTTPS protocol.
But, there is RecaptchaControl.OverrideSecureMode property that you can use just in case it doesn't work as expected. Set to True to force HTTPS mode.
Update:
I seem to have misunderstood the question. I am afraid there is no HTTPS endpoint for reCAPTCHA verification (between your server and theirs).
We are using the reCAPTCHA plugin for .NET, and we needed to do two things to get it working over SSL in our environment. Our dev environment does not use SSL, and our test and production environments do.
Set the RecaptchaControl.OverrideSecureMode property to true, as Adrian Godong mentioned in his original answer to this question. This allowed the control to work locally and in dev not using SSL, and in test and prod using SSL.
<recaptcha:RecaptchaControl
OverrideSecureMode="True"
ID="recaptcha"
runat="server"
Theme="blackglass"
/>
When we generated the public and private keys, we specified global keys. This allowed us to use recaptcha in all of our different environments (local, dev.mydomain.com, test.mydomain.com and mydomain.com) and fixed the "input error: invalid referrer" error.

Resources