I'm developing an ASP.NET MVC website with Web Api 2 also, C# and .NET Framework 4.5.1.
I'm trying to do versioning but I'm doing something wrong.
I have created a new version of my controller in a new namespace:
namespace MyProject.Web.API.Controllers.v2
{
[AllowAnonymous]
[ActionLogFilter]
public class ExternalCodesController : ApiController
{
public ExternalCodesController()
{
}
[HttpGet]
[Route("api/v2/ExternalCodes")]
public HttpResponseMessage Get()
{
[ ... ]
}
[HttpGet]
[Route("api/v2/ExternalCodes")]
public HttpResponseMessage Get(
byte? codeLevel,
int batchId,
int? lineId,
int productId,
string startingCode,
int? quantity)
{
[ ... ]
}
}
}
But, when I do a GET using this URI: http://myHost:53827/api/v2/ExternalCodes?codeLevel=&batchId=5&lineId=&productId=7&startingCode=&quantity= I get a NotFound HTTP status code. But I have also tested this URI: http://myHost:53827/api/v2/ExternalCodes with the same status code.
What am I doing wrong?
My WebApiConfig.cs is:
namespace MyProject.Web.API
{
/// <summary>
/// Class to config Web API routes and filters.
/// </summary>
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Json configuration
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
// Remove formatting to make json smaller.
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Filters.
config.Filters.Add(new ExceptionFilter());
config.Filters.Add(new UnhandledExceptionFilter());
}
}
}
This has actually happened to me before. It was due to having two controllers with the same name inside the root 'Controllers' folder, regardless of sub-directories and namespaces (this behavior is different inside areas). Try renaming the controllers to something like:
ExternalCodesV1Controller
and
ExternalCodesV2Controller
You should be able to keep the namespaces and even attribute routes the same.
A nasty solution for your reference. The idea is to change the default controller selector to your custom one.
file 'ValuesController.cs':
namespace WebApplication4.Controllers
{
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
}
file 'ValuesV2Controller.cs':
namespace WebApplication4.Controllers.V2
{
public class ValuesController : ApiController
{
public IEnumerable<string> Get()
{
return new string[] { "value3", "value4" };
}
}
}
create a custom controller selector:
public class MyHttpControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
public MyHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
_config = configuration;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var routeData = request.GetRouteData();
var routeTemplate = routeData.Route.RouteTemplate;
if (routeTemplate.IndexOf("v2/values") != -1)
{
return new HttpControllerDescriptor(
_config, "Values",
typeof(WebApplication4.Controllers.V2.ValuesController));
} else if (routeTemplate.IndexOf("values") != -1)
{
return new HttpControllerDescriptor(
_config, "Values",
typeof(WebApplication4.Controllers.ValuesController));
}
return base.SelectController(request);
}
}
Register the custom controller selector in the file 'WebApiConfig.cs':
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "route1",
routeTemplate: "values"
);
config.Routes.MapHttpRoute(
name: "route2",
routeTemplate: "v2/values");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Services.Replace(
typeof(IHttpControllerSelector),
new MyHttpControllerSelector(config));
}
}
So when you use url like 'http://localhost:13839/values', you get value1 and value2. And value3 and value4 for url 'http://localhost:13839/v2/values'.
Related
As Atrribute routing does not work in sitecore 8.1 out of the box, I am following https://github.com/Krusen/Sitecore.WebApi
And got the uget package for Krusen.Sitecore.WebApi.Custom.
This is my ConfigureWebApi class
public class ConfigureWebApi
{
public void Process(PipelineArgs args)
{
GlobalConfiguration.Configure(config => config.Routes.MapHttpRoute(
name: "myApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
));
GlobalConfiguration.Configure(config => config.MapHttpAttributeRoutes());
GlobalConfiguration.Configure(ReplaceControllerSelector);
}
private static void ReplaceControllerSelector(HttpConfiguration config)
{
config.Services.Replace(typeof (IHttpControllerSelector),
new CustomHttpControllerSelector(config, new NamespaceQualifiedUniqueNameGenerator()));
}
}
And this is my controller
[RoutePrefix("windows")]
public class WmsController : ApiController
{
[HttpGet]
[Route("hi")]
public IHttpActionResult Hello()
{
return Ok("Welcome to my Api.");
}
}
When I call this:
http://my.api.local/api/wms/hello
works.
But when I call
http://my.api.local/api/windows/hi
does not work. It says 404.
Am I missing something !!
The second call is not working because Attribute routing must be configured before Convention-based routes to avoid route conflicts.
public void Process(PipelineArgs args) {
GlobalConfiguration.Configure(config => {
// Map Attribute Routes
config.MapHttpAttributeRoutes();
// Map Convention-based Routes
config.Routes.MapHttpRoute(
name: "myApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Replace IHttpControllerSelector with our custom implementation
ReplaceControllerSelector(config);
});
}
Which I also believe is how it was shown in the documentation in the linked repo
Secondly based on the RoutePrefix("window") and Route("hi") in the ApiController the mapped attribute route would be mapped as
http://my.api.local/windows/hi
To get http://my.api.local/api/windows/hi to map to the desired action you would need to update the route prefix as already explained in one of the other answers.
You need to add "api/" into your controller attribute routing
[RoutePrefix("api/windows")]
public class WmsController : ApiController
{
[HttpGet]
[Route("hi")]
public IHttpActionResult Hello()
{
return Ok("Welcome to my Api.");
}
}
I am trying to add one more controller to our existing Web API. The controllers are like
public class TDataController : ApiController
{
[HttpGet]
public HttpResponseMessage Getdetails(string ROOM, DateTime DOB_GT, DateTime DOB_LT, string STATUS_TYPE)
{
// Code for the controller
}
}
and this is the controller I am trying to add in the same Application
public class TDataSubDateController : ApiController
{
[HttpGet]
public HttpResponseMessage Getdetails(string ROOM, string STATUS_TYPE, DateTime? SUBDATE_GT = null, DateTime? SUBDATE_LT = null)
{
//Code for the controller
}
}
When I am trying to call the second controller like
http://localhost:33823/api/TDataSubDate?ROOM=xxx&STATUS_TYPE=xxx&SUBDATE_GT=xxxx&SUBDATE_LT=xxxx
But it throws the HTTP 404 Page Not Founderror. Do I have to create a different route in the WebConfig.cs. The RouteConfig.cs currently looks like
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 }
);
}
}
You can use attribute routing if you are using web api 2. For more details please visit https://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
Here is an example with single controller and multiple actions
[RoutePrefix("api/tdata")]
public class TDataController : ApiController
{
[HttpGet]
[Route("{ROOM}/preview")]
public IHttpActionResult Getdetails(string ROOM, [FromUri]DateTime DOB_GT, [FromUri]DateTime DOB_LT, [FromUri]string STATUS_TYPE)
{
return Ok(string.Format("Room {0} Preview", ROOM));
}
[HttpGet]
[Route("{ROOM}/details")]
public IHttpActionResult Getdetails(string ROOM, [FromUri]string STATUS_TYPE, [FromUri]DateTime? SUBDATE_GT = null, [FromUri]DateTime? SUBDATE_LT = null)
{
return Ok(string.Format("Room {0} Details", ROOM));
}
}
OR into a separate controller
[RoutePrefix("api/tdatasubdate")]
public class TDataSubDateController : ApiController
{
[HttpGet]
public IHttpActionResult Getdetails([FromUri]string ROOM, [FromUri]string STATUS_TYPE, [FromUri]DateTime? SUBDATE_GT = null, [FromUri]DateTime? SUBDATE_LT = null)
{
return Ok(string.Format("Room {0} Details", ROOM));
}
}
And here is how webapiconfig.cs looks like
public static class WebApiConfig
{
/// <summary>
/// configure global routes
/// </summary>
/// <param name="config"></param>
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
I have my MVC 4 Project API Routing configured as follow:
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var company = System.Configuration.ConfigurationManager.AppSettings["DbCompany"];
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "MyApp/"+ company +"/{id}",
defaults: new { controller = "main" , id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
}
}
and MainController.cs contains the following methods:
public JToken Get(string id)
{
...
}
public JToken Get()
{
...
}
[HttpPost]
public JToken DoQuery([FromBody] String query)
{
...
}
public void Post([FromBody] JObject JsonObject)
{
...
}
What I would like to achieve is for any route that is not :
route: /MyApp/MyComp/DoQuery
method: POST
ContextType: text/plain
Returns: JToken
To use normal Get/Post of the main controller
Otherwise use DoQuery in the main controller.
Seems like all you are missing is the special case route to map to DoQuery.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var company = System.Configuration.ConfigurationManager.AppSettings["DbCompany"];
config.Routes.MapHttpRoute(
name: "DoQuery",
routeTemplate: "MyApp/"+ company +"/DoQuery",
defaults: new { controller = "main", action = "DoQuery" }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "MyApp/"+ company +"/{id}",
defaults: new { controller = "main" , id = RouteParameter.Optional }
);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
}
}
I am reading about Attribute Routing in Web API 2 from here
The article says,
Here are some other patterns that attribute routing makes easy.
API versioning
In this example, “/api/v1/products” would be routed to a different controller than “/api/v2/products”.
/api/v1/products
/api/v2/products
How come?
EDIT: I would do this in Normal Routing:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v2/products",
defaults: new { controller = V2_Products }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/products",
defaults: new { controller = V1_Products }
);
}
}
Could anyone explain me how to do this in Attribute Routing way ? And how come using Attribute routing is easier and convenient for this example (according to the article) ?
There are many ways to implement versionning with attribute routing ; A really basic way is to use RoutePrefix attribute for each version of your ApiController
[RoutePrefix("v1")]
public class V1_ProductsController : ApiController
{
[Route("products")]
public IEnumerable<string> Get()
{
return new string[] { "v1-product1", "v1-product2" };
}
//....
}
[RoutePrefix("v2")]
public class V2_ProductsController : ApiController
{
[Route("products")]
public IEnumerable<string> Get()
{
return new string[] { "v2-product1", "v2-product2" };
}
//....
}
/v1/products goes to the first version of /v2/products goes to the second one.
You can do it by overriding DefaultHttpControllerSelector
there you override method to selectcontroller
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor controllerDescriptor = null;
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
object apiVersion;
if (!routeData.Values.TryGetValue("Version", out apiVersion))
{
apiVersion = "1";
}
object controllerName;
if (!routeData.Values.TryGetValue("controller", out controllerName))
{
controllerName = string.Empty;
}
if (controllerName == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
string newControllerName = String.Concat(controllerName.ToString(), "V", apiVersion);
if (controllers.TryGetValue(newControllerName, out controllerDescriptor))
{
return controllerDescriptor;
}
if (controllers.TryGetValue(controllerName.ToString(), out controllerDescriptor))
{
return controllerDescriptor;
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
Then you are adding routes webapiconfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and register controller selector in webapiconfig
config.Services.Replace(typeof(IHttpControllerSelector), new ApiVersioningSelector(config));
So from now if you name controller ProductsV1Controller it will reffer /api/v1/products. Also please note that my example also support routes without version so if v1 is not found it will try to check if ProductsController exists
PS. Code is update one bug was there :(
Another simple way is configuring your route as api/{folder}/{controller}/{action} where in to folder you can give name as V1 or V2.
A good way can be implementing your own Controller selector. You can use this link for more information.
The interface that Web API uses to select a controller is IHttpControllerSelector. The important method on this interface is SelectController, which selects a controller for a given HttpRequestMessage.
I am developping a RESTful WEB API using asp.net web API. When i came to implement the Oauth2.0 service i found a small routing problem.
Example:
I have 3 controllers:
public class A_Controller : ApiController
{
public string get()
{
return "call A controller";
}
}
public class B_Controller : ApiController
{
public string get()
{
return "call B controller";
}
}
public class C_Controller : ApiController
{
public string get()
{
return "cal lC controller";
}
}
All off them return some information.
What i want to do is:
For default call:
.../api/{Controller}
Proceed with basic authentication.
For this call:
.../api/oauth/{Controller}
Proceed with my Oauth implementation.
This have to work for all Controllers.
What i tried:
config.Routes.MapHttpRoute(
name: "OauthApi",
routeTemplate: "api/oauth/{Controller}",
defaults: new
{
token_num = RouteParameter.Optional,
action = "Oauth"
}
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}",
defaults: new
{
action = "Default"
}
);
public class A_Controller : ApiController
{
[ActionName("Default")]
public string get()
{
return "call withouth Oauth";
}
[ActionName("OauthAction")]
public string getOauth()
{
//do some Oauth check
return "Oauth with token in header!!!!";
}
Here is what I would do, assuming the user is already authorized.
Create your routes:
config.Routes.MapHttpRoute(
name: "OauthApi",
routeTemplate: "api/oauth/{Controller}/{Action}",
defaults: new
{
action = "Default"
}
);
// or if you have the token in the path..
config.Routes.MapHttpRoute(
name: "OauthApi",
routeTemplate: "api/oauth/{OAuthToken}/{Controller}/{Action}",
defaults: new
{
action = "Default"
}
); // This is really just for URL match, as the Token shouldn't be used
// by the controller or action
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{Controller}/{Action}",
defaults: new
{
action = "Default"
}
);
Controllers:
public class A_Controller : ApiController
{
[ActionName("Default")]
[CustomAuthorize()]
public string get()
{
return "Requested Data";
}
}
Custom Authorization
public CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// do OAuth checking
if (HttpContext.Current.Request.Path
or HttpContext.Current.Request.QueryString["OAuthToken"].Equals())
{
return true;
}
return base.AuthorizeCore(httpContext);
}
}
Now your controllers do only what they are suppose to, return data. And you have a reusable way to authorize all requests.
If you are using MVC 3 or higher, you could just global authorize everything with OAuth.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomAuthorizeAttribute());
}