ASP.NET middleware invoke another action - asp.net

with ASP.NET 6 Web API project, i write a middleware to test action invoke.
public class RequestResponseMiddleware
{
private RequestDelegate _next;
public RequestResponseMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext context)
{
// need server side url rewrite
if (context.Request.Path.HasValue && context.Request.Path == "/weatherforecast/Test")
{
context.Request.Path = PathString.FromUriComponent("/weatherforecast/User");
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
if (routeValuesFeature is not null)
{
routeValuesFeature.RouteValues = null!;
}
}
await _next.Invoke(context);
}
}
When client post to https://localhost:7290/weatherforecast/Test, i want it will invoke method /weatherforecast/User".
When using method context.Response.Redirect, method /weatherforecast/User will be inovked. but the request will case another url redirect.
Is it possile direct inovke /weatherforecast/User at server side.
---update---
I have downloaded asp.net core source code ,and checked out the RewriteMiddleware code, and it seams reset request.Path , likes
context.Request.Path = PathString.FromUriComponent("/weatherforecast/User");
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
if (routeValuesFeature is not null)
{
routeValuesFeature.RouteValues = null!;
}
then call
_next.Invoke(context);
but ,it not works.

In your case, I think you want to use url rewrite instead of url redirect, check this Docs to learn difference. So you can use rewrite middleware to achieve it.
var rewrite = new RewriteOptions()
.AddRewrite("weatherforecast/Test", "weatherforecast/User", true);
app.UseRewriter(rewrite);
In above middleware, If url contains weatherforecast/Test, Project will invoke weatherforecast/User at server side directly.

Related

Changing Request Path in .Net Core 3.1

Prior to 3.0, I could change the path of a request (without any form of browser redirection) by just accessing the HttpRequest property of the HttpContext and then changed the value of the Path.
As an example, to display a page for a user who needed to change his/her password (irrespective of the page the user intended to visit), I extended the HttpContext
public static void ChangeDefaultPassword(this HttpContext context)
=> context.Request.Path = "/Account/ChangePassword";
This piece of code takes the user to the action method ChangePassword in the AccountController without executing the action method the user intends to visit.
Then enters dotnet core 3.1.
In 3.1, the extension method changes the path. However, it never executes the action method. It ignores the updated path.
I am aware this is due to the changes in the routing.The endpoint can now be accessed with the extension method HttpContext.GetEndpoint(). There is also an extension method HttpContext.SetEndpoint which seems to be the right way to set a new endpoint. However, there is no sample of how to accomplish this.
The Question
How do I change the request path, without executing the original path?
What I Have Tried
I tried changing the path. It seems routing in dotnet core 3.1 ignores the value of the HttpRequest path value.
I tried redirecting with context.Response.Redirect("/Account/ChangePassword");. This worked but it first executed the original action method requested by the user. This behavior defeated the purpose.
I tried using the extension method HttpContext.SetEndpoint, but there was no example available to work with.
The way I worked around this issue is to use EndpointDataSource directly, which is a singleton service that is available from DI as long as you have the routing services registered. It works as long as you can provide the controller name and the action name, which you can specify at compile time. This negates the need to use IActionDescriptorCollectionProvider or build the endpoint object or request delegate by yourself (which is pretty complicated...):
public static void RerouteToActionMethod(this HttpContext context, EndpointDataSource endpointDataSource, string controllerName, string actionName)
{
var endpoint = endpointDataSource.Endpoints.FirstOrDefault(e =>
{
var descriptor = e.Metadata.GetMetadata<ControllerActionDescriptor>();
// you can add more constraints if you wish, e.g. based on HTTP method, etc
return descriptor != null
&& actionName.Equals(descriptor.ActionName, StringComparison.OrdinalIgnoreCase)
&& controllerName.Equals(descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
});
if (endpoint == null)
{
throw new Exception("No valid endpoint found.");
}
context.SetEndpoint(endpoint);
}
I was able to find a working solution. My solution works by manually setting a new endpoint with the SetEndpoint extension method.
Here is an extension method I created to resolve this issue.
private static void RedirectToPath(this HttpContext context, string controllerName, string actionName )
{
// Get the old endpoint to extract the RequestDelegate
var currentEndpoint = context.GetEndpoint();
// Get access to the action descriptor collection
var actionDescriptorsProvider =
context.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
// Get the controller aqction with the action name and the controller name.
// You should be redirecting to a GET action method anyways. Anyone can provide a better way of achieving this.
var controllerActionDescriptor = actionDescriptorsProvider.ActionDescriptors.Items
.Where(s => s is ControllerActionDescriptor bb
&& bb.ActionName == actionName
&& bb.ControllerName == controllerName
&& (bb.ActionConstraints == null
|| (bb.ActionConstraints != null
&& bb.ActionConstraints.Any(x => x is HttpMethodActionConstraint cc
&& cc.HttpMethods.Contains(HttpMethods.Get)))))
.Select(s => s as ControllerActionDescriptor)
.FirstOrDefault();
if (controllerActionDescriptor is null) throw new Exception($"You were supposed to be redirected to {actionName} but the action descriptor could not be found.");
// Create a new route endpoint
// The route pattern is not needed but MUST be present.
var routeEndpoint = new RouteEndpoint(currentEndpoint.RequestDelegate, RoutePatternFactory.Parse(""), 1, new EndpointMetadataCollection(new object[] { controllerActionDescriptor }), controllerActionDescriptor.DisplayName);
// set the new endpoint. You are assured that the previous endpoint will never execute.
context.SetEndpoint(routeEndpoint);
}
Important
You must make the view of the action method available by placing it in the Shared folder. Alternatively, you may decide to provide a custom implementation of IViewLocationExpander
Before accessing the endpoint, the routing middleware must have executed.
USAGE
public static void ChangeDefaultPassword(this HttpContext context)
=> context.RedirectToPath("Account","ChangePassword");
Check your middleware order.
The middleware exposed by .UseRouting() is what's responsible for deciding which endpoint to hit based on the incoming request path. If your path rewrite middleware comes later in the pipeline (like mine was), it'll be too late and the routing decision has been made.
Moving my custom middleware before UseRouting() ensured that the path was set as I needed it before the routing middleware had been hit.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration telemetryConfig)
{
//snip
app.UseMiddleware<PathRewritingMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//snip
}
I had a similar reroute issue. In my case, I want to reroute users to a "you don't have permissions" view when an AuthorationHandler fails. I applied the following code, notably (httpContext.Response.Redirect(...)) in (.Net Core 3.1) to route me to a NoPermissions action on a Home Controller.
In the handler class:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FooBarRequirement requirement) {
var hasAccess = await requirement.CheckAccess(context.User);
if (hasAccess)
context.Succeed(requirement);
else {
var message = "You do not have access to this Foobar function";
AuthorizeHandler.NoPermission(mHttpContextAccessor.HttpContext, context, requirement, message);
}
}
I wrote a static class to handle the redirect, passing in the url expected by the controller and action plus an error message, and the redirect permanent flag set to true:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Foo.BusinessLogic.Security {
public static class AuthorizeHandler {
public static void NoPermission(HttpContext httpContext,
AuthorizationHandlerContext context, IAuthorizationRequirement requirement, string
errorMessage) {
context.Succeed(requirement);
httpContext.Response.Redirect($"/home/nopermission/?m={errorMessage}", true);
}
}
}
Finally, the controller and action that handles the view and message
[AllowAnonymous]
public IActionResult NoPermission(string m) {
return View("NoPermission", m);
}
}
In my case, I am manually selecting the matching endpoint in a DynamicRouteValueTransformer. I have a mostly working solution but have to switch to other priorities. Perhaps someone else can create a more elegant solution using built in Action executors.
RequestDelegate requestDelegate = async (HttpContext x) =>
{//manually handle controller activation, method invocation, and result processing
var actionContext = new ActionContext(x, new RouteData(values), new ControllerActionDescriptor() { ControllerTypeInfo = controllerType.GetTypeInfo() });
var activator = x.RequestServices.GetService(typeof(IControllerActivator)) as ServiceBasedControllerActivator;
var controller = activator.Create(new ControllerContext(actionContext));
var arguments = methodInfo.GetParameters().Select(p =>
{
object r;
if (requestData.TryGetValue(p.Name, out object value)) r = value;
else if (p.ParameterType.IsValueType) r = Activator.CreateInstance(p.ParameterType);
else r = null;
return r;
});
var actionResultTask = methodInfo.Invoke(controller, arguments.ToArray());
var actionTask = actionResultTask as Task<IActionResult>;
if (actionTask != null)
{
var actionResult = await actionTask;
await actionResult.ExecuteResultAsync(actionContext);//errors here. actionContext is incomplete
}
};
var endpoint = new Endpoint(requestDelegate, EndpointMetadataCollection.Empty, methodInfo.Name);
httpContext.SetEndpoint(endpoint);

How to make a Blazor page update the content of one html tag with incoming data from gRPC service

So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/

Azure Function SignalR Negotiate function works but Send function fails

i have a xamarin app that is trying to talk to use SignalR in Azure functions.
i have 2 azure functions as per the documentation.
public static class NegotiateFunction
{
[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
//, UserId = "{headers.x-ms-client-principal-id}"
{
return connectionInfo;
}
}
and
public static class SendMessageFunction
{
[FunctionName("Send")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
// var chatObj = (ChatObject)(message);
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
// UserId = chatObj.ReciversId,
Target = "Send",
Arguments = new[] { message }
});
}
}
in my xamarin client i am connecting like this.
try
{
_connection = new HubConnectionBuilder()
.WithUrl("http://192.168.1.66:7071/api")
.Build();
_connection.On<string>("Send", (message) =>
{
AppendMessage(message);
});
await _connection.StartAsync();
}
I send message using this code in one of the pages of Xamarin app page.
try
{
await _connection.SendAsync("Send", MessageEntry.Text);
MessageEntry.Text = "";
}
connection code works it hits "negotiate" function properly but when i call SendAsync it does not hit break-point in [FunctionName("Send")] and nothing happens. It doesn't give me any exception as well.
local settings are like this
Update
i also tried Invoke. it didnt worked.
Should i try making a POST call to [FunctionName("Send")] ?
The way SignalR SaaS works in Functions is slightly different to using the NuGet package in a .NET Application.
You can't invoke a function using the SignalR library, as you can see on the attribute in your function, it's expecting a Http trigger so you have to do a POST to this endpoint instead of invoking it as you normally would.
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
You still want to listen to the Send target as normal.

How can I validate a custom token (which is not JWT) in ASP .NET Core 2.0 Web API?

In our ASP .NET Core 2.0, Web API, when the user logs in, we generate a GUID and return that to the user after storing it in database. What is the best practice to validate this token when the user submits a request to a controller having Authorize attribute on it.
Should I override AuthorizeAttribute.OnAuthorization and put my custom logic in there ? or is there any other place where I should place my custom logic ?
Thanks in advance.
In ASP .NET Core 2.0 you can write you own Middleware to validate token. You can see this video as exapmle - https://www.youtube.com/watch?v=n0llyujNGw8.
Summarily:
1. Create TokenMiddleware:
public class TokenMiddleware
{
// always should be RequestDelegate in constructor
private readonly RequestDelegate _next;
public TokenMiddleware(RequestDelegate next)
{
_next = next;
}
// always should be defiened Invoke or InvokeAsync with HttpContext and returned Task (You can also inject you services here - for example DataContext)
public async Task InvokeAsync(HttpContext context, DataContext dataContext)
{
var validKey = true;
// than you logic to validate token
if (!validKey)
{
context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
// if validm than next middleware Invoke
else
{
await _next.Invoke(context);
}
}
}
// Extension to IApplicationBuilder (to register you Middleware)
public static class TokenExtensions
{
public static IApplicationBuilder UseTokenAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TokenMiddleware>();
}
}
Registred you Middleware in Startup:
app.UseTokenAuth();
Question was made long time ago, but for people that might stumble upon it, here is the way I did it, taking advantage of authentication and authorization middlewares. The question doesn't have details about the way the token is passed in the request but I am assuming a standard Authorization header.
Create a custom AuthenticationHandler
MyCustomTokenHandler.cs
public class MyCustomTokenHandler: AuthenticationHandler<AuthenticationSchemeOptions>
{
public MyCustomTokenHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.NoResult();
}
if (!AuthenticationHeaderValue.TryParse(Request.Headers["Authorization"], out AuthenticationHeaderValue? headerValue))
{
return AuthenticateResult.NoResult();
}
if (!Scheme.Name.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
{
return AuthenticateResult.NoResult();
}
if (headerValue.Parameter == null)
{
return AuthenticateResult.NoResult();
}
//The token value is in headerValue.Parameter, call your db to verify it and get the user's data
var claims = new[] { new Claim(ClaimTypes.Name, "username found in db") };
//set more claims if you want
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Register the handler and enable authorization
Program.cs
builder.Services.AddAuthentication("Bearer").AddScheme<AuthenticationSchemeOptions, MyCustomTokenHandler>("Bearer", null);
//...
var app = builder. Build();
app.UseAuthentication();
app.UseAuthorization();
Most of the code is inspired by this blog post: https://joonasw.net/view/creating-auth-scheme-in-aspnet-core-2

How to pass header in Azure endpoint..?

I am using Azure API , URL getting below error please help on this issue. please share codesnip, how to change in web.config and endpoints.
The HTTP request is unauthorized with client authentication scheme
'Anonymous'. The authentication header received from the server was
'AzureApiManagementKey
realm="https:/azure.azure-api.net/MethodName",name="Ocp-Apim-Subscription-Key",type="header"'.
I know this is a very old question still, my answer would help someone faces the same issue.
The solution is to create a custom endpoint behavior where you add a custom message handler to the binding parameters.
In the custom message handler, please add your request headers. After this, use any of the binding technique (like basichttpsbinding or NetHttpsBinding) with security mode as "Transport" and MessageEncoding as "Text" for creating soap client object. Add custom endpoint behavior to the soap client.
public class CustomEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
{
return new CustomMessageHandler(x);
}));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
public class CustomMessageHandler : DelegatingHandler
{
public CustomMessageHandler(HttpClientHandler handler)
{
InnerHandler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Add("xxxx", "abcde");
return base.SendAsync(request, cancellationToken);
}
}
The console app to consume the service.
static async Task Main(string[] args)
{
var client = GetSOAPClient();
try
{
var result = await client.MyOperation().ConfigureAwait(false);
if(result.Body != null && result.Body.status == "Success")
{
Console.WriteLine(result.Body.myValue);
}
}
catch (Exception ex)
{
Console.WriteLine(ex?.Message);
}
Console.ReadKey();
}
static MyServiceClient GetSOAPClient()
{
NetHttpsBinding binding = new NetHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.MessageEncoding = NetHttpMessageEncoding.Text;
EndpointAddress ea = new EndpointAddress(new Uri("https://myazureurl"));
var client = new MyServiceClient(binding, ea);
client.Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior());
return client;
}
}
This is complaining that your Subscription key is wrong. If you check the response body, it will give you a readable message of what the real problem is. Double check you are entering the correct subscription key for your Azure API access.
You get your subscription key from the Developer Portal under your profile menu. You can see an example of the subscription key being used in this article under the section "Call an operation from the developer portal": https://learn.microsoft.com/en-us/azure/api-management/api-management-get-started
Also, the 'The HTTP request is unauthorized with client authentication scheme 'Anonymous'.' part of the message is a red herring and a separate problem with how responses work.

Resources