So I already have 1 Web API set up and working great, but now that I am trying to set up my own admin panel ( which I did ), I need to use the DeleteUser() function from the Web API named AdminApi but I can't seem to get it working. I keep getting 404 error while giving the path that the API should be at.
Web Api Config:
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}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Global :
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//Create the custom role and user
RoleActions roleActions = new RoleActions();
roleActions.AddUserAndRole();
}
AdminApi :
[Authorize(Roles = "admin")]
public class AdminApiController : ApiController
{
public string test()
{
return "test";
}
[HttpPost]
public string DeleteUser(string id)
{
ApplicationDbContext db = new ApplicationDbContext();
var user = db.Users.Find(id);
if (user != null)
{
string email = user.Email;
db.Users.Remove(user);
return "Succesfully deleted user : " + email;
}
else
return "Failed to delete user.";
}
}
Ajax :
function deleteUser (id)
{
$.ajax({
url: '../api/AdminApi/DeleteUser',
type: 'POST',
contentType: "application/json",
dataType: 'json',
data: JSON.stringify(id),
success: function (data) {
alert(data);
},
error: function (x, y, z) {
alert(x + '\n' + y + '\n' + z);
}
});
}
The ajax function is called on the page /Admin/AdminPage
so to get to the web api -> ../api/AdminApi
and the function to delete users is DeleteUser
-> ../api/AdminApi/DeleterUser
I don't get why I get a 404 error. I can understand if my function DeleteUser is not working since I haven't tested it, but I can't test it if I can't get in the function.
The issue is related to how you use the attribute:
[Route("DeleteUser")]
If you use the Attribute Route. at Method level what it does is to define new route or more routes therefore the way you should use it is like [Route('Url/path1/route1')]:
As an example of how it works:
//GET api/customer/GetMetaData
[Route('/api/customer/GetMetaData')]
public string Get2(){
//your code goes here
}
If you will be declaring several Routes in your class then you can use RoutePrefix attribute like [RoutePrefix('url')] at class level. This will set a new base URL for all methods your in Controller class.
For example:
[RoutePrefix("api2/some")]
public class SomeController : ApiController
{
// GET api2/some
[Route("")]
public IEnumerable<Some> Get() { ... }
// POST api2/some/DeleteUser/5
[HttpPost]
[Route("DeleteUser/{id:int}")]
public Some DeleteUser(int id) { ... }
}
Update
By default Web API looks at the routing URL first, what is in your [Route] I mean and it tries to match it against your post. However if your method has a complex object as parameter WebApi can't get the values from the request URI because the parameter is a complex type Web API uses a media-type formatter to read the value from the request body.
Since your string id is not a complex object and it is part of your Route WebApi expects it as part of your URL not the body. Try this instead:
[HttpPost]
public string DeleteUser([FromBody]string anotherName)
Related
I created a WEB API for getting some list . I created another API for login which is working fine. But whenever call the API which provide me some list, it will show error as
No HTTP resource was found that matches the request URI 'http://localhost:85476/API/EmployeeMobile/GetEmployeeForMobile,
No action was found on the controller 'EmployeeMobile' that matches the request.
My controller:
public class EmployeeMobileController : ApiController
{
private PCommon _pCommon;
IEmployeeBusiness _employeeBusiness;
Mapper _mapper;
public EmployeeMobileController()
{
_pCommon = new PCommon();
_employeeBusiness = new EmployeeBusiness();
_mapper = new Mapper();
}
[HttpPost]
public string GetEmployeeForMobile(string userID, string connectionString)
{
DataSet dsEmployee = _employeeBusiness.GetAllEmployeeForMobile(Guid.Parse(userID), connectionString);
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
object routes_list = (object)json_serializer.DeserializeObject(JsonConvert.SerializeObject(dsEmployee.Tables[0]));
return JsonConvert.SerializeObject(routes_list);
}
}
My WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
My PostMan Calling details
http://localhost:61557/API/EmployeeMobile/GetEmployeeForMobile
Body: {"userID":"fc938df0-373c559f","connectionString":"Data\tSource=WIN\\MSSQLSERVER2016;Initial\tCatalog=PDefault;User\tID=db;Password=db123"}
Please help me to recover this problem.
Please note, WebAPI doesn't support binding of multiple POST parameters. You are defining your POST like a GET method. Change it to return a single Model
Your model should look like following.
public class Input
{
public string uSerID { get; set; }
public string connectionString { get; set; }
}
Now change the POST method like
[HttpPost]
public string GetEmployeeForMobile(Input request)
{
//Your implementation here
}
and you JSON for the post should look like following.
{uSerID:"abc", connectionString :"xyz"}
I have the custom AuthorizeAttribute where I need to use one of the business layer services to validate some data in the database before giving user a permission to view the resource. In order to be able to allocate this service within the my AuthorizeAttribute I decided to use service location "anti-pattern", this is the code:
internal class AuthorizeGetGroupByIdAttribute : AuthorizeAttribute
{
private readonly IUserGroupService _userGroupService;
public AuthorizeGetGroupByIdAttribute()
{
_userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
}
//In this method I'm validating whether the user is a member of a group.
//If they are not they won't get a permission to view the resource, which is decorated with this attribute.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
return _userGroupService.IsUserInGroup(currentUserId, groupId);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContex)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(actionContex);
}
else
{
actionContex.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
I have couple of other attributes like this in my application. Using service locator is probably not a good approach. After searching the web a little bit I found some people suggesting to use IAuthorizationFilter with dependency injection instead. But I don't know how to write this kind of IAuthorizationFilter. Can you help me writing IAuthorizationFilter that will do the same thing that the AuthorizeAttribute above?
So after struggling for a while I think I managed to resolve this issue. Here are the steps you have to do in order to that:
1) First you have to make GetGroupByIdAttribute passive, and by passive I mean an empty attribute without any logic within it (it will be used strictly for decoration purposes)
public class GetGroupByIdAttribute : Attribute
{
}
2) Then you have to mark a controller method, for which you want to add authorization, with this attribute.
[HttpPost]
[GetGroupById]
public IHttpActionResult GetGroupById(int groupId)
{
//Some code
}
3) In order to write your own IAuthorizationFilter you have to implement its method ExecuteAuthorizationFilterAsync. Here is the full class (I included comments to guide you through the code):
public class GetGroupByIdAuthorizationFilter : IAuthorizationFilter
{
public bool AllowMultiple { get; set; }
private readonly IUserGroupService _userGroupService;
//As you can see I'm using a constructor injection here
public GetGroupByIdAuthorizationFilter(IUserGroupService userGroupService)
{
_userGroupService = userGroupService;
}
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
//First I check whether the method is marked with the attribute, if it is then check whether the current user has a permission to use this method
if (actionContext.ActionDescriptor.GetCustomAttributes<GetGroupByIdAttribute>().SingleOrDefault() != null)
{
Dictionary<string, string> parameters = actionContext.Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value);
int groupId = int.Parse(parameters["groupId"]);
int currentUserId = HttpContext.Current.User.Identity.GetUserId();
//If the user is not allowed to view view the resource, then return 403 status code forbidden
if (!_userGroupService.IsUserInGroup(currentUserId, groupId))
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Forbidden));
}
}
//If this line was reached it means the user is allowed to use this method, so just return continuation() which basically means continue processing
return continuation();
}
}
4) The last step is to register your filter in the WebApiConfig.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Here I am registering Dependency Resolver
config.DependencyResolver = ServiceLocator.Instance.DependencyResolver;
//Then I resolve the service I want to use (which should be fine because this is basically the start of the application)
var userGroupService = ServiceLocator.Instance.Resolve<IUserGroupService>();
//And finally I'm registering the IAuthorizationFilter I created
config.Filters.Add(new GetGroupByIdAuthorizationFilter(userGroupService));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Now, if needed, I can create additional IActionFilters that use IUserGroupService and then inject this service at the start of the application, from WebApiConfig class, into all filters.
Perhaps try it like shown here:
Add the following public method to your class.
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
// gets the dependecies from the serviceProvider
// and creates an instance of the filter
return new GetGroupByIdAuthorizationFilter(
(IUserGroupService )serviceProvider.GetService(typeof(IUserGroupService )));
}
Also Add interface IFilterMetadata to your class.
Now when your class is to be created the DI notices that there is a CreateInstance method and will use that rather then the constructor.
Alternatively you can get the interface directly from the DI in your method by calling
context.HttpContext.Features.Get<IUserGroupService>()
Currently I am creating a web application using a non-mvc web API in .NET. We are also using AngularJS to call the API with.
Here is the Router:
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 }
);
}
}
Here is the controller:
public class signOnController : ApiController
{
public IHttpActionResult GetSingleSignOn()
{
singleSignOn sign = new singleSignOn();
var check = sign.executeStoredProcedure();
if (check == 0)
{
return Ok("http://localhost:64325/EmployeeIndex.html#/form/begin");
}
else if (check == 1)
{
return Ok("http://localhost:64325/ManagerIndex.html#/form/begin");
}
else
{
return Unauthorized();
}
}
}
Here is the angularJS:
formApp.service('ApiCall', ['$http', function ($http) {
var result;
this.GetApiCall = function (controllerName, methodName) {
result = $http.get('api/' + controllerName + '/' + methodName).success(function (data, status) {
result = data;
}).error(function () {
alert("API Get call error");
});
return result;
};
I keep getting this error when I try to run it:
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:51463/api/signOnController/GetSingleSignOn'.
</Message>
<MessageDetail>
No type was found that matches the controller named 'signOnController'.
</MessageDetail>
</Error>
Anyone know what I'm doing wrong with it?
You shouldn't be mentioning Controller inside route name. It should be
http://localhost:51463/api/signOn/GetSingleSignOn
Though you can use Route Attribute to define Routing to make route more verbose.
[Route("SingleSignOn")]
public IHttpActionResult GetSingleSignOn() {
....
}
Then URL will be like http://localhost:51463/api/signOn/SingleSignOn
Firstly, the route should be http://localhost:51463/api/signOn/GetSingleSignOn. controller name is just for identifying it is a controller.in routes you must use this without the word Controller. but when creating a controller make sure you include the word
secondly, i think what you exactly want to achieve is a route like in MVC. change your router to include action.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
the default asp webapi routes are for pure REST API implementation which is not suitable for most cases.
I need a custom IHttpControllerSelector which should be applied to a specific route only. All other web api routes should use the default implementation of IHttpControllerSelector.
While researching I found the following code that is meant to replace the IHttpControllerSelector at application start, but it replaces the default controller selector completely, which causes that all routes in the application use my custom controller selector:
config.Services.Replace(typeof(IHttpControllerSelector),
new CustomControllerSelector(config));
Is there a way to configure the IHttpControllerSelector for a single route?
You can assign a per-route message handler to the route that needs to use a different controller selection logic. This handler would mark the HttpRequestMessage with a flag that this request needs to be treated differently.
Then simply make the CustomControllerSelector inherit from DefaultHttpControllerSelector and inspect that flag:
if it's set, continue with your custom logic
if it's not set, return to base (DefaultHttpControllerSelector)
Here is the code:
1) message handler, setting the flag
public class RouteSpecificHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Properties["UseCustomSelector"] = true;
return base.SendAsync(request, cancellationToken);
}
}
2) assigning per route message handler to the specific route only (do not run for other routes)
config.Routes.MapHttpRoute(
name: "MyRoute",
routeTemplate: "api/dummy/{id}",
defaults: new {controller = "Dummy", id = RouteParameter.Optional},
constraints: null,
handler: new RouteSpecificHandler { InnerHandler = new HttpControllerDispatcher(config) }
);
3) custom selector respecting the flag:
public class CustomSelector : DefaultHttpControllerSelector
{
public CustomSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
if (request.Properties.ContainsKey("UseCustomSelector") &&
request.Properties["UseCustomSelector"] as bool? == true)
{
//your logic goes here
}
return base.SelectController(request);
}
}
4) registering the selector:
config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelector(config));
edit
If you wish to not inherit from DefaultHttpControllerSelector - then implement IHttpControllerSelector directly, and instead of calling the base.SelectController(request) save the old selector as a field/property in your class
public class CustomSelector : IHttpControllerSelector
{
private HttpConfiguration _config;
public IHttpControllerSelector PreviousSelector {get; set;}
public CustomSelector(HttpConfiguration configuration)
{
_config = configuration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
if (request.Properties.ContainsKey("UseCustomSelector") &&
request.Properties["UseCustomSelector"] as bool? == true)
{
//your logic goes here
}
return PreviousSelector.SelectController(request);
}
}
Then just change the registration:
var previousSelector = config.Services.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
config.Services.Replace(typeof(IHttpControllerSelector), new CustomSelector(config) { PreviousSelector = previousSelector});
I am trying to abstract the auto-generated ODataController class in VS 2013 because the code looks identical across different controllers except the name of the POCO, so, I did the following:
public abstract class ODataControllerBase<T,DB> : ODataController
where T : class, IIdentifiable, new()
where DB : DbContext, new()
{
protected DB _DataContext;
public ODataControllerBase() : base()
{
_DataContext = new DB();
}
// only one function shown for brevity
[Queryable]
public SingleResult<T> GetEntity([FromODataUri] int key)
{
return SingleResult.Create(_DataContext.Set<T>().Where(Entity => Entity.Id.Equals(key)));
}
}
IIdentifiable is an interface that forces the T parameter to have a readable/writable Id integer property.
The implementation looks like this (POCOs and DataContexts should've already been created)
public class MyObjectsController : ODataControllerBase<MyObject,MyDbContext>
{
public MyObjectsController() : base()
{
}
// That's it - done because all the repetitive code has been abstracted.
}
Now, my WebApiConfig's Register function contains the following only:
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyObject>("MyObjects");
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
}
I run the project, http://localhost:10000/odata/MyObjects and I get the response:
<m:error>
<m:code/>
<m:message xml:lang="en-US">No HTTP resource was found that
matches the request URI `http://localhost:10000/odata/MyObjects.`
</m:message>
<m:innererror>
<m:message>No routing convention was found to select an action
for the OData path with template '~/entityset'.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
</m:error>
What is missing? What should I remove? Is this something we can't do, i.e. are we really required to inherit ODataController directly with no intermediate parent class?
In one of our projects We also use a generic ODataController base class where we actually use GetEntity for retrieving single entities and GetEntitySet for retrieving a list of entities.
According to your supplied URL and the resulting error message, the ODATA framework cannot find an ODataAction for ~/entityset. As you have given http://localhost:10000/odata/MyObjects as the example, the action in question cannot be public SingleResult<T> GetEntity([FromODataUri] int key) as this only corresponds to a query like this http://localhost:10000/odata/MyObjects(42).
Our code for a generic controller looks like this:
public abstract class OdataControllerBase<T> : ODataController
where T : class, IIdentifiable, new()
{
protected OdataControllerBase(/* ... */)
: base()
{
// ...
}
public virtual IHttpActionResult GetEntity([FromODataUri] long key, ODataQueryOptions<T> queryOptions)
{
// ...
return Ok(default(T));
}
public virtual async Task<IHttpActionResult> GetEntitySet(ODataQueryOptions<T> queryOptions)
{
// ...
return Ok<IEnumerable<T>>(default(List<T>));
}
public virtual IHttpActionResult Put([FromODataUri] long key, T modifiedEntity)
{
// ...
return Updated(default(T));
}
public virtual IHttpActionResult Post(T entityToBeCreated)
{
// ...
return Created(default(T));
}
[AcceptVerbs(HTTP_METHOD_PATCH, HTTP_METHOD_MERGE)]
public virtual IHttpActionResult Patch([FromODataUri] long key, Delta<T> delta)
{
// ...
return Updated(default(T));
}
public virtual IHttpActionResult Delete([FromODataUri] long key)
{
// ...
return Updated(default(T));
}
}
The code for a specific controller then is as short as this:
public partial class KeyNameValuesController : OdataControllerBase<T>
{
public KeyNameValuesController(/* ... */)
: base()
{
// there is nothing to be done here
}
}
However we found out that both Get methods (for single result and enumerable result) actually have to start with Get. First we tried List instead of GetEntitySet and this did not work, as the framework then expects a POST for the List action).
You can actually verify and diagnose the resolving process by supplying a custom IHttpActionSelector as described in Routing and Action Selection in ASP.NET Web API (ahving a look at ASP.NET WEB API 2: HTTP Message Lifecycle might also be worth it).
So actually it is possible to use GetEntity as your method name as you originally tried in your example and there is no need to rename it to simple Get. In addition, there is no need for any modification in your ODATA configuration.
To determine which action to invoke, the framework uses a routing table. The Visual Studio project template for Web API creates a default route:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Routing by Action Name
With the default routing template, Web API uses the HTTP method to select the action. However, you can also create a route where the action name is included in the URI:
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I configured config as follows:
config.Routes.MapHttpRoute(
name: "GetMessage",
routeTemplate: "api/{controller}/{action}/{quoteName}",
defaults: new { quoteName = RouterParameters.Optional }
);
Access your URI like this:
http://localhost:42201/api/Extract/GetMessage/Q3
OR
http://localhost:42201/api/Extract/GetMessage/?quotename=Q3