Question:
What is a clear, safe and systematic way to call my MVC controllers/actions from JavaScript, but keep the related URLs in a central place for updating when controllers change? Or to dynamically generate the URLs at run-time? Or to get compile-time checking for AJAX controller linkage?
Research/Past Attempts:
I've recently plugged in T4MVC to my MVC project. Great! Compile-time checking for controller linkage in my views - but I still have controller URLs littered all over my JavaScript. Worse, I have evolved how I do it over the project lifetime, so I do it multiple ways that I'm still not satisfied with.
1) One way:
locationAutoComplete = new AjaxQueue({
Controller: "Utilities", // null for current controller
Action: "GetMatchingLocations",
DataModel: null, // null for raw JSON, or a local model to populate
MaxQueuedRequests: 0
});
2) Another way:
locationAutoComplete = function (location) {
return $.ajaxPost(resolveUrl("~/Utilities/GetMatchingLocations"));
}
3) Both 1 and 2 above make use of a project root defined in a Master/Layout JavaScript snippet, but still use unchecked URL strings. So now I've started benefiting from T4MVC by simply defining controller strings in my .CSHTML view:
var locationLookupController = #Url.Action(MVC.Utilities.GetMatchingLocations());
4) The SO article ( Ajax call Into MVC Controller- Url Issue ) addresses this a little. It talks about method 3, but also suggests using the AjaxHelper namespace. Since I make heavy use of jQuery AJAX with some pretty customized interactions (e.g. loading results into a local data model, not at a click event but at a timed interval), I don't think it will help me much. Today I don't even include the MS AJAX JavaScript code on any of my pages.
Thanks!
Continue to use T4MVC. Mix it in with a little jQuery and HTML5 data-* attributes. Here's an example:
<div data-location-lookup-url="#Url.Action(MVC.Utilities.GetMatchingLocations())">
...
locationAutoComplete = function (location) {
var url = $('[data-location-lookup-url]').data('location-lookup-url');
return $.ajaxPost(url);
}
Related
In Sails.js, a route is set up against a controller method which can render a view. For the most part, this is straightforward, i.e. you could set up a GET /users route that points to UserController.find (which is usually set up automatically anyway).
However, say the home page of a blog renders the 10 most recent posts in the main section and a column with a list of authors and categories. The controller method has to fetch posts, authors, and categories before rendering the view and sending it back to the client. Clearly, a method like this doesn't really belong in PostController, AuthorController, or CategoryController.
What's the best thing to do in this situation? Create a controller for rendering views that rely on data from multiple models? Is there a good name for such a controller?
Thanks!
What I would do (this is purely opinion-based) is creating a PageController and create an action for each page you'd want.
For your home page example you can create a home action, get whatever you need and then render it with res.ok() (if everything is fine).
Another option would be to use Sails as a pure API and use HTTP requests (Ajax) or sockets to get your data in JSON. If you want to do so, I'd advise you to use a front end framework such as Angular, Ember, React...
By the way you could also create actions rendering HTML in your existing controllers and create a route to hit them through Ajax requests and just print them in your page. I'd prefer the 2nd solution because it takes full advantage of the Blueprint API (you don't need new controller or action whatsoever).
As Yann pointed out, this answer has to be a little opinionated. It seems that you are using the views system and not building a single page application. For the home page, I would go for an IndexController.js file with a home(req, res) action.
// api/controllers/IndexController.js
module.exports = {
home: function (req, res) {
// Retrieve all the information you need
// Take care about managing the asynchronous calls before rendering the view
return res.view('homepage');
}
};
Declare the route
// config/routes.js
module.exports.routes = {
'get /': 'IndexController.home'
}
Create the view in views/homepage.ejs.
I am new to Django but i am advanced programmer in other frameworks.
What i intend to do:
Press a form button, triggering Javascript that fires a Ajax request which is processed by a Django View (creates a file) that return plain simple JSON data (the name of the file) - and that is appended as a link to a DOM-Element named 'downloads'.
What i achieved so far instead:
Press the button, triggering js that fires a ajax request which is process by a Django view (creates a file) that return the whole page appended as a duplicate to the DOM-Element named 'downloads' (instead of simple JSON data).
here is the extracted code from the corresponding Django view:
context = {
'filename': filename
}
data['filename'] = render_to_string(current_app+'/json_download_link.html', context)
return HttpResponse(json.dumps(data), content_type="application/json")
I tried several variants (like https://stackoverflow.com/a/2428119/850547), with and without RequestContext object; different rendering strats.. i am out of ideas now..
It seems to me that there is NO possibility to make ajax requests without using a template in the response.. :-/ (but i hope i am wrong)
But even then: Why is Django return the main template (with full DOM) that i have NOT passed to the context...
I just want JSON data - not more!
I hope my problem is understandable... if you need more informations let me know and i will add them.
EDIT:
for the upcoming questions - json_download_link.html looks like this:
Download
But i don't even want to use that!
corresponding jquery:
$.post(url, form_data)
.done(function(result){
$('#downloads').append(' Download CSV')
})
I don't understand your question. Of course you can make an Ajax request without using a template. If you don't want to use a template, don't use a template. If you just want to return JSON, then do that.
Without having any details of what's going wrong, I would imagine that your Ajax request is not hitting the view you think it is, but is going to the original full-page view. Try adding some logging in the view to see what's going on.
There is no need to return the full template. You can return parts of template and render/append them at the frontend.
A template can be as small as you want. For example this is a template:
name.html
<p>My name is {{name}}</p>
You can return only this template with json.dumps() and append it on the front end.
What is your json_download_link.html?
assuming example.csv is string
data ={}
data['filename'] = u'example.csv'
return HttpResponse(simplejson.dumps(data), content_type="application/json")
Is this what you are looking for?
Here is what I'm tyrnig to do:
Make ajax request to retrieve JSON data from a PHP script
Insert that information into DataStore Models
Store those models within a controller
Display the information using {{#each}} with a handlebars template
Does ember-data have a built in way of retrieving data? If not, where
should the AJAX request be implemented?
What is the best way to insert the JSON data into the DS model?
What is the best way to then sync the models up with a Controller?
Any examples that implement all of the 4 steps would also be very helpful, since I can't seem to find any.
<edit>
Like I said in the comments, this questions asks a lot at once, so to follow up, here's a work in progress fiddle: http://jsfiddle.net/schawaska/dWcUp/
This is not 100%, but covers some of your questions. It uses the FixtureAdapter.
I'll be updating it as I find time.
</edit>
1 Make ajax request to retrieve JSON data from a PHP script
Ember-Data will take care of that for you. Consider the following:
window.App = Em.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.RESTAdapter'
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
imageUrl: DS.attr('string')
})
The code above defines a data store (almost like an ORM) within your client app. Ember uses convention over configuration (heavily), so as per configuration this code expects your backend to have a resource in the same domain as /products which talks to GET, POST, PUT and DELETE.
2 Insert that information into DataStore Models
Considering what I said above, by calling one of the following:
App.store.find(App.Product) or App.Product.find()
EmberData will fire a GET request through AJAX targeting your resource /products, and if you say App.Product.find(1), it will target /products/1.
Your app store will use its adapter and the adapter's serializer to translate the JSON result and materialize its data into your App.Product model.
3 Store those models within a controller
This is done when defining your application router. Regardless of what you do, Ember will run its own workflow, but it provides you several hooks, giving you the control of that specific action. Consider the following:
App.Router.map(function() {
this.resource('products');
});
App.ProductsRoute = Ember.Route.extend({
model: function() {
return App.Product.find();
}
});
The code above populates the model used in the products route (which you would access at http://yourdomain.com/#/products). Internally it will generate the code for your ProductsController, or you can define your own, which should extend ArrayController.
Controllers will have a content property which is an alias to the model or model collection. Again, convention over configuration.
4 Display the information using {{#each}} with a handlebars template
Providing you're following the conventions, in your handlebars template, you should iterate through your collection like this:
{{#each product in controller}}
{{#linkTo 'product' product}}
{{product.name}}
{{/linkTo}}
{{/each}}
Does ember-data have a built in way of retrieving data? If not, where
should the AJAX request be implemented?
Yes, simply call App.Product.find() for a product model and it return you a blank ModelArray while firing the AJAX request to the products resource in your backend, then materialize/populate your data into each model once it receives the data back from the server.
What is the best way to insert the JSON data into the DS model?
You shouldn't be concerned about this if you're using ember-data. The framework does that for you in most cases. That's why we love it. You might, however, have to configure mapping, namespace and plurals depending on your backend.
What is the best way to then sync the models up with a Controller?
Something similar to this:
var product = App.Product.createRecord({
name: 'laptop',
imageUrl: 'path/to/image.png'
});
product.save();
This should fire a POST or PUT request to your backend API.
You should definitely check:
http://emberjs.com/guides/
https://peepcode.com/products/emberjs
http://toranbillups.com/blog/archive/2013/01/03/Intro-to-ember-js-and-the-new-router-api/
Making the AJAX request
// Find all pets.
var pets = App.Pet.find();
// Find pet with ID of 1.
var pet = App.Pet.find(1);
Into DataStore Models
pets above will be a DS.RecordArray containing many App.Pet models, whereas pet is just one App.Pet.
Store in Controller
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Pet.find(4);
}
});
The router is used to setup the controller, and so we specify here that the IndexController should hold one App.Pet with the ID of 4. This can of course be dynamic. Since your controller represents only one model, it should be of the type ObjectController, but if it was used to store many pets, then it should be of the type ArrayController.
By specify the model, you will have access to it in your IndexController and index view (data-template-name="index"). This is because when you move into your index route, the IndexController is located/instantiated, IndexView is instantiated and placed into the DOM, all after consulting the IndexRoute for setting up the controller.
You can now do something like this in your template (although model. is not necessary): {{model.name}}, which will get you your pet's name.
Display using #each
Find all your pets using a modified version of the above code, but returning all of the pets. Remember, this is done by specifying no arguments to your find method:
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Pet.find();
}
});
Now we can do loop through all of the pets in the index template. Whilst there are many ways to loop, such as including content./model., excluding .content/model, using this, controller, et cetera..., it's not necessary, but that's for another day. What matters at the moment is that this will work for you, and will be the most self-intuitive:
{{#each pet in controller}}
{{pet.name}}
{{/each}}
I'll put together a jsFiddle for this if you would like. Please let me know.
Questions
Does ember-data have a built in way of retrieving data? If not, where
should the AJAX request be implemented?
Yes, that's Ember Data module which has some good guides on EmberJS.com.
What is the best way to insert the JSON data into the DS model?
Using Ember Data as per the examples up above.
What is the best way to then sync the models up with a Controller?
Using the model hook in the appropriate route to specify which model(s) your controller represents.
I am building MVC application and would like to put caching in my application. I have read about caching that you need to just put [OutputCache(Duration=60, VaryByParam="none")] above controller method and it will work. However in my case i would like to cache not for whole controller methods but inner methods which would call from index controller and which will return IEnumerable<SelectListItem> and that result i want to cache.
But caching is only work for controller methods which will result view result, i have also searched for caching for non view methods and found some MVCDonutCaching and read this articles and by installing tried this also as per below
[DonutOutputCache(Duration=60, VaryByParam="none")]
public IEnumerable<SelectListItem> GetRegionList()
{
Region region = new Region();
return region.GetRegionsList();
}
But not luck to achieve my goal! have anyone done this type of caching here? then please help me to achieve this things.
Thanks in Advance.
I believe your problem is not with mvc itself but with caching model data. Caching your controller action is like forming static page and saving it in IIS cache. Every time user asks for the "Home/Index" (for example), the IIS takes well-formed page (html) from cache without executing any code.
What you're trying to do is cache model data. That's quite a different thing since it has nothing to do with IIS and html page forming. You could use existing solutions like:
Unity Application Block - http://msdn.microsoft.com/en-us/library/ff649102.aspx
Or this post from stackexchange: https://softwareengineering.stackexchange.com/questions/35709/recommendations-for-a-net-distributed-caching-framework
I see people using Html.ActionLink() and Url.RouteUrl() etc. etc.
But surely this will lead to a maintenance nightmare if routes need to be redesigned?
How are people organising the generation of URLs in a typesafe and manageable way?
Strongly typed URL generation via lambda expressions was available for a period of time during the MVC 1.0 beta timeframe. It was removed since the MVC architecture does not actually have a 1-to-1 mapping between action names and controller method names. See this Phil Haack blog post for details.
It is of course still possible to do it, and assuming you're not using action names that differ from method names, it should work fine.
You can use T4MVC to generate typesafe checks at compile time for your MVC urls.
T4MVC analyses your Controller classes, and generates code that will generate typesafe url's.
Instead of
#Html.ActionLink("New customer", "Create", new { Controller = "Customer", orgID = orgID })
You can use code like:
#Html.ActionLink("New customer", MVC.Customer.Create(orgID))
If you want to call a action you use the Html.ActionLink(). This will create a <a href="..." ></a> hyperlink to chosen action.
If you want to create a url and use it not for a hyperlink, you can use the Url.Content() or the Url.RouteUrl(). The content accepts a string and gerenates a safe url. The Route url takes a route object.