I'm trying to make OData return the number of entities from the database when I pass it $inlinecount=allpages in the uri. I have read in Stack Overflow that I should return IQueryable instead of IHttpActionResult, however that didn't solve the problem. I also tried to follow the tutorials on the asp.net website, but that also didn't give any results.
[EnableQuery(
PageSize = BuildingConstants.BuildingsPerPage,
MaxTop = BuildingConstants.MaxBuildingsPerPage,
AllowedArithmeticOperators = AllowedArithmeticOperators.None,
AllowedLogicalOperators = AllowedLogicalOperators.None,
AllowedFunctions = AllowedFunctions.SubstringOf,
AllowedQueryOptions = AllowedQueryOptions.Filter | AllowedQueryOptions.OrderBy | AllowedQueryOptions.Top | AllowedQueryOptions.Skip | AllowedQueryOptions.InlineCount)]
[ResponseType(typeof(IEnumerable<ListedBuildingResponseModel>))]
public IQueryable<ListedBuildingResponseModel> Get()
{
var buildings = this.buildings
.GetBuildings()
.ProjectTo<ListedBuildingResponseModel>();
return buildings;
}
This is everything I have written regarding OData. The controller inherits ApiController, not ODataController. In the Register method I haven't added any OData routes or model builders. $top, $skip, $orderby and everything else is working just fine, but $inlinecount=allpages. Any suggestions on how to solve the problem?
I managed to make count=true work by adding the following code:
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("odata", "odata", model: GetEdmModel());
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<ListedBuildingResponseModel>("Buildings");
builder.EnableLowerCamelCase();
var edmModel = builder.GetEdmModel();
return edmModel;
}
Now everything is working fine except I can't use my other actions in the class, because I get 406 error status code and I don't know why. I don't want my other actions to support OData, so I am thinking for possible solutions. Doing a separate controller only for the OData action is an option, but I have also households, expenses and debts controllers whose Get methods I want to support OData. This means, I need to create 4 more controllers with only 1 Get method. Is there a way I can create a single SearchController with methods GetBuildings, GetHouseholds, GetExpenses, and GetDebts that return different types? Because, as far as I understand this line of code
uilder.EntitySet<ListedBuildingResponseModel>("Buildings");
somehow "binds" the BuildingsController to ListedBuildingResponseModel.
Related
I would like to know how we will create a Route URL to access the above function. Error message comes stating that I cannot access the controller
[HttpGet]
[Route("api/TeacherData/ListTeachers/{SearchKey?}&{order?}")]
public List<Teacher> ListTeachers(string SearchKey = null, string order = null)
{
}
I know it's your API and your design, but following the REST API patterns we should stick to simple API URLs, something like api/teachers could be easier to understand by the consumers if they know that the endpoint uses the GET method.
About your actual question, you could change the code to use [FromQuery] to expect parameters that should come from the query string:
[HttpGet]
[Route("api/teachers")]
public List<Teacher> ListTeachers([FromQuery] string searchKey = null, [FromQuery] string order = null)
{
}
Then, from the consumer side, you could trigger this endpoint using the following URL:
GET http://myapi.com/api/teachers?searchKey=keyValue&order=orderValue
If you keep your URL structure it should something like this:
GET http://myapi.com/api/TeacherData/ListTeachers?searchKey=keyValue&order=orderValue
I can see a lot of ways to do it online but most of them are messy, for me I was using these two ways
Using scopes, I did one for mobile and another one for the website
var webScope = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<WebAuthorize>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
var mobileScope = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<MobileAuthorize>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
And it worked because I had two different ways in authorizing the api calls, as you can see I had a Mobile Authorize and a Web Authorize so my api calls would look something like this:
[HttpGet]
[Route("something")]
[WebAuthorize(Code = PermissionCode, Type =PermissionType)]
public async Task<Dto> Getsomething()
{
return await unitOfWork.GetService<ISomething>().GetSomething();
}
Issues I face when using scopes is that all calls that have web authorize will share the same headers so for the special calls I used another way to add custom headers.
Using apiDescription.RelativePath, and I will check it if the relative path is equal to the api call I want to add that custom header, example:
[HttpPost]
[Route("rename")]
[InHouseAuthorize(Code = PermissionCode, Type =PermissionType)]
public async Task<HttpResponseMessage> RenameDevice()
{
HttpRequestMessage request = Request ?? new HttpRequestMessage();
String deviceName = request.Headers.GetValues("deviceName").FirstOrDefault();
String deviceGuid = request.Headers.GetValues("deviceGuid").FirstOrDefault();
await unitOfWork.GetService<IDeviceService>().RenameDevice(deviceGuid, deviceName);
await unitOfWork.Commit();
return new HttpResponseMessage(HttpStatusCode.OK);
}
And then I would add to the AddRequiredHeaderParameter.cs the following
if (apiDescription.RelativePath.Contains("device/rename"))
{
operation.parameters.Add(new Parameter
{
name = "deviceGuid",
#in = "header",
description = "Add the Device Guid",
type = "string",
required = false
});
operation.parameters.Add(new Parameter
{
name = "DeviceName",
#in = "header",
description = "Add the Device Name",
type = "string",
required = false
});
}
At first this was convenient and good enough fix but things are turning ugly as I'm adding a lot of calls that need custom headers and if the same URL have a Get and Post then it will even get uglier.
I am searching for the best way to deal with this issue.
It's possible to use attribute [FromHeader] for web methods parameters (or properties in a Model class) which should be sent in custom headers. Something like this:
[HttpGet]
public ActionResult Products([FromHeader(Name = "User-Identity")]string userIdentity)
For me it looks like the easiest solution. At least it works fine for ASP.NET Core 2.1 and Swashbuckle.AspNetCore 2.5.0.
I'm pretty new to C# and need to realise a REST Service so i stumbled over Grapevine.
I need to have parts of the URL of the service handed over on service start via config file but I don't manage to hand over the value "clientId" of the config file to the Route's Pathinfo because it's not constant.
Here's the part of the code:
[RestResource(BasePath = "/RestService/")]
public class Rest_Resource
{
public string clientId = ConfigurationManager.AppSettings["ClientId"];
[RestRoute(PathInfo = clientId + "/info")]//<-how do I fill Pathinfo with dynamic values?
public IHttpContext GetVersion(IHttpContext context)
{....}
}
I'm using grapevine v4.1.1 as nuget package in visual studio.
While it is possible to change attribute values at runtime, or even use dynamic attributes, an easier solution in this case might be to not use the auto discovery feature exclusively, but use a hybrid approach to route registration.
Consider the following class that contains two rest routes, but only one of them is decorated with the attribute:
[RestResource(BasePath = "/RestService/")]
public class MyRestResources
{
public IHttpContext ManuallyRegisterMe(IHttpContext context)
{
return context;
}
[RestRoute(PathInfo = "/autodiscover")]
public IHttpContext AutoDiscoverMe(IHttpContext context)
{
return context;
}
}
Since you want to register the first route using a value that is not known until runtime, we can manually register that route:
// Get the runtime value
var clientId = "someValue";
// Get the method info
var mi = typeof(MyRestResources).GetMethod("ManuallyRegisterMe");
// Create the route
var route = new Route(mi, $"/RestService/{clientId}");
// Register the route
server.Router.Register(route);
This takes care of manually registering our route that needs a runtime value, but we still want the other routes to be automatically discovered. Since the router will only autodiscover if the routing table is empty when the server starts, we'll have to tell the router when to scan the assemblies. You can do this either before or after you manually register the route:
server.Router.ScanAssemblies();
I am trying to set up an Asp.Net forms site with an API.
I have succeeded in adding in selective authentication, so that pages starting "\api\" do not get redirected, but instead challenge for basic authentication.
I am now trying to use MS Web Api 2 to do the API routing.
The idea is to be as RESTful as possible. I have a resource, a TradableItem, and initially I would want to allow API users to use HTTP GET in one of two ways.
If the API user passes no item key, the user receives a list of possible item keys
["ABC","DEF"...]
If the API user passes an item key as part of the URI, eg "/api/tradables/abc", a representation of the TradableItem is returned for the one with the key=ABC. (To my understanding, this is standard REST behaviour).
In Global.ASAX's Application_Start() function I have a route map like so...
RouteTable.Routes.MapHttpRoute(
name: "TradableItemVerbs",
routeTemplate: "api/tradables/{item}",
defaults: new { item = System.Web.Http.RouteParameter.Optional, controller = "Tradable" });
The TradableController.cs file looks like this...
public class TradableController : ApiController
{
private static CustomLog logger = new CustomLog("TradableController");
// GET api/<controller>
public IEnumerable<string> GetKeys()
{
var prefix = "GetKeys() - ";
string msg = "";
msg = "Function called, returning list of tradable pkeys...";
logger.Debug(prefix + msg);
// Get a list of tradable items
return TradableManager.GetTradablePkeys();
}
// GET api/<controller>/<pkey>
public string GetTradable(string pkey)
{
string msg = string.Format("Would get Tradable data for key: >{0}<", pkey);
return msg;
}
}
The problem is that only the GetKeys() function fires, whether I call GET to "/api/tradables" or "/api/tradables/abc".
For reference, using VS2015 Community, IIS 7.5, targeting .Net 4.6.1. I used Rick Strahl's blog as a guide on this (among other sources).
http://weblog.west-wind.com/posts/2012/Aug/21/An-Introduction-to-ASPNET-Web-API#HTTPVerbRouting
please, change the name of your param to item (because, this is the name define in the routes):
public string GetTradable(string item)
{
....
}
or when you call the method be explicit with the parameter name: /api/tradables?pkey=abc
I am trying to create Odata method that satisfy url like
domain:port/products/100/RedirectUrl()
[ODataRoute("{id}/RedirectUrl()")]
public IHttpActionResult RedirectUrl(int id)
{
return Redirect("myUrl" + id);
}
but i got exception like
The path template '{id}/RedirectUrl()' on the action 'RedirectUrl' in
controller 'Products' is not a valid OData path template
My Webapi config contains
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntityType<Product>().Function("RedirectUrl").Returns<IHttpActionResult>();
Any way to achieve this?
I think you want to enable Key as segment, track this https://github.com/OData/WebApi/pulls, then you can set the UrlConventions.ODataUrlConventions to enable it. Or you have to override the DefaultODataPathHandler to achieve this, may need to copy some private method like Parse.