Description
Summary
Blazor should provide convenient mechanisms for working with querystrings. This is a popular community request: #22388
Motivation and goals
Developers often want to make some component states bookmarkable/linkable. Classic scenarios for this include:
- Filtering, searching, and navigating
- Making shareable state without having to write to a database
Example of filtering/searching/navigating:
Another:
Example of more general input state being tracked in the URL (technically this uses #
rather than querystring but is conceptually the same):
To make this sort of thing work, developers need to be able to:
- Read incoming data from querystrings on arrival
- Update the query parameters as the user interacts with the UI. This might be:
- In response to an event, such as clicking a button or dragging an element
- In response to editing form elements such as
<select>
boxes, checkboxes, textboxes - In response to clicking on a link, such as for pagination links (e.g.,
<a href="items?page=2">
)
- Receive notification of query parameters changing when the user clicks back/forwards
In all three cases, the developer usually also needs to perform async data access based on the query parameter (whether it's initial state, a change triggered by the user through the UI, or a change due to back/forwards navigation). Ideally a single place for "loading" logic would handle all three without duplication.
Why this is difficult today
Blazor doesn't currently provide any special support for working with querystrings. Technically you can do it, but it's not a streamlined experience:
- Technically, you can read/write querystring values via manual URL parsing and formatting, but this is inconvenient and error-prone (e.g., figuring out when to encode/decode).
- Technically, you can detect when navigation changes a querystring (e.g., via
NavigationManager
'sLocationChanged
event), but working with events is inconvenient (e.g., remembering to unsubscribe) and results in duplicated code, compared with reacting to navigation inOnParametersSetAsync
- Technically, you can bind a textbox to a querystring parameter with
oninput
, but this involves inventing a whole non-obvious mechanism on your own. Unless you're very careful, you'll be likely to create potential keystroke-loss bugs because you have an async binding cycle.
Do querystrings even matter?
You could argue that anything that can be done with querystrings could also be done with the existing route parameter mechanism. That's perhaps one of the reasons Blazor has managed to go so long without special support here. However,
- Querystrings are convenient if you have multiple optional parameters, since any subset of them can appear in the URL, and in any order
- Querystrings have an obvious and standard way to escape user-supplied values so that parsing is robust, unlike route segments where people wouldn't be quite sure if or how to escape values
- Querystrings help make a more explicit distinction between the part of the URL used for
@page
selection (the path) versus additional parameters (the query). This is an aesthetics thing.
Web developers instinctively want to use querystrings when they are creating some kind of bookmarkable/linkable state within a page, especially when that state is user-constructed rather than just representing a choice among pre-existing entities. For example, it feels far better to have /dotnet/aspnet/issues?search=Blazor+is+too+cool&sort=upvotes
than something like /dotnet/aspnet/issues/sortby-upvotes/milestone-notset/search-Blazor+is+too+cool
, even though the latter is technically possible.
In scope
The key end-to-end scenario is having a component that tracks aspects of its state in querystring values, and that state automatically updates if the user uses back/forwards or opens a link where the URL contains pre-existing state.
However we can break this down to lower-level pieces (which collectively cover other end-to-end scenarios too):
- A convenient way to read and write individual querystring values by name. This should:
- Be strongly-typed
- Automatically take care of formatting/parsing (both the whole URL, and converting individual values to/from relevant .NET types)
- Cause the browser to actually navigate (via client-side navigation) so the URL updates and any applicable navigation events fire
- Support specifying whether to append to the history or replace the current entry
- A convenient way to receive notifications when querystring values change.
- This should be possible at least in the current
@page
component. It's not clear we need to support this for arbitrary descendants that aren't even receiving parameters from the router.
- This should be possible at least in the current
- A simple pattern for binding a textbox value to a querystring value, e.g., on each keystroke
You might think that item 3 is redundant because it can be constructed from 1 and 2. However, unless we're careful about it, it's very likely that binding would exhibit gotchas or glitches due to the async nature of navigation events on Blazor Server. For example, after a keystroke you append a character, but because navigation hasn't happened yet, the textbox would re-render without your added character, reverting your keystroke temporarily. This is similar to the async @bind
loop bug from the early days of Blazor Server (fixed long before the first public release). We need to be sure this works very conveniently with @bind
, without weird problems.
Out of scope
-
Querystrings should explicitly not be involved in the router's selection of
@page
components.- This is to match both the current behavior of server-side ASP.NET Core routing, and the historical behavior of most web frameworks (e.g.,
/folder/somepage.aspx?a=1&b=2
matchesfolder/somepage.aspx
regardless of the querystring). - This means you cannot use URLs like
products/123?view=details
vsproducts/123?view=reviews
to select between two different routable (@page
) components. You would need to have a single component matchingproducts/{id}
, and then inside that, use whatever logic you like (possibly based on querystring) to render different content or child components.
- This is to match both the current behavior of server-side ASP.NET Core routing, and the historical behavior of most web frameworks (e.g.,
-
I don't think we need to have any formatting/parsing options other than "invariant culture"
- Justification: URLs are treated as invariant in other contexts in ASP.NET Core / Blazor. People who want to do something different can always use the "string" APIs and do their own formatting/parsing.
Risks / unknowns
Unknown: What's the most useful way to handle unparseable querystring values? If the developer is asking for a value called page
of type System.Int32
but the value is abc
, should we throw, or just treat it as "no value supplied"?
Unknown: Should we treat empty-string values as being equivalent to unset/null
, or should they be distinguishable? Generally it's good to preserve information, but it's also quite odd to see ?filter=
in a URL, forcing developers to write their own special-case logic if they want empty-string values to disappear from the URL completely.
Risk: Since this API looks and sounds like something from ASP.NET Core (server), some developers might mix up the two. TBH I don't really see how there's an actual problem here, since Blazor already has a client-side routing and parameter passing system and this is just a small extension of it.
I haven't been able to think of any other ways that this feature could be misused, cause security issues, or restrict us from other enhancements in the future. If you think of any, please post below!
Examples
Suggestion from #22388:
@code {
// Supports two way binding
[FromQuery] int Page { get; set; }
[FromQuery(Format = "yyyyMMddHHmmss")] string Date { get; set; }
}
TBH I'm not convinced about some aspects of this:
- Two-way binding:
- Doing this for something that looks like a
[Parameter]
property will mislead people, since it's not legal to write to other[Parameter]
properties - How does the framework even know that the setter got invoked? If we don't control your setter, we don't get to make it do anything, such as update the URL.
- Doing this for something that looks like a
- Supporting formatting options can be out-of-scope as justified in the Out of scope section above
However, I do agree that [FromQuery]
itself looks very usable and convenient as a way to receive values.
Another suggestion from #22388:
var page = int.Parse(navigationManager.GetQuery("page"));
navigationManager.SetQuery("page", page + 1);
I think the get/set APIs look decent but we could improve them with generics and automatic parsing/formatting. Also the SetQuery
method needs some way to control append-vs-replace in the history stack.
Another suggestion from #22388:
@code {
[QueryParameter("q", IsTwoWay=true, ReplaceHistory=true)]
string Query { get; set; }
[QueryParameter(IsTwoWay=true)]
int Page { get; set; }
}
If we were going to support two-way binding on an inbound property like this then I agree this looks good. However this is not the design I'm going to recommend (e.g., because of the issues mentioned above).
Detailed design
I spent quite a bit of time trying to answer questions like "should you be able to control whether setting a query parameter causes a navigation event" and "how can you get updates without having to remember to unsubscribe" and "how can you control whether it invokes your OnParametersSet
or not". Then of course there's the challenge of "how can @bind
avoid the async binding loop gotcha (losing keystrokes if you type too fast)". There are ugly solutions to all of these, but we want something that feels obvious and minimal.
It turns out that these questions mostly just vanish if we completely separate reading/writing querystring values from receiving notifications. They should be two independent mechanisms.
Receiving querystring values, with update notifications
The natural way to receive initial querystring values, plus notifications if they change, is to treat them exactly like other route parameters. That is, they go onto a [Parameter]
property via SetParametersAsync
. Benefits:
- Retains the component's encapsulation (its choice of how to handle incoming values, and even the choice to model parameters as properties at all)
- Gives you a natural place to do any async I/O, e.g., when a
filter
criteria changes. YourOnParametersSetAsync
is already the right place to do this, and deals with things like "loading" states, asynchrony, error handling, etc. - Means you don't have to distinguish "initial values" vs "updated values" if you don't want. Just put your loading logic in one place.
The router can naturally supply component parameters from the querystring, just like it supplies them from URL segments, as long as it knows which querystring parameters a selected component wants to receive:
- It can't just supply all querystring parameters, as components typically throw if you supply unknown parameters.
- By omitting unconsumed parameters, we get to avoid triggering re-renders on the page component if the querystring parameter that changed isn't something it even cares about
The best way I can think of identifying which querystring parameters to pass is a new attribute. Consider handling URLs like /category/123?page=4&filter=chicken%20nuggets
:
@page "/category/{id:int}"
@code {
[Parameter] public int Id { get; set; }
[Parameter] [FromQuery] public string Filter { get; set; }
// Equally valid syntax:
[Parameter, FromQuery("page")] public int CurrentPage { get; set; }
}
The benefit of it actually being a [Parameter]
property (rather than just using [FromQuery]
alone) is that the whole parameter-supplying mechanism will just work following its normal semantics about notifying-only-if-changed. All existing mechanisms already know how to deal with this.
The benefit of having a separate [FromQuery]
, rather than [Parameter(FromQuery = true)]
is that routing/URLs is an independent concept and isn't good to merge into the concept of parameters.
Drawback: It looks as if you can put [FromQuery]
on any parameter in any component, but it would not have any effect except on @page
components, because the Router
isn't the thing supplying parameter values to descendant components. Possible mitigation: Compiler error if you use [FromQuery]
in a page without [RouteAttribute]
?
Alternative considered
We could specify querystring parameters as part of the route pattern:
@page "/category/{id:int}?page={page:int}&filter={filter}"
@code {
[Parameter] public int Id { get; set; }
[Parameter] public string Filter { get; set; }
[Parameter] public int Page { get; set; }
}
This has the nice benefit that it's obvious that only the @page
component can receive the query values. However it also has lots of worse drawbacks:
- Inconsistent with server-side routing
- Makes it look as if the query parameters are mandatory and the ordering is specific
- The use of constraints makes it look as if you could stop a
@page
from matching if the value isn't parseable
Getting/setting query parameters procedurally
Besides receiving query parameters with update notifications via [Parameter]
, any other arbitrary code that has access to the NavigationManager
should be able to get/set values:
var page = navigationManager.GetQueryParameter<int>("page");
// Or:
navigationManager.SetQueryParameter("fromDate", startDate);
// Or:
navigationManager.RemoveQueryParameter("page");
Get/set take a generic type T
(usually inferred for "set" based on the value). Supported types are the same as for route constraints: string, bool, DateTime, decimal, double, float, Guid, int, long. Passing any other generic type results in a NotSupportedException
. Unknown: Should we support Nullable<...>
for the relevant subset of these too? I guess so.
Get
Returns the value via culture-invariant parsing, if possible. If there's no value or it's unparseable, we return default(T)
. We only support there being one value for a given query parameter, so we return the first.
An interesting aspect of this is: what do we do if you call SetQueryParameter
, and then before that navigation occurs, you GetQueryParameter
it back? Do we tell you the value you just set, or the value that's still in the URL? By default, I think we need to return the value you just set, even before it updates in the URL. This is essential for @bind
to work sensibly without losing keystrokes due to asynchrony.
However for the edge case where someone really wants to see the value that's still in the URL:
var page = navigationManager.GetQueryParameter<int>("page", ignorePendingNavigations: true);
Note that, on each incoming navigation event, we discard any pending values and from then on only reflect the actual URL state.
Drawbacks: This means the value returned by GetQueryParameter
can be temporarily inconsistent with navigationManager.Url
. It's for a good reason, and you can opt out. However some people will still get confused about it. A potentially bigger worry is if navigation could be cancelled and then the GetQueryParameter
value would remain inconsistent indefinitely. Currently I'm not aware this could happen but we may need to account for it in the future, resetting the temporary state if navigation is actually cancelled.
Set
Does add-or-update, via culture-invariant formatting. If you pass null
for a nullable type T
, it's the same as "Delete".
Setting a query parameter should ideally preserve the existing parameter ordering, appending at the end if it's a new key.
Setting a query parameter of course also causes an actual navigation. By default, we should append to the history stack, since that's the default for all the JS APIs. However people will often want to replace the existing entry:
navigationManager.SetQueryParameter("fromDate", startDate, replace: true);
Setting query parameters only triggers client-side navigation. We know there's never a need to force a full page load, because by definition, query parameters don't control page selection.
Who receives notification that you did this? The current @page
component, but only if it has a corresponding [Parameter, FromQuery]
. That's the mechanism for opting into receiving notifications. There will also be scenarios where people have no need to receive a notification because they don't need to trigger anything in OnParametersSet
, as the code writing to the query parameter can also perform whatever actions construct the updated state.
If you want to set multiple query parameters at once, use navigationManager.NavigateTo
and do your own URL formatting.
Delete
Needed only if T
is non-nullable and you want to remove the value entirely. It triggers the same navigation and other effects as SetQueryParameter
. It also supports replace
.
Two-way binding to a query parameter
The most explicit and low-tech way to do two-way binding to a query parameter is to define your own get
/set
pair that uses the GetQueryParameter
/SetQueryParameter
APIs. Because of the behavior of "set" followed by "get" returning the most-recently-written value, @bind
will just do the right thing and there won't be any async binding loop bug:
@page "/something"
@inject NavigationManager Nav
<input @bind="FilterBindable" @bind:event="oninput" />
@code {
// This is optional. Have it only if you want to receive notifications when the value
// changes (e.g., on back/forward, or user data entry). Notifications trigger OnParametersSetAsync.
[Parameter, FromQuery] public string Filter { get; set; }
string FilterBindable
{
get => Nav.GetQueryParameter<string>("filter");
set => Nav.SetQueryParameter("filter", value);
}
}
Declaring a get
/set
pair like this is reasonably conventional for controlling what a @bind
does. I know some people will ask why they can't just @bind
directly to the Filter
parameter, but we can't do that (without introducing new magic) because the framework doesn't even know when you call the setter, so can't know to trigger navigation. Benefits of defining your own get
/set
pair like this:
- It's obvious how it works
- It's obvious how to pass other parameters like
replace
or do custom formatting conversions - You can easily cause side-effects before the navigation or synchronously after it (not waiting for the round-trip to the browser, depending on your needs.
For people who don't want to define their own get
/set
pair, we could offer a built-in API that hides away this detail:
@page "/something"
@inject NavigationManager Nav
<input @bind="@Nav.Query("filter").Value" @bind:event="oninput" />
@code {
// This is optional. Have it only if you want to receive notifications when the value
// changes (e.g., on back/forward, or user data entry). Notifications trigger OnParametersSetAsync.
[Parameter, FromQuery] public string Filter { get; set; }
}
Nav.Query(name, replace=true)
would return a readonly struct
that's simply a way to call GetQueryParameter
/SetQueryParameter
so it can be used with @bind
. There can also be a generically-typed overload of it for non-string query parameters:
<input type="date" @bind="@(Nav.Query<DateTime>("startdate").Value)" />
Note that due to Razor syntax limitations, it's necessary to surround the expresion with @(...)
because of the generic type parameter. People who find this too cumbersome can of course still define their own get
/set
pair manually.
We don't have to support Nav.Query(name).Value
. We could just say you have to declare your own get
/set
pair. However I suspect the community will gravitate towards Nav.Query(name).Value
as a more compact way of doing binding.
Alternative considered
Instead of the [Parameter, FromQuery]
property being of primitive types like string
, int
, etc., we could define a custom type that specificially represents either a single query parameter or the whole dictionary of query parameters. This would then be usable with @bind
:
@page "/something"
<input @bind="Filter.Value" @bind:event="oninput" />
@code {
[Parameter] public QueryAccessor<string> Filter { get; set; }
}
I know this looks very appealing at first glance. It has fewer lines of code than the proposal above and seems more basic. I was pretty keen on going this way for a while, but it just doesn't line up with how diffing works. The core issue is: should QueryAccessor<T>
be treated as deeply immutable for diffing purposes?
- If yes, then changes to a query parameter wouldn't cause the page to re-render, defeating a key goal.
- It's also pretty confusing to consider it deeply immutable when its
.Value
property must be mutable, since that's also a core part of its purpose.
- It's also pretty confusing to consider it deeply immutable when its
- If no, then every time a router/layout re-renders, that would cause the page to re-render, whether or not any route parameters have changed. This would be a loss of capabilities compared with the existing routing system. Simply declaring that you receive a
QueryAccessor<T>
would force you to give up on not re-rendering when unrelated things happen in the layout. - If we wanted something more sophisticated where it's only considered changed if the corresponding primitive query value has changed since the last render, well, that's outside the scope of what diffing keeps track of today. Potentially we could enhance diffing along those lines in the future, but it would be a big new feature, and query parameter support doesn't warrant such a change (given that we have a simple and basic alternative above).
- Note that I did try thinking through many possible schemes, for example where it's a reference object whose identity changes if and only if the underlying primitive has changed, or where it's a struct and we cache the boxed instance, but they are all too weird for customers to reasonably understand.
Also, this wouldn't provide any good way to bind to the query parameter value in a non-@page
component, unlike the proposal above which does support that without any additional APIs.
Also, syntactically it looks a lot like any component should be able to receive a QueryAccessor<T>
, but in fact that wouldn't be possible for anything other than a @page
component. Changing this means reinventing something like cascading parameters but special-cased for routing. It would be better to let developers manage this flow in their own code, like they do for other route data. This is worse than [FromQuery]
because there's nothing clearly indicating the developer intends to get the value from routing as opposed to just passing through a QueryAccessor<T>
from a parent.
In summary, I don't think [Parameter] QueryAccessor<T>
is a good design today, but is something we could consider additively in the future if we choose to make the diffing system have some additional powers that would be overkill for this alone.
Appendix: Async binding loop bugs
In a number of places above I've cited the hazard of "async binding loop bugs". Here's an explanation.
We must not recreate the async 2-way binding bug that long-ago affected Blazor Server (in alpha builds, before the first release). This happens if a component both reads and writes some state in an async binding cycle, e.g.:
- On keystroke in textbox, set updated query value
- On query value updated, set updated textbox value
The issue is if keystrokes arrive faster than this cycle completes, they can get lost:
- Component reads initial query param value
abc
and sets it to be the initial state of a textbox - User presses
d
, quickly followed bye
, in that textbox - Because of the first keystroke, .NET receives an event saying the textbox contains
abcd
- .NET sets some model state to
abcd
- .NET renders, and diffing updates the textbox to contain
abcd
(losing thee
that was already typed)
Even if the abcde
event later gets processed and the UI updated, it's not OK to temporarily undo keystrokes in the UI. Also if they type f
while the textbox is temporarily showing abcd
, .NET will receive a message saying the textbox contains abcdf
and will permanently lose the e
.
The @bind
mechanism overcomes this by eliminating step 5. It specifically knows that a certain event handler represents an update from the value of a certain element, so pre-patches the "last rendered" tree to contain what we know must already be in the browser-side DOM. Then the diff algorithm sees it as not-a-change and hence there is no step 5.
Activity
SteveSandersonMS commentedon Jun 7, 2021
cc @dotnet/aspnet-blazor-eng
SteveSandersonMS commentedon Jun 7, 2021
See also: prototype implementation at https://github.com/dotnet/aspnetcore/compare/stevesa/querystring-prototype
campersau commentedon Jun 7, 2021
Would it also be possible to get / set multiple values for one querystring name?
E.g. mapping
?id=123&id=456
like thisSteveSandersonMS commentedon Jun 7, 2021
@campersau We could support that, but it might be something we choose to leave as a possible future enhancement because you could always do it yourself by (for example) comma-separating the values or using some other encoding.
pranavkm commentedon Jun 7, 2021
The design looks great to me. I would get someone more familiar with routing (@javiercn / @captainsafia) to vet all of the use cases, but this feels very compelling. A couple of notes:
If you pass null for a nullable type T, it's the same as "Delete".
This feels iffy. Is there any reason to special case this as opposed to simply letting it be an empty value?
FromQueryAttribute
- I think @davidfowl is still bitter about us re-usingRouteAttribute
, so this feels like a repeat. Could we name itQueryParameter
while still requiring theParameterAttribute
e.g.Also open to other names.
I feel like
QueryBindable<T>
(the value returned byNav.Query
) could be left as an exercise to users. It's not really hard to write the getter-setters. Having the helper would mean we'd have to maintain 2 sets of overloads if we ever add more options toNavigationManager
I only skimmed the async update issue, didn't understand the solution too well, but I'll trust your expertise on it 👍🏽
SteveSandersonMS commentedon Jun 7, 2021
This aspect of naming is awkward. There probably isn't an ideal solution. Other considerations:
FromQuery
is ideal from that point of view.[QueryParameter]
in MVC.My main concern would be around the discoverability through intellisense. If VS is likely to show both options, or the wrong option, in code completions, then we have a bad situation. But if in realistic/default cases it only prompts with the right one then I think it would be fine.
Another option would be unsealing
ParameterAttribute
and then having aFromQueryParameterAttribute
inherited from it. This might be fine or might lead to weird effects in tooling or the Razor compiler since they might have any number of assumptions thatParameterAttribute
doesn't get subclassed, and would certainly complicate things in the future like using a source generator to produce the parameter assignment logic.I'm fine with keeping it out of scope initially. It's achievable in user code as an extension method.
captainsafia commentedon Jun 8, 2021
Thanks for writing this up. Looks sensible overall.
Is this worth addressing? My concern is that introduces an extra layer of complexity to the API. My inclination here is to treat the value the user sets as the source of truth and avoid the flag in the first iteration of the API.
Hmmm. It actually makes sense to me with the mental model that I've built of this. Nulling out a query param effectively unsets it.
Seems sensible to me. I'd imagine an initial implementation would have a:
replace
flagShould this be
TryGetQueryParameter
?javiercn commentedon Jun 8, 2021
For me, I would distill this in two levels:
For the first case, we need APIs to provide a way to parse the URL query parameters and to generate a new URL. In this vein, it might make sense to have a type for this, rather than APIs directly on NavigationManager. This is similar to how is done in ASP.NET Core and offers more flexibility:
nm.NavigateTo(query.BuildUrl(nm.Url))
.For the second part, there are already ways to achieve this in navigation manager:
I'm concerned with us getting out of our depth to solve the async binding issue. I worry that we are tackling on a lot of complexity here to deal with quite an edge case scenario. I strongly suggest we avoid this given how narrow the application is (Binding to query string parameter within an OnInput event) and instead we provide a sample for people who need to do this. If you do
OnChange
(regular binding) this wouldn't be a problem since there's no "high-frequency" updates.So to summarize, here are my suggestions:
@bind
scenarios all-together. It sounds tempting because is very powerful yet simple, however its likely something you end up not using frequently, and that if you do, can abstract within its own component for reuse.SteveSandersonMS commentedon Jun 9, 2021
Thanks for the feedback. Also @javiercn and I spent some time talking through lots of details of this and the pros/cons of offering different levels of specialised built-in support.
The whole thing is quite unsatisfying because all the nice solutions are invalidated by async-state-losing gotchas (on Blazor Server). There seems to be only two broad types of solution:
@bind
(which leads to other gotchas) like this, or write a bunch of non-obvious code including manually dispatching exceptions, callingStateHasChanged
, etc. like thisIf we choose to go for the "solve it in the framework" design, the implementation breakdown looks a bit like this:
NavigationManager.NavigateTo
[FromQuery]
or similar way to have router or maybeRouteView
pass in additional params from queryNavLink
to ignore querystring when matching (like this)<QueryBinder>
component to allow hooking up arbitrary non-routed components to query notificationsAdd on 50% for contingency and further design changes/reviews, and this is a substantial investment (which still leaves us with a pretty specialized solution).
@dotnet/aspnet-blazor-eng We need to decide if this meets the bar for 6.0 with this cost/benefit in mind. We could:
SteveSandersonMS commentedon Jun 9, 2021
Also ping @DamianEdwards: don't know if you have time to read all 50 pages or whatever we have here, but you were interested in getting this feature and might have views on the scenarios/scope.
DamianEdwards commentedon Jun 9, 2021
Great write up! This was one of the very first issues I hit when writing my first Blazor Server app, as the main UI for the app was an open search text field. As pointed out, I wanted the search terms and other parameters to be preserved in the querystring as is typical in web search UX for the usual reasons.
The code example @SteveSandersonMS links to as an example of what the user would have to do if we only do the first three items in the list he stated is effectively similar to what one has to do today, but vastly more simple and first-class looking. Of course ideally we would make interacting with the querystring as UX as first-class/elegant as other parameters but given the cost involved I'd take that suggestion as a great "round one" improvement for .NET 6 if we can, leaving open the possibility to further refine the experience with higher-level concepts in a future release.
4 remaining items
Thaina commentedon Jun 16, 2021
On
GetQueryParameter
andSetQueryParameter
I think it should always be nullable for struct. Any missing or unparseable will be null instead of default.SetQueryParameter
with null is removingOr maybe have the same old
bool TryGetQueryParameter<T>(string s,out T value)
. But really, I think nullable should be commonGetQueryParameter
might also have overload for default. That was another way we can easily bypass generic parameter and get default value at the same timeT GetQueryParameter<T>(string s,in T defaultValue)
While nullable get is still required when we need to distinguish logic between set as default and really missing
On the generic ambiguity of nullable. I commonly design my get related API to always have 3 similar methods
SteveSandersonMS commentedon Jul 8, 2021
All that remains now is #34115, so closing this.