Search

Categories

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Send mail to the author(s) E-mail

# Tuesday, 18 October 2011
( Massive | SQL )

Clarifying how to get different types in and out of Massive elegantly:

image

test database of MassiveTypeTest

image

showing correct types coming out of Massive ie DateTime, Int32 and String.  NZ locale.

Getting Correct Types/Data into Massive

image

CreateFrom just puts the formcolleciton into an ExpandoObject, of strings. Making sure that column names are in db.

image

using prototype I can put in default values from the db.  This is very handy in Create.  As then can easily send back a failed create and display the correct data without having to check for nulls initally:

image

Best practice seems to be to insert as: yyyy-MM-dd

//convert nz to sql date
            //nz is dd/mm/yyyy   sql is yyyy-mm-dd
            string x = itemToCreate.DateCreated;
            string[] bits = x.Split('/');
            var day = bits[0];
            var month = bits[1];
            var year = bits[2];
            var sqlDate = year + "-" + month + "-" + day;
            itemToCreate.DateCreated = sqlDate;

 

image

or

image

if we set getdate() in default for the datecolumn in sql.  Then when prototype runs, it makes a new shortdatestring for us.

Summary

Data coming out of Massive

The type is figured out by the sqldatareader

  • Query – calls RecordToExpando..then yield returns an IEnumerable of expandoObjects
  • RecordToExpando – uses sqldatareader to put the type into the expando

Data going in to Massive:

anything.. Massive just makes SQL from the value that are passed in the expando.ToString()

  • CreateFrom checks to see if column name is in db, then makes an expandoObject
  • Insert(expandoObject) calls CreateInsertCommand which just loops over the expando as a dictionary
| | # 
# Thursday, 06 October 2011

While working on the TricksController of my project, I came across .All(), but then .FindBy which needs dynamics.  So now is the time to have a good look at massive and understand:

  • what is going on under the hood of massive
  • what can I strip out to make even simple

First up, is to do the simplest possible thing ie console app.

class Program {
        static void Main(string[] args) {
            Products _tbl = new Products();
            IEnumerable<dynamic> products = _tbl.All();

            foreach (var item in products) {
                Console.WriteLine(item.ProductName);
            }

            Console.WriteLine("number of products is: " + products.Count());
            Console.ReadLine();
        }
    }

    public class Products : DynamicModel {
        public Products() : base("Northwind", "Products", "ProductID") {
        }
    }

This shows all 77products (checked against the db).

namespace Massive3 {
    public class DynamicModel : DynamicObject {
        DbProviderFactory _factory;
        string _connectionString;
        public string TableName { get; set; }
        public string PrimaryKeyField { get; set; }

        public DynamicModel(string connectionStringName, string tableName, string primaryKeyField) {
            TableName = tableName;
            PrimaryKeyField = primaryKeyField;
            string _providerName = "System.Data.SqlClient";
            _factory = DbProviderFactories.GetFactory(_providerName);
            _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
        }

        public IEnumerable<dynamic> All(string where = "", string orderBy = "", int limit = 0, string columns = "*") {
            string sql = BuildSelect(where, orderBy, limit);
            return Query(string.Format(sql, columns, TableName));
        }

        private static string BuildSelect(string where, string orderBy, int limit) {
            string sql = limit > 0 ? "SELECT TOP " + limit + " {0} FROM {1} " : "SELECT {0} FROM {1} ";
            if (!string.IsNullOrEmpty(where))
                sql += where.Trim().StartsWith("where", StringComparison.OrdinalIgnoreCase) ? where : "WHERE " + where;
            if (!String.IsNullOrEmpty(orderBy))
                sql += orderBy.Trim().StartsWith("order by", StringComparison.OrdinalIgnoreCase) ? orderBy : " ORDER BY " + orderBy;
            return sql;
        }

        public IEnumerable<dynamic> Query(string sql) {
            using (var conn = OpenConnection()) {
                var rdr = CreateCommand(sql, conn).ExecuteReader();
                while (rdr.Read()) {
                    yield return rdr.RecordToExpando(); ;
                }
            }
        }

        public DbConnection OpenConnection() {
            var result = _factory.CreateConnection();
            result.ConnectionString = _connectionString;
            result.Open();
            return result;
        }

        DbCommand CreateCommand(string sql, DbConnection conn) {
            var result = _factory.CreateCommand();
            result.Connection = conn;
            result.CommandText = sql;
            return result;
        }
    }

    public static class ObjectExtensions {
        public static dynamic RecordToExpando(this IDataReader rdr) {
            dynamic e = new ExpandoObject();
            var d = e as IDictionary<string, object>;
            for (int i = 0; i < rdr.FieldCount; i++)
                d.Add(rdr.GetName(i), DBNull.Value.Equals(rdr[i]) ? null : rdr[i]);
            return e;
        }
    }
}

Refactored to a very simple tool.. only All() and Query() work.

Constructor - Optional Parameters (C#4)

however the 2nd, 3rd, and 4th parameters calling the base constructor are optional:

class Program {
        static void Main(string[] args) {
            Products _tbl = new Products();
            IEnumerable<dynamic> products = _tbl.All();

            foreach (var item in products) {
                Console.WriteLine(item.ProductName);
            }

            Console.WriteLine("number of products is: " + products.Count());
            Console.ReadLine();
        }
    }

    public class Products : DynamicModel {
        public Products() : base("Northwind") {
        }
    }

So this still works.. how does it know to get the Products table and the appropriate ID?

public DynamicModel(string connectionStringName, string tableName = "", string primaryKeyField = "", string descriptorField = "") {
            //if no tableName passed, use the calling classes name
            TableName = tableName == "" ? this.GetType().Name : tableName;
            //if no primaryKeyField passed, use ID
            PrimaryKeyField = string.IsNullOrEmpty(primaryKeyField) ? "ID" : primaryKeyField;
            string _providerName = "System.Data.SqlClient";
            _factory = DbProviderFactories.GetFactory(_providerName);
            ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
        }

DbProviderFactory is the stuff needed to connect to a specific database.  This is a way of not tying into MSSQL

ALL() – Named Parameters

/// <summary>
        /// Returns all records complying with the passed-in WHERE clause and arguments,
        /// ordered as specified, limited (TOP) by limit.
        /// </summary>
        public virtual IEnumerable<dynamic> All(string where = "", string orderBy = "", int limit = 0, string columns = "*", params object[] args) {
            string sql = BuildSelect(where, orderBy, limit);
            return Query(string.Format(sql, columns, TableName), args);
        }

All is virtual, meaning that I could override it in Products if I wanted to.

All parameters are optional so I could call:

IEnumerable<dynamic> products = _tbl.All(where: "ProductName like '%s%'",
                                                    orderBy: "ProductName",
                                                    limit: 5,
                                                    columns: "ProductName, UnitPrice");

or go directly to the method that All calls ie Query:

IEnumerable<dynamic> products2 = _tbl.Query(@"SELECT * FROM Products");

Query(sql, args)

In order to understand what is going on, am going to strip down Massive to only do what I need:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Data.Common;
using System.Configuration;
using System.Data;

namespace Massive3 {
    public static class ObjectExtensions {
        public static void AddParams(this DbCommand cmd, params object[] args) {
            foreach (var item in args) {
                AddParam(cmd, item);
            }
        }
        public static void AddParam(this DbCommand cmd, object item) {
            var p = cmd.CreateParameter();
            p.ParameterName = string.Format("@{0}", cmd.Parameters.Count);
            if (item == null) {
                p.Value = DBNull.Value;
            }
            else {
                if (item.GetType() == typeof(Guid)) {
                    p.Value = item.ToString();
                    p.DbType = DbType.String;
                    p.Size = 4000;
                }
                else if (item.GetType() == typeof(ExpandoObject)) {
                    var d = (IDictionary<string, object>)item;
                    p.Value = d.Values.FirstOrDefault();
                }
                else {
                    p.Value = item;
                }
                if (item.GetType() == typeof(string))
                    p.Size = ((string)item).Length > 4000 ? -1 : 4000;
            }
            cmd.Parameters.Add(p);
        }
        public static dynamic RecordToExpando(this IDataReader rdr) {
            dynamic e = new ExpandoObject();
            var d = e as IDictionary<string, object>;
            for (int i = 0; i < rdr.FieldCount; i++)
                d.Add(rdr.GetName(i), DBNull.Value.Equals(rdr[i]) ? null : rdr[i]);
            return e;
        }
    }

    //DynamicObject is .NET
    public class DynamicModel : DynamicObject {
        DbProviderFactory _factory;
        //strange naming.. would expect _connectionString
        string ConnectionString;
        //auto-implemented properties which can be overridden
        public virtual string TableName { get; set; }
        public virtual string PrimaryKeyField { get; set; }
        
        public DynamicModel(string connectionStringName, string tableName = "", string primaryKeyField = "", string descriptorField = "") {
            //if no tableName passed, use the calling classes name
            TableName = tableName == "" ? this.GetType().Name : tableName;
            //if no primaryKeyField passed, use ID
            PrimaryKeyField = string.IsNullOrEmpty(primaryKeyField) ? "ID" : primaryKeyField;
            string _providerName = "System.Data.SqlClient";
            //stuff needed to connect to a database
            _factory = DbProviderFactories.GetFactory(_providerName);
            //connectionStringName has to be passed into this constructor
            ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
        }

        public virtual IEnumerable<dynamic> All(string where = "", string orderBy = "", int limit = 0, string columns = "*", params object[] args) {
            string sql = BuildSelect(where, orderBy, limit);
            return Query(string.Format(sql, columns, TableName), args);
        }

        private static string BuildSelect(string where, string orderBy, int limit) {
            string sql = limit > 0 ? "SELECT TOP " + limit + " {0} FROM {1} " : "SELECT {0} FROM {1} ";
            if (!string.IsNullOrEmpty(where))
                sql += where.Trim().StartsWith("where", StringComparison.OrdinalIgnoreCase) ? where : "WHERE " + where;
            if (!String.IsNullOrEmpty(orderBy))
                sql += orderBy.Trim().StartsWith("order by", StringComparison.OrdinalIgnoreCase) ? orderBy : " ORDER BY " + orderBy;
            return sql;
        }

        public virtual IEnumerable<dynamic> Query(string sql, params object[] args) {
            using (var conn = OpenConnection()) {
                //in my case rdr is a SqlDataReader
                var rdr = CreateCommand(sql, conn, args).ExecuteReader();
                //read results and put into an Expando taking into consideration nulls
                while (rdr.Read()) {
                    yield return rdr.RecordToExpando(); ;
                }
            }
        }

        public virtual DbConnection OpenConnection() {
            var result = _factory.CreateConnection();
            result.ConnectionString = ConnectionString;
            result.Open();
            return result;
        }

        DbCommand CreateCommand(string sql, DbConnection conn, params object[] args) {
            var result = _factory.CreateCommand();
            result.Connection = conn;
            result.CommandText = sql;
            if (args.Length > 0)
                result.AddParams(args);
            return result;
        }
    }
}

Even Simpler

class Program {
        static void Main(string[] args) {
            DynamicModel _tbl = new DynamicModel("Northwind", "Products", "ProductID");
            IEnumerable<dynamic> products = _tbl.Query(@"SELECT * FROM Products").ToList();

            foreach (var item in products) {
                Console.WriteLine(item.ProductName);
            }

            Console.WriteLine("number of products is: " + products.Count());
            Console.ReadLine();
        }
    }

calling Massive directly (as opposed to the Model - Product calling it)

Notice we’re not using dynamic here, so _tbl.Single() won’t work (as need _tbl to be dynamic)

Taken out everything except All().. so above console app will still work.

Simplifying even further – have taken out params.

So this code can do SELECT

  • Specific columns
  • Where
  • OrderBy
  • Top

and return an IEnumerable<dynamic>, then dynamic being an ExpandoObject

Deferred Execution

Because Query returns an IEnumerable<dynamic> it is only executed when iterated.

Code only executes Query method when iterating over products in foreach.

Heart of Massive

Query returns an IEnumerable<dynamic>.. which is a IEnumerable<System.Dynamic.ExpandoObject>

each ExpandoObject in this case has

  • ProductName
  • UnitPrice
  • etc..

TryInvokeMember –Useful Query Tool

Single doesn’t exist in DynamicModel (Massive).  So DynamicObject’s TryInvokeMember is called to kick start the dynamic query stuff.

dynamic tbl = new DynamicModel("Northwind", "Products", "ProductID");
            var product = tbl.Single();

can use:

  • FindBy
  • Last (descending)
  • Single
  • First (ascending)
  • Count
  • Sum
  • Max
  • Min
  • Avg
| | # 
# 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
| | #