I have the below route mapped in my AreaRegistration:
public override void RegisterArea(AreaRegistrationContext context)
{
if (context != null)
{
context.Routes.MapHttpRoute(
name: "API_Default",
routeTemplate: "Areas/Test/AIO/api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
});
When I look at the Global.asax file, I can see the HttpRoute is being Registered, and is listed in the RouteTable.Routes as a {System.Web.Http.WebHost.Routing.HttpWebRoute}.
Problem is, when I go to the url... https://myRoot/Areas/Test/AIO/api/AioApi/test or https://myRoot/Areas/Test/AIO/api/AioApi, it's giving me a 500 internal server error.
I'm not sure how to view the actual error, when stepping thru the code I cannot see anything after it leaves Application_BeginRequest.
My controller code:
public class AioApiController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Any insight as to why I cannot hit the API controller? I can hit my regular MVC controller in the same context.
Any help is greatly appreciated!
One thing I found out with WebApi is that there are two Route collections: System.Web.Routing.RouteCollection and System.Web.Http.HttpRouteCollection. I believe (but can't remember) that you need to use the latter in order for your ApiController derivations to work properly (luckily the syntax is the same).
Related
I have been working with Web API for well over a year, and haven't run into this problem before. I am really at my witt's end after spending hours googling, and looking at stack overflow posts etc. I am wondering if I just have some brain fart thing going on.
I have a controller where I want a get and a put against the same route:
[Route("api/strings")]
[HttpGet]
public IHttpActionResult GetStrings()
{
try
{
var siteStrings = svc.GetSiteStrings(_appId);
return Ok(new { strings = siteStrings });
}
catch(Exception)
{
return InternalServerError();
}
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings")]
public IHttpActionResult PutString(String key, String text)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(key, text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
My routing is just the default out of the box routing as can be seen here:
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
After a seeming eternity doing configs over and over based on so many other stack overflow questions about 405 errors, I realized that I can verify that mine is trying to route my PUT method to my GET's endpoint. However if I change the route on my put to something else, I always get a 404. I can't figure out why it is putting all my puts or posts through to my GET verb and thus saying that the action is not allowed on my route. I am sure I am just missing something trivial at this point but mental fatigue and tired eyes are just holding me back. Anyone see something stupid I am doing wrong?
All I had to do was create a class that my put has to model bind to rather than two parameters, and the routing started working. That is surprising, but I just had to do the following and it works:
public class temp
{
public String key { get; set; }
public String text { get; set; }
}
[HttpPut]
[AcceptVerbs("PUT")]
[Route("api/strings/")]
public IHttpActionResult PutString(temp data)
{
//TODO: add authorization to this one.
try
{
if (svc.UpdateString(data.key, data.text, _appId))
{
return Ok();
}
return InternalServerError();
}
catch (Exception)
{
return InternalServerError();
}
}
Obviously I won't keep that temp class there, but it was a quick stab in the dark to see if the put would route correctly that way. Sure enough it did. Must be something in the model binding specs for Web API that I wasn't aware of.
After a lengthy discussion in terms of how our API would need to behave due to limitations of the backend design, I'd like to have the following possibilities:
1. /students/251/employment <--- allow GET, PUT, POST
2. /students/251/employment/jobs <--- allow GET only
3. /students/251/employment/jobs/435 <--- allow all verbs
4. /students/251/employment/internships <--- allow GET only
5. /students/251/employment/internships/664 <--- allow all verbs
These cases are working for GET requests. I'm struggling when I try to do a PUT request for case #1 and #3:
Case #1 Error
No HTTP resource was found that matches the request URI '/students/251/employment/221'.,
No action was found on the controller 'Employment' that matches the name '221'.
Case #3 Error
The requested resource does not support http method 'PUT'.
Here's an abridged version of my controller methods:
public ApiEmploymentGetResult Get(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiJob> Jobs(long id) {
// code omitted
}
[HttpGet]
public IEnumerable<ApiOwnVenture> OwnVenture(long id) {
// code omitted
}
public void Put(long id, MyModel model) {
// breaks before getting here
}
My routing looks like this, but I'm not sure it's quite right, even though the GETs are working.
context.Routes.MapHttpRoute(
name: "V1/EmploymentApi",
routeTemplate: "api/v1/Employment/{action}/{jobId}",
defaults: new { controller = "Employment", jobId = RouteParameter.Optional, action = "Get" }
);
Case #1 seems to be conflicting due to the framework expecting an action rather than the 221. I'd like to be able to get all of these cases working.
You may want to look at Attribute Routing (Web API 1 and Web API 2).
public class StudentsController : ApiController
{
[HttpPut]
[Route("students/{studentId}/employment")]
public void UpdateStudentEmployment(int studentId) { ... }
[HttpPut]
[Route("students/{studentId}/employment/jobs/{jobId}")]
public void UpdateStudentEmploymentJob(int studentId, int jobId) { ... }
}
I have just downloaded AttributeRouting NuGet package for WebAPI and having a problem within my controller.
I thought the way to use it was to have something like:
public class InboxController : ApiController
{
private IInboxService _inboxService;
public InboxController(IInboxService inboxService)
{
_inboxService = inboxService;
}
public IEnumerable<MessageModel> GetAll()
{
return _inboxService.GetAllMessages();
}
[HttpGet("Inbox/Count")]
public int GetInboxCount()
{
return _inboxService.GetMessageCount();
}
}
However I get the following error:
Error 2 'System.Web.Http.HttpGetAttribute' does not contain a constructor that takes 1 arguments
I need to get this up and running fairly quickly. Is there any reason why the HttpGet attribute doesn't have an overloaded constructor?
UPDATE
[GET("Inbox/EnquiryCount")]
public EnquiryCountModel GetEnquiryCounts()
{
var model = new EnquiryCountModel();
model.EnquiryCount = _inboxService.GetCustomerEnquiriesCount();
model.EnquiryResponseCount = _inboxService.GetCustomerEnquiryResponseCount();
return model;
}
In routes:
routes.MapHttpRoute("InboxEnquiryApi", "api/inbox/{action}", new { Controller = "Inbox" }, null, new WebApiAuthenticationHandler(GlobalConfiguration.Configuration));
When I hit the URL at 'api/inbox/EnquiryCount' I get the this error:
**No HTTP resource was found that matches the request URI 'http://localhost:49597/api/inbox/enquirycount'**
Attribute routing is supported in Web api 2
Here is the details: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
This syntax has been changed in newer versions of the webapi. The [HTTPPOST] is now standalone and there is a new attribute for the route aptly name ROUTE which takes the route url
eg.
[Route("GetRes/{month}")]
I have a ASP.NET Web API (.NET 4) application which has a few controllers. We will run several instances of the Web API application on IIS with one difference. Only certain controllers will be available under certain IIS instances. What I was thinking is to disable/unload the controllers that are not applicable to an instance when the instance starts up.
Anyone got some information that could guide me in the right direction on this?
You can put your own custom IHttpControllerActivator in by decorating the DefaultHttpControllerActivator. Inside just check for a setting and only create the controller if allowed.
When you return null from the Create method the user will receive 404 Not Found message.
My example shows a value in App Settings (App.Config or Web.Config) being checked but obviously this could any other environment aware condition.
public class YourCustomControllerActivator : IHttpControllerActivator
{
private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();
public YourCustomControllerActivator()
{
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
if (ConfigurationManager.AppSettings["MySetting"] == "Off")
{
//Or get clever and look for attributes on the controller in controllerDescriptor.GetCustomAttributes<>();
//Or use the contoller name controllerDescriptor.ControllerName
//This example uses the type
if (controllerType == typeof (MyController) ||
controllerType == typeof (EtcController))
{
return null;
}
}
return _default.Create(request, controllerDescriptor, controllerType);
}
}
You can switch your activator in like so:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());
Update
It has been a while since I looked at this question but if I was to tackle it today I would alter the approach slightly and use a custom IHttpControllerSelector. This is called before the activator and makes for a slightly more efficient place to enable and disable controllers... (although the other approach does work). You should be able to decorate or inherit from DefaultHttpControllerSelector.
Rather than unloading the controllers, I think I'd create a custom Authorize attribute that looked at the instance information in deciding to grant authorization.
You would add the following to each controller at the class level, or you could also add this to individual controller actions:
[ControllerAuthorize (AuthorizedUserSources = new[] { "IISInstance1","IISInstance2","..." })]
Here's the code for the Attribute:
public class ControllerAuthorize : AuthorizeAttribute
{
public ControllerAuthorize()
{
UnauthorizedAccessMessage = "You do not have the required access to view this content.";
}
//Property to allow array instead of single string.
private string[] _authorizedSources;
public string UnauthorizedAccessMessage { get; set; }
public string[] AuthorizedSources
{
get { return _authorizedSources ?? new string[0]; }
set { _authorizedSources = value; }
}
// return true if the IIS instance ID matches any of the AllowedSources.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
//If no sources are supplied then return true, assuming none means any.
if (!AuthorizedSources.Any())
return true;
return AuthorizedSources.Any(ut => ut == httpContext.ApplicationInstance.Request.ServerVariables["INSTANCE_ID"]);
}
The IHttpControllerActivator implementation doesn't disable the routes defined using attribute routing , if you want to switch on/off a controller and have a default catch all route controller. Switching off using IHttpControllerActivator disables the controller but when the route is requested it doesn't hit the catch all route controller -it simply tries to hit the controller that was removed and returns no controller registered.
What I have is the following extension method:
public MyCustomAttribute[] GetActionAttributes(
this Controller #this,
string action,
string controller,
string area,
string method)
{
}
How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?
To this moment I have nothing... no clues on how to do this.
I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.
Why I need these attributes
My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.
Other uses
I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.
Not a duplicate
Yes, some will say this is possibly a duplicate of this question...
that does not have the answer I want:
How can i get the MethodInfo of the controller action that will get called given a request?
That's why I have specified the circumstances of my question.
I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it.
I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.
At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.
Works with
MVC 3 (I think)
MVC 4 (tested)
Drawbacks of my solution
Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:
static object ControllerBuilder.Current (very bad for unit testing)
a lot of classes from MVC (high coupling is always bad)
not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))
The code
public static Attribute[] GetAttributes(
this Controller #this,
string action = null,
string controller = null,
string method = "GET")
{
var actionName = action
?? #this.RouteData.GetRequiredString("action");
var controllerName = controller
?? #this.RouteData.GetRequiredString("controller");
var controllerFactory = ControllerBuilder.Current
.GetControllerFactory();
var controllerContext = #this.ControllerContext;
var otherController = (ControllerBase)controllerFactory
.CreateController(
new RequestContext(controllerContext.HttpContext, new RouteData()),
controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(
otherController.GetType());
var controllerContext2 = new ControllerContext(
new MockHttpContextWrapper(
controllerContext.HttpContext.ApplicationInstance.Context,
method),
new RouteData(),
otherController);
var actionDescriptor = controllerDescriptor
.FindAction(controllerContext2, actionName);
var attributes = actionDescriptor.GetCustomAttributes(true)
.Cast<Attribute>()
.ToArray();
return attributes;
}
EDIT
Forgot the mocked classes
class MockHttpContextWrapper : HttpContextWrapper
{
public MockHttpContextWrapper(HttpContext httpContext, string method)
: base(httpContext)
{
this.request = new MockHttpRequestWrapper(httpContext.Request, method);
}
private readonly HttpRequestBase request;
public override HttpRequestBase Request
{
get { return request; }
}
class MockHttpRequestWrapper : HttpRequestWrapper
{
public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
: base(httpRequest)
{
this.httpMethod = httpMethod;
}
private readonly string httpMethod;
public override string HttpMethod
{
get { return httpMethod; }
}
}
}
Hope all of this helps someone...
Happy coding for everybody!
You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.
public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
{
/// <summary>
/// Use for validate user permission and when it also validate user session is active.
/// </summary>
/// <param name="filterContext">Filter Context.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
string actionName = filterContext.ActionDescriptor.ActionName;
string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
if (!IsUserHasPermission(controller, actionName))
{
// Do your required opeation
}
}
}
if you have a default route configured like
routes.MapRoute(
"Area",
"",
new { area = "MyArea", controller = "Home", action = "MyAction" }
);
you can get the route information inside the controller action like
ht tp://localhost/Admin
will give you
public ActionResult MyAction(string area, string controller, string action)
{
//area=Admin
//controller=Home
//action=MyAction
//also you can use RouteValues to get the route information
}
here is a great blog post and a utility by Phil Haack RouteDebugger 2.0
This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];
Good Luck.