I have a OData web api using ADO.NET Framework in which the controller action is somehow not being reached.
The API correctly receives the HTTP request and parses it to go to the correct action but the action is not reached.
And in return the chrome browser shows the authentication window.
I have been debugging so long but cannot figure out how to solve this.
The controller is (stripped version):
public class DataController : ODataController
{
private readonly DataModel DataAccessModel = new DataModel();
public DataController()
{
.......
}
[HttpGet, EnableQuery]
public IQueryable<Record> GetRecord(ODataQueryOptions<Record> options)
{
try
{
IQueryable<ActivationRequestLog> result;
try
{
result = DataAccessModel.Recordss;
}
catch (Exception ex)
{
......
}
}
}
}
Can you show how the controller has been registered in the WebApiConfig class?
If you're using the ODataConventionModelBuilder, then you have to follow certain naming conventions for controllers of entity sets.
e.g. If I register an Airlines entity set of type Airline
builder.EntitySet<Airline>("Airlines");
....then by default/convention I need to implement
public class AirlinesController : ODataController<Airline>
{
[EnableQuery]
public IQueryable<Airline> Get()
{
DB db = Request.GetContext();
return db.Airlines();
}
}
Related
I'm starting learning asp.net core, and I have problems with understanding some basic patterns. Can I use ApiController inside PageController?
For example
//Home controller
public async Task<IActionResult> Index()
{
var artists = await _artistController.GetAll();
var t = artists.GetValue();
var indexModel = new Models.Pages.IndexModel
{
Artists = artists.GetValue() //Extension method
};
return View(indexModel);
}
//ArtistRestController
[HttpGet]
public async Task<ActionResult<IEnumerable<Artist>>> GetAll()
{
try
{
return Ok(await _repository.GetAll());
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return BadRequest(ex.Message);
}
}
It works, but is is ok by design? Maybe I should directly call _repository.GetAll() and do not use other controller?
First, you should avoid using one Controller in another Controller. For Controller, it is used to handle http request from client. It is unreasonable to use it as a class even through it is a class.
Second, for combining ApiController and PageController will make your application unstatable and too coupling. If you change anything later in ArtistRestController.GetAll will make HomeController broken. And, for your current code, if there are some exception in PageController, your ApiController will be down.
You should inject repository to query the data.
I am using a Repository pattern with Entity Framework, When i run my code instead of Json Data I am getting -> System.Collections.Generic.List`1[MovieInfo.Core.Models.Models.User]
Repository
public interface IRepository<T> where T : class
{
IEnumerable<T> Get();
T GetDetails(Guid Id);
void Insert(T data);
void Delete(T data);
void Update(T data);
void Save();
}
public GenericRepository()
{
context = new Entities();
dbEntity = context.Set<T>();
}
Services
public class TestService : ITestService
{
public TestService(
IRepository<User> userRepository
)
{
_userRepository = userRepository;
}
private readonly IRepository<User> _userRepository;
public IEnumerable<User> Get()
{
var result = _userRepository.Get();
return result;
}
}
Controller
public class HomeController : Controller
{
public HomeController(ITestService testService)
{
_testService = testService;
}
private readonly ITestService _testService;
public IEnumerable<User> Index()
{
var result = _testService.Get();
return result;
}
}
I found some solution on here (StackOverflow) that I need to add WebApiConfig, I added as below but it also not work. I am getting the output
System.Collections.Generic.List`1[MovieInfo.Core.Models.Models.User]
public static class WebAppConfig
{
public static void Register(HttpConfiguration config)
{
config.Formatters.Remove(new XmlMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data"));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
}
}
Here is my Git Repository Link
https://github.com/Harshk16/MovieInfo
Please help me out how to get output in Json Format
If you want to return JSON response form your MVC controller action method, you may use the Json method.
The Json method is defined inside System.Web.Mvc.Controller, from which you are inherting your HomeController. So you have access to this method.
The Json method returns JsonResult typw. So make sure your method's return type is either JsonResult or ActionResult. (The JsonResult class inhertis from ActionResult)
public JsonResult Index()
{
var result = _testService.Get();
return Json(result,JsonRequestBehavior.AllowGet);
}
Or
public ActionResult Index()
{
var result = _testService.Get();
return Json(result,JsonRequestBehavior.AllowGet);
}
Another option is to use a web api controller instead of MVC controller. Register your web api routes before your MVC action method routing in your global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebAppConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
// Your other existing code
}
and create a webapi controller which inherits from System.Web.Http.ApiController. Here I removed the Dependency Injection part for simplicity. You can wire up DI in your web api controllers by following the AutoFac web api integration tutorial
public class UsersController : ApiController
{
public IEnumerable<User> Get()
{
var result = new List<User>
{
new User { Id = Guid.NewGuid() , DisplayName = "Shyju"},
new User { Id = Guid.NewGuid(), DisplayName = "Scott"}
};
// Hard coded list of users.
// Once you fix your DI setup, you can use your service to get data.
return result;
}
}
By default, web api uses content negotioation. It reads the "Accept" request header value and based on that execute the corresponding formatter. With your current web api routing template, you can access this endpoint with the below request URL
http://yourSiteBaseUrl/api/users/get
If you do not want the get in your API Urls, simply fix your route template when registering the web api routing
routeTemplate: "api/{controller}/{id}"
To use the web api, which you define in your WebApiConfig, your controllers need to inherit from the Web Api base controller ApiController instead of the MVC base controller controller.
When inheriting from the ApiController, the default Formatter will take care of turning your controller methods output to your desire result. In your case, as you removed the Xml-formatter, the default will be Json.
public class HomeController : ApiController
{
public HomeController(ITestService testService)
{
_testService = testService;
}
private readonly ITestService _testService;
public IEnumerable<User> Index()
{
var result = _testService.Get();
return result;
}
}
This is my api configuration class:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
}
}
This is my api controller class:
public class RoleController : ApiController
{
// Some action that works fine...
// Another action that works fine...
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
I am calling my actions using POST and they are working fine.
But, when I try to call the Delete action using POST I get the following error:
405 Method Not Allowed
The requested resource does not support http method 'POST'.
Clearly, this is because ApiController enforces REST convention
which expects DELETE verb for Delete action.
Now, how do I disable this REST convention constraints
and write my actions in a classic manner?
You can use the HttpPostAttribute to enforce the Action to accept only POST:
public class RoleController : ApiController
{
[HttpPost]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
You may want to keep the REST conventions while allowing certain clients (like HTML forms) to properly use you actions.
So, you can use a combination of HttpPostAttribute and HttpDeleteAttribute or AcceptVerbsAttribute (which allows multiple verbs) to allow multiple verbs:
public class RoleController : ApiController
{
[HttpPost, HttpDelete]
// OR
[AcceptVerbs("DELETE", "POST")
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
If you don't want magic verbs and magic action names you can use route attributes.
Delete config.Routes.MapHttpRoute and set:
config.MapHttpAttributeRoutes();
Now you have to set the routes manually:
[RoutePrefix("~/Role")]
public class RoleController : ApiController
{
[HttpPost]
[Route("~/Delete")]
public Result Delete([FromBody]int RoleID)
{
return RoleBL.Delete(RoleID);
}
}
In your case I'd stop using any kind of REST conventions.
Instead of having a Delete method on the Role controller you can have a DeleteRole method and allow POST on it. This way nothing will interfere with what you want to do. Nothing forces you to build a REST api if that's not what you need.
There are several things you could do to still build a nice api.
For example, you could return an IHttpActionResult
your method could look like this:
public class RoleController : ApiController
{
[HttpPost]
public IHttpActionResult DeleteRole([FromBody]int RoleID)
{
if ( RoleID <= 0 )
{
return BadRequest();
}
var deleteResult = RoleBL.Delete(RoleID);
return Ok(deleteResult);
}
}
You still return the same object but it's wrapped inside an object with a proper http code so your code which deals with the result, won't change much.
I am trying to set up a C# Web API that uses OData 4. I created an OdataController and believe I routed it correctly in the WebApiConfig. I put a debugger on each function in the controller to see if the request enters the method(s). When I hit GET http://localhost:10013/odata/Call the debugger in the first method hits but once I let the debugger move on the request fails. In Chrome I see the request returns with '406 Not Acceptable' but there is nothing in the preview or response tabs. What am I doing wrong? I can see that the request enters the controller correctly but why does it not return the string "call" as well as send a 406?
Secondly, if I send the request http://localhost:10013/odata/Call(0) the first method in the controller gets hit not the second (desired) or even the third. What am I doing wrong here if I want it to hit the second or third method?
I've included the controller and the WebApiConfig that I am using.
namespace JamesMadison
{
public static class Call
{
}
}
using System.Web.Http.OData;
namespace JamesMadison.Controllers
{
public class CallController : ODataController
{
public string GetCall()
{
return "call";
}
public string GetCall([FromODataUri] int id)
{
return "call";
}
public string GetCall([FromODataUri] string key)
{
return "call";
}
}
}
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
namespace JamesMadison
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("odata", "odata", model: GetModel());
}
public static Microsoft.OData.Edm.IEdmModel GetModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Call>("Call");
return builder.GetEdmModel();
}
}
}
In the controller, I had using System.Web.Http.OData; and replaced it with using System.Web.OData;
I have spent a about 2 days setting up my Odata Web API project that was before a simple Asp.net mvc4 project.
But still I am not successful in operating even CRUD Operations.
It says this:
<m:message xml:lang="en-US">
No HTTP resource was found that matches the request URI 'http://localhost:53208/odata/Product'.
</m:message>
<m:innererror>
<m:message>
No action was found on the controller 'Product' that matches the request.
</m:message>
<m:type/>
<m:stacktrace/>
</m:innererror>
It means its reaching the Controller but not finding actions or there is some problem in my routing settings.
I have following in my WebApiConfig:
config.Routes.MapODataRoute("OData","odata",GetEdmModel());
My getEdmModel method:
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Product");
builder.EntitySet<OfferedProduct>("OfferedProduct");
IEdmModel model = builder.GetEdmModel();
return model;
}
My controller is like this:
public class ProductController : EntitySetController<Product,int>
{
private OfferAssistantDbContext db = new OfferAssistantDbContext();
List<Product> Products = new OfferAssistantDbContext().Products.ToList();
// GET api/Product
[Queryable(PageSize = 10)]
public override IQueryable<Product> Get()
{
return Products.AsQueryable();
}
// GET api/Product/5
public Product GetProduct([FromODataUri] int id)
{
return Products[id];
}
/// and so on... but for this time lets work only on GET operation
Now when I write this in my browser:
http://localhost:53208/odata/Product
it says the error I showed above..
Please guide me where is the problem?
I believe that your controller needs to inherit from ODataController:
public class ProductController : ODataController