Response Caching using VaryByQueryKeys with dictionary querystring argument - asp.net-core-mvc

My ASP.NET Core MVC application has a controller method with a dictionary argument, passed via the request query string:
public async Task<IActionResult> Get([FromQuery] Dictionary<string, string> filterCriteria)
{
}
The application uses Response Caching with the VaryByQueryKeys option to return cached responses for requests that have previously been served. This works as required for simple querystring values, using an attribute on the method with the following syntax: [ResponseCache(VaryByQueryKeys = new[] { "argName" }, Duration = 20)]
However, I want to use it with a dictionary argument illustrated above.
Can it be used with an object argument like a dictionary? What is the syntax to use when specifying the list of querystring keys in the ResponseCache attribute?

It turns out you can specify queryKeys for a dictionary argument using the following syntax:
[ResponseCache(VaryByQueryKeys = new[] { "argName[key1]", "argName[key2]", ... }, Duration = 20)]

Related

How to get Spring Boot to map query parameters separately from form data?

#RequestMapping(value = "/**", consumes = MediaType.ALL_VALUE)
#ResponseBody
public Object mirror(HttpServletRequest req, #Nullable #RequestBody Map<String, String> form) {
...
}
I just want the plain key and value for all form data entries here but it also includes query parameters in the map.
I need to be able to tell the difference between what came from the form and what came from the query.
Getting the query parameters separately is easily done using URI parsing but it's not so easy to remove the query parameters from the form map. Especially in the case they have the same keys.
Changing the parameter to MultiValueMap adds values with the same key into an array. Using just a Map causes the query parameters to overwrite the form data with equal keys.
I found where this is happening, for the MockHttpServletRequest at least: buildRequest method:
String query = this.url.getRawQuery();
if (!this.queryParams.isEmpty()) {
String s = UriComponentsBuilder.newInstance().queryParams(this.queryParams).build().encode().getQuery();
query = StringUtils.isEmpty(query) ? s : query + "&" + s;
}
if (query != null) {
request.setQueryString(query);
}
addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams());
this.parameters.forEach((name, values) -> {
for (String value : values) {
request.addParameter(name, value);
}
});
It's combining the form data and query data into one map. So is there an alternative way to parse the form data ONLY or exclude query params from the map!?
From the javadoc for #RequestParam:
In Spring MVC, "request parameters" map to query parameters, form data, and parts in multipart requests. This is because the Servlet API combines query parameters and form data into a single map called "parameters", and that includes automatic parsing of the request body.
Not sure if there's a more elegant way, but you could possibly use Spring's UriComponentsBuilder class to parse the URI string and get back the query parameters.

Is breeze provide support to work with c# Enums

I want to populate my dropdowns with enum using breeze. It show enum in breeze metadata but I did not find a way how to consume it. Is breeze provide support yet to work c# enums?
If you are using a standard Breeze WebApi server then enum values should all be coming down as strings and can be saved back to the server also as strings. Out of the box, the default Breeze WebApi server implementation will convert the strings to the correct enum values automatically in both directions.
(Note: this will not work with a server using the current MS WebApi/OData provider because MS does not yet not support enums with this provider - MS will support this in their upcoming 4.0 release.)
So, for example, assuming that you had a server side enum that looked like this:
public enum RoleType {
Guest = 0,
Restricted = 1,
Standard = 2,
Admin = 3
}
in conjunction with a 'Role' class that had a 'RoleType' property, then the the following query would work:
var query = new EntityQuery("Roles").where("RoleType", "==", 'Restricted');
myEntityManager.executeQuery(query).then(...);
Similarly, if you wanted to change a RoleType for a given role then you could
role.RoleType = "Standard"; // assuming breeze backingStore adapter.
myEntityManager.saveChanges();
If you are generating offline breeze metadata then you can get all Enums definition in metadata itself and you can create a JS Dictionary(associated array) to populate all enum types and their definitions.
Well My situation was that I was using NancyFx(No EF) + Breeze + AngularJS for my web project. I generated metadata offline and stored it in a metadata.js file.
After creating EntityManager, I extracted enum defintions in a JS dictionary which I used later for binding dropdown, display corresponding enum strings etc.
Extract Enum Definition & Store in JS Dictionary
JSON.parse(window.app.metadata).schema.enumType.forEach(function (enumType) {
var newEnumValues = [];
enumType.member.forEach(function (enumValue) {
var newEnumValue = { id: enumValue.value, name: enumValue.name };
newEnumValues.push(newEnumValue);
});
enumDictionary[enumType.name] = newEnumValues;
});
Method to retrieve enum value on base of enum name and valueid
function GetEnumDictionaryValue(enumName, enumValueId) {
var result = null;
enumDictionary[enumName].some(function (enumValue) {
if (enumValue.id == enumValueId) {
result = enumValue.name;
return;
}
});
return result;
}
Binding Values to Dropdown
Now for binding dropdowns, just call enumDictionary[] and store result in a controller variable.
I was using Angular JS, so I used below code on html page
<select ng-options="type.id as type.name for type in <VariableName>"
></select>

Pass null String to Web API 2 (Visual Studio 2013)

I have a method like this:
public IEnumerable<Test> GetTest(Int32 idTest, String codTest, Int value)
and a test client like this:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
response = await client.GetAsync("Test/GetTest/0/null/1/");
I need to pass null in the second parameter (that is not optional), but on server side I get a string with "null" inside and not the null value. I would not to put conversion functions for each parameter. I see that works if I put [FromBody] attribute to just one parameter, but if I set [FromBody] for all the string parameters I get internal server error.
Many thanks.
You typically shouldn't have optional parameters in the middle of a route.
I would recommend changing the order of your parameters, or add them as query parameters so they can be optional.
e.g. for http://example.com/Test/GetTest?a=0&b=&c=1
public IEnumerable<string> GetTest(Int32 idTest)
{
var queryPairs = Request.GetQueryNameValuePairs();
DoSomething(queryPairs);
...
}

MapODataRoute and ODataQueryOptions

Im building a WebAPI OData solution that handles untyped entity objects, as described in this excellent post. Like that post, I define my EdmModel upfront, and use the MapODataRoute method and pass in the model to use:
config.Routes.MapODataRoute("odata", "odata", ModelBuilder.GetEdmModel());
However, this does not seem to work with ODataQueryOptions parameter in my methods:
Get(ODataQueryOptions query)
{
}
It gives the following error: The given model does not contain the type 'System.Web.Http.OData.IEdmEntityObject'. Parameter name: elementClrType
Is there any way to get ODataQueryOptions to work with MapODataRoute?
You should build the ODataQueryOptions manually in your controller action in untyped mode. Sample code follows,
ODataPath path = Request.GetODataPath();
IEdmType edmType = path.EdmType;
IEdmType elementType = edmType.TypeKind == EdmTypeKind.Collection
? (edmType as IEdmCollectionType).ElementType.Definition
: edmType;
// build the typeless query options using the element type.
ODataQueryContext queryContext = new ODataQueryContext(Request.GetEdmModel(), elementType);
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, Request);
Ive managed to do it as follows:
ODataPath path = Request.GetODataPath();
IEdmType edmType = path.EdmType;
private ODataQueryOptions GetODataQueryOptions(IEdmType edmType)
{
IEdmModel model = Models.ModelBuilder.GetEdmModel();
ODataQueryContext queryContext = new ODataQueryContext(model, edmType);
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, this.Request);
return queryOptions;
}
This works for most query options, but crashes with $select and $expand: The type 'Collection([Org.Microsoft.Product Nullable=False])' is not an entity type. Only entity types support $select and $expand. Is there a way to avoid this exception gracefully when a client tries to filter with $select and $expand, or should i just write something like
if (Request.RequestUri.Query.Contains("select")) { return errormessage }
Also, and more importantly, how would one apply these query options to an EdmEntityObjectCollection which gets returned in the first method?
queryOptions.ApplyTo(collectionProduct.AsQueryable()); // wont work...
(perhaps it is better practice to dynamically build your collection in regards to the query options anyway)

How do I store a comma-separated list in Orchard CMS?

Using Orchard CMS, I am dealing with a record and a part proxy, but cannot figure out how to save it into the DB. In fact, I confess I don't even know how to get the items I'm trying to save into this paradigm. I was originally using enum's for choices:
MyEmum.cs:
public enum Choices { Choice1, Choice2, Choice3, Choice4 }
MyRecord.cs:
public virtual string MyProperty { get; set; }
MyPart.cs:
public IEnumerable<string> MyProperty
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyProperty)) return new string[] { };
return Record
.MyProperty
.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set { Record.MyProperty = value == null ? null : String.Join(",", value); }
}
Now, in my service class, I tried something like:
public MyPart Create(MyPartRecord record)
{
MyPart part = Services.ContentManager.Create<MyPart>("My");
...
part.MyProperty = record.MyProperty; //getting error here
...
return part;
}
However, I am getting the following error: Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable<string>'
Essentially, I am trying to save choices from a checkboxlist (one or more selections) as a comma-separated list in the DB.
And this doesn't even get me over the problem of how do I use the enum. Any thoughts?
For some background:
I understand that the appropriate way to handle this relationship would be to create a separate table and use IList<MyEnum>. However, this is a simple list that I do not intend to manipulate with edits (in fact, no driver is used in this scenario as I handle this on the front-end with a controller and routes). I am just capturing data and redisplaying it in the Admin view for statistical/historical purposes. I may even consider getting rid of the Part (considering the following post: Bertrand's Blog Post.
It should be:
part.MyProperty = new[] {"foo", "bar"};
for example. The part's setter will store the value on the record's property as a comma-separated string, which will get persisted into the DB.
If you want to use enum values, you should use the Parse and ToString APIs that .NET provide on Enum.

Resources