Web Api in MVC site - Implementing MessageHandler in pipeline - c#

A few weeks ago I decided to start building an API for my system which is fronted by an MVC portal. I built Web Api capability into my existing MVC site by adding:
class WebApiConfig
{
public static void Register(HttpConfiguration configuration)
{
configuration.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
}
}
in my app_start folder, and modifying my Global.asax by adding:
GlobalConfiguration.Configure(WebApiConfig.Register);
It worked absolutely fine for calling simple methods in my Values controller either without the [Authorize] tag or in my browser by logging in first, but since then I've been reading around implementing basic authentication in asp.net web api and I've found a few examples I've tried to work into my implementation.
I have implemented a code example of a Message Handler I found online to authorize requests to it, at this stage simply comparing an ApiKey header string to one stored locally in my handler class to test it worked.
The handler looks like this:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> apiKeyHeaderValues = null;
if (request.Headers.TryGetValues("ApiKey", out apiKeyHeaderValues))
{
var apiKeyHeaderValue = apiKeyHeaderValues.First();
// ... your authentication logic here ...
var username = (apiKeyHeaderValue == "12345" ? "Authorised" : "OtherUser");
var usernameClaim = new Claim(ClaimTypes.Name, username);
var identity = new ClaimsIdentity(new[] { usernameClaim }, "ApiKey");
var principal = new ClaimsPrincipal(identity);
Thread.CurrentPrincipal = principal;
}
return base.SendAsync(request, cancellationToken);
}
I then added it to my global.asax:
GlobalConfiguration.Configuration.MessageHandlers.Add(new ApiAuthHandler());
Now I took this code in it's entirety from here: https://dzone.com/articles/api-key-user-aspnet-web-api as I'm new to this and lots of implementations of authorisation seemed too complex for my needs/too complex for me to begin my learning with.
I do understand it's flow and from debugging it when receiving a request it does the ApiKey comparison correctly and creates the principle. The response however is not correct...and it never reaches the requested api method. I get this response:
Redirect
To:http://localhost:2242/Account/Login?ReturnUrl=%2Fapi%2Fvalues with status: 302 Show explanation HTTP/1.1 302 Found
It is redirecting me to my register method, as the [Authorize] tag is meant to, and it's actually returning my Register.cshtml in it's entirety. I can't figure out how to ignore this and let the ApiAuthHandler Authorize for me. I'm assuming I need to change something in the MVC pipeline somewhere but I'm not sure what.
Just want to get something very simple working so that I can get my head around it more to explore more complicated API authentication. Any ideas what I've done wrong?
Edit added api controller:
public class ValuesController : ApiController
{
ApplicationDbContext context = new ApplicationDbContext();
InboundDBContext inboundContext = new InboundDBContext();
// GET api/<controller>
[Authorize]
public string Get()
{
return user.Identity.Name;
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}

Your WebApiConfig.cs file should look something like this...
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//add the authorization handler to the pipeline
config.MessageHandlers.Add(new new ApiAuthHandler()());
}
}

Related

Why is my .NET web api controller not being found?

We have been developing a .NET MVC application and recently are looking to integrate a webhook for an email service. I'll admit, this is my first attempt at webhooks and Web API, but it looks fairly straight forward.  I've followed several of the best practice and code examples from SendGrid and keep getting the "No type was found that matches the controller named 'xxxxxx'" message. I'm testing locally with Postman and can not get the controller(s) to be found. My initial goal is to test with the most basic configuration and just pass a POST to our web application, parse the data, and return 'ok'.  
 I've have enabled attribute routing in WebApiConfig.cs, have tested multiple different controller configurations, added "GlobalConfiguration.Configure(WebApiConfig.Register);" to my Global.asax.cs file, and made sure my classes are public.  
Am I missing something? I've been troubleshooting this for several hours over multiple days and have not been able to figure it out.
In my postman request I am not sending any parameters, have the content type header set to jason, and am only including  a sample SendGrid event in the body.  I've verified the port number, and am not passing any authentication via http to our local application. The POST request is being sent to the following url: http://localhost:59998/api/sample
I've followed several stack overflow posts on similar issues and have made sure I'm not shooting myself in the foot (private classes, plural vs singular, api config settings).
My api config:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I've removed most of the action methods to simplify the code for the controller. All I'm looking to do right now is to accept a POST, debug locally, and return ok. I've tried with multiple different class types and have back-tracked to just the most simple options possible.
I have breakpoints set in my controller and I've been troubleshooting with multiple testing variables, which I've removed to clean up the code (example: int test = 0).
namespace StickerAppWeb.Controllers
{
public class SampleController : ApiController
{
[HttpPost]
public string Index()
{
return "API method";
}
public void Post()
{
//int test = 0; //breakpoint here
}
}
}
My Global config:
namespace StickerAppWeb
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
Update1:
As a follow-up from yesterday, I also have this project published to Azure and get the same response from Postman when submitting a POST to the application in Azure. Is there any reason that the application is not finding that controller both locally, and in Azure?
Your code has no problem and should work. To send a post request to the api, you can follow the code below.
public string MYMethod()
{
string str= Task.Run(() => GetFromWeb).Result;
return str;
}
If you want to use the post method
private async Task<string> GetFromWeb()
{
HttpClient client = new HttpClient();
string Resturl = "domainname/api/sample/Index";
var response = await client.PostAsync(Resturl, null);
var result = await response.Content.ReadAsStringAsync();
return result;
}
If you want to use the get method
private async Task<string> GetFromWeb()
{
var client = new HttpClient();
var result = await client.GetAsync("domainname/api/sample/Index");
var response = await result.Content.ReadAsStringAsync();
return response;
}

Custom route in web api just redirects to current page

Being a noob in MVC web api there is probably something very obvious I'm missing..
However, In my ProjectController I have the following method with attributes (I know this kind of method should be POST but just testing easily...):
[Route("api/projects/archive/{id:int}")]
[HttpGet]
public void Archive(int id)
{
_projectService.Archive(id);
}
However, when I open my URL such as:
http://localhost:49923/api/projects/archive/1
The page simply redirects to the current URL, and the archive method is not called. I also have a breakpoint at the method to verify it's not hit.
My best guess is I also have to update my web api route config which is the default, but I just assumed the route attribute was enough?
Here is my web api route config:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional});
config.Formatters.JsonFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("text/html"));
}
What am I doing wrong here? :-)
EDITS:
Clearification 1 - my ProjectController:
public class ProjectsController : ApiController
{
private ProjectService _projectService;
public ProjectsController()
{
_projectService = new ProjectService();
}
[Route("api/projects/archive/{id:int}")]
[HttpGet]
public void Archive(int id)
{
_projectService.Archive(id);
}
}
Clearification 2 - redirect:
So lets say I stand on the homepage (/). I then go to the URL "http://localhost:49923/api/projects/archive/1", it will just reload page and leave my back at the home page.
The Web API config is configured correctly.
Ensure that the controller and the action are constructed properly
public class ProjectController : ApiController {
//...other code removed for brevity
[HttpGet]
[Route("api/projects/archive/{id:int}")]//Matches GET api/projects/archive/1
public IHttpActionResult Archive(int id) {
_projectService.Archive(id);
return Ok();
}
}
Its bit late to answer but hope you find it useful,
Many times the way how we write the code help us find the solution to the problem,
As Nkosi already answered, that the constructing controller and the action method properly will resolve the issue.
Its always helpful to check the method first rather then looking at the route.config because by default it will be the same unless you provide your custom attribute.

web api in MVC no longer working

I am using VS 2017 community. I have been building web api s for years. But something must have changed as I cannot get the simplest example to work.
I have a simple controller in the controller folder
public class TestApi : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
I have the necessary code in application start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
But when I try and test the web api with a get like:
http://localhost:54014/api/testapi
I always get an xml message
Error
Message
No HTTP resource was found that matches the request URI
'http://localhost:54014/api/testapi'.
/Message
MessageDetail
No type was found that matches the controller named 'testapi'.
/MessageDetail
/Error
Here is the WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I am a couple of hours into head scratching on this. As I say I have built many MS web api implementations and this one has me baffled.
You should add Controller suffix to your class name.
public class TestApiController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
When the app starts, the asp.net mvc frameworks looks for classes (which are inherited from ApiController or Controller) with this suffix and register them when building the route table. When a request comes to your app, the framework again look into the route table and direct the request to the corresponding controller and action method.
Make this change, rebuild your project and try again.
In addition to the already provided answer (which is correct by the way) you can also consider using attribute routing over convention-based routing, where in this case it would not matter what the name of the controller is.
You already have it enabled Based on the WebApiConfig.cs and
config.MapHttpAttributeRoutes();
So now it is just a matter of putting the attributes where needed
[RoutePrefix("api/testapi")]
public class TestApi : ApiController {
[HttpGet]
[Route("")] //Matches GET api/testapi
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
}
Reference: Attribute Routing in ASP.NET Web API 2

.NET Web api 2 stops working after sometime in IIS

I have two web API project DLLs in one solution.
Structure of my Project Solution:
In my solution, the projects are located as follows:
1) Base Solution
2) Base Web API
3) Module Web API
Hence, my solution is something like a BASE solution which contains many MODULES. Each Modules can contain its own Web APIs. Also, my Base Solution contains its own Web API
This is our structure.
My Problem:
It is working fine in my local run solution. When I host it to the IIS, it is working for few hours and then it stops working by throwing the error message "Error 404 found". When I try to access through URL directly which is something like "http://127.0.0.1:51/Products/Get", not working.
Visual Studio version:
Visual Studio Professional - 2015
IIS Version:
IIS - 7.5.7600
My approach:
I have a simple project which simulates this scenario. It has the same problem with my original project.
Web API For Base Module:
WepApiConfig under App_Start:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Base API Controller:
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
WebApiConfig.cs For Module Web API:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
//config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultModuleApi",
routeTemplate: "api/module/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Module API Controller:
public class ModulesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
NOTE from the above code:
The difference between the two APIConfig files is :
For Module Code:
routeTemplate: "api/module/{controller}/{action}/{id}"
For Base Code:
routeTemplate: "api/{controller}/{action}/{id}"
Global.asax:
namespace BaseSln
{
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//GlobalConfiguration.Configure(WebApiConfig.Register);
var typesWithMyAttribute =
from a in AppDomain.CurrentDomain.GetAssemblies()
from t in a.GetLoadableTypes().Where(t1 => t1.Name == "WebApiConfig"
&& t1.GetMethod("Register") != null
&& t1.GetMethod("Register").IsStatic)
select new { Type = t, MethodInfo = t.GetMethod("Register") };
//register all the Routes
foreach (var type in typesWithMyAttribute)
{
var mi = type.MethodInfo;
Action<HttpConfiguration> action = null;
try
{
action = Delegate.CreateDelegate(typeof(Action<HttpConfiguration>), mi) as Action<HttpConfiguration>;
}
catch
{
//ignore the errors for now... should be handled somewhere else or logged.
}
if (action != null) GlobalConfiguration.Configure(action);
}
}
}
}
What I tried with the above project:
After hosting in IIS, I tried to access the path which is something like this:
For Base API:
http://localhost:51600/api/Values/Get
Returns:
value1
value2
For Module API
http://localhost:51600/api/Modules/Get
Returns:
value1
value2
My problem:
After sometime, when I try to access the same link, I am unable to get that. It says
status 404. Not Found
I have been working on this issue for 3 days, and I couldn't resolve the problem.
I have searched many articles in stackoverflow, but no luck.
Kindly help me to get rid off from this.
Thanks.
Can you check the GlobalConfiguration.Configuration.Routes in the Base Solution if you have all the routes for both the Base Web API and the Module Web API?

Sending data from angular to c# api controller binds to wrong action

Question:
Why does my action Add gets hit instead of my delete action?
Error message:
"ExceptionMessage": "Multiple actions were found that match the
request: Add on type blog.Controllers.api.UsersController Delete on
type blog.Controllers.api.UsersController
My angular delete
$scope.delete = function (email) {
$http.post('/api/users/Delete', email).success(function() {
initData();
});
}
alternative tested
//$http({
// url: '/api/users/Delete',
// method: 'POST',
// data: { 'userEmail': email }
// })
// .success(function(data, status, headers, config) {
// initData();
// }).
// error(function(data, status, headers, config) {
// $scope.showError = true;
// });
My MVC 5 api controller
private readonly UserDataAccess _userDataAccess;
// GET: Users/Add
[HttpPost]
public HttpResponseMessage Add(User user)
{
_userDataAccess.AddUser(user);
return Request.CreateResponse(HttpStatusCode.OK);
}
// GET: Users/Delete - never gets hit
[HttpPost]
public HttpResponseMessage Delete([FromBody]string userEmail)
{
_userDataAccess.DelereUserByEmail(userEmail);
return Request.CreateResponse(HttpStatusCode.OK);
}
Please note I do not want to solve it using routing.
In WebApi you can only have one POST action per Routing. In order to have more than one for the controller class you will need to define new routings.
If you are using WebApi 2 you can benefit from the RoutingAttribute and define these new sub-routes
In your controller class use RoutePrefixAttributte:
[RoutePrefix('api/users')]
public class Users: ApiController{ ...
Followed by your 2 sub-routes:
[Route('add')]
[HttpPost]
public HttpResponseMessage Add(User user){..
[Route('delete')]
[HttpPost]
public HttpResponseMessage Delete([FromBody]string userEmail) {
About Routings
Web Api v1 defines resources globally in the global.asax on application_start event. Assuming you are using Visual Studio 2013 and based on Microsoft's default template your method may look like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
WebApi routing configuration occurs here WebApiConfig.Register while your MVC configuration occurs here RouteConfig.RegisterRoutes
Your WebApi routing configuration should look like this
public static class WebApiConfig{
public static void Register(HttpConfiguration config){
config.Routes.MapHttpRoute(
name: "apiSample",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional }
);
...
As mentioned previously WebApi v2 introduced the Route Attributes and those can be used along with your Controller class and can facilitate the routing configuration.
For example:
public class BookController : ApiController{
//where author is a letter(a-Z) with a minimum of 5 character and 10 max.
[Route("html/{id}/{newAuthor:alpha:length(5,10)}")]
public Book Get(int id, string newAuthor){
return new Book() { Title = "SQL Server 2012 id= " + id, Author = "Adrian & " + newAuthor };
}
[Route("json/{id}/{newAuthor:alpha:length(5,10)}/{title}")]
public Book Get(int id, string newAuthor, string title){
return new Book() { Title = "SQL Server 2012 id= " + id, Author = "Adrian & " + newAuthor };
}
...
You may also refer this other POST with a similar question
How do I write REST service to support multiple Get Endpoints in .net MVC Web API
[HttpPost]
[Route('api/User/Delete/{userEmail}')]
public HttpResponseMessage Delete([FromBody]string userEmail)
{
}
You can manually configure the route as well
You can add the [HttpDelete] attribute on your delete method in your Api controller and then call it with $http.delete instead.
[HttpDelete]
public IHttpActionResult Delete(string email) {
return StatusCode(HttpStatusCode.NoContent);
}
To quote the docs of HttpDeleteAttribute Class.
Represents an attribute that is used to restrict an action method so that the method handles only HTTP DELETE requests.

Categories