Stop Episerver From Setting Cache-Control to Private
By default Episerver sets the header for Cache-Control to Private. You can see this by running curl
curl -kv -o /dev/null http://world.episerver.com/articles/items/relate-intranet-demo-site/
and the following is returned:
> HTTP/1.1 200 OK > Cache-Control: private > Content-Type: text/html; charset=utf-8 > ETag: "" > Server: Microsoft-IIS/7.5
This is a big deal if you want to put your site behind a CDN as the Cache-Control:private is telling your CDN not to cache the site. I got around this by adding the following to my base page.
var cache = System.Web.HttpContext.Current.Response.Cache; cache.SetCacheability(HttpCacheability.Public); cache.SetExpires(DateTime.UtcNow.AddMinutes(15));
This will set the cache-control header to public and sets the expiry to 15 minutes so it expires in the CDN every 15 mins.
TOTD Unit Testing cartHelper
TOTD stands for think of the developer and I feel its something that the commerce team should have as an objective. As a company, we are new to Episerver Commerce but it seems that we have to do way too much work to unit test our commerce code. This article will describe how we went about unit testing update quantity cart code. So lets look at the code to update the quantity of an item in the basket
<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming var lineItem = CartHelper .LineItems .FirstOrDefault(l => l.CatalogEntryId == skuId); if (lineItem == null) return; lineItem.Quantity = quantity; CartHelper.AcceptChanges(); </code>
ok, so we need to mock CartHelper, but its a static method, so we need to write a cart wrapper and inject that into the class we want to test.
<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming using System.Collections.Generic; using System.Linq; using Mediachase.Commerce.Catalog.Objects; using Mediachase.Commerce.Orders; using Mediachase.Commerce.Website.Helpers; namespace GMGShop.Domain.Wrappers { /// <summary> /// Cart Helper Wrapper so that we can test /// </summary> public class CartHelperWrapper : ICartHelperWrapper { private CartHelper cartHelper; /// <summary> /// Gets a value indicating whether this instance is empty. /// </summary> /// <value> /// <c>true</c> if this instance is empty; otherwise, <c>false</c>. /// </value> public bool IsEmpty { get { return cartHelper.IsEmpty; } } /// <summary> /// Gets the total. /// </summary> /// <value> /// The total. /// </value> public decimal Total { get { return cartHelper.Cart.Total; } } /// <summary> /// Sets the provider id. /// </summary> /// <value> /// The provider id. /// </value> public string ProviderId { set { cartHelper.Cart.ProviderId = value; } } /// <summary> /// Gets the line items. /// </summary> /// <value> /// The line items. /// </value> public IEnumerable<ILineItemWrapper> LineItems { get { return cartHelper .LineItems .Select(lineItem => new LineItemWrapper(lineItem)); } } /// <summary> /// Initializes a new instance of the <see cref="T:Mediachase.Commerce.Website.Helpers.CartHelper"/> class. /// </summary> /// <param name="cartName"> /// The cart Name. /// </param> /// <returns> /// The <see cref="ICartHelperWrapper"/>. /// </returns> public ICartHelperWrapper Create(string cartName) { cartHelper = new CartHelper(cartName); return this; } /// <summary> /// Accepts the changes. /// </summary> /// <exception cref="T:Mediachase.Commerce.Orders.Exceptions.OrderException">If a cart with same CustomerId, Name, and MarketId already exist.</exception> public void AcceptChanges() { cartHelper.Cart.AcceptChanges(); } /// <summary> /// Adds the entry with default warehouse code /// </summary> /// <param name="fullEntry"> /// The full Entry. /// </param> /// <param name="quantity"> /// The quantity. /// </param> /// <param name="fixedQuantity"> /// If true, lineitem's qty will be set to <paramref name="quantity"/> value. Otherwise, <paramref name="quantity"/> will be added to the current line item's qty value. /// </param> /// <returns> /// The line item. /// </returns> public LineItem AddEntry(Entry fullEntry, int quantity, bool fixedQuantity) { return cartHelper.AddEntry(fullEntry, quantity, fixedQuantity); } /// <summary> /// Runs the workflow and generates the error message for all the warnings. /// </summary> /// <param name="name">The name.</param> public void RunWorkFlow(string name) { cartHelper.RunWorkflow(name); } /// <summary> /// Deletes the current basket instance from the database. /// </summary> public void Delete() { cartHelper.Delete(); } } }</code></pre> <pre class="Plum_Code_Box">
You can see that we have to also wrap the line items in its own wrapper, so lets do that.
<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming using System; using System.Runtime.Serialization; using Mediachase.Commerce.Orders; namespace GMGShop.Domain.Wrappers { public class LineItemWrapper : ILineItemWrapper { private readonly LineItem lineItem; /// <summary> /// Initializes a new instance of the <see cref="LineItemWrapper"/> class. /// </summary> /// <param name="lineItem"></param> public LineItemWrapper(LineItem lineItem) { this.lineItem = lineItem; } /// <summary> /// Initializes a new instance of the <see cref="LineItemWrapper"/> class. /// </summary> public LineItemWrapper() { lineItem = new LineItem(); } /// <summary> /// Gets the list of discounts that were applied to that particular line item. /// </summary> /// <value> /// The discounts. /// </value> public LineItemDiscountCollection Discounts { get { return lineItem.Discounts; } } /// <summary> /// Gets the parent Order Form. /// </summary> /// <value> /// The parent. /// </value> public OrderForm Parent { get { return lineItem.Parent; } } /// <summary> /// Gets the line item id. /// </summary> /// <value> /// The line item id. /// </value> public int LineItemId { get { return lineItem.LineItemId; } } /// <summary> /// Gets or sets the order form id. /// </summary> /// <value> /// The order form id. /// </value> public int OrderFormId { get { return lineItem.OrderFormId; } set { lineItem.OrderFormId = value; } } /// <summary> /// Gets or sets the order group id. /// </summary> /// <value> /// The order group id. /// </value> public int OrderGroupId { get { return lineItem.OrderGroupId; } set { lineItem.OrderGroupId = value; } } /// <summary> /// Gets or sets the catalog. /// </summary> /// <value> /// The catalog. /// </value> public string Catalog { get { return lineItem.Catalog; } set { lineItem.Catalog = value; } } /// <summary> /// Gets or sets the catalog node. /// </summary> /// <value> /// The catalog node. /// </value> public string CatalogNode { get { return lineItem.CatalogNode; } set { lineItem.CatalogNode = value; } } /// <summary> /// Gets or sets the parent catalog entry id. Typically for Product/Sku(Variation) types of products, this will be a product code. /// </summary> /// <value> /// The parent catalog entry id. /// </value> public string ParentCatalogEntryId { get { return lineItem.ParentCatalogEntryId; } set { lineItem.ParentCatalogEntryId = value; } } /// <summary> /// Gets or sets the catalog entry code. /// </summary> /// <value> /// The catalog entry id. /// </value> public string CatalogEntryId { get { return lineItem.CatalogEntryId; } set { lineItem.CatalogEntryId = value; } } /// <summary> /// Gets or sets the quantity. /// </summary> /// <value> /// The quantity. /// </value> public Decimal Quantity { get { return lineItem.Quantity; } set { lineItem.Quantity = value; } } /// <summary> /// Gets or sets the min quantity. /// </summary> /// <value> /// The min quantity. /// </value> public Decimal MinQuantity { get { return lineItem.MinQuantity; } set { lineItem.MinQuantity = value; } } /// <summary> /// Gets or sets the max quantity. /// </summary> /// <value> /// The max quantity. /// </value> public Decimal MaxQuantity { get { return lineItem.MaxQuantity; } set { lineItem.MaxQuantity = value; } } /// <summary> /// Gets or sets the placed price. /// </summary> /// <value> /// The placed price. /// </value> public Decimal PlacedPrice { get { return lineItem.PlacedPrice; } set { lineItem.PlacedPrice = value; } } /// <summary> /// Gets or sets the list price. The price that the item is listed in the catalog. /// </summary> /// <value> /// The list price. /// </value> public Decimal ListPrice { get { return lineItem.ListPrice; } set { lineItem.ListPrice = value; } } /// <summary> /// Gets or sets the line item discount amount. /// </summary> /// <value> /// The line item discount amount. /// </value> public Decimal LineItemDiscountAmount { get { return lineItem.LineItemDiscountAmount; } set { lineItem.LineItemDiscountAmount = value; } } /// <summary> /// Gets or sets the order level discount amount. /// </summary> /// <value> /// The order level discount amount. /// </value> public Decimal OrderLevelDiscountAmount { get { return lineItem.OrderLevelDiscountAmount; } set { lineItem.OrderLevelDiscountAmount = value; } } /// <summary> /// Gets or sets the shipping address name. /// </summary> /// <value> /// The shipping address id. /// </value> public string ShippingAddressId { get { return lineItem.ShippingAddressId; } set { lineItem.ShippingAddressId = value; } } /// <summary> /// Gets or sets the name of the shipping method. /// </summary> /// <value> /// The name of the shipping method. /// </value> public string ShippingMethodName { get { return lineItem.ShippingMethodName; } set { lineItem.ShippingMethodName = value; } } /// <summary> /// Gets or sets the shipping method id. /// </summary> /// <value> /// The shipping method id. /// </value> public Guid ShippingMethodId { get { return lineItem.ShippingMethodId; } set { lineItem.ShippingMethodId = value; } } /// <summary> /// Gets or sets the extended price. /// </summary> /// <value> /// The extended price. /// </value> public Decimal ExtendedPrice { get { return lineItem.ExtendedPrice; } set { lineItem.ExtendedPrice = value; } } /// <summary> /// Gets or sets the description. /// </summary> /// <value> /// The description. /// </value> public string Description { get { return lineItem.Description; } set { lineItem.Description = value; } } /// <summary> /// Gets or sets the status. /// </summary> /// <value> /// The status. /// </value> public string Status { get { return lineItem.Status; } set { lineItem.Status = value; } } /// <summary> /// Gets or sets the name of the display. /// </summary> /// <value> /// The name of the display. /// </value> public string DisplayName { get { return lineItem.DisplayName; } set { lineItem.DisplayName = value; } } /// <summary> /// Gets or sets a value indicating whether [allow backorders and preorders] /// </summary> /// <value> /// <c>true</c> if [allow backorders and preorders]; otherwise, <c>false</c>. /// </value> public bool AllowBackordersAndPreorders { get { return lineItem.AllowBackordersAndPreorders; } set { lineItem.AllowBackordersAndPreorders = value; } } /// <summary> /// Gets or sets the in stock quantity. /// </summary> /// <value> /// The in stock quantity. /// </value> public Decimal InStockQuantity { get { return lineItem.InStockQuantity; } set { lineItem.InStockQuantity = value; } } /// <summary> /// Gets or sets the preorder quantity. /// </summary> /// <value> /// The preorder quantity. /// </value> public Decimal PreorderQuantity { get { return lineItem.PreorderQuantity; } set { lineItem.PreorderQuantity = value; } } /// <summary> /// Gets or sets the backorder quantity. /// </summary> /// <value> /// The backorder quantity. /// </value> public Decimal BackorderQuantity { get { return lineItem.BackorderQuantity; } set { lineItem.BackorderQuantity = value; } } /// <summary> /// Gets or sets the inventory status. /// </summary> /// <value> /// The inventory status. /// </value> public int InventoryStatus { get { return lineItem.InventoryStatus; } set { lineItem.InventoryStatus = value; } } /// <summary> /// Gets or sets the line item ordering. /// </summary> /// <value> /// The line item ordering. /// </value> public DateTime LineItemOrdering { get { return lineItem.LineItemOrdering; } set { lineItem.LineItemOrdering = value; } } /// <summary> /// Gets or sets the configuration id. The external component configuration id, used by bundle, kits and other /// combination products. /// </summary> /// <value> /// The configuration id. /// </value> public string ConfigurationId { get { return lineItem.ConfigurationId; } set { lineItem.ConfigurationId = value; } } /// <summary> /// Gets or sets the provider id. Used to identify the line item in the extrnal system. /// </summary> /// <value> /// The provider id. /// </value> public string ProviderId { get { return lineItem.ProviderId; } set { lineItem.ProviderId = value; } } /// <summary> /// Gets or sets the RMA item reason. ("Corrupted", "Mismatch" etc.) /// </summary> /// <value> /// The rma reason. /// </value> public string ReturnReason { get { return lineItem.ReturnReason; } set { lineItem.ReturnReason = value; } } /// <summary> /// Gets or sets the returned in stock quantity /// </summary> /// <value> /// The return quantity. /// </value> public Decimal ReturnQuantity { get { return lineItem.ReturnQuantity; } set { lineItem.ReturnQuantity = value; } } /// <summary> /// Gets or sets the identity orig line item for RMA line item /// </summary> /// <value> /// The rma orig line item id. /// </value> public int? OrigLineItemId { get { return lineItem.OrigLineItemId; } set { lineItem.OrigLineItemId = value; } } /// <summary> /// Gets or sets the warehouse code /// </summary> /// <value> /// The warehouse code. /// </value> public string WarehouseCode { get { return lineItem.WarehouseCode; } set { lineItem.WarehouseCode = value; } } /// <summary> /// Gets or sets a value indicating whether if /// the inventory for this item has already been allocated from /// the inventory system /// </summary> /// <value> /// The is inventory allocated. /// </value> public bool IsInventoryAllocated { get { return lineItem.IsInventoryAllocated; } set { lineItem.IsInventoryAllocated = value; } } /// <summary> /// Gets or sets the old quantity. /// </summary> /// <value> /// The old quantity. /// </value> public Decimal OldQuantity { get { return lineItem.OldQuantity; } set { lineItem.OldQuantity = value; } } /// <summary> /// Sets the parent. /// </summary> /// <param name="parent"> /// The parent. /// </param> public void SetParent(object parent) { lineItem.SetParent(parent); } /// <summary> /// Accepts the changes. /// </summary> public void AcceptChanges() { lineItem.AcceptChanges(); } /// <summary> /// Gets the object data. /// </summary> /// <param name="info"> /// The info. /// </param> /// <param name="context"> /// The context. /// </param> public void GetObjectData(SerializationInfo info, StreamingContext context) { lineItem.GetObjectData(info, context); } /// <summary> /// Deletes the object data /// </summary> public void Delete() { lineItem.Delete(); } } }</code></pre> <pre class="Plum_Code_Box">
So now we can finally write our test.
<pre class="Plum_Code_Box"><code class="csharp">// Copyright 2015 Green Man Gaming CartWrapper = new Mock<ICartHelperWrapper>(); factoryMock.Setup(x => x.GetCartHelper()).Returns(CartWrapper.Object); var firstLineItem = new Mock<ILineItemWrapper>(); firstLineItem .SetupGet(x => x.DisplayName) .Returns("My Test Product"); firstLineItem .SetupGet(x => x.CatalogEntryId) .Returns("Borderlands - PC"); firstLineItem .SetupGet(x => x.Quantity) .Returns(BorderlandsQnty); firstLineItem .SetupSet(x => x.Quantity = It.IsAny<Decimal>()) .Callback<Decimal>(value => BorderlandsQnty = value); LineItems = new List<ILineItemWrapper> { firstLineItem.Object }; CartWrapper.SetupGet(x => x.LineItems).Returns(LineItems); CartWrapper.Setup(x => x.AcceptChanges()); new BasketHelper(factoryMock.Object); //The test BasketHelper.UpdateLineItemQuantity("Borderlands - PC", 3); CartWrapper .Verify(x => x.AcceptChanges(), Times.Once); Assert.AreEqual(3, BorderlandsQnty);</code></pre> <pre class="Plum_Code_Box">
In essence to test 7 lines of code we have to write over 700 lines of wrapper code. If the team had spent 30 minutes to write interfaces around the two classes, it would have saved a lot of time wrapping the classes. This is just one example of some of the extra work we have to go to test commerce code.
If you fancy helping us with our project, we are currently hiring a full time EpiServer Engineer. Send your CV to [email protected]
Failstarter
I love kickstarter and have funded a few things. My first one came through the other day, Pi Crust. The problem is that there seems to be the odd one that is coming through that don’t seem to put any effort and expert to raise cash. The worst I have seen, came up today(Home-School Quizzer).
If you have ever written a visual basic project you will know that Form1 is always the title. It is obviose that this demo screen was completed after page one of the learn vb manual and he wants to raise $5,600. Look at the app picture, most peoples hello world is better than that. The guy couldn’t be bothered to set the title of the app, he couldn’t even be bothered to write a question in the form all he writes is dog. I do hope this is some sort of joke but deep down I think its a serious bid for funding:(
Do developers make things hard for themselves and fellow developers
Today has made me wonder if we are doing something wrong as developers. This weekend I gave myself two tasks. The first was to replace some immersion heaters. (Ours had stopped working and we have been without hot water for a couple of weeks. The shower and washing machine heat their own water so it hadn’t been a huge priority.) The second was to do some more work on a private project to try and get my head more around windsor, nhibernate, etc.
The immersion heater was relatively painless. I found, http://www.ultimatehandyman.co.uk/plumb_immersion_heater.htm on the internet which armed me with enough knowledge to go to screwfix direct and buy the bits. So the process was:
1. Buy bits.
2. Unwire old heaters
3. Remove heaters (this took a while as they didn’t want to budge)
4. Screw new heaters in.
5. Wire them back up.
6. Try and plug leak with sealant
The whole process was relatively painless and I never felt like I didn’t know what I was doing or felt like hitting my head against a wall. Now the second task my private project, wire up automapper and write data to a repository. I see automapper as a component, just like the immersion heater. If you don’t know Automapper is an awsome library that will try its best to map from one object to another without having to say object1.FieldX maps to object2.FieldX.
So the first step to get automapper to work is to register the mappers. Some googling later brings up this suggestion:
public static class AutoMapperRegistrar { public static void RegisterMappings() { var autoMapperClasses = Assembly.GetAssembly(typeof(AutoMapperRegistrar)).GetTypes().Where(t => String.Equals(t.Namespace, "restaurantBooker.Automapper.Maps", StringComparison.Ordinal)); foreach (var autoMapperClass in autoMapperClasses) { autoMapperClass.InvokeMember( "CreateMap", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static, null, null, null); } } }
So far so good. I add the relevant classes and I get my basic mapping working. I now have my objects mapping nicely. I try to save my object to the repository only to find I have forgotten to add a resolver to map from an Id to an entity in the database. I had done this at work so a quick google on resolveusing, refreshes my memory and I create a resolver.
public class RestaurantResolver : ValueResolver{ private INHibernateRepository _restaurantRepository; public RestaurantResolver(INHibernateRepository restaurantRepository) { _restaurantRepository = restaurantRepository; } protected override Restaurant ResolveCore(int source) { return _restaurantRepository.Load(source); } }
and update my mapping file
public class Table_KoViewModelsTable : BaseMapper, ITable_KoViewModelsTable { private readonly INHibernateRepository _restaurantRepository; public Table_KoViewModelsTable(INHibernateRepository restaurantRepository) { _restaurantRepository = restaurantRepository; } protected override void CreateMap() { Mapper.CreateMap (); Mapper.CreateMap
() .ForMember(dest => dest.Restaurant, opt => opt .ResolveUsing () .FromMember(src => src.RestaurantId) .ConstructedBy(() => new RestaurantResolver(_restaurantRepository)) ); } } So I run the code and find that _restaurantRepository is null. So the _restaurantRepository should be injected in by Castle Windsor but because I am using reflection to call the createMap, it doesn’t inject. After around an hours worth of googling, I draw blanks so I log onto work and see how we do it there. I then spent around another 40 minutes trying to work out how the create map is working. Eventually I find that all the mappers derive from a base mapper which has a constructor which setups the mappers. During the component registration, castle windsor runs the constructors on each of the classes, thus creating the maps. I then went on to create a similar setup and at last I have it working.
So whats my point?
Well with the immersion heater, the immersion heaters diameters are standard. Wiring the heater up consists of 3 wires, each are standardized in their colours. You can get either 11″ or 22″ in length immersion heaters but its obvious which one will fit in the boiler. Now if we look at the fun with automapper and windsor, we find that we have significantly more ways that we can implement these libraries, not all of which solve all the problems, so we have to play about to try and get the right combination that works for us. I think design patterns goes some way to help us try and standardize our code but this doesn’t help with implementation. When we design our code it is designed to be consumed in different ways. Sometimes I do wonder if we should try and have more standard ways of using libraries. After all if I can fit an immersion heater after reading one web page, it does suggest that as developers we make things difficult for ourselves.
Onlive, My Thoughts
I bought an onlive box,and 2 games for £2 plus delivery. For that price, I thought I had to give it a go. Onlive is moving computer gaming in to the cloud. The idea is that you will never have to upgrade your computer again as all the game processing is done on big computers on the internet and the graphics are sent to your device.
Onlive supports, its own console, PC, and promises to have Ipad and Android support soon. This interests me as it would be good to play my games where ever I have my Ipad and an internet connection. Also any saved games should follow me on my devices. Other people can watch you play and give you a thumbs up or thumbs down, which is a nice touch adding more social to game playing.
After seeing the quality at a gaming show and comparing it to my own, it seems my internet connection isn’t quite up to the job. I rarely get any problems with playing games, the graphics look blocky in comparison to what I saw at the show suggesting they downgrade the graphics for differnet internet connections.
What worries me about Onlive is their business model. They charge the same price for a game as you can buy in a shop although in some cases it can be more expensive. Which would be fine aside from when you purchase a game they say that you are buying this game but it will only be available to play for x period of time. This is understandable as they have to have these games loaded on a hdd somewhere and guaranteeing lifetime access to the game is a bit of a far fetch. However, when you think about this some more, you realise that if you went to the shop and bought that game or purchased it off of Steam, then you would own that game. You can play that for as long as you have a computer that can play it. I hear some people saying, but realistically I only play games for a few years and then they get dusty. Is that necessarily correct though? If that was true, would ebay have so many sales and purchases of old games? I mean you can even buy NES games.
My point is that when you buy a game via Onlive, you are effectively renting it compared to if you bought the game in the shop (remember the price is the same). So why would I do this. Another concern arises when you read the ts and cs where they state that at the moment, membership to their system is free but they reserve the right to charge a monthly fee and you can only play the game if you are a member. So worse still in the future I may only be able to play my game if I pay a monthly fee.
In conclusion I like the idea of online and think it is a very good innovation to the market. I do however beleive that they shouldn’t sell you games at full price but let you rent them, like you can from blockbuster. To me this seems how Onlive will evolve to and when it does, I would be more interested in spending more cash with them but at the moment, buying a game through them isn’t a better deal than buying from a shop.
First Open Source Project – Knockout Creator
Well it looks like this blog is all about firsts at the moment. Today I created my first open source project, Knockout Creator. This is a library that supports Knockout.Js.
What is Knockout.js?
Knockout.js can be found at http://knockoutjs.com/ . Knockout is a Javascript library that brings a MVC approach to Javascript. The idea is that you create a view model in javascript. You can then change the UI dependent on the view model. Its probably easier to understand if you look at a live demo http://knockoutjs.com/examples/twitter.html.
So whats your project do?
The problem I found with knockout is, you end up creating the view model in Javascript and then recreate the same model in c# when you want top pass the view model back to c# for saving or lookup. I hate duplicate code!! So Knockout Creator will generate the Javascript view model for you so the view model exists in just one place.Usage
Your View Model
namespace knockoutExample { public class Restaurant : Knockout.ViewModel { public int RestaurantId { get; set; } public string Name { get; set; } } }Setting up your controller
public string KnockOutJs() { restViewModel = new Restaurant(); koCreator = new Knockout.KoCreator() //Set your controller name koCreator.PageName = "Home"; //Add your viewmodel Restaurant is the name of the model we want in javascript koCreator.AddViewModel("Restaurant", restViewModel.GetType()); //Add a javascript function subscription. This will call the javascript function test() everytime the name is changed koCreator.AddJsSubscription("Name","test"); //This will return the javascript, we pass in the controller so that Knockout Creator can bind subscriptions return koCreator.GenerateJs(this); }Creating a method to be called using AJAX
//Add a custom attribute specifying the variable that when changed will trigger this via AJAX [KoMethod("RestaurantId")] //The method accepts one attribute, our viewmodel. This will be passed back using AJAX public JsonResult GetRestaurantById(knockoutExample.Restaurant viewModel) { //get the restaurantId that has been posted int restaurantId = viewModel.RestaurantId; //Load the record from the db Restaurant restaurant = _restaurantRepository.Load(restaurantId); // set the viewmodel name viewModel.Name = restaurant.Name //return the viewmodel back to the browser return Json(viewModel); }Setting up your view
What gets put in your browser?
So what now?
Every time the RestaurantId is updated a call will be made to the server and will run GetRestaurantById. GetRestaurantId will load the Restaurant from the database and will return the view model back.
How can I get the source?
Get over to github https://github.com/afinzel/KnockoutCreatorArduino Project 1 – LED Binary Counter
I’ve always had an interest in electronics although haven’t been good at it. A couple of my work mates bought http://www.arduino.cc/ and I had to buy one too. This is my first very simple project, a LED binary counter.
The circuit is boring, each LED is plugged up to a different digital out.
The code is a little clever but still simple. Below is a function that converts a number to turn on one of 4 LEDS on. It works by dividing the number by 8,4,2, and 1 in that order. If it is divisible by that number then we turn that light on and deduct it from the original number.
<pre class="Plum_Code_Box"><code class="">void convertIntToBinary(int number) { int divisible; divisible = 8;</code> if (number / 8) { number = number - 8; digitalWrite(LEDVAL8, HIGH); } else { digitalWrite(LEDVAL8, LOW); } if (number / 4) { number = number - 4 ; digitalWrite(LEDVAL4, HIGH); } else { digitalWrite(LEDVAL4, LOW); } if (number / 2) { number = number - 2 ; digitalWrite(LEDVAL2, HIGH); } else { digitalWrite(LEDVAL2, LOW); } if (number / 1) { number = number - 1 ; digitalWrite(LEDVAL1, HIGH); } else { digitalWrite(LEDVAL1, LOW); } //digitalWrite(number, HIGH); }</code> </pre>The main part of the code links up the LEDs and increments our number between 0 and 16(the max we can do with 4 LEDs)
<pre class="Plum_Code_Box"><code class="">#define LEDVAL1 4 #define LEDVAL2 5 #define LEDVAL4 6 #define LEDVAL8 7</code> int counter; void setup() { pinMode(LEDVAL1, OUTPUT); pinMode(LEDVAL2, OUTPUT); pinMode(LEDVAL4, OUTPUT); pinMode(LEDVAL8, OUTPUT); } void loop() { counter = counter + 1; convertIntToBinary(counter); if (counter == 16) { counter = -1; } delay(1000); }</code></pre>The full code:
<pre class="Plum_Code_Box"><code class="">#define LEDVAL1 4 #define LEDVAL2 5 #define LEDVAL4 6 #define LEDVAL8 7</code> int counter; void setup() { pinMode(LEDVAL1, OUTPUT); pinMode(LEDVAL2, OUTPUT); pinMode(LEDVAL4, OUTPUT); pinMode(LEDVAL8, OUTPUT); } void loop() { counter = counter + 1; convertIntToBinary(counter); if (counter == 16) { counter = -1; } delay(1000); } void convertIntToBinary(int number) { int divisible; divisible = 8; if (number / 8) { number = number - 8; digitalWrite(LEDVAL8, HIGH); } else { digitalWrite(LEDVAL8, LOW); } if (number / 4) { number = number - 4 ; digitalWrite(LEDVAL4, HIGH); } else { digitalWrite(LEDVAL4, LOW); } if (number / 2) { number = number - 2 ; digitalWrite(LEDVAL2, HIGH); } else { digitalWrite(LEDVAL2, LOW); } if (number / 1) { number = number - 1 ; digitalWrite(LEDVAL1, HIGH); } else { digitalWrite(LEDVAL1, LOW); } //digitalWrite(number, HIGH); }</code>New Blog
After registering finzel.co.uk, I guess its time to start a blog. In all likeliness, this will end up like everyone elses blog, not updated, and full of comment SPAM. So enjoy what you get and don’t get upset when life gets to busy to update it.
Search
December 2024 M T W T F S S « Jan 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31