Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Thursday, 06 October 2011

Whilst getting my head around Rob Conery’s Massive ORM, I needed to figure out what the dynamic keyword is in C#4:

class Program {
        static void Main(string[] args) {
            Calculator calc = new Calculator();
            int sum = calc.Add(1, 2);
            Console.WriteLine(sum);
            Console.ReadLine();
        }
    }

    public class Calculator {
        public int Add(int a, int b) {
            return a+b;
        }
    }

creation of an object, invokation of a method, and the collection of a return value

Var

Can represent any type that can be determined at compile time.

class Program {
        static void Main(string[] args) {
            //int
            var i = 5;
            //string
            var x = "hello";
            //int[]
            var y = new[] { 0, 1, 2, 3 };
            //anonymous type
            var anon = new { Name = "Dave", Age = 34 };
            //List<int>
            var list = new List<int>();
            //Calculator
            var calc = new Calculator();
            int sum = calc.Add(1, 2);
            Console.WriteLine(sum);
            Console.ReadLine();
        }
    }

    public class Calculator {
        public int Add(int a, int b) {
            return a+b;
        }
    }

 

Dynamic

at runtime you get the type

class Program {
        static void Main(string[] args) {
            //int
            dynamic i = 5;
            //string
            dynamic x = "hello";
            //int[]
            dynamic y = new[] { 0, 2, 3, 4 };
            //anonymous type
            dynamic anon = new {Name="Dave", Age=34};

            Console.WriteLine(i + x);

            dynamic calc = new Calculator();
            //don't get intellisense when press .
            int sum = calc.Add(1, 2);
            Console.WriteLine(sum);
            Console.ReadLine();
        }
    }

    public class Calculator {
        public int Add(int a, int b) {
            return a+b;
        }
    }

 

ExpandoObject and TryInvokeMember

An object whose members can be dynamically added and removed at runtime.

image

can’t do anything with the expandoObject though!

image

Much better.  Saying to the compiler – “Playing with a different set of rules”.

Why doesn’t Product need to by Dynamic?

           dynamic tbl = new DynamicModel("Northwind", "Products", "ProductID");
            //tbl.Single returns a dynamic of type ExpandoOnject, not an ExpandoObject.
            //acutally an IEnumrable<dynamic>.FirstOfDefault()
            var product = tbl.Single();

We don’t need product to be dynamic here as tbl.Single returns a dynamic of type Expando.

tbl has to be dynamic otherwise the TryInvokeMember wont work, as there is no method called Single.

| | # 
# Sunday, 02 October 2011
( Excel | Reporting | VidPub )

 

  • Use data to make decisions
  • Demonstrate validity and value of business idea

more important than tests/design pattern used??!!!

Annual sales

Categories

Tracking sales as well as costs

Good solid reporting makes a business.

Maintaining reporting is hard…. it will get dropped… and others will just login to database and run their own.

  • DevExpress, Dundas, Infragistics, Reporting Services etc…
  • Analysis Services, Cognos.. BI Tools.. Suites
  • Excel

Excel

how to get the data in.

CSV in.

Pump some test data into system.  Skew sales 2:1 for Microsoft videos.

image

Console app in Tasks to pump in test data.

1000 users, sales etc..

image

writing reports.

image

 

no foreign key in

Historical va Transactional Data

OrderItems….called Fact.

sum, average or count

ETL into DataWarehouse.

Star Schema around fact table

 

OLAP – Online Analytical Processor

Can run predictive algorithm

 

OrderItems table..taking ProductID out of OrderItems (historical data)

have tags in there.. but flat.. yuk.. could be subscritpion and yearly

lets have orderType nvarchar(50) – susbscirption or single

can take a while

Costs

cost of selling individual items, and delivery. 

eg how big a file is, and how much Amazon charges. 12cents per Gig.

add to episodes table filesize.

high dev mp4

mobile

tablet

streaming (FLV.. not h264 html5)

so have good test data with monthly, yearly and single subsciption… data transfer rates.

 

Run a tranform on date – quarter and year is fine.

image

group by vendor.

put into a StoredProcedure

image

hmm lets make the price per gig rate historical..and put in somewhere.

Report Viewing

image

Use an Area can view html

Or export to csv

Infrastrucutre/CSVResult

can send back a file result in MVC3.. cool.

image

excel is really good at working with data..

Hmmm better way is live data

Excel Web Query

Data, From Web

reporting/annual/sales

grouping, subtotal

whats selling the most?

what giving the most value?

what is net income for each sale? price-bandwidth

profit margin

 

Pivot table

Roll ups by Author

image

Add Pivot chart so can see which authors are most profitable.

image

so annual subscription is doing better…

Channel eg Linux, MS, RoR

image

which channel is giving most profit.. so can plan what to do next.

Review

Excel with Web Query

now – yes is good

1 year – yes

3 years – probably not.  SQL Analysis services..

 

Getting the correct data in the reporting db is most important bit.

| | # 
# Friday, 30 September 2011
( HighRise | MadMimi | MailChimp | Recurly | SaaS | Shopify | VidPub )

 

  • Sell stuff
  • Manage customers (CRM)
  • Email notifications
  • Reporting

Shopify

hosted storefront.

30 days free.

theme for store

Order mgt, customer mgt

app – Chimpified… adds into mailchimp.

- create new app

- templates use liquid language.

image

this is a product page and is hosted at shopify

image

so is this cart page.

image

Writing Integration Code

give our app the ability to receive pings that shopify will send back eg json

default IIS locally.. port 80 to test.  hmm can’t debug.  use logger.

image

setting up shopify to call back ie have asked shopify to send json post whenever a new order is received.

then can then send test notification from shopify back to local server.

Don’t have orders table.. but use the json sent back by shopify as a guide.

Order Items the same

 

Testing

Just doing a live test against the port.


namespace VidPub.Tests.Functionals {
    [TestFixture]
    public class ShopifyTests {
        Orders _orders;
        OrderItems _items;
        public ShopifyTests() {
            _orders = new Orders();
            _items = new OrderItems();

        }
        string GetJson() {
            var jsonFile = @"C:\dev\VidPub\Source\VidPub.Web\App_Data\ShopifyPing.js";
            var result = "";
            using (var stream = new StreamReader(jsonFile)) {
                result = stream.ReadToEnd();
            }
            return result;
        }
        void Ping(string url, string data) {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/json";

            //add the form data
            byte[] byteArray = Encoding.UTF8.GetBytes(data);
            request.ContentLength = byteArray.Length;

            using (var stream = request.GetRequestStream()) {
                // Write the data to the request stream.
                stream.Write(byteArray, 0, byteArray.Length);
            }

            var response = (HttpWebResponse)request.GetResponse();
            string result = "";

            using (Stream stream = response.GetResponseStream()) {
                StreamReader sr = new StreamReader(stream);
                result = sr.ReadToEnd();
                sr.Close();
            }
        }
        [SetUp]
        public void Init() {

            _items.Delete();
            _orders.Delete();
        }
        [Test]
        public void receiver_should_save_one_order() {
            var url = "http://localhost:50363/shopify/receiver";
            Ping(url, GetJson());
            Assert.AreEqual(1, _orders.All().Count());
        }
        [Test]
        public void receiver_should_save_two_order_items() {
            var url = "http://localhost:50363/shopify/receiver";
            Ping(url, GetJson());
            Assert.AreEqual(2, _items.All().Count());
        }
    }
}

asdf

        //need to have an action for Shopify pings
        public ActionResult Receiver() {
            //this is a ping from Shopify, record the order
            //var order = this.SqueezeJSON();

            //returns a string
            var json = this.ReadJson();
            var order = System.Web.Helpers.Json.Decode(json);

            Logger.LogInfo(order.order_number.ToString());

            //this is ugly
            //an anonymous object
            var newOrder = new {
                OrderNumber = order.order_number,
                ShopifyID = order.id,
                ShopifyName = order.name
                //lots more items to left right.
            };

            dynamic savedOrder = _orders.Insert(newOrder);

            //line items
            //need to put in the correct newOrder.ID
            foreach (var item in order.line_items) {
                var newItem = new {
                       OrderID = savedOrder.ID,
                       Name = item.Name
                       //etc..
                };
                _items.Insert(newItem);
            }

            return Content("OK");
        }

Shopify does it all for us, and sends back the data.

Membership and Billing

subscriptions?

Just run a credit charge once a month and quit whining!

  • retain users credit cards..ahhh!
  • the thing that runs charges.. tracks
  • a scheduler
  • dunning capability.. what happens when payments fail
  • upgrades and downgrades

hard to do right.

Chargify – expensive

Recurly – Recurring Billing

  • Simple monthly ($30pm)
  • Platinum which allows for downloads ($50pm)

image

1 time setup fee, trial periods

image

full blown checkout page. can dns with cname so people think are still on tekpub.

image

can also handle one-off charges

image

user has access to their own invoices.

MadMimi – Emailling

image

can edit the emails straight in madmimi

Rob has written a MadMimi wrapper which is on https://github.com/robconery/MadMimiMailer

Nice and async.

Welcome

Invoice

easily send in invoice# etc..

OpenID

number 1 support calls.

HighRise – CRM (37 signals)

whenever he sends an email out via madmimi, it is bcc’d to highrise

Can see all emails inside highrise

MailChimp – Build Emailling

True high powered email campaigns this is better.

Mailchimp to import from highrise

Can do big push promotional push.

 

HighRise API

Rob wrote a wrapper. 

Using XML for transport.

 

Hmm but would be nice to observe, or hook in, so that when a users details change it automagically updates CRM. 

So can use Massive hooks, inserted updated and deleted.  Overrides

HighRise – custom fields.

Review SaaS

help us now – yes

  • ecommerce (Shopify)
  • subscription billing (Recurly)
  • mailers (MadMimi and MailChimp)
  • templated mailing
  • mass mailing
  • CRM (HighRise)

1 year – absoltely..

CRM – sales guy has a CRM tool

Can enter productions, enter discount codes, do mailouts that look great

Data

Do I need to keep orders in own db… or trust shopify?  replication of data is bad.

Great to have these services, so he can focus on the core of the business which is creating videos.

| | # 
# Thursday, 29 September 2011
( BackboneJS | UX | VidPub )

image

catching save.

FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    events: {
        "change input": "updateModel",
        "submit #productionForm": "save"
    },
    save: function () {
        this.model.save(
            this.model.attributes,
            {
                success: function (model, response) {
                    alert(model.get("Title") + " saved");
                    model.collection.trigger("itemSaved", model);
                },
                error: function (model, response) {
                    alert("problem");
                    model.trigger("itemError", "There was a problem saving " + model.get("Title"));
                }
            }
        );
        return false;
    },

 

**BREAK**

heading towards this:

image

image

nice notifications!

Validations are built into backbone:

image

Is it Really Worth It?

  • Time spent on user experience is really valuable
  • Compelling to use
  • Good for us as developers to stretch
| | # 
# Wednesday, 28 September 2011

http://documentcloud.github.com/backbone/

MVC for js?

http://documentcloud.github.com/backbone/examples/todos/index.html

image

http://www.quora.com/What-are-some-good-resources-for-Backbone-js

this is hard!  Rob used peepcode.com  backbone screencast.  Took him 2 weeks to do this.

Use IIS Express.

Areas

Added an area called Api

image

http://localhost:50363/api/productions/

namespace VidPub.Web.Areas.Api.Controllers{
    public class ProductionsController : ApplicationController {
        DynamicModel _productions;

        public ProductionsController(ITokenHandler tokenStore):base(tokenStore) {
            _productions = new Productions();
        }

        [HttpGet]
        public ActionResult Index() {
            return VidpubJSON(_productions.All());
        }
    }
}

image

Alt+J – in chrome to open downloads, then tab lots to show in folder

Added backbone-min.js from another branch https://github.com/documentcloud/backbone/tree/gh-pages

and underscore-min.js http://documentcloud.github.com/underscore/

VidPub/Productions - View

Backbone is Event-driven.

el is short for Element.

Reference an ID: 

#production-list

Reference a class:

.production-list ?

ListView = Backbone.View.extend({

    render: function () {
        $(this.el).html("<h1>Hello!</h1>");
        return this;
    }

});

jQuery(function () {
    var view = new ListView();
    $("#production-list").html(view.render().el);

});

asdf

@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<div id="production-list"></div>

image

and refactoring:

ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },

    render: function () {
        var html = this.template.tmpl({ name: "Tekpub" });
        $(this.el).html(html);
        return this;
    }

});

jQuery(function () {
    var view = new ListView();
    $("#production-list").html(view.render().el);

});

view:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<script id="listTemplate" type="text/html">
    <h1>${name}</h1>
</script>
<div id="production-list"></div>

image

so all he’s doing here is pushing html into the dom.

ViewEvents

But its part of the template and not available when the page loads.

image

View:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<script id="listTemplate" type="text/html">
<h1>${name}</h1>
<a href="#">Click me!</a>
</script>
<div id="production-list"></div>

js

ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl({ name: "Tekpub" });
        $(this.el).html(html);
        return this;
    }

});

jQuery(function () {
    var view = new ListView();
    $("#production-list").html(view.render().el);

});

here is an example of el not wired up.. and event doesn’t work:

ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl({ name: "Tekpub" });
        //$(this.el).html(html);
        //sidestep el completly and getting jQuery to patch in the template
        $("#production-list").html(html);
        return this;
    }
});

jQuery(function () {
    var view = new ListView();
    //$("#production-list").html(view.render().el);
    view.render();
});

Working With Data

Production = Backbone.Model.extend({});


ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    var production = new Production({ name: "MVC3!" });
    var view = new ListView({model: production});
    $("#production-list").html(view.render().el);
});

image

pub/sub

binding/subscribing to the change event on this.clickityClick ** careful to use this inside function, otherwise js will look globally in the global window object.

Also setting model to a global variable so we can work with it:

image

Manually resetting the title to Peaches in chrome.

Bind our change event to render action in the view

Production = Backbone.Model.extend({});


ListView = Backbone.View.extend({

    initialize: function () {
        this.template = $("#listTemplate");
        this.model.bind("change", this.render);
        window.ViewModel = this.model;
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    var production = new Production({ name: "MVC3!" });
    listView = new ListView({model: production});
    $("#production-list").html(listView.render().el);
});

then in chrome:

listView.model.set({name:"EF"})

image

the this in this.model.toJSON()  keyword changes scope when fired outside of a view such as on a model or a collection

userscore library has a handy bindAll library which resets scope of this to the current view.

image

Production = Backbone.Model.extend({});


ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.model.bind("change", this.render);
        window.ViewModel = this.model;
    },
    events: {
        "click a:": "clickityClick"
    },
    clickityClick: function () {
        alert("I been clicked");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    var production = new Production({ name: "MVC3!" });
    listView = new ListView({model: production});
    $("#production-list").html(listView.render().el);
});

Backbone collections

What if we need more than 1 model.

Production = Backbone.Model.extend({});
Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});

image

image

these are backbone models.

image

actual model

reset the view to use a collection instead of a model.

“reset” whenever data is firehosed into collection.

  • page is going to load
  • view will be instantiated with empty collection
  • el is going to be specified
  • fetch is going to be called
  • once fetch is completed, collection will fire its reset event
  • which will fire the render event
  • render will then push the templated data into el

image

productions.reset([{Title:"One"}, {Title:"Two"}])   - this will changed the data on the screen as event driven will fire.

Production = Backbone.Model.extend({});
Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        //this.model.bind("change", this.render);
        this.collection.bind("reset", this.render);
        //window.ViewModel = this.model;
    },
//    events: {
//        "click a:": "clickityClick"
//    },
//    clickityClick: function () {
//        alert("I been clicked");
//    },

    render: function () {
        //var html = this.template.tmpl(this.model.toJSON());
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});

jQuery(function () {
    //var production = new Production({ name: "MVC3!" });
    //listView = new ListView({model: production});
    listView = new ListView({ collection: productions, el: "#production-list" });
    //$("#production-list").html(listView.render().el);
    productions.fetch();
});

Backbone router

24mins in.

using callback function success

Production = Backbone.Model.extend({});
Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.collection.bind("reset", this.render);
    },

    render: function () {
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});

var ProductionAdmin = Backbone.Router.extend({
    initialize: function () {
        listView = new ListView({ collection: productions, el: "#production-list" });
    },
    routes: {
        "": "index",
        "edit/:id": "edit"
    },
    index: function () {
        listView.render();
    },
    edit: function (id) {
        alert("hello");
        //grab the model from the production
        $("#production-list").html("This id is " + id);
        //pass it to an editor
        //listView.render();
    }
});

jQuery(function () {
    productions.fetch({
        success: function () {
            alert("hello2");
            //create the router
            window.app = new ProductionAdmin();
            Backbone.history.start();
        },
        error: function () {
            //display a nice error page
            alert("error");
        }
    });
});

editing: app.navigate("edit/1",true)

image

#allows back button to work, and hyperlinking back to a certain page to work..all with ajax.  Power of backbone!

Working with Data

<a href="#" id="${ID}">${Title}</a>

not using this.. instead HTML5 data storage:

image

Production = Backbone.Model.extend({});

Production = Backbone.Model.extend({
    idAttribute : "ID"
});

Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.collection.bind("reset", this.render);
    },
    events: {
        "click a": "showForm"
    },
    showForm: function (evt) {
        //get tje ID that was clicked
        var id = $(evt.currentTarget).data('production-id');
        //navigate
        app.navigate("edit/" + id, true);
        return false;
    },

    render: function () {
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});


FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        return this;
    }
});


var ProductionAdmin = Backbone.Router.extend({
    initialize: function () {
        listView = new ListView({ collection: productions, el: "#production-list" });
        formView = new FormView({ el: "#production-form" });
    },
    routes: {
        "": "index",
        "edit/:id": "edit"
    },
    index: function () {
        listView.render();
    },
    edit: function (id) {
        listView.render();
        $(formView.el).empty();
        //grab the model from the production
        var model = productions.get(id);
        formView.model = model;
        formView.render();
        //$("#production-list").html("This id is " + id);
        //pass it to an editor
        //listView.render();
    }
});

jQuery(function () {
    productions.fetch({
        success: function () {
            //create the router
            window.app = new ProductionAdmin();
            Backbone.history.start();
        },
        error: function () {
            //display a nice error page
            alert("error");
        }
    });
});

@{
    ViewBag.Title = "Productions";
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
}

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("underscore-min.js")
@Assets.Script("backbone-min.js")
@Assets.Script("production-admin.js")

<script id="listTemplate" type="text/html">
<li>
    <a href="#" data-production-id="${ID}">${Title}</a>
</li>
</script>

<script id="productionFormTemplate" type="text/html">
    <form id="productionForm">
        <input type="text" id="Title" name="Title" value="${Title}" />
    </form>
</script>

<div class="column span-5 colborder">
    <div id="production-list"></div>
</div>

<div class="column span-14">
    <div id="production-form"></div>
</div>

 

Form Binding

we want to bind the form to the model.. we can .. using events.  will wire ourselves.. people working on modelbinders.

image

showing the model being changed by the view.

formView.model.get("Title")

this should be automatic – use Derik Baily’s modelbinding.

Helpers:  copy existing Form.cshtml and rename to Template.cshtml

Nice button CSS, with jQuery template helper html.

image

nice form using form.css wired through the layout page.

image

date picker

image

just had to wire up the datepicker in formview, which wires us the jQueryUI datepicker.  jqueryUI is wired up through _adminlayout.

FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    events: {
        "change input": "updateModel",
        "submit #productionForm": "save"
    },
    save: function () {

        return false;
    },
    updateModel: function (evt) {
        var field = $(evt.currentTarget);
        var data = {};
        data[field.attr('ID')] = field.val();
        this.model.set(data);
    },
    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        $(".datepicker").datepicker();
        return this;
    }
});

saving back to server:

image

reason we’re using IIS Express… cassini doesn’t always work with put.

rails like conventions

image

 

image

so data has been passed back to server via the formView.model.save() call or button press.

Production = Backbone.Model.extend({});

Production = Backbone.Model.extend({
    idAttribute: "ID",
     url: function () {
        return this.isNew() ? "/api/productions/create" : "/api/productions/edit/" + this.get("ID");
    },
});

Productions = Backbone.Collection.extend({
    model : Production,
    url: "/api/productions"
});
productions = new Productions();

ListView = Backbone.View.extend({

    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#listTemplate");
        this.collection.bind("reset", this.render);
    },
    events: {
        "click a": "showForm"
    },
    showForm: function (evt) {
     
        //get the ID that was clicked
        var id = $(evt.currentTarget).data('production-id');
        //navigate
        app.navigate("edit/" + id, true);
        return false;
    },

    render: function () {
        var html = this.template.tmpl(this.collection.toJSON());
        $(this.el).html(html);
        return this;
    }
});


FormView = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this, 'render');
        this.template = $("#productionFormTemplate");
    },

    events: {
        "change input": "updateModel",
        "submit #productionForm": "save"
    },
    save: function () {
        this.model.save(
            this.model.attributes,
            {
                success: function (model, response) {
                    model.collection.trigger("itemSaved", model);
                },
                error: function (model, response) {
                    model.trigger("itemError", "There was a problem saving " + model.get("Title"));
                }
            }
        );
        return false;
    },
    updateModel: function (evt) {
        var field = $(evt.currentTarget);
        var data = {};
        data[field.attr('ID')] = field.val();
        this.model.set(data);
    },
    render: function () {
        var html = this.template.tmpl(this.model.toJSON());
        $(this.el).html(html);
        $(".datepicker").datepicker();
        return this;
    }
});


var ProductionAdmin = Backbone.Router.extend({
    initialize: function () {
        listView = new ListView({ collection: productions, el: "#production-list" });
        formView = new FormView({ el: "#production-form" });
    },
    routes: {
        "": "index",
        "edit/:id": "edit"
    },
    index: function () {
        listView.render();
    },
    edit: function (id) {
        listView.render();
        $(formView.el).empty();
        //grab the model from the production
        var model = productions.get(id);
        formView.model = model;
        formView.render();
        //$("#production-list").html("This id is " + id);
        //pass it to an editor
        //listView.render();
    }
});

jQuery(function () {
    productions.fetch({
        success: function () {
            //create the router
            window.app = new ProductionAdmin();
            Backbone.history.start();
        },
        error: function () {
            //display a nice error page
            alert("error");
        }
    });
});

MVC3 modelbinders did their job and title in the incoming json is title in our p object.

but we’re not using statically typed objects.. can’t access via FormCollection (as it isn’t a post).. so have coded around:

Multiple types were found that match the controller named

http://ediblecode.com/blog/dot-net/asp-net-mvc/fixing-multiple-types-were-found-that-match-the-controller-named

image

ControllerBuilder.Current.DefaultNamespaces.Add("VidPub.Web.Controllers");

Date Problem

Wrote and app to test:

namespace MassiveDateTest {
    class Program {
        static void Main(string[] args) {
            // test dates in massive for NZ locale
           

            //do an insert
            var table2 = new Productions();
            //do it up - the new ID will be returned from the query
            // fails with
            // exec sp_executesql N'INSERT INTO Productions (Title,CreatedOn)
            //VALUES (@0,@1)',N'@0 nvarchar(4000),@1 nvarchar(4000)',@0=N'Buck Fify Stuff',@1=N'30/08/2011'
   
            var newID = table2.Insert(new { Title = "Buck Fify Stuff", CreatedOn = "21/08/2011" });
            Console.WriteLine(newID);

            var table = new Productions();
            //grab all the productions
            var productions = table.All();
            foreach (var item in productions) {
                // coming through as 29/08/2011 12:00:00
                Console.WriteLine(item.Title + " " + item.CreatedOn);
            }

            // this works
            //var newID = table2.Insert(new { Title = "Buck Fify Stuff", CreatedOn = "08/30/2011" });
            
        }
    }

    public class Productions : DynamicModel {
         public Productions() : base("VidPub", "Productions", "ID") {}
    }
}

and massive:

public virtual DbCommand CreateInsertCommand(object o) {
            DbCommand result = null;
            var expando = o.ToExpando();
            var settings = (IDictionary<string, object>)expando;
            var sbKeys = new StringBuilder();
            var sbVals = new StringBuilder();
            var stub = "INSERT INTO {0} ({1}) \r\n VALUES ({2})";
            result = CreateCommand(stub, null);
            int counter = 0;
            foreach (var item in settings) {
                sbKeys.AppendFormat("{0},", item.Key);
                sbVals.AppendFormat("@{0},", counter.ToString());

                // DM hack dates
                if (item.Key == "CreatedOn" || item.Key == "UpdatedOn" || item.Key == "ReleasedOn") {
                    string fulldate = item.Value.ToString();
                    string[] dateArray = fulldate.Split('/');
                    string day = dateArray[0];
                    string month = dateArray[1];
                    string year = dateArray[2];

                    string dateInUSFormat = month + "/" + day + "/" + year;
                    result.AddParam(dateInUSFormat);
                } else
                    result.AddParam(item.Value);
                counter++;
            }

so got it writing a British date from C# and transforming into US in the insert code.

put into main project and got it working for

image

but time comes in on save:

image

create not working for some reason. **datepicker is in US format already.. need to set to UK

| | # 
# Tuesday, 27 September 2011
( jQuery | VidPub )

git mergetool

git checkout –b “admin_home”

Html helpers won’t work with dynamics

Make some Helpers

  • less abstraction
  • more control
@helper TextBox(string name, object value=null) {
    //if value is null, set to empty string
    var val = value ?? "";
    <p>
        <label>@name</label>
        <input type="text" name="@name" id="@name" value="@val" />
    </p>
}

then

<legend>Production</legend>
        @Form.TextBox("Title", @Model.Title)

However we want the Title property to be required:

In MVC3 this would be:"  Html.TextBoxFor(x=>x.Title);

and put a Required DataAnnotation attribute on the Production class.

could also tell it to do in unobtrusively with jquery, and jquery validations.

Getting rid of js files:

image

So now I just have:

image

Scripts loader for js

@helper Scripts(){
    @Assets.Script("jquery.validate.min.js")
    @Assets.Script("jquery.validate.unobtrusive.min.js")
}

in the Form.cshtml helpers

Validations


@helper ValidationMessage(string name) {
    <span class="field-validation-valid" data-valmsg-for="@name" data-valmsg-replace="true"></span>
}

@helper TextBoxRequired(string name, object value = null, string requiredMessage = "Required") {
    var val = value ?? "";
            //using HTML5 and jQuery validations
    <p>
        <label>@name</label>
        <input type="text" name="@name" id="@name" value="@val" data-val="true" data-val-required="@requiredMessage" />
        @ValidationMessage(name)
    </p>
}

then on the front end:

        <legend>Production</legend>
        @Form.TextBoxRequired("Title", @Model.Title)

nice.

FInally:

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Production</legend>
        @Form.TextBoxRequired("Title", @Model.Title)
        @Form.TextArea("Description", @Model.Description)
        @Form.TextBoxNumber("Price", @Model.Price)
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

and helpers:

@helper Scripts(){
    @Assets.Script("jquery.validate.min.js")
    @Assets.Script("jquery.validate.unobtrusive.min.js")
}

@helper ValidationMessage(string name) {
    <span class="field-validation-valid" data-valmsg-for="@name" data-valmsg-replace="true"></span>
}

@helper TextBox(string name, object value = null) {
    //if value is null, set to empty string
    var val = value ?? "";
    <p>
        <label>@name</label>
        <input type="text" name="@name" id="@name" value="@val" />
    </p>
}

@helper TextBoxRequired(string name, object value = null, string requiredMessage = "Required") {
    var val = value ?? "";
            //using HTML5 and jQuery validations
    <p>
        <label>@name *</label>
        <input type="text" name="@name" id="@name" value="@val" data-val="true" data-val-required="@requiredMessage" />
        @ValidationMessage(name)
    </p>
}

@helper TextBoxNumber(string name, object value = null, string numberMessage = "Should be a number") {
    var val = value ?? "";
            //using HTML5 and jQuery validations
    <p>
        <label>@name *</label>
        <input type="text" name="@name" id="@name" value="@val" data-val="true" data-val-number="@numberMessage" />
        @ValidationMessage(name)
    </p>
}

@helper TextArea(string name, string attributes = "", object value = null) {
    var val = value ?? "";
    <p>
        <label>@name</label>
        <textarea name="@name" id="@name" @attributes >@val</textarea>
    </p>
}

More Security

Not using ASP Net Membership, so how can control security on controllers?

We could just add [Authorize(Role)] attribute if we were using ASP Net Membership on each controller class or method.

[Authorize(Roles = "asdf")]

no notion of roles in this site

We are going to go simple – create an Action Filter.

 

Can’t pass a dynamic argument to an extension method.

Roll own Simple Authorization

make an attribute / ActionFilter

namespace VidPub.Web.Infrastructure {
    public class RequiredAdminAttribute : ActionFilterAttribute {
        public override void OnActionExecuting(ActionExecutingContext filterContext) {
            //pull the controller off the context passed in
            //and cast it as own ApplicationController
            var controller = (ApplicationController)filterContext.Controller;

            //user logged in?
            if (!controller.IsLoggedIn) {
                controller.TempData["Error"] = "You need to be logged in to do that";
                filterContext.HttpContext.Response.Redirect("/account/logon");
                return;
            }

            //is the user an admin
            var adminEmails = new string[] { "robconery@gmail.com", "rob@tekpub.com", "davemateer@gmail.com" };
            //CurrentUser is a field in our ApplicationController
            //can't use var... as it would be ynamic object..need to tell the compiler
            string userEmail = controller.CurrentUser.Email;
            if (!adminEmails.Contains(userEmail)) {
                controller.TempData["Error"] = "You are not authorized";
                filterContext.HttpContext.Response.Redirect("/account/logon");
            }
        }
    }
}

this wired up to the field on ApplicationController, which tells us if a user is logged in, and then which user.

then wire up on CruddyController the actions that need to be authorised.

   [RequiredAdmin]
        public virtual ActionResult Create() {
            return View(_table.Prototype);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [RequiredAdmin]
        public virtual ActionResult Create(FormCollection collection) {
            dynamic item = _table.CreateFrom(collection);
            try {
                _table.Insert(item);
                return RedirectToAction("Index");
            }
            catch {
                TempData["alert"] = "There was an error adding this item";
                return View();
            }
        }

        [RequiredAdmin]
        public virtual ActionResult Edit(int id) {
            var model = _table.Get(ID: id);
            model._Table = _table;
            return View(model);
        }

        [HttpPost]
        [RequiredAdmin]
        public virtual ActionResult Edit(int id, FormCollection collection) {
            var model = _table.CreateFrom(collection);
            try {
                _table.Update(model, id);
                return RedirectToAction("Index");
            }
            catch (Exception ex) {
                TempData["Error"] = "There was a problem editing this record";
                return View(model);
            }
        }

A Little jQuery

Firebug

Why:

  • Write less server code
  • More response app
  • More compelling app

Add in json serialization, so we can talk javascript!

Usually we’d go with something like:

return Json(_table.All(), JsonRequestBehavior.AllowGet);

json.net a lot of people use as a serialiser.

However lets see if can solve it on own without taking another dependency

Getting Json dates to displayed correctly is a long running problem.  Write own, then use strings everywhere to make life simpler.  Dave Ward http://encosia.com/ write this.

Couldn’t get json to output correctly, problem was glimpse…

image

Fiddler was very useful here.

Our new way of calling json serializer:

[HttpGet]
        public virtual ActionResult Index(string query) {
            //sidestep viewgeneration and return json
            //
            IEnumerable<dynamic> results = null;
            results = _table.All();

            return VidpubJSON(results);
        }

        public ActionResult VidpubJSON(dynamic content) {
            var serializer = new JavaScriptSerializer();
            serializer.RegisterConverters(new JavaScriptConverter[] { new ExpandoObjectConverter() });
            var json = serializer.Serialize(content);
            //Response.ContentType = "application/json";
            return Content(json);
        }

and Dave Wards converter which cracks open the IDictionary backing the Expando, explicitly assigns key value bindings that are required…

so then we can parse down to a shortDateString (the dates)

namespace VidPub.Web.Infrastructure {
    public class ExpandoObjectConverter : JavaScriptConverter {
        public override IEnumerable<Type> SupportedTypes {
            get { return new ReadOnlyCollection<Type>(new List<Type>(new Type[] { typeof(ExpandoObject) })); }
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) {
            ExpandoObject expando = (ExpandoObject)obj;

            if (expando != null) {
                // Create the representation.
                Dictionary<string, object> result = new Dictionary<string, object>();
                foreach (KeyValuePair<string, object> item in expando) {
                    var value = item.Value ?? "";
                    if (value.GetType() == typeof(DateTime))
                        result.Add(item.Key, ((DateTime)value).ToShortDateString());
                    else
                        result.Add(item.Key, value.ToString());
                }
                return result;
            }
            return new Dictionary<string, object>();
        }
        public override dynamic Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) {
            dynamic result = new ExpandoObject();

            if (dictionary != null) {
                // Create the representation.
                var dc = (IDictionary<string, object>)result;
                foreach (KeyValuePair<string, object> item in dictionary) {
                    var value = item.Value ?? "";
                    dc.Add(item.Key, value);
                }
            }
            return result;
        }
    }
}

Put verbs HttpPut on Edit post, and HttpDelete on a delete post.

jQuery

Making a searchbox.

<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();
            alert("You're looking for " + val);
            return false;
        });

    });
</script>

<h2>Vidpub Admin</h2>
<form id="searchForm">
    <label>Find:</label>
    <input type="text" id="search"/>
    <input type="submit" value="go" />
</form>

 

image

want to search over:

  • Productions
  • Episodes
  • Customers
namespace VidPub.Web.Models {
    public class Customers : DynamicModel {
        public Customers()
            : base("VidPub", "Customers", "ID") {
        }

        public dynamic FuzzySearch(string query) {
            return this.Query(@"select ID, email from customers
                            where email LIKE('%'+@0+'%')", query);
        }
    }
}

Lucene or full text indexing would be better if we get lots of data.. but for now it will be fine.

image

<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();
            //alert("You're looking for " + val);
            //third is a callback
            $.getJSON("/productions", { query: val }, function (data) {
                alert(data.length);
            });
            return false;
        });

    });
</script>
[HttpGet]
        public virtual ActionResult Index(string query) {
            //sidestep viewgeneration and return json
            IEnumerable<dynamic> results = null;
            if (!string.IsNullOrEmpty(query)) {
                results = _table.FuzzySearch(query);
            }
            else {
                results = _table.All();
            }
            if (Request.IsAjaxRequest()) {
                return VidpubJSON(results);
            }
            return View(results);
        }

it works – we can pass back search results back to the browser using json.

jQuery Templates

using jQuery template:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();
            //alert("You're looking for " + val);
            //third is a callback
            $.getJSON("/productions", { query: val }, function (data) {
                //alert(data.length);
                $("#searchTemplate").tmpl(data).appendTo("#searchResults");
            });
            return false;
        });
    });
</script>

the template:

<script id="searchTemplate" type="text/html">
    <li>
        ${Title}
    </li>
</script>

where to display results:

<div id="searchResults">
</div>

want a link, so need the controller passed in so we can give it that name:

however this will break the templating as there is an array inside…blah something:

this will fix using looping:

<script type="text/javascript">
    $(document).ready(function () {

        $("#searchForm").submit(function () {
            var val = $("#search").val();

            $("#searchResults").empty();

            $.getJSON("/productions", { query: val }, function (data) {
                var results = { controller: "productions", items: data };
                $("#searchTemplate").tmpl(results).appendTo("#searchResults");
            });
            return false;
        });
    });
</script>

<script id="searchTemplate" type="text/html">
    <table>
        {{each items}}
            <tr>
                <td><a href="${controller}/edit/${ID}">${Title}</a></td>
            </tr>
        {{/each}}
    </table>
</script>

 

image

Putting it All Together

Console in Google Chrome is Ctrl+Shift+I

abstracting code out to vidpub-search.js

image

debugging in console.  undefined means jQuery can’t fire a method on the selector I’ve defined.

Instead of selecting by ID, going to select by class:

var vidpubSearch = {
    resetResults: function (controller) {
        $(".search-results").empty();
    },
    getResults: function(query, controller) {
        $.getJSON("/"+controller, { query: query }, function (data) {
            var results = { controller: controller, items: data };
            $("#searchTemplate").tmpl(results).appendTo("#"+controller+"-search-results");
        });
    }
};
jQuery(function () {
    $("#searchForm").submit(function () {
        var val = $("#search").val();
        vidpubSearch.resetResults();
        vidpubSearch.getResults(val, "productions");
        return false;
    });
});

front end:

<script type = "text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
@Assets.Script("vidpub-search.js")

<script id="searchTemplate" type="text/html">
    <table>
        {{each items}}
            <tr>
                <td><a href="${controller}/edit/${ID}">${Title}</a></td>
            </tr>
        {{/each}}
    </table>
</script>


<h2>Vidpub Admin</h2>
<form id="searchForm">
    <label>Find:</label>
    <input type="text" id="search"/>
    <input type="submit" value="go" />
</form>

<div id="productions-search-results" class="search-results">
</div>

testing

image

Review

dynamics a possible problem..

we don’t have much code.. nice!

admin ui is simple.

| | # 
# Monday, 26 September 2011
( Entity Framework | Massive | MVC | MVC3 | T4 | VidPub )

Use wordpress theme!

What is the business model?

  • Sell videos (ie download)
  • Sell access to the videos (ie stream)
  • Have small chunks ie episodes

What is our model (starting in baby steps)?

  • A Customer buys a Production
  • A Production is one or more Episodes
  • A Customer buys a Subscription
  • A Subscription gives access to Productions

so episodes cannot be purchased individually.

Write Tests

  1. CustomerSpecs
  2. SubscriptionSpecs
  3. ProductionSpecs
  4. EpisodeSpecs

Share them with the team as a discussion around business logic.

[TestFixture]
    public class CustomerSpecs : TestBase {
        [Test]
        public void a_user_should_be_able_to_add_production_to_cart() {
            this.IsPending();
        }

        [Test]
        public void a_user_that_owns_a_production_should_be_able_to_stream() {
            this.IsPending();
        }

        [Test]
        public void a_user_that_owns_a_production_should_be_able_to_download() {
            this.IsPending();
        }

        [Test]
        public void a_user_should_have_be_able_to_purchase_sub() {
            this.IsPending();
        }

        [Test]
        public void a_user_with_monthly_should_only_be_able_to_stream() {
            this.IsPending();
        }

        [Test]
        public void a_user_with_yearly_should_be_able_to_stream_and_download() {
            this.IsPending();
        }

        [Test]
        public void a_user_with_cancelled_sub_should_not_be_able_to_stream_or_download() {
            this.IsPending();
        }

        [Test]
        public void a_user_with_a_suspended_sub_should_not_be_able_to_stream_or_download() {
            this.IsPending();
        }

        [Test]
        public void a_user_with_overdue_sub_should_be_able_to_stream_or_download() {
            this.IsPending();
        }
        [Test]
        public void a_user_should_be_able_to_cancel_sub() {
            this.IsPending();
        }
        
    }
//A Subscription grants access over time
    //there is a monthly and a yearly
    //a monthly subscription offers stream only access
    //a yearly offers stream and download
    //a Customer can buy a subscription

    [TestFixture]
    public class SubscriptionSpecs : TestBase {

        [Test]
        public void a_subscription_is_only_valid_one_per_user() {
            this.IsPending();
        }

        [Test]
        public void a_subscription_can_be_pending_current_overdue_suspended_or_cancelled() {
            this.IsPending();
        }

        [Test]
        public void a_pending_subscription_can_turn_current() {
            this.IsPending();
        }

        [Test]
        public void a_current_subscription_can_become_overdue_if_payment_late() {
            this.IsPending();
        }

        [Test]
        public void an_overdue_subscription_can_become_current_if_payment_received_in_full() {
            this.IsPending();
        }

        [Test]
        public void an_overdue_subscription_can_become_suspended_if_payment_not_received_after_3_tries() {
            this.IsPending();
        }


        [Test]
        public void a_subscription_can_be_upgraded_from_monthly_to_annual() {
            this.IsPending();
        }

        [Test]
        public void a_subscription_cannot_be_downgraded_from_annual_to_monthly() {
            this.IsPending();
        }

    }
//A Production is a collection of Episodes
    //A Customer can buy a Production
    //A Customer cannot buy an individual episode

    [TestFixture]
    public class ProductionSpecs : TestBase {

        [Test]
        public void a_production_has_one_or_more_episodes() {
            this.IsPending();
        }

        [Test]
        public void a_production_can_cost_0_or_more_dollars() {
            this.IsPending();
        }

        [Test]
        public void a_production_can_be_in_production_published_suspended_or_offline() {
            this.IsPending();
        }

        [Test]
        public void a_production_is_viewable_if_not_offline() {
            this.IsPending();
        }

        [Test]
        public void a_production_can_be_downloaded_if_flagged() {
            this.IsPending();
        }


        [Test]
        public void episodes_can_be_released_offline_in_process() {
            this.IsPending();
        }
        
        [Test]
        public void episodes_are_viewable_if_released() {
            this.IsPending();
        }


        [Test]
        public void customers_can_see_notes_per_production() {
            this.IsPending();
        }

        [Test]
        public void customers_can_see_notes_per_episode() {
            this.IsPending();
        }

        [Test]
        public void customers_can_see_when_an_episode_and_production_was_released() {
            this.IsPending();
        }

        [Test]
        public void customers_can_see_who_authored_the_production() {
            this.IsPending();
        }

        [Test]
        public void customers_can_see_how_long_an_episode_is() {
            this.IsPending();
        }

        [Test]
        public void customers_can_see_total_duration_of_production() {
            this.IsPending();
        }
        

DB

DB First, Classes (or code) First, Migrations (from Rails world)

but .NET people don’t know migrations so leave for now.

EF Code-first

git checkout –b “codefirst”

EF Code-first

NuGet install in Web project.

Make classes with some properties:

public class Production {
        [Required]
        public string Title { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }


    }

Rob isn’t a fan of DataAnnotations…messy.  More attributes than code… aren’t descriptive.. hard to refactor.

eg in AccountModel:

namespace VidPub.Web.Models {

    public class ChangePasswordModel {
        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Current password")]
        public string OldPassword { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "New password")]
        public string NewPassword { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Confirm new password")]
        [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }

    public class LogOnModel {
        [Required]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [Display(Name = "Remember me?")]
        public bool RememberMe { get; set; }
    }

setup DbContext:

namespace VidPub.Web.Models {
    public class VidpubDBContext : DbContext {
        public DbSet<Production> Productions { get; set; }
    }
}

Trick#1 – EF will try to use .\SQLExpress

image

No PK defined.

Guids as PK’s make DBA’s cry?

EF Conventions will use these as PK’s:

  • ProductionID
  • ID
namespace VidPub.Web.Models {
    public class Production {
        [Required]
        public int ID { get; set; }
        [Required]
        public string Title { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
    }
}

image

with no connection string defined we have our db created.

However its made the nvarchar(MAX) which isn’t good..

namespace VidPub.Web.Models {
    public class Production {
        [Required]
        public int ID { get; set; }
        [Required]
        [MaxLength(200)]
        public string Title { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
    }
}

however on recompile:

image

Tip #2: EF Code-first won’t run ALTER… its drop or recreate.

FK

    public class Production {
        [Required]
        public int ID { get; set; }
        [Required]
        public string Title { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public ICollection<Episode> Episodes { get; set; }
    }

    public class Episode {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public int ProductionID { get; set; }
    }

and it gen’s up the database fine.

var db = new VidPub.Web.Models.VidpubDBContext();
            var p = new Production { Title = "My Production" };
            var e = new Episode { Title = "My Episode" };
            p.Episodes = new List<Episode>();
            p.Episodes.Add(e);
            db.Productions.Add(p);

            db.SaveChanges();

MVC Scaffolding

git checkout –b “scaffolding”

Install-Package MvcScaffolding

only have this in the model (no dbcontext from above)

namespace VidPub.Web.Models {
    public class Production {
        public int ID { get; set; }
        [Required]
        public string Title { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public ICollection<Episode> Episodes { get; set; }
    }

    public class Episode {
        public int ID { get; set; }
        public int ProductionID { get; set; }
        public string Title { get; set; }
    }
}
Scaffold Controller production
And it worked:
image
image

hmm – fast to go.. but even changes need to make eg varchar(max), database regens.. are going to get old v.soon.

Opinion

EF and Code-first… more work…in real world.

Make model in Database:

The less abstractions the better.. he loves rails activerecord.

Scaleable and Maintainable

More dev’s will know EF in the future years than anything else.

Good/Bad opinion of EF -

Massive

Bad – future hires won’t know it

Good

  • small and simple
  • high perf
  • this has worked for Rob in the past

Controller Design

Design RESTfully

REpresentational State Trasfer

“create an experience for a user that is predictable and understandable based on a url”

image

so now have a controller that is stubbed out and ready to go.

public class ProductionsController : Controller
    {
        Productions _table;
        public ProductionsController() {
            _table = new Productions();
        }

        public ActionResult Index()
        {
            return View(_table.All());
        }

then create a view:

image

fooling the tooling a bit as we’re going to be using dynamics, so chose any object.

image

Made db in the db! From the dbscript.sql file that came with VidPub.

So now we’ve got data reading from via Massive.

public class ProductionsController : Controller
    {
        Productions _table;
        public ProductionsController() {
            _table = new Productions();
        }

        public ActionResult Index()
        {
            return View(_table.All());
        }

via Massive (vidpub connection string in web.config)

public class Productions : DynamicModel {
        public Productions()
            : base("VidPub", "Productions", "ID") {
        }
<connectionStrings>
    <add name="VidPub" connectionString="server=.\;database=VidPub_Dev;integrated security=true" />

and rendered:

image

Views

[HttpPost]
        public ActionResult Create(FormCollection collection)
        {
            dynamic item = _table.CreateFrom(collection);
            try
            {
                _table.Insert(item);
                return RedirectToAction("Index");
            }
            catch
            {
                TempData["alert"] = "There was an error adding this item";
                return View();
            }
        }

CreateFrom creates a new Expando, white listed against the columns in the db.

Customizing the Generators - T4

As we can’t do @Html.TextBox(“title”, Model.Title)… extension methods and dynamics don’t play well together.

If we do string title = Model.Title then it all works.

image

This added a bunch of templates":

image

Then just took our ProductionsController code and put it into the template Controller.tt

<#@ template language="C#" HostSpecific="True" #>
<#
MvcTextTemplateHost mvcHost = (MvcTextTemplateHost)(Host);
#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using VidPub.Web.Models;

<#
var controllerName = mvcHost.ControllerName;
var nameSpace = mvcHost.Namespace;
var tableName = controllerName.Replace("Controller", "");
#>

namespace <#= nameSpace #> {
public class <#= controllerName #> : Controller
    {
        dynamic _table;
        public <#= controllerName #>() {
            _table = new <#= tableName #>();
            ViewBag.Table = _table;
        }

        public ActionResult Index()
        {
            return View(_table.All());
        }

So now it generates much more cleanly.

namespace VidPub.Web.Controllers
{
    public class ProductionsController : ApplicationController
    {
        dynamic _table;
        public ProductionsController() {
            _table = new Productions();
            ViewBag.Table = _table;
        }

        public ActionResult Index()
        {
            return View(_table.All());
        }

        public ActionResult Details(int id)
        {
            return View(_table.FindBy(ID: id, schema: true));
        }

        public ActionResult Create()
        {
            return View(_table.Prototype);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(FormCollection collection)
        {
            dynamic item = _table.CreateFrom(collection);
            try
            {
                _table.Insert(item);
                return RedirectToAction("Index");
            }
            catch
            {
                TempData["alert"] = "There was an error adding this item";
                return View();
            }
        }
        
        public ActionResult Edit(int id)
        {
            var model = _table.Get(ID: id);
            model._Table = _table;
            return View(model);
        }

        [HttpPost]
        public ActionResult Edit(int id, FormCollection collection)
        {
            var model = _table.CreateFrom(collection);
            try
            {
                _table.Update(model, id);
                return RedirectToAction("Index");
            }
            catch (Exception ex)
            {
                TempData["Error"] = "There was a problem editing this record";
                return View(model);
            }
        }

        public ActionResult Delete(int id)
        {
            return View();
        }


        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Delete(int id, FormCollection collection)
        {
            try
            {
                _table.Delete(id);
                return RedirectToAction("Index");
            }
            catch
            {
                TempData["Error"] = "There was a problem deleting this record";
                return View("Index");
            }
        }
    }
}

CruddyController

abstracting to a base class the cruddyness:

namespace VidPub.Web.Infrastructure {
    public class CruddyController : ApplicationController {
        // IoC will need to inject a tokenStore in everything that inhertis hmmmmmm
        public CruddyController(ITokenHandler tokenStore) : base(tokenStore) {}

        protected dynamic _table;

        //all virtual so can override if necessary
        public virtual ActionResult Index() {
            return View(_table.All());
        }

        public virtual ActionResult Details(int id) {
            return View(_table.FindBy(ID: id, schema: true));
        }

        public virtual ActionResult Create() {
            return View(_table.Prototype);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult Create(FormCollection collection) {
            dynamic item = _table.CreateFrom(collection);
            try {
                _table.Insert(item);
                return RedirectToAction("Index");
            }
            catch {
                TempData["alert"] = "There was an error adding this item";
                return View();
            }
        }

        public virtual ActionResult Edit(int id) {
            var model = _table.Get(ID: id);
            model._Table = _table;
            return View(model);
        }

        [HttpPost]
        public virtual ActionResult Edit(int id, FormCollection collection) {
            var model = _table.CreateFrom(collection);
            try {
                _table.Update(model, id);
                return RedirectToAction("Index");
            }
            catch (Exception ex) {
                TempData["Error"] = "There was a problem editing this record";
                return View(model);
            }
        }

        public virtual ActionResult Delete(int id) {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public virtual ActionResult Delete(int id, FormCollection collection) {
            try {
                _table.Delete(id);
                return RedirectToAction("Index");
            }
            catch {
                TempData["Error"] = "There was a problem deleting this record";
                return View("Index");
            }
        }
    }
}

Review

  • Use what you know and get app to market
  • Big ORM in startup can get in the way
  • Build whats needed, no more
  • Avoided orm here
| | # 
# Friday, 23 September 2011
( jQuery | MVC | VidPub )

Razor automatically encodes HTML for us

@Snippets.FooterText()

public static dynamic FooterText() {
        return string.Format("&copy;{0} VidPub, Inc.", DateTime.Now.Year);
    }

image

This is bad..

Most of the Html helpers eg Html.Action, Html.ActionLink return an MvcHtmlString which inherits from HtmlString.  This is around to control the encoding of html.

@functions {
    public static dynamic FooterText() {
        return new HtmlString(string.Format("&copy;{0} VidPub, Inc.", DateTime.Now.Year));
    }
}

better.  This means we sidestep mvc’s always on encoding.

Partials / Helpers / Functions

Do a quotes class and return a string array.

@Html.Partial got confused when we were handing off dynamics stuff.  So easier to use a helper (and a function in the same file…nice)

@functions{
    public static dynamic Scrub(string quote) {
        return new HtmlString(quote);
    }
}

@helper User(string quote) {
    <div class="quote">
        @Scrub(quote)
    </div>    
}

 

<div class="container">
    <div class="column span-24">
        @foreach (dynamic item in VidPub.Web.Models.Quotes.FromUsers()) {
            @Quotes.User(item)
        }
    </div>
</div>

image

public class Quotes {
        public static dynamic FromUsers() {
            var quotes = new string[] {"I just rocked the interview blah blah"
                ,"This is really great"
                ,"Bough mastering jquery from vidpub and so far it rocks"
                ,"2Bough mastering jquery from vidpub and so far it rocks"
                ,"3Bough mastering jquery from vidpub and so far it rocks"
                ,"4Bough mastering jquery from vidpub and so far it rocks"
            };
            Random rnd = new Random();
            return quotes.OrderBy(x => rnd.Next()).ToList();
        }
    }

Rob has html encoding in the quotes… ie <b> in the acutal quote string, which is why he’s use a Scrub function.  I dont need it.

Jquery Cycle

@HTML.CSS("jquery_cycle.js")

in css

id is a #

class is a .

#user_quotes
{
    margin-bottom:32px;
    padding: 24px;
}

.quote
{
    font-family:"georgia";
    font-style:italic;
    font-size:2.1em;
    color:#4d4d4d;
}

.main
{
    min-height:320px;
}

image

<script>
    $(document).ready(function () {
        $("#user_quotes").cycle({ fx: 'fade', pause: 1, speed: 900, timeout: 4000 })
    });
</script>
<div class="container">
    <div id = "user_quotes" class = "column span-24">
        @foreach (dynamic item in VidPub.Web.Models.Quotes.FromUsers()) {
            @Quotes.User(item)
        }
    </div>
</div>

image

then it cycles through – cool!

Rendering Cycle

image

 

_ViewStart.cshtml

Index.cshtml  (this is the view or view template).. so render view before layout!

_Layout.cshtml (top level component that loads css, js, footer etc..).. so two chunks of html already done are injected into @RenderBody method

then all put into a stringbuilder, then put into the ResponseStream and then we have a webpage!

Review

Partials..hmmm.. unless paired with modelBinder which he’s going to discuss later.

Helpers are simple and paired with functions are great.

| | # 
( Design | VidPub | Wordpress )

http://www.woothemes.com/  - ready mate templates

3 themes for USD70

Most themes are geared towards Wordpress, so best way to get the theme out of WordPress is to install WordPress on local box, as Wordpress.com is locked down quite hard especially around javascript.

Wordpress download using Web Platform Installer

image

MySQL 5.1 didn’t work.. just hung.

Downloaded and installed manually MySQL5.5

image

Then installed wordpress again from WebPlatformInstaller.

image

install a theme

image

 

Rob is using http://www.woothemes.com/2011/01/biznizz/

Blueprint.css

http://www.blueprintcss.org/ – css framework

image

copy and pasted html source from http://www.blueprintcss.org/tests/parts/sample.html

downloaded blueprint and put files into stylesheets as show above (all except site.css were from blueprint).

 

typekit

http://typekit.com/

simple, elegant

He is using Katarine Web .. regular and semi bold.

image

replacing all headings with a new font Katarine.

image

Ok, so they are different now.

image

Right hand side is antialiased IE9 rendering.. left is chrome14

Design – Logo

Most logos are simple elegant fonts

image

Robs likes Kontrapunkt

http://www.smashingmagazine.com/typography-guidelines-and-references/

Installing free Kontrapunkt font:

image

http://www.fontsquirrel.com/fonts/Kontrapunkt

Paint.NET

350*60 – Logo

920 * 240 – Splash Image

image

controlling my logo and splash image with css   http://www.iconarchive.com/

<div id='logo'>
    <img src="/Public/images/VidPubLogo.png" />
  </div>
  <div id='splash'>
    <img src="/Public/images/splashImage.png" />
  </div>

image

CSS Helper

Made a file in App_Code called HTML.cshtml

@helper CSS(string cssFile){
    <link rel="stylesheet" href="/public/stylesheets/@cssFile" type="text/css" />
}

then in _Layout.cshtml

@HTML.CSS("site.css")

Image Helper

@helper Image(string imageFile) {
    <img src = "/public/images/@imageFile" />
}
then
 @HTML.Image("VidPubLogo.png")

ImageLink Helper, jQuery Helper

@helper ImageLink(string link, string imageFile) {
    <a href="@link">
        @HTML.Image(imageFile)
    </a>
}

@helper jQuery(bool useGoogle = true){
    if (useGoogle) {
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
    } else {
        <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.4.js" type="text/javascript"></script>
        <script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/jquery-ui.js" type="text/javascript"></script>
    }
}

@HTML.jQuery(true)
    

 

  <div id='logo'>
        @HTML.ImageLink("/", "VidPubLogo.png")
    </div>

Functions

For code only.. no markup.

@functions{
    public static string SiteRoot(bool includeAppPath = true){
        var context = HttpContext.Current;
        var Port = context.Request.ServerVariables["SERVER_PORT"];
        if (Port == null || Port == "80" || Port == "443")
            Port = "";
        else
            Port = ":" + Port;
        var Protocol = context.Request.ServerVariables["SERVER_PORT_SECURE"];
        if (Protocol == null || Protocol == "0")
            Protocol = "http://";
        else
            Protocol = "https://";

        var appPath = "";
        if (includeAppPath) {
            appPath = context.Request.ApplicationPath;
            if (appPath == "/")
                appPath = "";
        }
        var sOut = Protocol + context.Request.ServerVariables["SERVER_NAME"] + Port + appPath;
        return sOut;      
    }
}

this gives us an absolute URL function we can use.

    <div id='logo'>
        @HTML.ImageLink(URL.SiteRoot(), "VidPubLogo.png")
    </div>

<div id='logo'>

<a href="http://localhost:50363">

<img src = "/public/images/VidPubLogo.png" />

</a>

Dynamic Route – Clean and Expressive Linking

From rails world:

So a path gives:

<h2>Here's What You Can Do...</h2>
    <p>
        <h1>@router.about_path()</h1>
        @Html.ActionLink("click here for more info", "about", "home");
    </p>

image

and url gives:

<h2>Here's What You Can Do...</h2>
    <p>
        <h1>@router.about_url()</h1>
        @Html.ActionLink("click here for more info", "about", "home");
    </p>

image

this is using the TryInvokeMember override of dynamics.. ie if a method can’t be found it runs this override.

public class DynamicRoute : DynamicObject {
        UrlHelper _helper;
        public DynamicRoute(UrlHelper helper) {
            _helper = helper;
        }

        // C# equivalent of methodmissing
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
            // read in the requested method
            var methodName = binder.Name;

            //split it for name of route and path/url
            var stems = methodName.Split('_');

            //lookup up the route by name
            var routeName = stems[0];
            var url = _helper.RouteUrl(routeName);

            //url or path?
            if (stems.Last() == "url") {
                url = Root(false) + url;
            }

            //craft up the URL
            result = url;
            return true;
        }

Pass in parameters:

<h1>@router.about_url(new { id="steve"})</h1>

gives:

image

and refactored a bit more so no parenthesis needed:

        <h1>@router.about_url</h1>

gives:

image

or

<h1>@router.about_path</h1>

gives

image

or

   <h1>@router.about_path(1)</h1>

image

Final code:

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
            var url = GetUrl(binder.Name, args);

            //craft up the URL
            result = url;
            return true;
        }

        private string GetUrl(string methodName, object[] args) {
            //split it for name of route and path/url
            var stems = methodName.Split('_');

            //lookup up the route by name
            var routeName = stems[0];
            var url = "";
            //if there are any parameters eg /about/2
            if (args.Length > 0)
                //pass in the first argument
                if (args[0].GetType() == typeof(string) || args[0].GetType().IsPrimitive) {
                    url = _helper.RouteUrl(routeName, new { id = args[0] });
                }
                else {
                    url = _helper.RouteUrl(routeName, args[0]);
                }
            else
                url = _helper.RouteUrl(routeName);

            //url or path?
            if (stems.Last() == "url") {
                url = Root(false) + url;
            }
            return url;
        }

        public string Root(bool includeAppPath = true) {
            var context = HttpContext.Current;
            var Port = context.Request.ServerVariables["SERVER_PORT"];
            if (Port == null || Port == "80" || Port == "443")
                Port = "";
            else
                Port = ":" + Port;
            var Protocol = context.Request.ServerVariables["SERVER_PORT_SECURE"];
            if (Protocol == null || Protocol == "0")
                Protocol = "http://";
            else
                Protocol = "https://";

            var appPath = "";
            if (includeAppPath) {
                appPath = context.Request.ApplicationPath;
                if (appPath == "/")
                    appPath = "";
            }
            var sOut = Protocol + context.Request.ServerVariables["SERVER_NAME"] + Port + appPath;
            return sOut;
        }
    }

Custom View Page

It would be nice to have this on every page without having to put a link in the header

@{
    Page.Title = "My Home Page: VidPub";
    dynamic router = new VidPub.Web.Infrastructure.DynamicRoute(Url);
}

but if we put a link in the _ViewStart as:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    Page.Title = "Welcome to VidPub";
    Page.Router = new VidPub.Web.Infrastructure.DynamicRoute(Url);
}

then would have to call:

<h1>@Page.Router.about_path</h1>

hmmm.. we can, by overriding the pages base class

Web.config in views directory.

<!--<pages pageBaseType="System.Web.Mvc.WebViewPage">-->
    <pages pageBaseType="VidPub.Web.Infrastructure.VidPubViewPage">

and here is our overridden base:

public class VidPubViewPage<TModel> : WebViewPage<TModel> {
        public dynamic Routes { get; set; }

        public override void InitHelpers() {
            //keep all the exisitng helpers in MBC
            base.InitHelpers();
            //add our own helper
            Routes = new DynamicRoute(Url);
        }
        
        public override void Execute() {
            base.ExecutePageHierarchy();
        }
    }
| | # 
# Thursday, 22 September 2011
( MVC | VidPub )

MVC3 Model binders are cool – form post can be structued into class, or method arguments.

So we did have a test for the original ASP.NET Membership:

Not looking for finite results, however testing the behaviour of the controller itself.

[HttpPost]
        public ActionResult Register(RegisterModel model) {
            if (ModelState.IsValid) {
                // Attempt to register the user
                MembershipCreateStatus createStatus;
                Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);

                if (createStatus == MembershipCreateStatus.Success) {
                    FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else {
                    ModelState.AddModelError("", ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

This test fails… Robs method of solving was to abstract out

        [Test]
        public void register_action_should_redirect_with_valid_email_pass() {
            var controller = new AccountController();
            var result = controller.Register(new RegisterModel {
                Email = "test@test.com",
                ConfirmPassword = "password",
                Password = "password",
                UserName = "test"
            });
            // if everything is okay in Register then it will call RedirectToActon which is a RedirectToRouteResult
            Assert.IsInstanceOf<RedirectToRouteResult>(result);
        }

am getting a createStatus of InvalidAnswer if I run in nunit

If I used the front end with exactly the same parameters it works fine.

Wiring up Custom Authentication

Changed AccountController from model binding

 public ActionResult Register(RegisterModel model) {

to

public ActionResult Register(string email, string password, string confirm) {
Either is fine, but this makes passing from our test easier:
[Test]
        public void register_action_should_redirect_with_valid_email_pass() {
            var controller = new AccountController();
            var result = controller.Register("test@test.com", "password", "password");
            // if everything is okay in Register then it will call RedirectToActon which is a RedirectToRouteResult
            Assert.IsInstanceOf<RedirectToRouteResult>(result);
        }

have wired up our new Users class Register method to handle registration:

[HttpPost]
        public ActionResult Register(string email, string password, string confirm) {
            var result = _users.Register(email, password, confirm);

            if (result.Success) {
                FormsAuthentication.SetAuthCookie(email, false /* createPersistentCookie */);
                return RedirectToAction("Index", "Home");
            }
            else {
                ViewBag.Message = result.Message;
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

so just hands it off to Users.Register:

// Have put in _db here instead of using base for simplicity.

    //public class Users : DynamicModel {
    public class Users {
        DynamicModel _db;
        public Users() {
            _db = new DynamicModel("Membership", "Users", "ID");
        }
        //public Users() : base("VidPub", "Users", "ID", "Email") { }

        public dynamic Register(string email, string password, string confirm) {
            dynamic result = new ExpandoObject();
            result.Success = false;
            if (email.Length >= 6 && password.Length >= 6 && password.Equals(confirm)) {
                try {
                    //result.UserID = this.Insert(new { Email = email, HashedPassword = Hash(password) });
                    result.UserID = _db.Insert(new { Email = email, HashedPassword = Hash(password) });
                    result.Success = true;
                    result.Message = "Thanks for signing up!";
                }
                catch (SqlException ex) {
                    result.Message = "This email already exists in our system";
                }
            }
            else {
                result.Message = "Please check your email and password - they're invalid";
            }
            return result;
        }
        public static string Hash(string userPassword) {
            return
                BitConverter.ToString(SHA1Managed.Create().ComputeHash(Encoding.Default.GetBytes(userPassword))).Replace
                    ("-", "");
        }

However test fails with a nullReferenceException in FormsAuthentication.SetAuthCookie

Mocking – Moq.. Failed.. then Abstract out worked!

Forms authentication requires that certain classes and properties are setup just so for it to work.

Just trying to set a cookie that has some encrypted data of user information.

Need to fool the framework..

Substituting own classes for what forms authentication needs:

public class TestBase {

        public void SetContext(Controller controller) {

            var context = new Mock<HttpContextBase>();
            var request = new Mock<HttpRequestBase>();
            var response = new Mock<HttpResponseBase>();
            var session = new Mock<HttpSessionStateBase>();
            var server = new Mock<HttpServerUtilityBase>();
            var user = new GenericPrincipal(new GenericIdentity("test"), new string[0]);

            context.Setup(ctx => ctx.Request).Returns(request.Object);
            context.Setup(ctx => ctx.Response).Returns(response.Object);
            context.Setup(ctx => ctx.Session).Returns(session.Object);
            context.Setup(ctx => ctx.Server).Returns(server.Object);
            context.Setup(ctx => ctx.User).Returns(user);

            request.Setup(r => r.Cookies).Returns(new HttpCookieCollection());
            request.Setup(r => r.Form).Returns(new NameValueCollection());
            request.Setup(q => q.QueryString).Returns(new NameValueCollection());
            response.Setup(r => r.Cookies).Returns(new HttpCookieCollection());

            var rctx = new RequestContext(context.Object, new RouteData());
            controller.ControllerContext = new ControllerContext(rctx, controller);

        }

But this didn’t work.. so he’s going with an abstraction, so can test against this interface using a Fake.

  • Set client access
  • Remove client access
  • Get the token
public class FormsAuthTokenStore : ITokenHandler {

        public void SetClientAccess(string token) {
            FormsAuthentication.SetAuthCookie(token, true);
        }

        public void RemoveClientAccess() {
            FormsAuthentication.SignOut();
        }

        public string GetToken() {
            return FormsAuthentication.Decrypt(HttpContext.Current.Response.Cookies["auth"].Value).ToString();
        }
    }

    public interface ITokenHandler {
        string GetToken();
        void RemoveClientAccess();
        void SetClientAccess(string token);
    }

the Fake:

public class FakeTokenStore : ITokenHandler {
        string _token;

        public string GetToken() {
            return _token;
        }

        public void RemoveClientAccess() {
            _token = "";
        }

        public void SetClientAccess(string token) {
            _token = token;
        }
    }

however MVC app won’t run now as it doesn’t know about tokenStore, and the framework will trying and inject something in for us.

public class AccountController : Controller {
        Users _users;
        ITokenHandler _tokenStore;

        public AccountController(ITokenHandler tokenStore) {
            _users = new Users();
            _tokenStore = tokenStore;
        }

We’ve already got Ninject working, so lets just wire that up.

private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<ILogger>().To<NLogger>();
            kernel.Bind<ITokenHandler>().To<FormsAuthTokenStore>();
        }

Custom functionality: Only want to allow 1 person with an account to be logged in at a given time ie single session requirement

The token is going to be a Guid.

Simulate 2 different users logging in at the same time.

ApplicationController – borrowing concept from Rails… all controllers inherit off ApplicationController, which provides an IsLoggedIn public property.

Change AccountController to inherit from ApplicationController… just putting a class in the middle.

taking the _tokenStore stuff out of AccountController and into it’s base class.

[Test]
        public void isloggedin_returns_false_for_first_user_on_dual_login() {
            // we'll need two controller instances here
            // and a way to see if the user is logged in
            _controller.Register("test@test.com", "password", "password");
            var result = _controller.LogOn("test@test.com", "password");
            Assert.True(_controller.IsLoggedIn);

            var c2 = new AccountController(new FakeTokenStore());
            c2.LogOn("test@test.com", "password");

            //see if the first controller instance returns true for logged on
            Assert.False(_controller.IsLoggedIn);
        }

So first user is logged out if second user comes in with same credentials.

GIT

image

cd ..

git status

git commit –am “got token based authentication working

git checkout master

git merge membership – merges the membership branch with the master

git push origin master

git checkout membership  - switch back to membership branch.

Review

Testing – started with it.. good

Logon

Registration

But framework got in the way.

SQLCE was good (not for me)

Massive was good

A beautiful application: “Reads well, simple to understand, and gets the job done”

image

Rob doesn’t like this as lots of repetition.. and why not have it expressed more simply ie in the Users class.

if (email.Length >= 6 && password.Length >= 6 && password.Equals(confirm)) {

A rough sketch of where we are now

Image0420

image

| | # 
# Wednesday, 21 September 2011
( Code Snippets | Git | TekPub | VidPub )

Custom Membership..forms.

Don’t advocate roll own membership

ASP.NET Membership works if

  • SQL Server forever
  • Forms auth
  • Can login multiple times

OpenID

TDD/BDD do this.

  • NUnit
  • TestDriven.net

Code Snippets

To help with mundane writing code in unit tests.

Ctrl K Ctrl B

http://weblogs.asp.net/kdente/archive/2005/05/05/405843.aspx  Kevin Dentes Blogs and nunit snippets **not imported yet as wasn’t in .snippet format

Libraries\Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets

Git console in VS

Tools/External menu.

image

which then appears in the Tools menu as git.  Change to &g for keyboard accelerator.

image

Feature branch in Git

Master is for finalised and deployed (?) code

git branch (to see current branches)

git checkout –b membership

.gitignore

*resharper.user

[Dd]ebug/

[Rr]elease/

build/

[Bb]in/

[Oo]bj/

*.suo

*.sln.cache

_ReSharper.*/

*.user

git add .

Global GitIgnore

http://jqr.github.com/2009/02/03/global-git-ignore.html

git config –global core.excludesfile c:/dev/Global.gitignore

Test 1 – Not Accept Email with < 6 Chars

From http://weblogs.asp.net/nunitaddin/

Alt + T  reassigned for run tests in TestDriven.Net

Alt + D run tests in debug

Code Snippet
public class TestBase {
        public void Describes(string description) {
            Console.WriteLine("-------------------------------");
            Console.WriteLine(description);
            Console.WriteLine("-------------------------------");
        }

        public void isPending() {
            Console.WriteLine(GetCaller() + " -- PENDING --");
            Assert.Inconclusive();
        }

        public string GetCaller() {
            StackTrace stack = new StackTrace();
            return stack.GetFrame(2).GetMethod().Name.Replace("_", " ");
        }
    }

So can get a bit of nice debug in output window:

image

YAGNI – writing smallest amount of code to make test pass.. weird but good.

 

Code Snippet
[TestFixture]
    public class MembershipSpecs : TestBase{
        public MembershipSpecs() {
            this.Describes("User Registration");
        }

        [Test]
        public void registration_should_not_accept_email_with_lt_6_chars() {
            var membership = new Membership();
            var result = membership.Register("test@test.com", "password", "password");
            Assert.False(result.Success);
        }
    }

and simplest possible implementation (not really, but Rob has an end in mind)

used Ctrl . in VS to make Membership class and file, and method.

Code Snippet
public class Membership {
        public dynamic Register(string email, string password, string confirm) {
            dynamic result = new ExpandoObject();
            result.Success = false;
            return result;
        }
    }

 

Code Snippet
[Test]
      public void registration_should_not_accept_email_with_lt_6_chars() {
          var result = _membership.Register("e", "password", "password");
          Assert.False(result.Success);
      }

      [Test]
      public void registration_should_not_accept_password_with_lt_6_chars() {
          var result = _membership.Register("test@test.com", "x", "x");
          Assert.False(result.Success);
      }

      [Test]
      public void registration_should_not_accept_mismatched_passwords() {
          var result = _membership.Register("test@test.com", "password1", "password2");
          Assert.False(result.Success);
      }

testing the False cases

Code Snippet
public dynamic Register(string email, string password, string confirm) {
            dynamic result = new ExpandoObject();
            result.Success = false;
            if (email.Length >= 6 && password.Length >=6 && password.Equals(confirm))
                result.Success = true;
            return result;
        }

Persist Information to DB – Massive

http://blog.wekeroad.com/helpy-stuff/and-i-shall-call-it-massive

Rails doesn’t matter to goto db..can be fast enough.

SQL CE4 in App_Data in Test project.  **This didn’t work.. am using SQL Server**

image

Seed on ID the PK.  getdate() on 3 date fields as default.

How to persist data?  Simplest possible for now is Massive.. a Dynamic ORM in 500lines or do.

https://github.com/robconery/massive

 

Test 2 – Registration Should Not Accept Duplicate Emails

Test is something like:

[Test]
        public void registration_should_not_accept_duplicate_emails() {
            var result = _membership.Register("test@test.com", "password", "password");
            var result2 = _membership.Register("test@test.com", "password", "password");
            Assert.False(result2.Success);
        }

so need persistence to test this:

Going to clean the db before each test!

Massive works by translating the properties of the anonymous object into column names:

Problem:  Couldn’t get Massive to connect to SQLServerCompact4.0 database with connection string:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>   <connectionStrings>     <add name="Membership"      connectionString="Data Source=C:\Dev\VidPub\Source\VidPub.Tests\App_Data\Membership_Test.sdf"      providerName="System.Data.SqlServerCE.4.0" />   </connectionStrings>
</configuration>

installed classic 2000 Northwind sample:

http://www.microsoft.com/download/en/confirmation.aspx?id=23654   then look in C:\SQL Server 2000 Sample Databases

http://ndc2011.macsimum.no/mp4/Day2%20Thursday/Track1%201140-1240.mp4 – Interesting talk from Norway on Data access history.

Now I can get a console App to talk to SQL Server Compact 4.0 :

string connectionString = ConfigurationManager.ConnectionStrings["southwind"].ConnectionString;

            //SqlCeConnection connection = new SqlCeConnection(@"Data Source=SouthWind.sdf");
            SqlCeConnection connection = new SqlCeConnection(connectionString);

            SqlCeCommand command = new SqlCeCommand("SELECT * FROM People", connection);
            SqlCeDataAdapter dataAdapter = new SqlCeDataAdapter(command);
            DataSet ds = new DataSet();
            dataAdapter.Fill(ds);
            Console.WriteLine(ds.GetXml());

now I need to get Massive talking to that db.

I can kind of get it working by changing the _providerName to

            var _providerName = "System.Data.SqlServerCE.4.0";

https://github.com/robconery/massive/pull/65 here is a fix by pulling from the App.config.

Getting strange errors!

Reverting to SQL Server

<configuration>
  <connectionStrings>
    <!--<add name="Membership" providerName="System.Data.SqlServerCE.4.0" connectionString="Data Source=C:\Dev\VidPub\Source\VidPub.Tests\Membership_Test.sdf" />-->
    <add name="Membership" providerName="System.Data.SqlClient" connectionString="Data Source=.\;Initial Catalog=Membership_Test;Integrated Security=SSPI;" />
  </connectionStrings>
</configuration>

and change provider name back to:

var _providerName = "System.Data.SqlClient";
and to put a unique constrant on SQL Server column: http://stackoverflow.com/questions/64981/sql-server-2005-how-create-a-unique-constraint

Code Snippets

image

testn is setup for me to produce:

 [Test]         public void MyTestMethod() {                      }
| | # 
# Tuesday, 20 September 2011
( Git | Glimpse | MVC | Ninject | NLog | VidPub )

Notes from TekPub ASP.NET MVC3 series of tutorials.

  • Startup – big idea.. charge people money for
  • Good idea, well executed
  • Get site out there and pay the bills!

Why MVC3 in this Fast Paced Startup?

  • Good stable version of MVC
  • I know C#

What are we doing (Elevator pitch) and when are we going to do it?

1 sentence!

Platform and Tools

MVC3

Unfuddle for project mgt

image

or agilezen (kanban)

Source is on http://github.com/tekpub/mvc3

Setup a new MVC3 solution called VidPub.Web

image

Lib is for external dlls.  Docs are for docs.  Everything is under git

git init in \dev\VidPub

git add .

git commit –am “Initial load”

DropBox

mkdir e:\dropbox\repositories\vpub

image

in e:\dropbox\repositories\vpub

git init –bare

from \dev folder  git push origin master

image

So other people can then pull from that repository… it is the whole repo up there, not just source.

Test Project

Have just created a container.

Authentication

Don’t want concurrent authentcation.. ie users sharing accounts.  Could use token based.

Now, 1 Year, 3 Years

ASP.NET Membership – complicated..

Consistency of FileNames

Public\javscripts

Public\stylesheets

no more content directory

Model\AccountModels.cs to AccountModel

change CSS path in _Layout.cshtml

CDN

http://code.google.com/apis/libraries/devguide.html#jquery

image

make faster for user as they may have this cached in their browser.  Also save us a bit of bandwidth.  Cache up to a year.

If using Azure to host http://www.microsoft.com/windowsazure/msdn-benefits/ MSDN have got some benefits.

Logging

NLog using NuGet

Code Snippet
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >

  <targets>
    <!--Useful for debugging-->
    <target name="console" xsi:type="ColoredConsole"
     layout="${date:format=HH\:mm\:ss}|${level}|${stacktrace}|${message}" />

    <target name="file" xsi:type="File" fileName="${basedir}/App_Data//logs/site.log"
     layout="${date}: ${message}" />

    <target name="eventlog" xsi:type="EventLog" source="My App" log="Application"
    layout="${date}: ${message} ${stacktrace}" />

  </targets>

  <rules>

    <logger name="*" minlevel="Info" writeTo="file" />
    <logger name="*" minlevel="Fatal" writeTo="eventlog" />

  </rules>

</nlog>

in dev we’re going to log everything to a file in App_Data/logs

In \Infrastructure we’ve got some code to help us log with an interface extracted.

Code Snippet
public class NLogger : VidPub.Web.Infrastructure.Logging.ILogger {
        Logger _logger;
        public NLogger() {
            _logger = LogManager.GetCurrentClassLogger();
        }
        public void LogInfo(string message) {
            _logger.Info(message);
        }

        public void LogWarning(string message) {
            _logger.Warn(message);
        }

        public void LogDebug(string message) {
            _logger.Debug(message);
        }

        public void LogError(string message) {
            _logger.Error(message);
        }
        public void LogError(Exception x) {
            LogError(BuildExceptionMessage(x));
        }
        public void LogFatal(string message) {
            _logger.Fatal(message);
        }
        public void LogFatal(Exception x) {
            LogFatal(BuildExceptionMessage(x));
        }
        string BuildExceptionMessage(Exception x) {

            Exception logException = x;
            if (x.InnerException != null)
                logException = x.InnerException;

            string strErrorMsg = Environment.NewLine + "Error in Path :" + System.Web.HttpContext.Current.Request.Path;

            // Get the QueryString along with the Virtual Path
            strErrorMsg += Environment.NewLine + "Raw Url :" + System.Web.HttpContext.Current.Request.RawUrl;


            // Get the error message
            strErrorMsg += Environment.NewLine + "Message :" + logException.Message;

            // Source of the message
            strErrorMsg += Environment.NewLine + "Source :" + logException.Source;

            // Stack Trace of the error

            strErrorMsg += Environment.NewLine + "Stack Trace :" + logException.StackTrace;

            // Method where the error occurred
            strErrorMsg += Environment.NewLine + "TargetSite :" + logException.TargetSite;
            return strErrorMsg;
        }
    }

IoC

So we don’t have anything coupled tightly right from the start (eg logging which we’re doing next).. lets use IoC

Ninject.MVC3

Wire up in global.asax

 public class MvcApplication : NinjectHttpApplication  {

**NO this is not correct as in MVC3 it is bootstrapped, so just leave as:

public class MvcApplication : System.Web.HttpApplication {

Setting up logging in App_Start bootstrapper file.

“Every time you see a request for ILogger interface in a controller, return a new NLogger class”

 

kernel.Bind<ILogger>().To<NLogger>();

added in Logs/Site.log into our project.

Glimpse

Added in bookmarks into bar to turn on and off.

image

git add .

git commit –am “Added logging, IoC and rearranged stuff”

git push origin master (to save to dropbox)

 

current state of play of our filesystem.

image

| | #