Maintaining WebAPI endpoints in JavaScript - javascript

I was wondering if there were any good techniques in keeping your WebAPI controller routes in sync with the client side.
For instance, you have a WebAPI controller BooksController. On the client you could invoke a method by calling the endpoint:
$.get('books/1');
Then one day you decide to rename the controller, or add a RoutePrefix. This breaks the client side code, as the endpoint has changed.
I came across the library WebApiProxy, which looks interesting. Does anyone have a good approach to solving this problem? Is there a reason to use string literals on the client that I may be overlooking?

I created a blog bost on te subject. Take a look :)
http://blog.walden.dk/post/2017/02/02/export-all-your-asp-net-webapi-endpoints-to-json
Im working on a post consuming it in javascript.. Anyway, this code exports the endpoints runtime, and will work on refactorings and route changes. It exports uri parameters as well, they can be used to be parsed in javascript and replaced with values from the client.
The simplest way to achieve waht you want, is to use the built-in ApiExplorer in ASP.NET WEBAPI. It searches for all "ApiController" implementations, and reads the route-attribute metadata.
public class EndpointManager
{
public IEnumerable<ApiMethodModel> Export()
{
//Use the build-in apiexplorer to find webapi endpoints
IApiExplorer apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
//exclude endpoints without the attribute
var apiMethods = apiExplorer.ApiDescriptions.Select(ad => new ApiMethodModel(ad)).ToList();
return apiMethods;
}
}
You can create an endpoint that returns that generated data.
[RoutePrefix("api/endpoint")]
public class EndpointApiController : ApiController {
[HttpGet]
[Route("all")]
public IEnumerable<ApiMethodModel> All()
{
var endpoints = new EndpointManager().Export();
return endpoints;
}
}
Now all the endpoints can be reached at "/api/endpoint/all"

Here is an sample I was talking about in my comment to your question:
function getUrl(uri) {
var bookRoute = /books(.*?)/i;
var otherRoute = /something(.*?)/i;
if(uri.match(bookRoute)) {
return uri.replace(bookRoute, "http://localhost/webapi/books$1")
}
if(uri.match(otherRoute)) {
return uri.replace(otherRoute, "http://mydomain/api/something$1")
}
return uri;
}
alert(getUrl("books/1"));
alert(getUrl("something/realy/different/1"));
All you need is to define the routes in the body of your function.

Related

Passing parameters between actions ASP.NET CORE MVC

I'm trying to pass multiple parameters from action A to action B.
public IActionResult A(string name){ // some code }
public IActionResult B() { return RedirectToAction("A", new {name = "John" }); }
This works fine. But what i want to achieve is that these parameters does not show in url, because when i do like the example above, my url looks like http://myapp.com/users?name=John. I want url to be clear without ? and following string.
I tried using TempData[] and it works locally, but when i publish my application to azure, it not working.
Tried with ViewData[] but found that it goes empty with redirecting.
I tried using javascript to split url, but it has some unnecessary reloadings.
Is there some way to acomplish this, with javascript or .net or something else?
Thanks
I don't understand why you don't like to use the ...?name=John&p2=val2 in your url, while your action is accepting a GET method...
Anyway, you can write this if you are in a same controller (which is not a good practice)
public IActionResult A(string name){}
public IActionResult B(){return A("world");}
Or
use TempData: here, your problem would be TempData[] not working on azure. So you should set up the cookie consent like this
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
Bear in mind that you have to clear your browser navigation data including cookies and also accept the app to store/collect cookie.

cannot connect to Web API

I am new to WebAPI programming .Here is what have I done
Created ASP.NET web Application SampleWebApiProject in Visual Studio 2013
under .NET Framework 4.5.2
Selected MVC and checked Web API under [Add Folders and core references for].
using Nuget package installed knockout.js ,knockout-validation.js etc etc.
In my code for Login.cshtml I have html button
<div>
<button type="button" class="btn btn-info" data-bind="click:$parent.login">
Login
</button>
</div>
And on my click button I have
self.viewModelHelper.apiPost('api/account/login', unmappedModel,
function (result) {
}
And I have created API Controller called AccountApiController
public class AccountApiController : ApiController
{
[HttpPost]
[POST("api/account/login")]
public HttpResponseMessage Login(HttpRequestMessage request, [FromBody]AccountLoginModel accountModel)
{
return null;
}
}
However when I inspect the click event in Chrome developer tools I get an error response
POST http://localhost:64436/api/account/login 404 (Not Found).
this is my WebApiConfig
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Am I working with right type of application ?
Screenshot
Try:
self.viewModelHelper.apiPost('api/accountapi/login', unmappedModel,
function (result) {
}
and API Controller
public class AccountApiController : ApiController
{
[HttpPost]
[POST("api/accountapi/login")]
public HttpResponseMessage Login(HttpRequestMessage request, [FromBody]AccountLoginModel accountModel)
{
return null;
}
}
Your account controller is named accountapi and not account, so webapi can't find any controller called account.
I'm not sure, but your parameters look wrong in your webapi controller...
Why would you add HttpRequestMessage as a parameter?
You have called your controller AccountApiController and so api/account/login should be accountapi/login
Web API has a strict calls when it comes to MVC architecture.
If you call POST. It means that the API will really CREATE a new Entity, and Does NOT, make other request to be returned.
So meaning, the WebAPI is not custom API function Call that you thought it might be.
It is different from creating an individual API to Creating an web API inside an MVC Application.
Here is are some Notes.
GET : Retrieve an entity
PUT : update an entity
POST : create a new entity
DELETE : remove an existing entity.
so let us say you have an API for Account Models. I will say Models cause when creating an Web API. You need a Model. Unless you're creating your custom API. Outside the MVC.
Now you did this. api/account/test
What it will do is use the [GET] function.
Whatever function you have in the account controllers that have a Data Annotation of [GET] will be executed. And return you something.
And No. Don't use Login as the name of the Method just use GET as you can't really tell the Web API which function to use. It WILL use the one with the GET data annotation. So entering
api/account/ login <---- this will not call the login method, it is entering a string data to be passed to the Get Method.
[HttpGet]
public IEnumerable<string> Get()
{
return "No Value";
}
[HttpGet("{id}")]
public IEnumerable<string> Get(int id)
{
return "There is a value";
}
Now if you want the POST to be Called. Simply create a A Form that has a method of POST. Or JQuery Javascript and call generate the POST method for them. You can't write the Method call in the address bar. You just have to use the right kind of request to call the specific function or function with overload.

AngularJS and Jersey REST DELETE operation fails with 415 Status code

I have a AngularJS webapplication with a Jersey Backend Application.
Now everything is working fine using ngResource to access REST resource out of AngularJS. The only problem is with the DELETE option.
I have the following code to delete a course using my ngResource:
Course.deleteCourse = function(course) {
course.$remove({
courseId:course.id
});
return course;
};
In the backend (Jersey) I have the following code:
#DELETE
#Path("{id}")
public final void remove(#PathParam("id") final String id) {
System.out.println("DELETE ID = " + id);
}
If I try to delete an item the following url is called from Angular:
DELETE http://localhost:8080/manager-api/courses/5
This is fine (after me). If I call this url from CURL, i get the ssystem.out from the Backend posted to the console.
In the client-app (AngularJS) i get the following exception on the browser console:
DELETE http://localhost:8080/manager-api/courses/5 415 (Unsupported Media Type)
Anyone an idea what the problem might be? POST + GET are working fine.
I have the following consume/produce annotations:
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
Thanks in advance for your help.
Greets
Marc
EDIT:
I have tried to replace the way of accessing the REST services out of AngularJS with $http.
Now my service looks as below:
MyApp.factory("Course", ["$http", function ($http) {
var courseBaseUrl = "/api/courses/";
return {
show: function show(courseId) {
return $http.get(courseBaseUrl + courseId);
},
list: function list() {
return $http.get(courseBaseUrl, {});
},
remove: function remove(courseId) {
return $http.delete(courseBaseUrl + courseId, {});
},
save: function save(course) {
return $http.post(courseBaseUrl, course, {});
}
};
}]);
The result is still the same. The application calls e.g
DELETE http://localhost:8080/manager-api/courses/1
and receives a
DELETE http://localhost:8080/manager-api/courses/1 415 (Unsupported Media Type)
If I call the same DELETE call on Curl, everything works fine.
Thanks for your help
Marc
I came across this as well, the problem is angular always sets the Content-Type header to xml on DELETE requests and jersey will chuck an error as you have specified that your api consumes/produces JSON with the annotations.
So to fix it (from the client side), set the content-type header, eg:
.config(function($httpProvider) {
/**
* make delete type json
*/
$httpProvider.defaults.headers["delete"] = {
'Content-Type': 'application/json;charset=utf-8'
};
})
However, for reasons I dont understand/dont know of, angular will strip away the content-type header from the request if it has no data. This would make sense if it wasn't for the fact that browsers (chrome at least) will always send a content-type... Anyway you will have to go to the trouble of finding this in the angular source:
// strip content-type if data is undefined
if (isUndefined(config.data)) {
delete reqHeaders['Content-Type'];
}
and get rid of it. I dont know of a way to do this without editing the source. Maybe someone with better JS know-how, erm, knows how.
Alternatively, from the server side, you can do as Rob has suggested and change the Jersey configuration to allow consuming MediaType.APPLICATION_XML
#Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public final void remove(#PathParam("id") final String id) {
System.out.println("DELETE ID = " + id);
}
I had same issue, Try returning new instance of Course object in your delete method.
#DELETE
#Path("{id}")
public final Course remove(#PathParam("id") final String id) {
System.out.println("DELETE ID = " + id);
return new Course();
}
Using angularjs $resource (instead of $http), without "payload" in the request, the content-type is setted as text/plain.
So IMHO it's better a server side support.
"Postel's Law states that you should be liberal in what you accept and conservative in what you send. -- source"
#DELETE
#Path("{id}")
#Consumes({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
public void remove(#PathParam("id") Long id) { ...

Accessing .json file to get bank holidays access denied

I am very new to jQuery and json. I am trying to get UK Bank Holiday data from the following API/link so that I can create a function which determines if a selected date is a working day.
https://www.gov.uk/bank-holidays.json
I had an error for No Transport which I fixed by putting in:
jQuery.support.cors = true;
I am now getting Access Denied. I have no idea how to gain access to this because as I say, I am still learning all this.
function IsWorkingDay(date) {
var day = new moment(value, "DD-MM-YYYY").day();
jQuery.support.cors = true;
var bankHolidays = $.getJSON("https://www.gov.uk/bank-holidays.json").done(function(data) {
alert(data);
})
.fail(function(a, b, c) {
alert(b + ',' + c);
return day != 0 && day != 6;
}
});
My question is in two phases:
How do I get access? (Main question)
How do I move on to access the data? I have downloaded the json onto my computer to look at, just how I am going to go about translating this to javascript is what I am struggling on.
If you are blocked by CORS, and the service doesn't support JSONP, the easiest way to solve it is to create a proxy service for the actual service. So if you create a service on your server (which is the same that is serving the javascript), you can call that service, which in turn will fetch the data from the external service. On the server side, there is no CORS to worry about.
I don't know what your backend is, but the steps are as follows:
Create a service on your side that is exposed with a URL (e.g. /myapp/workingday)
Call this service instead of the real service
Your proxy service will get the JSON data and return it to the javascript
Edit
I don't know MVC4, but I suspect it's some of the same concepts as Spring MVC, so here's a Java example:
#Controller
public class HolidaysController {
#RequestMapping("/workingday")
public void isworkingDay(#RequestParam("day") Date day, HttpServletResponse response) {
// Call external service and get JSON
String json = callExternalService(day);
response.setContentType("application/json");
response.getWriter().write(json);
}
}
And in your javascript:
function IsWorkingDay(date) {
var day = new moment(value, "DD-MM-YYYY").day();
var bankHolidays = $.getJSON("/workingday").done(function(data) {
// process data
});
}
For this to work you need to use jsonp as illustrated here
BUT
running the above code throw an invalid label exception which as explained here and as #NilsH suggests is due to the server blocking it

Is there an ASP.Net MVC view engine that supports JavaScript views?

I would like to generate some JavaScript on the server side in ASP.Net MVC. Is there a view engine that supports this? Ideally I would like to be able to get JavaScript from an url like:
http://myapp/controller/action.js
I've looked at the MonoRail project, and they seem to have this feature, but it's very lacking in documentation, and I can't find any ports to ASP.Net MVC.
Edit: The idea is to be able to render a page both as standard HTML by using a url like:
http://myapp/controller/action
and as js (specifically an ExtJS component) by using the first url in the question. There would be only a single action in the controller, but two views: one for HTML and one for JS.
Edit 2: I basically wanted to achieve the same result as router extension parsing/request handling in CakePHP.
I wanted to extend this idea to not only allow Javascript views, but more or less any type of document. To use it, you just put the views for *.js urls in a subfolder of your controller's view folder:
\Views
+-\MyController
+-\js
| +-Index.aspx <- This view will get rendered if you request /MyController/Index.js
+-Index.aspx
The class is a decorator for any type of ViewEngine, so you can use it with NVelocity/WebForms/Whatever:
public class TypeViewEngine<T> : IViewEngine where T : IViewEngine
{
private readonly T baseEngine;
public T BaseEngine
{
get { return baseEngine; }
}
public TypeViewEngine(T baseEngine)
{
this.baseEngine = baseEngine;
}
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"TypeViewEngine",
"{controller}/{action}.{type}",
new {controller = "Home", action = "Index", type = "html"}
);
}
public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
{
var vars = controllerContext.RouteData.Values;
if(vars["type"] != null && vars["type"].ToString() != "html")
{
viewName = string.Format("{0}/{1}", vars["type"], viewName);
}
return baseEngine.FindView(controllerContext, viewName, masterName);
}
public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
{
return baseEngine.FindPartialView(controllerContext, partialViewName);
}
public void ReleaseView(ControllerContext controllerContext, IView view)
{
baseEngine.ReleaseView(controllerContext, view);
}
}
Then, in your Global.asax.cs file:
protected void Application_Start()
{
var ve = new TypeViewEngine<WebFormViewEngine>(new WebFormViewEngine());
ve.RegisterRoutes(RouteTable.Routes);
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(ve);
}
Thanks for everyone's help with this!
Based on your edit I'll try with a new answer asumming you need json data for ExtJS. I've just tested it in the app I'm building and it works fine. First you need two routes
{controller}/{action}.{format}
{controller}/{action}
Now the Controller class has a Json method to serialize whatever object you want and it's a JsonResult so you can just use:
public ActionResult List(string format) {
// logic here
if (string.IsNullOrEmpty(format)) {
return View();
} else if (format == "js") {
return Json(object_to_serialize);
}
}
The July 2007 ASP.NET Futures release has the new Managed JScript (I can't find a newer one). I've successfully batted it around a bit - but beware, you will probably be forced to do some extra work, the .ASPX parsing for JScript is unusably buggy.
http://www.microsoft.com/downloads/details.aspx?FamilyId=A5189BCB-EF81-4C12-9733-E294D13A58E6&displaylang=en
You don't necessarily need view engine for that, just return plain text result.
Here is a controller (MonoRail) I've used to render some user culture settings in a javascript object:
[ControllerDetails("js")]
public class JavascriptController : Controller
{
private ISessionContext sessionContext;
public JavascriptController(ISessionContext sessionContext)
{
this.sessionContext = sessionContext;
}
public void CultureInfo()
{
var scriptformat = #"var cultureInfo = {0};";
var json = Context.Services.JSONSerializer.Serialize(getJSONiZableCultureInfo(sessionContext.CurrentCulture));
RenderText(String.Format(scriptformat, json));
}
object getJSONiZableCultureInfo(System.Globalization.CultureInfo culture)
{
return new
{ // add more there
culture.NumberFormat
};
}
}
for more complex things for which raw text rendering would smell, any view engine would work.
Also you are not forced to use .js extension to put the url in your script tag.
I had to do something very similar, and I used the nvelocity view engine from MVCContrib - technically you could use the default aspx view engine, but I found that the nvelocity syntax was a lot more straightforward for puting out script (don't worry if you haven't used it before - I spent about 10 minutes figuring it out!).
Then you just need to add a route to the route table to handle directing your .js url to the action!
EDIT
Can't verify this as I don't have visual studio to hand but for the route, you might have something like this:
RouteTable.Routes.Add(new Route
{
Url = "[controller]/[action].js",
Defaults = new { controller="home", requestType="javascript" }, // Whatever...
RouteHandler = typeof(MvcRouteHandler)
});
RouteTable.Routes.Add(new Route
{
Url = "[controller]/[action]",
Defaults = new { controller="home"}, // Whatever...
RouteHandler = typeof(MvcRouteHandler)
});
Requests ending in .js should go through the first route - extensionless requests fall through to the second.
Then your action could have a requestType param:
public ActionResult MyAction (RequestType requestType)
{
if(requestType == RequestType.JavaScript)
{
... new nvelocity view to render javascript
}
else
{
...
}
}
As for directory structure - you're on your own with that! Not because I don't want to be helpful, but more down to the fact that you have flexibility to do what you want with it!
If you just want to generate a javascript based on the ViewData you can create your own custom result. Here is an example.

Categories