Backbone.js REST URL with ASP.NET MVC 3 - asp.net-mvc-3

I have been looking into Backbone.js lately and i am now trying to hook it up with my server-side asp.net mvc 3.
This is when i discovered a issue. ASP.NET listens to different Actions, Ex: POST /Users/Create and not just POST /users/. Because of that, the Model.Save() method in backbone.js will not work.
How should we tackle this problem? Do i have to rewrite the Backbone.Sync?

The answer is not to override Backbone.sync. You rarely would want to do this. Instead, you need only take advantage of the model's url property where you can assign a function which returns the url you want. For instance,
Forum = Backbone.Model.extend({
url: function() {
return this.isNew() ? '/Users/Create' : '/Users/' + this.get('id');
}
});
where the url used for a model varies based upon whether the model is new. If I read your question correctly, this is all you need to do.

You either have to tell ASP.NET MVC to route proper REST urls or fix Backbone.sync so it sends the GET/POST requests at the proper URLs.
Backbone works with REST not with RESTful URLs. There may be an OS implementation of Backbone.sync that matches your urls though.
Recommend URLs that play more nicely with Backbone:
GET /forums -> index
GET /forums/new -> new
POST /forums -> create
GET /forums/:forum -> show
GET /forums/:forum/edit -> edit
PUT /forums/:forum -> update
DELETE /forums/:forum -> destroy

I wrote a blog post recently describing how to bind .NET MVC to the default Backbone service layer.
Like previous posters have mentioned, there are a number of approaches you could take. I prefer this approach because it requires little configuration.
The controller:
public class ZocController : Controller
{
public ActionResult Docs()
{
return Json(GetDocs(), JsonRequestBehavior.AllowGet);
}
[ActionName("Docs")]
[HttpPost]
public ActionResult HandlePostDoc(Doctor doc)
{
doc.id = Guid.NewGuid();
CreateDoc(doc);
return Json(doc);
}
[ActionName("Docs")]
[HttpPut]
public ActionResult HandlePutDoc(Doctor doc)
{
UpdateDoc(doc);
return new EmptyResult();
}
[ActionName("Docs")]
[HttpDelete]
public ActionResult HandleDeleteDoc(Guid id)
{
DeleteDoc(id);
return new EmptyResult();
}
}
The Backbone
window.Doctor = Backbone.Model;
window.Doctors = Backbone.Collection.extend({
model: Doctor,
url: '/zoc/docs'
});

i found the following code in https://github.com/sgentile/BackboneContacts
/// <reference path="backbone.js" />
ModelBase = Backbone.Model.extend({
defaults: {
id: null
},
url: function (type) {
//expecting the following conventions on the server:
//urlRoot should be the controller : controller/
//create → POST /action
//read → GET /action[/id]
//update → PUT /action/id
//delete → DELETE /action/id
var fqUrl = this.urlRoot;
switch (type) {
case "POST":
fqUrl += "create";
break;
case "PUT":
fqUrl += "update";
break;
case "DELETE":
fqUrl += "delete/" + this.get('id');
break;
case "GET":
fqUrl += "read/" + this.get('id');
break;
}
return fqUrl;
}
});
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read': 'GET'
};
// Helper function to get a URL from a Model or Collection as a property
// or as a function.
var getUrl = function (object) {
if (!(object && object.url)) return null;
return _.isFunction(object.url) ? object.url() : object.url;
};
// Throw an error when a URL is needed, and none is supplied.
var urlError = function () {
throw new Error('A "url" property or function must be specified');
};
Backbone.sync = function (method, model, options) {
var type = methodMap[method];
options.url = _.isString(this.url) ? this.url : this.url(type);
// Default JSON-request options.
var params = _.extend({
type: type,
dataType: 'json'
}, options);
// Ensure that we have a URL.
if (!params.url) {
params.url = getUrl(model) || urlError();
}
// Ensure that we have the appropriate request data.
if (!params.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON());
}
// For older servers, emulate JSON by encoding the request into an HTML-form.
if (Backbone.emulateJSON) {
params.contentType = 'application/x-www-form-urlencoded';
params.data = params.data ? { model: params.data} : {};
}
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (Backbone.emulateHTTP) {
if (type === 'PUT' || type === 'DELETE') {
if (Backbone.emulateJSON) params.data._method = type;
params.type = 'POST';
params.beforeSend = function (xhr) {
xhr.setRequestHeader('X-HTTP-Method-Override', type);
};
}
}
// Don't process data on a non-GET request.
if (params.type !== 'GET' && !Backbone.emulateJSON) {
params.processData = false;
}
// Make the request.
return $.ajax(params);
};

Related

Angular2 : Reduce number of Http calls

I'm using Angular2 RC5 with an ASP.NET Core server that makes the API calls to get my data.
I'm actually wondering if there is a way to reduce the number of http calls you make with Angular2, because I fear there will be a lot if I keep using components the way I do. Here is a concrete example.
I want to get a text value from the database, which is defined by an ID and a Language. I then made the following component :
dico.component.ts
#Component({
selector: 'dico',
template: `{{text}}`,
providers: [EntitiesService]
})
class Dico implements AfterViewInit {
#Input() private id: string;
#Input() private lang: string;
private text: string = null;
// DI for my service
constructor(private entitiesService: EntitiesService) {
}
ngAfterViewInit() {
this.getDico();
}
// Call the service that makes the http call to my ASP Controller
getDico() {
this.entitiesService.getDico(this.id, this.lang)
.subscribe(
DicoText => this.text = DicoText
);
}
}
#Component({
template: `<dico [id] [lang]></dico>`,
directives: [Dico]
})
export class DicoComponent {
}
Here is the code from my service :
entities.service.ts
getDico(aDicoID: string, aLangue: string) {
// Parameters to use in my controller
let params = new URLSearchParams();
params.set("aDicoID", aDicoID);
params.set("aLangue", aLangue);
// Setting up the Http request
let lHttpRequestBody = params.toString();
let lControllerAction: string = "/libelle";
let lControllerFullURL: string = this.controllerURL + lControllerAction;
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
return this.http.post(lControllerFullURL, lHttpRequestBody, options)
.map((res: any) => {
// Parsing the data from the response
let data = res.json();
// Managing the error cases
switch (data.status) {
case "success":
let l_cRet: string = data.results;
if (l_cRet != null && !l_cRet.includes("UNDEFINED")) {
return data.results;
} else {
throw new Error("Erreur récupération Dico : " + l_cRet);
}
case "error":
throw new Error("Erreur récupération Dico : " + data.message);
}
}
).catch(this.handleError);
}
Then I can use my newly made component in my app :
randomFile.html
<dico id="201124" lang="it"></dico>
<dico id="201125" lang="en"></dico>
<dico id="201126" lang="fr"></dico>
But this application will eventually use hundreds of these "dico" and I was wondering how I could manage some pre-fetch or something like that before the app fully loads. Does it even matter ? Will that affect performance in the long term ?
Any advice would be greatly appreciated.
EDIT : These dico allow me to fetch from the database a text translated into the langage I want. Here, in the example above, I have 3 "dico" that will output some text in italian, french, and english.
My application will use a lot of them, as every text in every menu will be a "dico", and the problem is that there will be a lot of them, and right now for every "dico" I make, my service is called and makes one http call to get the data. What I want is to somehow define all my dicos, call the service which will give me the text of all my dicos in an array to avoid making several calls (but I don't really know how to do that).
A basic untested approach (I don't know observables too well myself)
class DicoService {
private subjects = {}
private ids = [];
getDico(String id):Observable<Dico> {
var s = this.subjects[id];
if(!s) {
this.ids.push(id);
s = new Subject();
this.subjects[id]=s;
}
return s.asObservable().share().first();
}
sendRequest() {
http.get(....) /* pass this.ids */
map(response => response.json())
.subscribe(data => {
for(item in data) { // don't know how to iterate exactly because I don't know how the response will look like
this.subject[item.id].next(item.langText);
}
// you might cache them if other components added by the router also request them
// this.subjects = {};
// this.ids = []
});
}
}
<dico [text]="dicoService.getDico('someId') | async"></dico>
ngAfterViewInit() {
this.dicoService.sendRequest();
}

Navigate to another Page after appointment has been saved in kendo scheduler

I have got this scheduler displayed but not binding to tasks. The scheduler in the view. I am using java script method to read/create call to web api
#(Html.Kendo().Scheduler<TaskViewModel> ()
.Name("AppointmentSearchScheduler")
.DataSource(dataSource => dataSource
.Custom()
.Schema(schema => schema
.Model(m => {
m.Id(f => f.TaskID);
m.Field(f => f.OwnerID).DefaultValue(1);
}))
.Transport(new {
read = new Kendo.Mvc.ClientHandlerDescriptor() {
HandlerName = "customRead"
},
create = new Kendo.Mvc.ClientHandlerDescriptor() {
HandlerName = "customCreate"
}
})))
Below is javascript handler method I am not including create handler for brevity.
function customRead(options){
//get the selected Row of the kendo grid
var selectedRow = $("#locationgridKendo").find("tbody tr.k-state-selected");
var scheduler = $("#AppointmentSearchScheduler").data("kendoScheduler")
//get SelectedRow data
var rowData = $('#locationgridKendo').data("kendoGrid").dataItem(selectedRow);
if (rowData !== null) {
//Convert data to JSON
var rowDataJson = rowData.toJSON();
//extract the location ID
var locationId = rowDataJson.LocationID;
var CalenderweekStartDate = new Date().toISOString();
baseUrl = $('base').attr('href');
$.ajax({
url: baseUrl + 'Schedular/api/GetAppPerLocation?locationId=' + locationId + '&date=' + CalenderweekStartDate,
type: 'GET',
cache: false,
contentType: 'application/json',
success: function (result) {
//This method is hitting and i can see the data being returned
console.log('data is received : ' + result.Data);
options.success(result.Data);
},
error: function (xhr, status, error) {
//alert("Error: Search - Index.js - submitAppointment()");
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
}
});
}
}
Here is the web API controller called by making ajax call . The controller works perfectly when i used the basic read/create syntax . The ajax call complete and it does hit back the success method and returns the data but scheduler for some reason is not binded to incoming data. Here is my controller code
[HttpGet]
[Route("api/GetAppPerLocation")]
public DataSourceResult GetAppointmentPerLocation([ModelBinder(typeof(Usps.Scheduling.Web.ModelBinders.DataSourceRequestModelBinder))] DataSourceRequest request, int locationId, DateTime date) {
List < TaskViewModel > locationAvailableAppointmentList = new List < TaskViewModel > ();
locationAvailableAppointmentList = data.Select(appt => new TaskViewModel() {
TaskID = appt.ServiceAppointmentId,
Title = "Appointment Available",
Start = DateTime.SpecifyKind(appt.AppointmentBegin, DateTimeKind.Local),
End = DateTime.SpecifyKind(appt.AppointmentEnd, DateTimeKind.Local),
Description = "",
IsAllDay = false
}).ToList();
return locationAvailableAppointmentList.ToDataSourceResult(request);
}
For some reason the scheduler is not binding to incoming data . the incoming data works perfectly when i use a basic binding approach but not using transport . My goal for using this approach is once i am done with read(scheduler is not binding now) , on create I need to grab the ID of the newly created task returned by my controller and then pass that id to another mvc controller to render a confirmation page. Any other approach to accomplish this goal will be highly recommended.
Please excuse me for any mistake since this is my first question on stackoverflow.
My goal for using this approach is once i am done with read(scheduler is not binding now) , on create I need to grab the ID of the newly created task returned by my controller and then pass that id to another mvc controller to navigate render a confirmation page.
I speculated that read was not returning correct result so i had to fix that .Also my basic goal was redirection to another page after with appointment id and displaying a confirmation screen. This is how accomplished it . I understand this is not the best approach but it has been more than a year no body answered by question. Here is the approach i took .
I added a error to the model state like this in my controller
if (!String.IsNullOrEmpty(task.TaskID.ToString()))//redirect to confirmation page if the appointment was added to the queue
ModelState.AddModelError("AppointmentID", confirmationNumber);
then on client side i configure the error event on grid like this
.Events(
events => events.Error("RedirectToConfirmationPage"))
Here is the Javascript method details
function RedirectToConfirmationPage(e) {
console.log('RedirecToConfirmationPage method......');
console.log(e);
if (e.errors) {
var appointmentID = "";
// Create a message containing all errors.
$.each(e.errors, function (key, value) {
console.log(key);
if ('errors' in value) {
$.each(value.errors, function () {
appointmentID += this + "\n";
});
}
});
console.log('Newly Generated AppointmentID = ' + appointmentID);
// Redirect URL needs to change if we're running on AWS instead of on local developer machines
if (window.location.href.indexOf('/TestProject.Scheduling') > 1) {
window.location.href = '/Scheduler/AppointmentConfirmation?confirmationNumber=' + appointmentID
}
else {
window.location.href = '/Scheduler/AppointmentConfirmation?confirmationNumber=' + appointmentID
}
}
}
Hope it is helpfull to someone down the road.

How to put many parameters in a route in FOSJsRoutingBundle?

I have a route with many parameters; but when I generate it with FOSJsRoutingBundle the navigator takes just the first parameter and generate a 404 Error
Example:
var id = $(this).val();
var name = "aaa";
$.ajax({
url: Routing.generate('my_route', {
'id': id,
'name': name
}),
// rest of code
This syntax is it correct ?
EDIT 1 :
My route
my_route:
path: /homepage/{id}/{name}
defaults: { _controller: AcmeBundle:Personal:changename}
options:
expose: true
Just incase someone else comes across this (I just wasted several hours)... If the parameter you are passing in matches the default, Routing.generate doesn't include the parameter.
For example:
Controller:
/**
* #Route("/plc/data/{systemID}/{tagID}", name="web_plc_data", options = { "expose" = true })
*/
public function indexAction(Request $request, $systemID=1, $tagID=16)
{
}
From twig:
var url = Routing.generate('web_data', { systemID: 10, tagID: 16 });
Will generate route:
/plc/data/10 (note 'tagID' parameter is ignored)
From twig:
var url = Routing.generate('web_data', { systemID: 10, tagID: 17 });
Will generate route:
/plc/data/10/17 (tagID parameter now included as it doesn't match default)
Best solution I could find was to set default parameters to NULL in the route, then initialise in the function itself (if null, set to some value).
Ie:
/**
* #Route("/plc/data/{systemID}/{tagID}", name="web_plc_data", options = { "expose" = true })
*/
public function indexAction(Request $request, $systemID=null, $tagID=null)
{
if ($systemID==NULL)
{
$systemID = 1;
}
if ($tagID==NULL)
{
$tagID = 16;
}
}
Implementation makes sense, just a bit confusing as it causes unexpected behavour.
I don't know why the navigator doesn't take the second parameter but i have solved the problem like this :
var id = $(this).val();
var name = "aaa";
var url = Routing.generate('my_route', {
id: id,
}) + "/" + name;
$.ajax({
url: url,
// rest of code
In case someone else is dealing with the same issue, I just added the option expose true to the route since i'm using FOSJsRoutingBundle to generate the route in javascript, and everything work fine.
Here is the route definition:
/**
* #Route("/show/{id}/{toValidate}", name="contribution_show", methods={"GET"}, options={"expose"=true})
*/
public function show(Contribution $contribution, $toValidate): Response
And here is my ajax call to generate the route:
url : Routing.generate('contribution_show', {id: id, toValidate: toValidate }),

Kendo grid how to pass additional parameter from java script

in telerik extenstion to pass additional data to ajax request I used
function onDataBinding(e)
{
e.data = {argument : 4};
}
where e was div cointainer with data object inside,
How can I do this using kendo ? I tried the same but for Kendo e arqument is sth totally different.
Finally i got the answer my own and it is :
$('#grid').data('kendoGrid').dataSource.read({name:value})
Sorry for the terrible late at the party, but i've got some special cake that you may find tasty:
function readData()
{
return {
anagId: selectedItem.ID
};
}
$("#grid").kendoGrid({
dataSource: {
type: "ajax",
transport: {
read: {"url":"#Url.Action("RecordRead", "Tools")","data":readData}
}
[ rest of the grid configuration]
I came across this code by inspecting the code generated by Kendo Asp.Net MVC helpers.
I don't know if this is a further implementation that didn't exist at the age of the post, but this way looks really the most flexible compared to the other answers that i saw. HTH
Try this:
Add this to your grid read function or any CRUD operation:
.Read(read => read.Action("ReadCompanyService", "Admin").Data("CompanyServiceFilter"))
Add javascript:
function CompanyServiceFilter()
{
return {
company: $("#ServiceCompany").val()
}
}
In your controller:
public ActionResult ReadCompanyService([DataSourceRequest]DataSourceRequest request, string company)
{
var gridList = repository.GetCompanyServiceRateList(company);
return Json(gridList.ToDataSourceResult(request));
}
Please note, only string type data is allowed to be passed on read, create, update and delete operations.
If you want to pass some param to ajax request, you can use parameterMap configuration on your grid.
This will get passed on to your Ajax request.
parameterMap: function (options, operation) {
if (operation === "read") {
var selectedID = $("#SomeElement").val();
return {ID: selectedID }
}
return kendo.stringify(options.models) ;
}
Try this:
.Read(read => read.Action("Controller", "Action")
.Data(#<text>
function() {
return {
searchModel: DataFunctionName(),
userName: '#=UserName#'
}
}
</text>)
)
JS function
function DataFunctionName() {
var searchModel = {
Active: $("#activityMonitorIsActive").data('kendoDropDownList').value(),
Login: $("#activityMonitorUsers").data('kendoComboBox').value()
};
return searchModel;
}

Codeigniter rest issue with backbone

I just started using rest library wrote by Phil Sturgeon. I started using it by writing some simple examples. I short of get 'post' and 'get' work, but not for put and delete. I have some questions based on the code below.
// a simple backbone model
var User = Backbone.Model.extend({
urlRoot: '/user',
defaults:{
'name':'John',
'age': 17
}
});
var user1 = new User();
//user1.save(); // request method will be post unless the id attr is specified(put)
//user1.fetch(); // request method will be get unless the id attr is specified
//user1.destroy(); // request method will be Delete with id attr specified
In my CI REST controller
class User extends REST_Controller
{
public function index_get()
{
echo $this->get(null); //I can see the response data
}
public function index_post()
{
echo $this->post(null); //I can see the response data
}
public function index_put()
{
}
public function index_delete()
{
}
}
Basically, the get and post in the controller will be called when I save a model or fetch a model. With a id specified in the model, I can make a put or delete request to the server using model.save() and model.destroy(). however, I got a server error. it looks like index_put or index_delete can not be called. does anyone know How I can handle:
put request in the controller
delete request in the controller
get a single record with id specified
From the git, I only saw him to list index_post and index_put. there is no index_put and index_delete demo. should anyone can help me out? thanks
I faced the same exact problem, it looks like that DELETE, PUT, PATCH methods are not fully supported by browsers/html/server yet. You may want to look at this stack overflow question: Are the PUT, DELETE, HEAD, etc methods available in most web browsers?
A simple solution would be to change the methodMap of backbone line 1191 to the following:
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
'create': 'POST',
'update': 'POST', //'PUT',
'patch': 'POST', //'PATCH',
'delete': 'POST', //'DELETE',
'read': 'GET'
};
and then include the action type as an attribute of the model
var Person = Backbone.Model.extend({
defaults:{
action_type : null,
/*
* rest of the attributes goes here
*/
},
url : 'index.php/person'
});
now when you want to save a model, do the following
var person = new Person({ action_type: 'create' });
person.set( attribute , value ); // do this for all attributes
person.save();
in the application/controllers folder you should have a controller called person.php with class named Person extending REST_Controller, that has the following methods:
class Person extends REST_Controller {
function index_get() { /* this method will be invoked by read action */ }
/* the reason those methods are prefixed with underscore is to make them
* private, not invokable by code ignitor router. Also, because delete is
* might be a reserved word
*/
function _create() { /* insert new record */ }
function _update() { /* update existing record */ }
function _delete() { /* delete this record */ }
function _patch () { /* patch this record */ }
function index_post() {
$action_type = $this->post('action_type');
switch($action_type){
case 'create' : $this->_create(); break;
case 'update' : $this->_update(); break;
case 'delete' : $this->_delete(); break;
case 'patch' : $this->_patch(); break;
default:
$this->response( array( 'Action '. $action_type .' not Found' , 404) );
break;
}
}
}
Having said that, this solution is an ugly one. If you scroll up in the backbone implementation, you will find the following code at line 1160:
// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
// And an `X-HTTP-Method-Override` header.
if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
params.type = 'POST';
which means you need to set the emulate options of backbone configurations. add the following lines to your main.js
Backbone.emulateHTTP = true;
Backbone.emulateJSON = true;
To test the effect of that, I created a simple model and here are the results
you need a controller called Api in applications/controllers folder, in a file named api.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
require_once APPPATH.'/libraries/REST_Controller.php';
class Api extends REST_Controller
{
function index_get()
{
$this->response(array("GET is invoked"));
}
function index_put()
{
$this->response(array("PUT is invoked"));
}
function index_post()
{
$this->response(array("POST is invoked"));
}
function index_patch()
{
$this->response(array("PATCH is invoked"));
}
function index_delete()
{
$this->response(array("DELETE is invoked"));
}
}
and in your js/models folder, create a model called api_model.js
var Api = Backbone.Model.extend({
defaults:{
id: null,
name: null
},
url: "index.php/api/"
});
var api = new Api();
api.fetch({ success: function(r,s) { console.log(s); } }); // GET is invoked
api.save({},{ success: function(r,s) { console.log(s); } }); // POST is invoked
//to make the record old ( api.isNew() = false now )
api.save({id:1},{ success: function(r,s) { console.log(s); } }); // PUT is invoked
api.destroy({ success: function(r,s) { console.log(s); } }); //DELETE is invoked
I don't know how to do patch, but hope this helps.
Edit
I found out how to do patch, which is not included in the REST implementation of code ignitor. In REST_Controller line 39, you will find the following,
protected $allowed_http_methods = array('get', 'delete', 'post', 'put');
you need to add 'patch' at the end, to accept this method, also, after doing that add this code
/**
* The arguments for the PATCH request method
*
* #var array
*/
protected $_patch_args = array();
also, you need to add the following code to parse patch arguments:
/**
* Parse PATCH
*/
protected function _parse_patch()
{
// It might be a HTTP body
if ($this->request->format)
{
$this->request->body = file_get_contents('php://input');
}
// If no file type is provided, this is probably just arguments
else
{
parse_str(file_get_contents('php://input'), $this->_patch_args);
}
}
Now, according to backbone docs, you need to pass {patch: true} to send a PATCH method, when you call the following line, you execute a patch:
api.save({age:20},{patch: true, success: function(r,s) { console.log(s); } });
// PATCH is invoked

Resources