Not a valid Odata path template - c#

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.

Related

How to form a URL to access the webapi controller?

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

Grapevine Rest Server Route Pathinfo with dynamic values

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();

WebApi OData inlinecount not working

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.

How to route Optional URI parameters to API Controller functions

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

ASP.NET MVC4 Web API MediaTypeFormatter Converter to convert XElement to JSON

I am building an API in ASP.NET MVC4 Web API, and one of my actions returns XML (currently in the form of an XElement). I have no control of the data, I am just passing it on. There is no standard object that I can de-serialize it to.
public Task<XElement> Get( string queryName, string query )...
What I want to do is use a MediaTypeFormatter to convert that to JSON if it is requested as such. I have started writing the MediaTypeFormatter and hooked it up, but when I call the "Get" on the controller, it calls
protected override bool CanWriteType( Type type )
{
return true;
}
in the MediaTypeFormatter, but never gets as far as the OnWriteToStreamAsync method. The result is just the XML as a string, e.g.
"<testXmlHere\/>"
Does anyone have any idea how to fix this up?
Thanks
Your custom formatter is probably inserted in the formatters list after the JsonMediaTypeFormatter in the formatters collection. That formatter can write XElement, and it does so by writing the XML representation as a JSON string (whether this is a good or bad idea is another discussion). When adding your formatter to the collection, use the Insert instead of the Add method:
GlobalConfiguration.Configuration.Formatters.Insert(
0, new MyCustomMediaTypeFormatter());
Here's a wild suggestion... First create an HttpResonponse message and set the content to whatever data you're retrieving. Try creating a custom action filter (System.Web.HttpFilters.ActionFilterAttribute) and implement the OnActionExecuted method.
In your method, get the respective HttpRequest and HttpResponse objects from the HttpActionExecutedContext. You can retrieve the Accept header from the HttpRequest and your data from the HttpResponse. Format your data based on the Request's Accept header if necessary and reassign it to the Response content.
You get where I'm coming from??
public HttpResponseMessage<SensorUpdate> Post(int id)
{
SensorUpdate su = new SensorUpdate();
su.Id = 12345;
su.Username = "SensorUpdateUsername";
su.Text = "SensorUpdateText";
su.Published = DateTime.Now;
HttpResponseMessage<SensorUpdate> response = new HttpResponseMessage<SensorUpdate>(su,
new MediaTypeHeaderValue("application/json"));
return response;
}
MVC4 Quick Tip #3–Removing the XML Formatter from ASP.Net Web API
in Global.asax:
add the line:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
like so:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
BundleTable.Bundles.RegisterTemplateBundles();
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
}

Categories