Web API: how to read action attribute and parameters from HttpContext - asp.net-web-api

In regular class, I need to read following from the HttpContext:
Controller and action name
Action's attribute (I could get that through HttpActionContext.ActionDescriptor.GetCustomAttributes<type>() but here I don't have HttpActionContext - I only have HttpContext)
Read argument (like actionContext.ActionArguments["paramName"], but again - I only have a HttpContext)
It's not an action filter and not a controller class. But, I can access HttpContext.

From asp.net core 3.0 https://stackoverflow.com/a/60602828/10612695
public async Task Invoke(HttpContext context)
{
// Get the enpoint which is executing (asp.net core 3.0 only)
var executingEnpoint = context.GetEndpoint();
// Get attributes on the executing action method and it's defining controller class
var attributes = executingEnpoint.Metadata.OfType<MyCustomAttribute>();
await next(context);
// Get the enpoint which was executed (asp.net core 2.2 possible after call to await next(context))
var executingEnpoint2 = context.GetEndpoint();
// Get attributes on the executing action method and it's defining controller class
var attributes2 = executingEnpoint.Metadata.OfType<MyCustomAttribute>();
}

Related

Use MVC's model binding from application code

As a sample of what I'm trying to accomplish, here in MapPost I'm manually parsing the body of the HTTP request.
// Program.cs
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
Type[] types = new[] { typeof(SampleDto1), typeof(SampleDto2), <other unknown types> };
foreach (var type in types)
{
app.MapPost(type.Name, async (HttpContext httpContext) =>
{
var request = await JsonSerializer.DeserializeAsync(
httpContext.Request.Body,
type,
new JsonSerializerOptions(JsonSerializerDefaults.Web),
httpContext.RequestAborted);
return Results.Ok(request);
});
}
app.Run();
internal record SampleDto1(string Input) { }
internal record SampleDto2(string Input) { }
This works, yay! However, ... ASP.NET Core's MVC has all these sophisticated ModelBinding functionality and I really would like to use that. Because that opens up possibilities for binding to querystring parameters and other sources instead of only the request body.
Basically I want to replace the call to JsonSerializer with a call to framework code.
I've been browsing the ASP.NET Core source code and at first the DefaultModelBindingContext looked promising. However, I soon stumbled on some internal classes which I couldn't access from my code.
Long story short, .. is it at all possible to plug-in to MVC's model binding from application code?
Update: Although it doesn't show from the initial question, the solution should work dynamically with any request type. Not only SampleDto1 and SampleDto2. That's why explicit parameter binding from Minimal API won't do the trick.
You could try the codes :
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
builder.Services.AddSingleton<Service>();
app.MapPost("/{id}", ([FromRoute] int id,
[FromQuery(Name = "p")] int page,
[FromBody]SampleDto1 sample1,
[FromBody] SampleDto2 sample2,
[FromServices] Service service,
[FromHeader(Name = "Content-Type")] string contentType)
=> { });
app.Run();
internal record SampleDto1(string Input) { }
internal record SampleDto2(string Input) { }
You could read the official document for more details:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#explicit-parameter-binding

Best way to add and retrieve new post parameters to the GrantRefreshToken() in OWIN Web API

the default request parameters to get new JWT using refresh token are:
grant_type , refresh_token and client_id .
I need to control the claims identity modification by adding new body parameter when requesting a new refresh token.
let say the parameter is named by grant_claims, which can hold true or false boolean value.
how can I get that custom parameter in the GrantRefreshToken() overridden method?
Many Thanks
Finally, I found the answer from this post :
owin oauth send additional parameters
in the ValidateClientAuthentication we can add additional params
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// other code ...
var grantClaims = context.Parameters.Get("grant_claims");
// other code ...
context.OwinContext.Set<string>("grant_claims", grantClaims);
// other code ...
}
then get the values in the authentication and refresh token methods
// auth
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var grantClaims = context.OwinContext.Get<string>("grant_claims");
}
//refresh token
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var grantClaims = context.OwinContext.Get<string>("grant_claims");
}

Calling an ASP.Net RESTful POST controller API method with HTTPClient.PostAsync

A colleague has written an Azure Mobile Service API which includes the following controller method:
public class SegmentationController : ApiController
{
// [...]
// POST api/<controller>/id
public async Task<string> Post(string id)
{
// [...]
I am trying to call that from a Windows Universal app. The calls to GET methods work without issue but I am failing to call that POST method. Here is what I've tried:
response = await client.PostAsync("api/segmentation/", new StringContent(item.Id));
// 405 Method Not Allowed
response = await client.PostAsync("api/segmentation/" + item.Id, new StringContent(""));
// 500 Internal Server Error
response = await client.PostAsync("api/segmentation/", new StringContent("id=" + item.Id));
// 405 Method Not Allowed
response = await client.PostAsync("api/segmentation/", new StringContent("{\"id\":" + item.Id + "}"));
// 405 Method Not Allowed
(N.B. System.Collections.Specialized.NameValueCollection used in Marc's answer is not available on WinRT / Windows Universal.)
It is possible that my second call is correct and that the error is in the server side code; we are exploring that possibility.
What is the correct way to make a POST call to an ASP.Net RESTful API method which expects a parameter called "id" of type string?
Your parameter is the problem. You have two options:
Use a query parameter instead of body. e.g. api/segmentation?id=abc
Add [FromBody] Attribute to your parameter. e.g. public async Task<string> Post([FromBody]string id)
Now your parameter is read from body. by default only complex types are read from body.
For more details see Parameter Binding in ASP.NET Web API
It was a server error. Once we had added error reporting code we could see that the problem was the server failing to load a C++ DLL it relied on due to an x64 /x86 mismatch on Azure. The call style that now works is the second one I list in the question:
response = await client.PostAsync("api/segmentation/" + item.Id, new StringContent(""));

How to set up Web API Routing for a Proxy Controller?

Part of my application needs to act as a Proxy Server for a third party RESTful web service. Is there a way to set up Web API routing so that all requests of the same type will go to the same method?
For example, if the client sends in either of these GET requests I want them to go into a single GET action method that then sends on the request to the downstream server.
api/Proxy/Customers/10045
api/Proxy/Customers/10045/orders
api/Proxy/Customers?lastname=smith
The single action method for GET would pick up any one of these three requests and send them on to the respective service (I know how to work with HttpClient to make that happen effectively):
http://otherwebservice.com/Customers/10045
http://otherwebservice.com/Customers/10045/orders
http://otherwebservice.com/Customers?lastname=smith
I don't want to have to tightly couple my web service to the third party web service and replicate their entire API as method calls inside mine.
One workaround that I have thought of is to simply encode the target URL in JavaScript on the client and pass this into the Web API which will then only see one parameter. It would work, but I'd prefer to use the routing capabilities in Web API if possible.
Here's how I got this to work. First, create a controller with a method for each verb you want to support:
public class ProxyController : ApiController
{
private Uri _baseUri = new Uri("http://otherwebservice.com");
public async Task<HttpResponseMessage> Get(string url)
{
}
public async Task<HttpResponseMessage> Post(string url)
{
}
public async Task<HttpResponseMessage> Put(string url)
{
}
public async Task<HttpResponseMessage> Delete(string url)
{
}
}
The methods are async because they're going to use an HttpClient. Map your route like this:
config.Routes.MapHttpRoute(
name: "Proxy",
routeTemplate: "api/Proxy/{*url}",
defaults: new { controller = "Proxy" });
Now back to the Get method in the controller. Create an HttpClient object, create a new HttpRequestMessage object with the appropriate Url, copy everything (or almost everything) from the original request message, then call SendAsync():
public async Task<HttpResponseMessage> Get(string url)
{
using (var httpClient = new HttpClient())
{
string absoluteUrl = _baseUri.ToString() + "/" + url + Request.RequestUri.Query;
var proxyRequest = new HttpRequestMessage(Request.Method, absoluteUrl);
foreach (var header in Request.Headers)
{
proxyRequest.Headers.Add(header.Key, header.Value);
}
return await httpClient.SendAsync(proxyRequest, HttpCompletionOption.ResponseContentRead);
}
}
The URL combining could be more sophisticated, but that's the basic idea.
For the Post and Put methods, you'll also need to copy the request body
Also please note a HttpCompletionOption.ResponseContentRead parameter passed in SendAsync call, because without it, ASP.NET will spend an exremeley long time reading the content if the content is large (in my case, it changed a 500KB 100ms request into a 60s request).

Asp.net MVC 3 view engine error

I am trying for custom error handling at global level in Application_Error.
Exception ex = Server.GetLastError();
Server.ClearError();
AssortmentDefinitionLogManager.LogException(ex);
Context.RewritePath("/Error/Error");
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(Context);
But i get this error
Error Message - The view '~/Views/Shared/Error' or its master was not found or no view engine supports the searched locations.
I have also tried this
var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
var redirectUrl = urlHelper.Action("Error", "Error");
Response.Redirect(redirectUrl);
create a controller called ErrorController and inside this controller create an action method called Error which returns an ActionResult (or ViewResult).
Then create a view under ~/Views/Error/ called error.cshtml.
This will solve your problem.

Resources