Jun
10
2012

Bundling Javascript and CSS

One of the very cool new features with MVC 4 is the ability to bundle all your JavaScript files. Suppose we have a new application with custom JavaScript files as shown below.   We can add these js-files into one bundle, this bundle will compress and minify the js-files into one single file and therefor will be an improvement to speed and performance of our site. In the Application_Start method in Global.asax we will add the following code to create the new bundle. ScriptBundle appBundle = new ScriptBundle("~/bundles/appscripts"); appBundle.IncludeDirectory("~/scripts/app/", "*.js"); BundleTable.Bundles.Add(appBundle); We are using the ScriptBundle for the js-files. The ScriptBundle by default uses the JsMinify transformation. After this we can use the bundle in our views by adding the following call in our _layout.cshtml. @Scripts.Render("~/bundles/appscripts") One of the very nice new features in 2012 RC is the way these scripts get rendered. When we have set <compilation debug="true" /> in the Web.config the scripts get rendered as shown below. <script src="/scripts/app/app.layoutmanager.js" type="text/javascript"></script> <script src="/scripts/app/app.startup.js" type="text/javascript"></script> When debug="false" it gets rendered as this <script src="/bundles/appscripts?v=GTx53orclSDCOjQqfBxLBeKakyF6SDyq4FEopF52glE1" type="text/javascript"></script> With these 2 ways of rendering, our released app will only use a bundled and minified version of our js-files. But during debugging the files aren't minified and therefor we can still easily debug our js-files.   Back in the Beta-day i used to achieve the same by adding the bundle as followed. #if DEBUG Bundle appBundle = new Bundle("~/Scripts/App/js"); #else Bundle appBundle = new Bundle("~/Scripts/App/js", new JsMinify()); #endif appBundle.AddDirectory("~/Scripts/App", "*.js", searchSubdirectories: true); appBundle.AddFile("~/Scripts/App/app.string.extensions.js"); bundleCollection.Add(appBundle); With this approach i used compiler directives to determine when i wanted the js-files to be minified and when not to. Thankfully, not minifying during debug is now supported out of the box.   Another nice new feature with bundles is the abillity to determine the order in which the files are loaded. By default a bundle will use the DefaultBundleOrderer, this will render the scripts in the same order as you added them to to bundle. You can override this behaviour by creating a new class that implements the IBundleOrderer interface. public class AppJsOrderer : IBundleOrderer { public IEnumerable<System.IO.FileInfo> OrderFiles(BundleContext context, IEnumerable<System.IO.FileInfo> files) { return files.OrderBy(f => f.Name == "app.startup.js" ? -1 : 1); } } This is just a simple example that always makes sure the app.startup.js file is the first script to be used. A real world example would probably be a little more sophisticated than this one. And then we can use this with our bundle. appBundle.Orderer = new AppJsOrderer();   All of this can also be used for your css-files. The only difference is that you need to use the StyleBundle class instead of the ScriptBundle.
May
21
2012
MVC4 // SPA

Building a Single Page Application without Entity Framework - Part II

In the previous post we showed how we can use Upshot with the DataController instead of the DbDataController.   Suppose in the previous example a customer can also have several orders. We would add an Order POCO class to our project that would look like this: public class Order { [Key] public int Id { get; set; } public DateTime CreatedDate { get; set; } [Required] public virtual int CustomerId { get; set; } [Association("Customer_Order", "CustomerId", "Id", IsForeignKey = true)] public virtual Customer Customer { get; set; } public Order() { CreatedDate = DateTime.Now; } }  This class also has an Id property with the Key-attribute, this is the same as in the Customer class. The CustomerId and Customer properties are used to store the reference to the appropriate customer. Using the Association-attribute, in System.ComponentModel.DataAnnotations namespace, we can tell what the relationship between Customer and Order is. The first parameter is the unique name for this relationship and the second and third parameters define the key in the current class and the referenced class. So "CustomerId" is the mapping for the Order.CustomerId property and "Id" is the mapping for the Customer.Id property. With this association defined upshot will know what the relationship between Customer and Order is.   As an example, suppose we add a view where the user can create an new order and select the customer for it in a dropdown. We would first add a new ViewModel to our project. function Order(initialData) { var self = this; upshot.map(initialData, upshot.type(Order), self); self.CustomerText = ko.computed(function () { if (self.Customer()) { return self.Customer().FirstName() + ' ' + self.Customer().LastName(); } else { return "(None)"; } }); } function OrdersViewModel() { var self = this; self.customerDataSource = upshot.dataSources.Customers.refresh(); self.customers = self.customerDataSource.getEntities(); self.orderDataSource = upshot.dataSources.Orders.refresh(); self.orders = self.orderDataSource.getEntities(); self.newOrder = ko.observable(new Order({})); self.orderValidationRules = $.extend({}, self.orderDataSource.getEntityValidationRules(), { submitHandler: function () { self.orders.push(self.newOrder()); self.newOrder(new Order({})); } }); } There are a couple of things going on in this ViewModel. First we add the Order object which uses the upshot.map function to create the new object with all Order properties mapped to knockout observables. We've added an additional knockout computed property CustomerText to make sure we can always display something in the UI even when no customer is assigned to the order.   The OrdersViewModel itself uses two upshot DataSources for Customer and Order. Next it creates a newOrder observable and initializes it with an empty Order object. This object will be used in the OrderAddForm and will be populated by the user. After this there is some code for validating the OrderAddForm. Upshot looks at the validation Attributes (like Required, StringLength, Range etc) on our POCO's to build these rules. We've added the RequiredAttribute on CustomerId in the Order class. The UpshotContext has read this attribute and has generated the appropriate JavaScript upshot needs on the client to enable validation. Using JQuery Extend we extend the orderDataSource.getEntityValidationRules (which contains the the validationRules for an order object) with an SubmitHandler. This SubmitHandler will be triggered after validation and only when the form is valid. We then push the newOrder into our orders array and reset the newOrder observable so it can be reused when the user wants to add an additional order.   With this ViewModel completed we can then add a new View to our project. <form id="OrderAddForm" action="#" data-bind="validate: $root.orderValidationRules, with: $root.newOrder"> <table> <tbody> <tr> <td>Customer</td> <td> <select name="CustomerId" data-bind="value: CustomerId, options: $root.customers, optionsText: 'LastName', optionsValue: 'Id', optionsCaption: 'Choose...', autovalidate: true"></select> </td> </tr> </tbody> </table> <button type="submit">Add</button> </form> <ul data-bind="foreach: orders"> <li> <div>Name: <strong data-bind="text: CustomerText"></strong></div> </li> </ul> @(Html.UpshotContext(bufferChanges: true) .DataSource<MvcApplication27.Controllers.CustomerServiceController>(x => x.GetCustomers()) .DataSource<MvcApplication27.Controllers.OrderServiceController>(x => x.GetOrders()) .ClientMapping<MvcApplication27.Models.Order>("Order")) <script src="~/Scripts/OrdersViewModel.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { var viewModel = new OrdersViewModel(); ko.applyBindings(viewModel); }); </script> First defined in the View is a Form for the newOrder. Using knockout bindings for the newOrder and validating using the orderValidationRules defined in our ViewModel, assigning the Validate parameter with the orderValidationRules will automatically show validation-errors using unobstrusive validation. The dropdown in the form uses the knockout options binding as explained here. Most important thing to notice are the Value and OptionValue parameters. The Value parameters maps to the CustomerId property of newOrder observable, the OptionValue parameter maps to the Id property of the selected customer in the dropdown. Knockout will automatically update the CustomerId in newOrder with the Id of the selected Customer. With the AssociationAttribute we defined in our Order POCO class, the Customer property of newOrder will also be updated automatically. If this attribute would not have been set, the Customer property would not have been set automatically and we would not be able to show the customer's name. Also upshot would fail when we submit the changes to the server.   Next in the view is a bit of knockout so we can render all the newly added orders and we can see that the Customer property has been set correctly. The UpshotContext has the two DataSources for Customers and Orders. Additional is the ClientMapping. This is used so upshot will know it should use the Order function in our ViewModel to create a clientside object for a Mvcapplication27.Models.Order instance received from the server.   Attached to this post is a sample application that shows the scenarios described in this and the previous post. As a test you could remove the AssociationAttribute in the Order class to see what happens when adding a new order and upshot does not know about the mapping between Customer and Order. MvcApplication27.rar (5,27 mb)
May
15
2012
MVC4 // SPA

Building a Single Page Application without Entity Framework - Part I

A new feature in MVC 4 Beta is the ASP.NET Single Page Application. SPA provides a very nice way of delivering high-end websites with amazing client-side interaction. On Steven Sanderson's blog there is a great post about SPA with a very nice DeliveryTracker sample and on the ASP.NET SPA website there are several good walkthroughs and samples explaining how to build common scenarios with SPA. These samples, however, all show how you can build a SPA site with Entity Framework as the database back-end. This post will explain how you can use SPA (or upshot to be specific) without Entity Framework.   A part of SPA is the upshot JavaScript library which provides data access and caching. Upshot uses the new WebAPI for retrieving and saving the data. The UpshotContext HtmlHelper is provided, which generates the necessary JavaScript that upshot needs to be able to use the WebAPI datasource you specified. Below shows the UpshotContext as it is used in the Home view in the DeliveryTracker sample from Steven Sanderson. Html.UpshotContext(bufferChanges: true).DataSource<DataServiceController>(x => x.GetDeliveriesForToday())   Suppose we have a simple Customer POCO. public class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } If we want upshot to retrieve a list of Customers, we need to add a WebAPI Controller to our application and use this in the UpshotContext in our view. The UpshotContext expects a TDataController where TDatacontroller is of type System.Web.Http.Data.DataController, which derives from ApiController. In the existing samples i found, this TDataController is always of type System.Web.Http.Data.EntityFramework.DbDataController, which means it is using EF.   When we implement the new controller and derive it from DataController, we are not using any EF specific classes, so we're not using EF at all. namespace MvcApplication27.Controllers { public class CustomerServiceController : DataController { public IEnumerable<Customer> GetCustomers() { List<Customer> result = new List<Customer>(); result.Add(new Customer() { Id = result.Count + 1, FirstName = "John", LastName ="Doe" }); result.Add(new Customer() { Id = result.Count + 1, FirstName = "Jane", LastName = "Doe" }); return result; } } } Off course in a real-world application the list of Customers will not be recreated each time in the GetCustomers method, but will be retrieved from some kind of storage/database.   At this time we can add the UpshotContext to our view. Html.UpshotContext().DataSource<MvcApplication27.Controllers.CustomerServiceController>(x => x.GetCustomers()) But now, when we run our application, the UpshotContext will give us this error : "queryOperation 'GetCustomers' must return an entity type or an IEnumerable/IQueryable of an entity type"   After digging through the MVC 4 Beta sourcecode (available here) i found out that this error is occuring because upshot doesn't know what the 'identity' property of Customer is. Thankfully we can tell upshot what property this is by using the System.ComponentModel.DataAnnotations.KeyAttribute in our Customer POCO. public class Customer { [Key] public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }   After this we can add a small ViewModel to our project. function CustomersViewModel() { var self = this; self.dataSource = upshot.dataSources.Customers.refresh(); self.customers = self.dataSource.getEntities(); }   And add a bit of Knockout.js to our view and we're done @(Html.UpshotContext().DataSource<MvcApplication27.Controllers.CustomerServiceController>(x => x.GetCustomers())) <h2>Customers</h2> <ul data-bind="foreach: customers"> <li> <div>Name: <em data-bind="text: FirstName"></em> <strong data-bind="text: LastName"></strong></div> </li> </ul> <script src="~/Scripts/CustomersViewModel.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { ko.applyBindings(new CustomersViewModel()); }); </script>   The next post will explain what needs to be done to have upshot understand relationships between POCO's and how to handle CRUD operations. It will continue were this post ends and will add Order, Product and OrderProduct POCO's so we have one-to-many and many-to-many relationships.

About me

I'm Marco, Developer to the bone living in The Netherlands..

The name Blue Basher is coming from the fact that i break a keyboard at least once a year.. Smile