Routing & message handlers: request processing order issue - c#

I'm facing an issue with the execution order of the ASP.NET Web API request pipeline.
According to the ASP.NET Web API documentation (available here), global message handlers are supposed to be executed before the routing mechanism.
On this image, MessageHandler1 is a global message handler whereas MessageHandler2 is specific to Route 2.
I created a very simple example to show that there seems to be an issue in the execution order… or I'm really missing something important.
I have this controller
public class FooController : ApiController {
[HttpPut]
public string PutMe() {
return Request.Method.Method;
}
}
It only accepts PUT requests.
The application is configured as such:
protected void Application_Start() {
var configuration = GlobalConfiguration.Configuration;
configuration.MessageHandlers.Add( new SimpleMethodOverrideHandler() );
configuration.Configuration.Routes.MapHttpRoute(
name: "Foo",
routeTemplate: "api/foo",
defaults: new { controller = "foo", action = "putme" },
constraints: new { put = new HttpPutOnlyConstraint() }
);
}
SimpleMethodOverrideHandler is a very simple DelegatingHandler that just changed the request's method according to a "method" parameter in the query string.
public class SimpleMethodOverrideHandler : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken ) {
var method = request.RequestUri.ParseQueryString()["method"];
if( !string.IsNullOrEmpty( method ) ) {
request.Method = new HttpMethod( method );
}
return base.SendAsync( request, cancellationToken );
}
}
So basically, requesting /api/foo?method=put in my browser would fire up FooController's PutMe method.
Indeed, as seen earlier, the message handler treats the requests before it gets passed to the HttpRoutingDispatched.
Finally, here's how the the constaint HttpPutOnlyConstraint is defined:
public class HttpPutOnlyConstraint : IHttpRouteConstraint {
public bool Match( HttpRequestMessage request,
IHttpRoute route,
string parameterName,
IDictionary<string, object> values,
HttpRouteDirection routeDirection ) {
return request.Method == HttpMethod.Put;
}
}
Well the problem is that when I request /api/foo?method=put within my browser, the program first enters HttpPutOnlyConstraint's Match method, which is wrong.
If we refer to the previously linked image, the message handler is supposed to be executed first, unfortunately it is not.
So, of course, Match returns false and no controller/action is found for the request, 404 happens.
If I remove the constraint from the route definition, the program enters SimpleMethodOverrideHandler, the request's method gets changed successfully and it is able to match and execute my controller's method.
Am I doing something wrong? Is there a secret configuration parameter to know in order to do such things? :-)
If anyone needs the whole project, it's available here [7KB zip file].
Thank you.

You are confusing routing engine with Web API pipeline. HttpRoutingDispatcher is not a routing engine concept. The route constraints will be processed first because your underlying host needs to build a route table and match the route for your request.
HttpRoutingDispatcher is simply another implementation of HttpMessageHandler and all it does is it examines the IHttpRoute of the route that has been matched, and chooses which message handler to call next. If there is no per-route handler present, it delegates the processing to HttpControllerDispatcher.

Related

.NET CORE Web API Routing

I am new to .NET Core Web API and i'm trying to create Web API with 3 POST methods.
AddUser
UpdateUser
DeleteUser
I was able to create a .NET core web api project with AddUser POST method and its working fine but they way I want it be uri is
https://localhost:1234/api/Project/AddUser
https://localhost:1234/api/Project/UpdateUser
https://localhost:1234/api/Project/DeleteUser
When I run the application in default swagger uri shows POST /api/Project i.e. https://localhost:1234/api/Project
I am using .NET core web api 5.0
Here code from my controller
namespace ProjectAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiKeyAuth]
public class ProjectController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<Response>> AddUser([FromBody] Request request)
{
var _message = await DoSomething(request);
Response response = new Response
{
Message = _message
};
return response;
}
private async Task<string> DoSomething(Request request)
{
string msg = string.Format("Add user {0} to {2} is successful", request.User, request.FromRole, request.ToRole);
return msg;
}
}
}
#joshykautz is right, you can add routing to each action
Another way is just to change controller routing and not touching actions:
[Route("api/[controller]/[action]")]
public class ProjectController : ControllerBase
....
but after this, if you need, you can still can assign a very special route for some action, for example
[Route("~/api/Project/AddNewUser")]
public async Task<ActionResult<Response>> AddUser( Request request)
Don't miss "~/". It will work for url
https://localhost:1234/api/Project/AddNewUser
Adding the [action] token to your controller route will yield your desired route format:
[Route("api/[controller]/[action]")]
However, I would discourage using verbs in your route naming. The HTTP method already sufficiently describes the action taken when calling a given endpoint.
POST /api/user creates a new user.
GET /api/user gets users.
PUT /api/user/{id} updates an existing user.
DELETE /api/user/{id} deletes a user.
In a RESTful approach, the route describes the resource that you're interacting with on the server, and the HTTP method used describes the action. Mixing actions/verbs into your routes goes against this mindset.
What I would do in your situation is create a new UserController, which will contain the endpoints for your user resource. Having them in a ProjectController, which to me sounds like something that should handle projects, mixes responsibilities and makes your code difficult to understand.
I kindly recommend not to use ../adduser ../updateuser ../deleteuser in your routing. It also causes security weakness.
You can build your API as /user and add;
yourrouter.route('/user/:id?')
.get(user.get)
.post(user.post)
.put(user.put)
.delete(user.delete);
for the same /user route.
It means, the client calls the same ./user with a specific request (GET, POST, PUT etc.) and with ID and also other parameters if required.
You can test your API and routing via POSTMAN, by selecting the method when you call the API (e.g. https://yourdomain/api/user/{parameters})
Give a routing attribute to each action.
[HttpPost]
[Route("api/[controller]/AddUser")]
public async Task<ActionResult<Response>> AddUser([FromBody] Request request)
{
var _message = await DoSomething(request);
Response response = new Response
{
Message = _message
};
return response;
}
And remember to remove the routing attribute that you've defined for the class.
You could also use [Route("api/[controller]/[action]")] since your method is already named AddUser.
[HttpPost]
[Route("api/[controller]/[action]")]
public async Task<ActionResult<Response>> AddUser([FromBody] Request request)
{
var _message = await DoSomething(request);
Response response = new Response
{
Message = _message
};
return response;
}
You can read more here on the Microsoft Docs.

Overwrite basic functionality of Action Mapping and Parameters in a .net Web API

I would like to encode the URL (excluding the origin) of all the requests to Base64. Whenever a request is made it should decode the URL, find the respective Controller and Action and call it with the respective parameters.
Is there a function that I can overwrite (perhaps in global.asax or webapiconfig.cs) that will get called whenever a request is being made?
Assuming you work with asp.net mvc and all fancy .net core middleware are not a thing yet, you could look into custom handler.
You theoretically could write the bootstrap code directly in global.asax, but as it by default calls through to WebApiConfig.Register():
GlobalConfiguration.Configure(WebApiConfig.Register);
it's probably a better place for things to do with WebAPI.
App_Start/WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MessageHandlers.Add(new TestHandler()); // if you define a handler here it will kick in for ALL requests coming into your WebAPI (this does not affect MVC pages though)
config.MapHttpAttributeRoutes();
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config)); // you likely will want to override some more services to ensure your logic is supported, this is one example
// your default routes
config.Routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter.Optional});
//a non-overlapping endpoint to distinguish between requests. you can limit your handler to only kick in to this pipeline
config.Routes.MapHttpRoute(name: "Base64Api", routeTemplate: "apibase64/{query}", defaults: null, constraints: null
//, handler: new TestHandler() { InnerHandler = new HttpControllerDispatcher(config) } // here's another option to define a handler
);
}
}
and then define your handler:
TestHandler.cs
public class TestHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//suppose we've got a URL like so: http://localhost:60290/api/VmFsdWVzCg==
var b64Encoded = request.RequestUri.AbsolutePath.Remove(0, "/apibase64/".Length);
byte[] data = Convert.FromBase64String(b64Encoded);
string decodedString = Encoding.UTF8.GetString(data); // this will decode to values
request.Headers.Add("controllerToCall", decodedString); // let us say this is the controller we want to invoke
HttpResponseMessage resp = await base.SendAsync(request, cancellationToken);
return resp;
}
}
Depending on what exactly you want your Handler to do, you might find that you will also have to supply your custom ControllerSelector implementation:
WebApiConfig.cs
// add this line in your Register method
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));
MyControllerSelector.cs
public class MyControllerSelector : DefaultHttpControllerSelector
{
public MyControllerSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override string GetControllerName(HttpRequestMessage request)
{
//this is pretty minimal implementation that examines a header set from TestHandler and returns correct value
if (request.Headers.TryGetValues("controllerToCall", out var candidates))
return candidates.First();
else
{
return base.GetControllerName(request);
}
}
}
I don't know enough about your specific environment so this is far from being complete solution, but hopefully it outlines one avenue for you to explore

Is there any way to audit all changes in crud generated by Entity Framework? ASP.NET C# [duplicate]

I am looking for a way to intercept/grab the request being made before matching to a route. For example, I have multiple controllers and routes set up, but I want some mechanism in which will be hit before the route method is hit. It would be highly preferable if this mechanism were able to get the route params that were sent.
I have been unable to find something similar to what I am looking for (but perhaps not being well versed in Web API I am searching with the wrong keywords).
What you need is action filters. You can apply action filters directly to controllers as attributes, the caveat with Action filters is that at this point the controller route is already known but you can still control (very much like AOP) if the action method can be executed or not:
ASP.NET Web API ActionFilter example
Look at how you can use an action filter, in this case for logging:
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
Log(actionExecutedContext.ActionContext.RequestContext.RouteData);
base.OnActionExecuted(actionExecutedContext);
}
private void Log(System.Web.Http.Routing.IHttpRouteData httpRouteData)
{
var controllerName = "controller name";
var actionName = "action name";
var message = String.Format("controller:{0}, action:{1}", controllerName, actionName);
Debug.WriteLine(message, "Action Filter Log");
}
}
How to log which action method is executed in a controller in webapi
You can also use message handlers, which are executed before the controller is resolved:
HTTP Message Handlers in ASP.NET Web API
I'm using mentioned technique to log all requests and responses.
Speaking shortly, the best way to do it is to use Handlers.
First, create handler:
public class CustomHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
//get route values and process them
var routeValues = (IHttpRouteData[]) HttpContext.Current.Request.RequestContext.RouteData.Values["MS_SubRoutes"];
//let other handlers process the request
return await base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
//once response is ready, do something with it
return task.Result;
}, cancellationToken);
}
}
Then, register it in WebApiConfig:
config.MessageHandlers.Add(new CustomHandler());
Would it work to create a HttpHandler (or do it just in Global asax Application_BeginRequest event) to capture the requests and inside the handler parse the URL against route config, similar to this link.
Like Joel Etherton mentioned in the comments, I think what you are looking for is something like adding the following code in your global.asax:
protected void Application_EndRequest()
{
/*
if(HttpContext.Current.Response.StatusCode == 404)
Debug.WriteLine("404 something something")
if(HttpContext.Current.Response.StatusCode == 500)
Debug.WriteLine("500 something something")
if(HttpContext.Current.Response.StatusCode == 200)
Debug.WriteLine("200 something something")
*/
Debug.WriteLine($"{context.Response.StatusCode} - {request.Url.PathAndQuery}");
}
I got my inspiration from here: ASP.NET MVC 404 Error Handling

Web API 2 Attribute Routing Controller Selection

I use Web API 2 Attribute Routing in my project to provide JSON interface over my data. I am facing weird behaviour of controller selection, not decided yet whether it's a bug or a feature :)
Let me describe my approach.
I would like to simulate OData syntax with help of attribute routing (direct OData usage has been refused due to design principles). For example, to get entity with id=5 I use HTTP GET request to URI http://mydomain.com/api/Entity(5) . I expect to use the same URI with HTTP PUT verb to update the entity. This is where the journey begins...
I would like to have separate controller for getting entities (FirstController in the example provided below) and another one for modifying entities (SecondController). Both controllers handles the same URI (e.g. http://mydomain.com/api/Entity(5)) the only difference is HTTP verb used with the URI - GET should be handled by FirstController, PUT should be handled by SecondController. But the URI is handled by none of them; instead HTTP 404 error is returned.
When I "merge" GET and PUT actions to only one controller (commented out in FirstController), both verbs are handled correctly.
I am using IIS Express and all conventional routes are disabled, only attribute routing is in charge.
It looks like the controller selection process does not work with HTTP verb. In another words, HttpGet and HttpPut attributes just limit action usage but they do not serve as criteria during controller selection. I am not so familiar with MVC / Web API fundamentals, so let me ask you my big question:
Is the behaviour, described herein before, a feature intentionally implemented by MVC / Web API 2 or a bug to be fixed?
If it is considered as a feature, it prevents me to follow design principles. I can live with "merged" controllers but still considering it as a bad practice...
Or am I missing something in my train of thought?
My environment setup:
Windows 7 (virtual machine using Oracle VirtualBox)
Visual Studio 2013
.NET 4.5.1
Web API 2
The following is implementation of FirstController class:
public class FirstController : ApiController
{
[HttpGet]
[Route("api/Entity({id:int})")]
public Output GetEntity(int id)
{
Output output = new Output() { Id = id, Name = "foo" };
return output;
}
//[HttpPut]
//[Route("api/Entity({id:int})")]
//public Output UpdateEntity(int id, UpdateEntity command)
//{
// Output output = new Output() { Id = id, Name = command.Name };
// return output;
//}
}
The following is implementation of SecondController class:
public class SecondController : ApiController
{
[HttpPut]
[Route("api/Entity({id:int})")]
public Output UpdateEntity(int id, UpdateEntity command)
{
Output output = new Output() { Id = id, Name = command.Name };
return output;
}
}
The following is implementation of a console application to test the described behaviour:
class Program
{
static void Main(string[] args)
{
// HTTP client initialization
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://localhost:1567");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP GET - FirstController.GetEntity
HttpResponseMessage getEntityResponse = httpClient.GetAsync("/api/Entity(5)").Result;
Output getOutput = getEntityResponse.Content.ReadAsAsync<Output>().Result;
// HTTP PUT - SecondController.UpdateEntity
UpdateEntity updateCommand = new UpdateEntity() { Name = "newEntityname" };
HttpResponseMessage updateEntityResponse = httpClient.PutAsJsonAsync("/api/Entity(10)", updateCommand).Result;
Output updateOutput = updateEntityResponse.Content.ReadAsAsync<Output>().Result;
}
}
For completion, the following are used DTOs:
public class UpdateEntity
{
public string Name { get; set; }
}
public class Output
{
public int Id { get; set; }
public string Name { get; set; }
}
Thanks in advance for your responses,
Jan Kacina
This design was intentional as we thought it to be an error case where a user would be having same route template on different controllers which can cause ambiguity in the selection process.
Also if we keep aside attribute routing, how would this work with regular routing? Let's imagine we have 2 regular routes where first one is targeted for FirstController and the second to SecondController. Now if a request url is like api/Entity(5), then Web API would always match the 1st route in the route table which would always hit the FirstController and would never reach SecondController. Remember that once Web API matches a route it tries to go till the action selection process and if the action selection process doesn't result in an action being selected, then an error response is sent to the client. You probably are assuming that if an action is not selected in one controller then Web API would route it to the next one in the route configuration. This is incorrect.
Route probing occurs only once and if it results in a match, then the next steps take place...that is controller and action selection. Hope this helps.

Mapping to an ASMX service using routing in ASP.NET MVC

I wonder if there's any way to map a URL to ASMX service much like it is done with pages (using routes.MapPageRoute() method).
When I tried to do it simply by pointing the MapPageRoute to my service I get the error
Type 'MvcApplication1.Services.EchoService' does not inherit from 'System.Web.UI.Page'.
Matthias.
I stumbled upon this question trying to find the answer myself, and since I did figure out a way to do it, I figured I'd answer it.
The reason I needed this is because I'm converting an old ASP.NET website to ASP.NET MVC, and for compatibility purposes I need a web service available at a specific URL. However, the path of that URL is now handled by a Controller in the new site, so I cannot have a physical directory with the same name (since that will prevent the controller from being invoked for other URLs with that path other than the web service).
The PageRouteHandler, which is used by RouteCollection.MapPageRoute, indeed requires that the handler for the target path derives from System.Web.Page, which isn't the case for web services. So instead, it is necessary to create a custom page handler:
using System;
using System.Web;
using System.Web.Routing;
using System.Web.Services.Protocols;
public class ServiceRouteHandler : IRouteHandler
{
private readonly string _virtualPath;
private readonly WebServiceHandlerFactory _handlerFactory = new WebServiceHandlerFactory();
public ServiceRouteHandler(string virtualPath)
{
if( virtualPath == null )
throw new ArgumentNullException("virtualPath");
if( !virtualPath.StartsWith("~/") )
throw new ArgumentException("Virtual path must start with ~/", "virtualPath");
_virtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
// Note: can't pass requestContext.HttpContext as the first parameter because that's
// type HttpContextBase, while GetHandler wants HttpContext.
return _handlerFactory.GetHandler(HttpContext.Current, requestContext.HttpContext.Request.HttpMethod, _virtualPath, requestContext.HttpContext.Server.MapPath(_virtualPath));
}
}
This route handler will create an appropriate handler for the web service based on the request and mapped virtual path of the service.
You can add a route for a web service now as follows:
routes.Add("RouteName", new Route("path/to/your/service", new RouteValueDictionary() { { "controller", null }, { "action", null } }, new ServiceRouteHandler("~/actualservice.asmx")));
Note: you must specify the controller and action values in the route value dictionary (even though they're set to null), otherwise the Html.ActionLink helper will always use this route for every single link (unless a match was found in the list before this route). Since you probably want to add this route before the default MVC route, it's important that it doesn't get matched that way.
Of course, you can create your own extension method to alleviate this task:
public static Route MapServiceRoute(this RouteCollection routes, string routeName, string url, string virtualPath)
{
if( routes == null )
throw new ArgumentNullException("routes");
Route route = new Route(url, new RouteValueDictionary() { { "controller", null }, { "action", null } }, new ServiceRouteHandler(virtualPath));
routes.Add(routeName, route);
return route;
}
After which you can simply do:
routes.MapServiceRoute("RouteName", "path/to/your/service", "~/actualservice.asmx");
I hope this helps someone, despite the age of this question. :)
Now that we waited two years with an anwer, how about using Web API instead? :)
EDIT: Kidding aside if that doesn't work for you and you still need an answer, leave a comment and I will see if I can't come up with a better one.
I attempted the original post's solution (also posted here), but I encountered a serious problem. I couldn't target the web method within the web service. When attempting to do so I got an exception stating the file didn't exist.
If you truly want to map an MVC route to a .ASMX web service the solution is explained here.
I believe that solution to be a hack by abusing the built-in types, because it uses reflection to bypass the intentional restrictive access members on the built-in .NET types.
Here is the method I'm taking which I believe to be much more straightforward.
First off, you should design your web services in the .ASMX file so that all the web service does is act as a published interface. At that point we don't need to target the .ASMX web service's methods directly. The important code has been made re-useable in core classes that are agnostic to the application's entry-point. We need this anyway so we can run automated tests!
Replace the MVC's web service method with a new route that has a custom route handler and http handler.
Old Route:
routes.MapRoute(
"Lead",
"lead/{action}.mvc",
new { controller = "Lead" });
New Route:
var dict = new RouteValueDictionary
{
{ "controller", null },
{ "action", null }
};
var handler = new LeadRouteHandler();
var route = new Route("lead/MVC_General.mvc", dict, handler);
routes.Add("Lead", route);
Note that the new route has the action hard-coded "MVC_General". This is because I wish to improve upon the giant controller class and create a handler for each action that way I can have small class with a single responsibility for each web method.
Implement the route's handlers.
IRouteHandler:
public class LeadRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new LeadHttpHandler();
}
}
IHttpHandler:
public class LeadHttpHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
// Just enough code to preserve the route's json interface for tests
var typedResult = new PsaLeadSubmissionResult();
typedResult.Registered = false;
typedResult.Message = new List<string>
{
"Not Implemented"
};
var jsonResult = JsonConvert.SerializeObject(typedResult);
context.Response.Write(jsonResult);
}
}
From within IHttpHandler's ProcessRequest method we gain full control over that route's action. With a well designed web service architecture all we need to do is call the class's that support the .ASMX web method you are trying to map the route to.
The result is a very clean Global.asax file. We could have done all of this without the URL routing just by manually inspecting the URL, but that's too important of a file to bloat.

Categories

Resources