13

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?

3
  • 1
    Why not just POST /users/? ASP.NET MVC Doesn't stop you. Commented Sep 7, 2011 at 18:11
  • Well, while it is possible to do that, the standard way is to post to the same url you're at. Som if i am creating a user, i would be on /user/create. But as you say, that would probably work if you wanted it too. Commented Sep 8, 2011 at 13:19
  • This is better handled in the new MVC4 Web Api. i Would recommend others to look at it asp.net/whitepapers/mvc4-release-notes#_Toc317096197 Commented Mar 5, 2012 at 12:59

4 Answers 4

15

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.

Sign up to request clarification or add additional context in comments.

3 Comments

This was a very good alternative to re-writing stuff server side. Thanks!
It's still somewhat of a hack/workaround. Raynos has a more appropriate answer below.
Agreed. Prefer compatible URLs, however, if you have no control then the above approach is what you are left with. When I answered this, I perceived this inflexibility.
12

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

3 Comments

I always thought ASP.NET MVC's implementation of RESTful URL's were pretty solid? Is there anyone who have done this before? What way should i go, change Backbone.sync or MVC routing?
@Anders That's a choice you have to make. Depends what URLs you want. (updated answer).
@Anders Raynos is right... the "standard" REST is to use routes that map to objects/nouns, leaving your "actions" implicitly applied via HTTP verbs used to access them. /users/create then is not REST... it attempts to be "REST-like" or "RESTful" but it is not as POST is the only verb applicable. GET is being used to grab a form for creating a user rather than fetch a User object, PUT and DELETE make no sense. Create a REST api you can work with, and use Backbone the way it was intended.
9

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'

});

3 Comments

This looks like a very nice way to deal with it! Anyone else have experience using this method?
@Anders, yes only using int id instead of guid. Ran into this question fighting trying to use strings instead of guid. so +1 helping me with that
I feel this is more correct than switching the URL around for every case in javascript
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.