OData V3 action in ASP.NET web api doesn't get trigger - odata

I am using OData V3 endpoints using asp.net with webapi 2.2. I have successfully implemented CRUD operation with it. Now, I would like to add some custom actions along with CRUD operations. I have followed the article ( http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v3/odata-actions ) to create the action with OData V3 with web api.
When I type
URI:
http://localhost:55351/odata/Courses(1101)/AlterCredits
it throws following error:
<m:error><m:code/><m:message xml:lang="en-US">No HTTP resource was found that matches the request URI 'http://localhost:55351/odata/Courses(1101)/AlterCredits'.</m:message><m:innererror><m:message>No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.</m:message><m:type/><m:stacktrace/></m:innererror></m:error>
I have also tried adding a custom route convetion for non-bindable actions. (https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v3/ODataActionsSample/ODataActionsSample/App_Start/WebApiConfig.cs ) Not sure if I have to use this.
Here is my code:
WebApiConfig.cs :---
namespace ODataV3Service
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
IList<IODataRoutingConvention> conventions = ODataRoutingConventions.CreateDefault(); //Do I need this?
//conventions.Insert(0, new NonBindableActionRoutingConvention("NonBindableActions"));
// Web API routes
config.Routes.MapODataRoute("ODataRoute","odata", GetModel(), new DefaultODataPathHandler(), conventions);
}
private static IEdmModel GetModel()
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.ContainerName = "CollegeContainer";
modelBuilder.EntitySet<Course>("Courses");
modelBuilder.EntitySet<Department>("Departments");
//URI: ~/odata/Course/AlterCredits
ActionConfiguration atlerCredits = modelBuilder.Entity<Course>().Collection.Action("AlterCredits");
atlerCredits.Parameter<int>("Credit");
atlerCredits.Returns<int>();
return modelBuilder.GetEdmModel();
}
}
}
CoursesController.cs:----
[HttpPost]
//[ODataRoute("AlterCredits(key={key},credit={credit})")]
public async Task<IHttpActionResult> AlterCredits([FromODataUri] int key, ODataActionParameters parameters)
{
if (!ModelState.IsValid)
return BadRequest();
Course course = await db.Courses.FindAsync(key);
if (course == null)
{
return NotFound();
}
int credits = course.Credits + 3;
return Ok(credits);
}
Global.asax:----
namespace ODataV3Service
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
I have done research online and found this link. Web API and OData- Pass Multiple Parameters But this one is for OData V4. I am using OData V3 and Action.
Thanks,

First, your action AlterCredits is defined as:
ActionConfiguration atlerCredits = modelBuilder.Entity<Course>().Collection.Action("AlterCredits");
It means AlterCredits bind to the collection of Course.
Second, your method AlterCredits in your controller is defined as:
public async Task<IHttpActionResult> AlterCredits([FromODataUri] int key, ODataActionParameters parameters)
{
...
}
It means AlterCredits listen to the call on the entity of Course.
Therefore, you got the No HTTP resource was found error message.
Based on your sample code, I create a sample method for your reference:
[HttpPost]
public async Task<IHttpActionResult> AlterCredits(ODataActionParameters parameters)
{
if (!ModelState.IsValid)
return BadRequest();
object value;
if (parameters.TryGetValue("Credit", out value))
{
int credits = (int)value;
credits = credits + 3;
return Ok(credits);
}
return NotFound();
}
Then, if you send a request:
POST ~/odata/Courses/AlterCredits
Content-Type: application/json;odata=verbose
Content: {"Credit":9}
You can get a response like this:
{
"d":{
"AlterCredits":12
}
}
For your questions:
IList conventions = ODataRoutingConventions.CreateDefault(); //Do I need this?
Answer: No, you needn't. Just using the default as:
config.Routes.MapODataServiceRoute("ODataRoute", "odata", GetModel());
//[ODataRoute("AlterCredits(key={key},credit={credit})")]
Answer: No, you needn't the ODataRouteAttribute for bind action.
Thanks.

Related

How to pass data from custom authorize attribute to web api action method?

I am creating a MVC Web API application with forms authentication for the Web API Controllers as well as the regular MVC controllers. In the form authentication cookie I am storing user information which I need to read and pass it to the Web API action methods.
I am trying to do this by creating a custom Authorization attribute and adding this value to ActionArguments. However I don't know how to access this data in the Web API action. Below is the code block
public class MyAuthorization : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var isAuthorised = base.IsAuthorized(actionContext);
if (isAuthorised)
{
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var identity = new GenericIdentity(ticket.Name);
actionContext.ActionArguments["UserInfo"] = ticket.UserData;
}
}
}
And here is the controller code
[RoutePrefix("api/test")]
[MyAuthorization]
public class TestWebAPIController : ApiController
{
[HttpGet]
[Route("")]
public IHttpActionResult Get() {
//How to get it here
return Ok();
}
}
It was really stupid of me. As the name suggests it is the ActionArgument (actionContext.ActionArguments). So this can be easily accessed in the Web API control by means of an ActionContext.ActionArguments or directly by passing an action argument.
Here is code sample by ActionContext.ActionArguments
public IHttpActionResult Post() {
var userInfo = ActionContext.ActionArguments["UserInfo"];
return Ok();
}
And here is by means of action argument
public IHttpActionResult Post(UserInfo userinfo) {
//Code here
return Ok();
}

Attribute Routing related issue using Asp.Net MVC 5 and Web API 2

I am trying to change my existing web api controller from convention based routing to attribute routing, so I changed my existing code to the following:
[Route("api/Visitors")]
public IQueryable<Visitor> GetVisitors()
{
return db.Visitors;
}
and GET method works great, but when I am applying same attribute to POST method like:
[Route("api/Visitors")]
[ResponseType(typeof(Visitor))]
public async Task<IHttpActionResult> PostVisitor(Visitor visitor)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Visitors.Add(visitor);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = visitor.Id }, visitor);
}
It is throwing following exception:
An exception of type 'System.Net.Http.HttpRequestException' occurred in System.Net.Http.dll but was not handled in user code
Additional information: Response status code does not indicate success: 500 (Internal Server Error).
at this line resp.EnsureSuccessStatusCode(); of the following method:
public bool SaveVisitor(Visitor visitor)
{
HttpResponseMessage resp = client.PostAsJsonAsync<Visitor>("/api/Visitors", visitor).Result;
resp.EnsureSuccessStatusCode();
if (resp.StatusCode == System.Net.HttpStatusCode.Created)
return true;
else
return false;
}
client is an instance of HttpClient at class level.
I am not able to get the reason behind this exception. What am I missing here? If I remove attribute routing data is saved/retrieved as expected. I am referring to the article on Attribute Routing
Please help.
Try adding the [HttpPost] attribute to your "Post" endpoint. As follows:
[Route("api/Visitors")]
[HttpPost]
[ResponseType(typeof(Visitor))]
public async Task<IHttpActionResult> PostVisitor(Visitor visitor)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Visitors.Add(visitor);
await db.SaveChangesAsync();
return CreatedAtRoute("DefaultApi", new { id = visitor.Id }, visitor);
}
Another possibility... it seems from your code that your controller class might be called "VisitorsController". Visitors being plural.
Maybe try to add an 's' to your post method?
public async Task<IHttpActionResult> PostVisitors(Visitor visitor)

odata routing not working as expected

This is my first OData application with asp.net MVC and i am not able to make it work. I need to return a single Summary object from the SummaryController, but facing an issue.
routing configuration -
public static IEdmModel CreateEdmModel()
{
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Summary>("Summary");
return modelBuilder.GetEdmModel();
}
public static void Register(HttpConfiguration config)
{
config.Routes.MapODataRoute("OData", "odata", CreateEdmModel());
config.EnableQuerySupport();
config.EnableSystemDiagnosticsTracing();
}
controller and action method -
public class SummaryController : ODataController
{
public Summary Get()
{
//....
return someObj;
}
}
The route that does not work -
/odata/Summary
Can anyone please help me understand how can i make the routing work?
In the model, you have setup Summary to be an EntitySet, this would return a collection of Summary objects. If you want that URL to always return one object then you need a Singleton.
The CreateEdmModel method should look like this:
public static IEdmModel CreateEdmModel()
{
ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.Singleton<Summary>("Summary");
return modelBuilder.GetEdmModel();
}

Getting System.InvalidOperationException exception with breeze and WebApi

I am very new to breeze. I have downloaded the template for AngularBreeze and trying to create a sample application but i am stuck on Breeze WebApi Controller.
BreezeController]
public class ProductsBreezeController : ApiController
{
private readonly ProductRepository context;
public ProductsBreezeController()
{
context = new ProductRepository();
}
[HttpGet]
public string Metadata()
{
return context.Metadata();
}
//// GET api/productsbreeze
public IQueryable<Product> GetAllProducts()
{
return context.TodoLists;
}
}
public class ProductRepository : EFContextProvider<SampleEntities>
{
public DbQuery<Product> TodoLists
{
get { return Context.Products; }
}
}
Exception Message
Multiple actions were found that match the request: System.String Metadata() on type AngularWebApi.ApiControllers.ProductsBreezeController System.Linq.IQueryable`1[AngularWebApi.DataAccess.Model.Product] GetAllProducts() on type AngularWebApi.ApiControllers.ProductsBreezeController
ExceptionType: "System.InvalidOperationException"
You need to set your breezewebapiconfig.cs up to accept an action parameter as we'll. currently you have a controller only probably.
Open appstart folder and BreezeWebApiConfig.cs and add it there (should see something like ) -
Breeze/{controller}/{action}/{id}
And you need to add the action part in there
Edit
In your question it clearly shows the route for that controller action is api/productsbreeze. If that is the route you are hitting then you need to adjust that route to accept an action as well. If it is the Breeze route you are trying to hit then add an HttpGet controller attribute on the action
//// GET api/productsbreeze
[HttpGet]
public IQueryable<Product> GetAllProducts()
{
return context.TodoLists;
}
You need to make sure that your BreezeWebApiConfig is also registered in the Global.asax, of course.
Requesting URL should be matched with Breeze Api Configuration.
Server Side Configuration
GlobalConfiguration.Configuration.Routes.MapHttpRoute("BreezeApi", "breeze/{controller}/{action}");
Client Side
var manager = new breeze.EntityManager("/breeze/ProductsBreeze");

can't figure out routing in asp.net mvc

I'm doing my first steps in asp.net mvc trying to develop web api.
I have the following routing function:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "cdApiDefault",
url: "api/{controller}/{action}/{token}/{mid}/{id}",
defaults: new {
token = UrlParameter.Optional,
mid = UrlParameter.Optional,
id = RouteParameter.Optional }
);
}
and the following controller:
namespace cdapi.Controllers
{
public class PostsController : ApiController
{
// GET api/posts
public IEnumerable<string> Get()
{
return new string[] { "GET_value1", "GET_value2" };
}
// GET api/posts/5
public string Get(int id)
{
return "value!!!!!!!!!!!!!!!!!";
}
// POST api/posts
public void Post([FromBody]string value)
{
}
// PUT api/posts/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/posts/5
public void Delete(int id)
{
}
public String GetTest(String token, String mid)
{
return token + " - " + mid;
}
}
}
the following call
hxxp://localhost:52628/api/posts/5
(in my browser) yields some result, i.e., the function GET is being called and return a value.
However, when I try
hxxp://localhost:52628/api/posts/GetTest/MyTestToken/myTestMid
comes back with 'the resource can not be found' error message.
I thought that the {Action} should contain the function to call and that the 'token' and 'mid' should contain the values I specify. What do I do wrong?
ApiControllers work differently than regular MVC Controllers. Here, method names (GET, POST, PUT, DELETE) represent HTTP VERBS, not url fragment. In your first call,
/api/posts/5
this invokes Get(int).
To do routing like you want, switch to standard MVC by inheriting from System.Web.Mvc.Controller instead of System.Web.Http.ApiController and modify your methods to return ActionResult

Resources