question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Infer default sources for parameters in MapAction

See original GitHub issue

How to determine each parameter’s source

The numbered and lettered rules for determining the source of a parameter should be applied in the order. The bullet points are unordered.

  1. If an attribute that implements one of the “IFrom…” interfaces in Microsoft.AspNetCore.Http.Metadata is applied to the parameter, populate the parameter from the source specified by the attribute. Only one of these attributes can be specified per parameter, and these attributes must implement no more than one of these interfaces.
    • IFromRouteMetadata
      • Reads from HttpRequest.RouteValues[name] where name is specified by IFromRouteMetadata.Name or the parameter name if IFromRouteMetadata.Name is null.
    • IFromBodyMetadata
      • Reads from HttpRequest.Body(Reader) as JSON using HttpRequest.ReadFromJsonAsync<{ParameterType}>().
      • Can only be used once per parameter list (there’s only one body)
      • Cannot be used in the same parameter list as IFromFormMetadata
      • Will cause requests with an empty body to be rejected without calling the action unless IFromBodyMetadata.AllowEmpty returns true. (The default interface implementation returns false.)
    • IFromFormMetadata
      • Reads from HttpRequest.Form[name] where name is specified by IFromFormMetadata.Name or the parameter name if IFromFormMetadata.Name is null.
      • Can be used multiple times in a single parameter list (presumably for different names)
      • Cannot be used in the same parameter list as IFromBodyMetadata (there’s only one body)
    • IFromHeaderMetadata
      • Reads from HttpRequest.Headers[name] where name is specified by IFromHeaderMetadata.Name or the parameter name if IFromHeaderMetadata.Name is null.
    • IFromQueryMetadata
      • Reads from HttpRequest.Query[name] where name is specified by IFromQueryMetadata.Name or the parameter name if IFromQueryMetadata.Name is null.
    • IFromServiceMetadata
      • Normally resolves via HttpRequest.RequestServices.GetRequiredService<{ParameterType}>().
      • If the parameter is a nullable reference type (i.e. has the System.Runtime.CompilerServices.NullableAttribute), resolve via HttpRequest.RequestServices.GetService<{ParameterType}>().
      • If the parameter is optional (i.e. has a default value), resolve via HttpRequest.RequestServices.GetService<{ParameterType}>() ?? parameter.DefaultValue.
  2. If the parameter is a scalar value type that lives in the System namespace, first check HttpRequest.RouteValues contains an entry for the parameter name. If it does, use HttpRequest.RouteValues[{ParameterName}]. Otherwise, use HttpRequest.Query[{ParameterName}].
    • TODO: Come up with actual list of scalar value types that live in the System namespace.
    • Question: Do we want to allow types to opt-in to this? Today MVC uses TypeConverter which is hard to implement.
    • Alternatives:
      • Something convention-based like the presence of a bool TryParse(string, out T) method
      • The explicit implementation of an interface with that method to make it more opt in
  3. If HttpRequest.RequestServices.GetService<{ParameterType}>() is not null, use that value.
    • TODO: Figure out how to make this work well with compile-time source generation. This might need to be determined at runtime no matter what.
  4. Otherwise, treat the parameter as if it had an attribute implementing IFromBodyMetadata where IFromBodyMetadata.AllowEmpty is false.

How we will convert to the parameter type.

  1. For parameters bound from the body (has an IFromBodyMetadata attribute or is treated by such by convention), we will continue to assume JSON and initialize the parameter with HttpRequest.ReadFromJsonAsync<{ParameterType}>().
    • We need to figure out if we want make this configurable or force people to read they body themselves if they want configurability. If we decide on configurability, we need to decide on how much.
  2. For all other parameters bound from the request (i.e. not a service or from the body) we need to convert from a string.
    • Since there is a finite list of “scalar value who lives in the System namespace” we’ll bind to by convention, we’ll use their TryParse methods or equivalent until we decide on something different for those.
    • Parameters explicitly bound by attribute to a string-based source also need to be converted much like those read from the RouteValues or Query by convention. I doubt we also want to limit these parameter types to a finite list. For now, we’ll continue doing what we do today to convert from string as described below.

How we convert to the parameter type today in the “MapAction” APIs

Today, all inputs are converted to their respective parameter types as follows:

  1. For parameters bound from the body, we already call HttpRequest.ReadFromJsonAsync<{ParameterType}>().
  2. All other parameters start as strings from the various sources listed above.
    1. If {ParameterType} is string, we’re done!
    2. If there’s a {ParameterType}.Parse({ValueString}) method, use that.
      • NOTE: Going forward, prefer/require TryParse instead.
    3. Lastly, try Convert.ChangeType({ValueString}, typeof{ParameterType}, CultureInfo.InvariantCulture)
      • NOTE: Going forward, we should stop using CultureInfo.InvariantCulture

Original issue:

  1. Complex type -> FromBody (See https://source.dot.net/#Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs,606)
  2. Name appears as a route value -> FromRoute
  3. Everything else -> FromQuery

From: https://github.com/dotnet/aspnetcore/pull/29878#discussion_r569766403

New inference rules unique to MapAction:

  1. [FromServices] should work implicitly for types that can be resolved from RequestServices.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:8 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
halter73commented, Feb 26, 2021

~As part of this let’s make sure optional parameters work well and that there are good error logs when non-optional parameters are missing.~

Fixed by #30434

0reactions
halter73commented, Apr 14, 2021

Follow up based on yesterday’s meeting discussing #31603:

  • Look for TryParse on method on any parameter first, signatures supported:
    • For enums, use static bool Enum.TryParse<T>(string, out T).
    • Special case for some built-in types:
    • static bool TryParse(string, IFormatProvider, out T)
    • static bool TryParse(string, out T)
  • Use invariant culture always (and will wait for feedback)
  • Assume interfaces are services, classes will require an explicit [FromServices]
  • Assume classes are from the body
  • Remove form as a source
Read more comments on GitHub >

github_iconTop Results From Across the Web

Lambda improvements - C# 10.0 draft feature specifications
NET MapAction with attributes and natural types for lambda expressions: ... metadata emitted by the compiler such as default parameters.
Read more >
Configuring additional parameters for notification channels
In Dev Studio, you can configure additional parameters that the notification channels may require at run time. You can define these parameters as...
Read more >
How to pass an argument to functions mapped using .. ...
1 Answer. see working fiddle here: you can see passed parameter in the alert :) You can see it takes the args passed...
Read more >
dotnet/aspnetcore#32378
Use nullibility to infer optionality of service and body parameters P1ASP.NET CoreASP.NET ... Add default global usings to Microsoft.NET.Sdk.Web ASP.
Read more >
Tanium™ Map User Guide
By default, the Computer Group Targets setting for the Map action group ... A map is a set of parameters that are used...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found