ASP.NET Core ResponseCacheAttribute - VaryByCustom? - c#

I've been using Microsoft.AspNetCore.Mvc.ResponseCacheAttribute for the first time and have come across an issue that I would have solved previously using the VaryByCustom property in OutputCachein ASP.NET (and using public override string GetVaryByCustomString(HttpContext context, string s) in the global.asax).
This VaryByCustom caching seems to no longer exist in ASP.NET Core. Is there a built-in alternative that I'm missing here or will I need to implement this myself to achieve something similar?

From my understanding, you have two flexible options in ASP.NET core:
Use the VaryByHeader or VaryByQueryKeys if you're using the ResponseCacheAttribute.
When using Headers, you need to write the value to vary by as a header, which could be any arbitrary value (no need to expose data to the client):
Response.Headers.Add("X-My-Vary-Header", "this-is-variable");
In essence, this is all the VaryByCustomString ever did for you anyway. The way I see it, you're no longer forced to put this code in a specific method/file (e.g. global.asax).
Try the <cache> Tag Helper when caching in Razor.
Here you have a wide range of things to "vary" by: vary-by-header, vary-by-route, vary-by-user, and even a custom vary-by.
Have a look here and decide whether to use the attribute or the cache tag helper: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/cache-tag-helper

Related

Apply SuppressImplicitRequiredAttributeForNonNullableReferenceTypes only to particular paths

Can you apply the SuppressImplicitRequiredAttributeForNonNullableReferenceTypes option to only a particular path pattern, e.g., .../v3/...?
We've gone through the trouble of enabling nullable contexts throughout our code, and ensuring all our parameters have the correct nullability. Now we want to utilize that for API validation. But since we don't want break any of our exising API behavior, we only want to apply the implicit Required attribute behvaior on paths for particular API versions. I.e., v2 would NOT have the validation, but v3+ would.
Is there any way to do this?
I can show you the way, but you have to walk through it and complete the implementation!
Ok, to see how SuppressImplicitRequiredAttributeForNonNullableReferenceTypes works, let's check the source code first!
Here's when the parameter is being used in DataAnnotationsMetadataProvider class.
https://github.com/dotnet/aspnetcore/blob/3ea008c80d5cc63de7f90ddfd6823b7b006251ff/src/Mvc/Mvc.DataAnnotations/src/DataAnnotationsMetadataProvider.cs#L343
Now let's see where DataAnnotationsMetadataProvider is used! It is added as a ModelMetadataDetailsProviders to MvcOptions.ModelMetadataDetailsProviders.
https://github.com/dotnet/aspnetcore/blob/3ea008c80d5cc63de7f90ddfd6823b7b006251ff/src/Mvc/Mvc.DataAnnotations/src/DependencyInjection/MvcDataAnnotationsMvcOptionsSetup.cs#L54
So you would need to create your own CustomValidationMetadataProvider and add it to MvcOptions.
builder.Services.AddControllers(op =>
{
op.ModelMetadataDetailsProviders.Add(new CustomValidationMetadataProvider());
});
public class CustomValidationMetadataProvider : IValidationMetadataProvider
{
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
// context.ValidationMetadata.IsRequired = ???
}
}
Here you can have your own logic to set context.ValidationMetadata.IsRequired. Unfortunately I'm not sure if you can access the request path here, but you do have access to attributes on the model. So theoretically you could add an attribute to the models on your v3.
There are a few things that I could suggest here:
If you on C# 8 you can try nullable properties/fields for places where you want to allow nullable values. (recommended simplest one)
You can use custom Parameter Binding (non-trivial approach). You can find more details here https://www.strathweb.com/2013/04/asp-net-web-api-parameter-binding-part-1-understanding-binding-from-uri/
You can disable standard model validation and provide your own (where you should be able to specify path) and again non-trivial approach.

How to use ambient route values in ASP.NET Core 3 Razor Pages?

Using ASP.NET Core 3 Razor Pages, suppose you would like to set up pages with the following URL structure:
http://contoso.com/course/4231/details
http://contoso.com/course/4231/students
where 4231 is the course ID. This can be achieved by using the #page directive:
#page "/course/{courseId}/details"
Now suppose you want to create links between the /details and the /students pages. You then want the courseId parameter to be added to every link, which can be accomplished using the anchor tag helper as follows:
<a asp-page="./students" asp-route-courseid="#RouteData.Values["courseId"]>Students</a>
As far as I've understood it, in ASP.NET Core 2.1 and earlier versions it was not necessary to add the asp-route-* attribute for this purpose, because route values would 'propagate' to other pages, being added automatically to any anchor tag unless manually overridden ("ambient route values", as it was called). This was apparently removed in version 2.2, but I'm not quite sure why.
Having to always remember to manually propagate route values using the above scheme seems like a very error-prone workflow, and could get out of hand quick if you have many route values that always needs to be added to anchor tags.
Is it possible to manually enable ambient route values in later versions of ASP.NET Core, at the very least for individual parameters? Is there any reason not to do this?
Essentially, I'd like for the anchor tag helpers to be relative, so that just linking to ./students from /course/4231/details automatically resolves to /course/4231/details.
Since I managed to find a solution for this, I'm going to answer my own question.
As mentioned in this GitHub issue (credit to Oliver Weichhold), you can override the default anchor tag helper (and form action tag helper) to achieve the desired behaviour of having route values automatically added to links.
I ended up implementing derived versions of the tag helpers that will automatically add route values if they are part of the route template, because that's the behaviour I was looking for. To keep it short, the Process method of the tag helpers looks like this:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var routeTemplate = ViewContext.ActionDescriptor.AttributeRouteInfo.Template;
Regex regex = new Regex(#"\{(\w+)(:.+)?\??\}");
var matches = regex.Matches(routeTemplate);
if (matches.Count > 0)
foreach (var routeValueKvp in ViewContext.HttpContext.Request.RouteValues)
if (matches.Any(x => x.Groups[1].Value == routeValueKvp.Key))
RouteValues[routeValueKvp.Key] = routeValueKvp.Value.ToString();
base.Process(context, output);
}
This solution works, but it's not optimal. One of the reasons is that the custom tag helpers needs to be decorated with the HtmlTargetElement attributes manually, which means that if the base versions of the tag helpers gets extended with new attributes in future releases of the framework, the custom versions will need to be updated manually.
IMO, it is expected. When you redirect to the same action/page, the current ambient route values are being reused.
<a asp-page="details">Studentdetails</a>
It generates url: /course/4231/details.
When you redirect to a different action/page, the ambient values are correctly ignored.
<a asp-page="./students">Students</a>
it will not generate correct url.
Refer to https://github.com/dotnet/aspnetcore/issues/3746

.NET OData Web api

I have 2 ways to use a model generated by Entity Framework. I can not find which to use when and why.
Method 1
ODataQueryOptions<Key_Result> options (Passed as function argument)
private ODataQuerySettings settings = new ODataQuerySettings();
IQueryable<Key_Result> result;
try
{
result = options.ApplyTo(DataAccessFunction.Key(keyIds), settings) as IQueryable<Key_Result>;
}
Method 2
IQueryable<Log> result;
try
{
result = AccessModel.Log;
}
So far, I have used them in my code without knowing what is correct or why both are even used. I can't find any material to help me too.
Also, the first one I am using in Odata endpoints created using the table valued functions in sql while the second one I am using with endpoints created using simple tables and views.
But if Entity framework is consistent, it shouldn't matter. And I should be able to use the two approaches interchangeably. Can they be used interchangeably, what is the difference which makes them preferred for one situation (Table valued function) and not preferred for the other one (Tables, views).
Both can be used but both have different uses. If my settings parameters such as null propagation, stable sort or page size have to be set I could use method 1.
However, setting page size etc. could also be done without this. Method 2 is the simplest but does not handle any page sizing or null propagation etc.

Orchard client-side validation - how SHOULD it look/work?

I'm attempting to use client-side validation in an admin page in Orchard. I've been successful at making it work using the techniques discussed in this question, but after doing some digging in the Orchard source and online, it seems to me that commenting out these lines
// Register localized data annotations
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider());
is subverting some built-in Orchard functionality which allows for localized error strings. At this point, either having these lines in our out of OrchardStarter.cs is the only difference between validation working and not working for me.
What I'm hoping for is some guidance on this, maybe from the Orchard team. If these lines have to be out in order for validation to work, why are they there in the first place? If they are there for a good reason, what am I (and others) doing wrong in our attempts to get client-side validation working? I'm happy to post code samples if needs be, although it's a pretty standard ViewModel with data annotations. Thanks.
The lines are there to replace the DataAnnotationsModelValidatorProvider (DAMVP) with Orchard's own implementation, which allows localizing the validation messages the Orchard way. The way it does this is by replacing e.g. [Required] with [LocalizedRequired] before passing control on to the DAMVP. Note that DAMVP does get to do its job - but only after Orchard has "messed" with the attributes.
The problem is that DAMVP uses the type of the Attribute to apply client validation attributes. And now it won't find e.g. RequiredAttribute, because it's been replaced by LocalizedRequiredAttribute. So it won't know what - if any - client validation attributes it should add.
So, out-commenting the lines will make you lose Orchard's localization. Leaving them in will make you lose client validation.
One workaround that might work (haven't looked enough through Orchard's code, and haven't the means to test at the moment) would be to make DAMVP aware of Orchard's Localized attributes and what to do with them.
DAMVP has a static RegisterAdapter() method for the purpose of adding new client rules for attributes. It takes the type of the attribute and the type of the Client-side adapter (the class that takes care of adding client attributes) to use.
So, something like the following might work:
In OrchardStarter.cs:
// Leave the LocalizedModelValidatorProvider lines uncommented/intact
// These are the four attributes Orchard replaces. Register the standard
// client adapters for them:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(LocalizedRegularExpressionAttribute),
typeof(RegularExpressionAttributeAdapter)
);
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(LocalizedRequiredAttribute),
typeof(RequiredAttributeAdapter)
);
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(LocalizedRangeAttribute),
typeof(RangeAttributeAdapter)
);
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(LocalizedStringLengthAttribute),
typeof(StringLengthAttributeAdapter)
);
As for the official word, it would seem this hasn't worked since localized validation was introduced in 1.3, and the impact is considered low: http://orchard.codeplex.com/workitem/18269
So, at the moment, it appears the official answer to the question title is, "it shouldn't".

URL Querystring - Find, replace, add, update values?

We inherited some C# code as part of a project from another company which does URL redirects that modifies the existing query string, changing values of items, adding new params, etc as needed. The issue however is that the code is buggy at best, and ends up duplicating items in the query string instead of updating them properly. The code works on the first pass but on additional calls the duplication issues become apparent.
Ex: MyPage.aspx?startdate=08/22/09&startdate=09/22/09
Instead of duplicating the item it needs to be either updated with the new value if it already exists, or added if not there already.
Is there a C# class or set of functions for handling query strings, allowing a simple means to access and update/add parameters that gets around these issues instead of the blind add approach that seems to be in use now with the code? This needs to be able to handle multiple parameters that may or may not exists at all times and be added and updated on subsequent calls.
We would sooner use existing logic than recreate something if possible so as to get this resolved quickly in a semi standard way for future maintainability and reuse.
Yes I would suggest converting the querystring to a collection by using HttpUtility.ParseQueryString()
You can then find/add/update/replace values directly in the collection, before re-creating the querystring from this collection.
This should make it easier to spot duplicates.
You can access and manipulate all values of your Querystring through the Request.QueryString collection. Here's a link.
this seems a basic design problem.
instead of updating the current query string, what SHOULD be done is simply adding all the parameters to the base at every time.
sure, you CAN update it, but (pseudocode)
if querystring exists
then update query string
else
add query string
will get crazy when you start using more than 1 variable.
redesign would be best, effort allowing.
The WCF REST Starter Kit available on ASP.NET also include a new "HttpQueryString" helper class that will most likely be included in the .NET 4.0 time frame into the base class library.
See an excellent screencast on how to use this utility class here:
http://channel9.msdn.com/shows/Endpoint/endpointtv-Screencast-HttpClient-Query-String-and-Form-Input-Management/
Marc

Categories

Resources