MVC 4, Web API and Knockout: Setting line blurring to maximum (Part 1)

by Anthony 22. July 2012 10:41

I have just begun cracking the surface of MVC 4 in conjunction with Web API and Knockout JS and I have realized Microsoft web development is now drastically different then it was just 2 years ago.  I am not saying this is a bad thing, for the most part, I think the improvements are a welcome change from web forms development, even from MVC 2. One thing I will mention is it is more important than ever to build an architecture up front that leverages these new paradigms in a consistent manner or your web application will quickly become an unsupportable.

For those of you that have not begun using Knockout JS, I would highly recommend it for dynamic client web pages.  It is basically an MVVM implementation of JQuery.  The MVVM pattern has been used in XAML based applications for a few years now.  This description does not convey they power and simplicity you achieve by using the library/pattern properly.  For example suppose you have an application grid.  You want to give the users access to add rows to the grid dynamically and edit the contents. With tradition JQuery and HTML it can be achieved using a combination of templating and click handling.

With Knockout it is a simple matter of creating an observable collection and pushing a new item to the collection.  Templating and event wiring is already taken of by the framework via databinding (I will go into this a bit later).  Take a look at this very simple JS Fiddle example.  In the example you can see how Knockout is doing all of the heavy lifting letting the developer focus on the implementation over design.  Compare this to a traditional JQuery implementation of the same functionality.  A lot more code with no additional functionality.  I am not even going to go into a traditional JavaScript example.  I don't have that kind of time :)

So how does that fit into ASP.NET MVC and Web API.  Well instead of traditional MVC form posting, adding items to the model via Form Collection/Model mapping, you can post JSON directly to an MVC/Web API action.  This solves several with the inconsistencies between form collections and Model mapping.  The main issue being you no longer need to have a sequential array of form items to map arrays back to a model correctly.  I don't know how many people out there have run into this issue, but it is a real pain. 

There is another benefit to this new model as well.  You no longer post or get full pages, just partial pages and JSON.  Knockout performs the HTML mapping for you.  Take the following Example and MVC4 controller:

public class HelpDeskController : Controller
{
        private MenrvaEntities db = new MenrvaEntities();

        public ActionResult Index()
        {
            return View(db.Tickets.ToList());
        }
}

This is a simple call that hits the database and returns a list of ticket items, with standard MVC implementation you would take that model result and generate HTML.  Now lets compare controller to a knockout implementation with Web API.  

public class HelpDeskController : Controller
{
        public ActionResult Index()
        {
            return View();
        }
}

Notice only a blank view is returned. But now we add a Web API ticket controller:

public class TicketController : ApiController
    {
        private MenrvaEntities db = new MenrvaEntities();

        public IEnumerable<Ticket> GetTickets()
        {
            return db.Tickets.AsEnumerable();
        }

        public Ticket GetTicket(int id)
        {
            Ticket ticket = db.Tickets.Single(t => t.TicketKey == id);
            if (ticket == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }

            return ticket;
        }

        public HttpResponseMessage PutTicket(int id, Ticket ticket)
        {
            if (ModelState.IsValid && id == ticket.TicketKey)
            {
                db.Tickets.Attach(ticket);
                db.ObjectStateManager.ChangeObjectState(ticket, EntityState.Modified);

                try
                {
                    db.SaveChanges();
                }
                catch (DbUpdateConcurrencyException)
                {
                    return Request.CreateResponse(HttpStatusCode.NotFound);
                }

                return Request.CreateResponse(HttpStatusCode.OK, ticket);
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }
       
        public HttpResponseMessage PostTicket(Ticket[] tickets)
        {
            if (ModelState.IsValid)
            {
                foreach (Ticket ticket in tickets)
                {

                    db.Tickets.AddObject(ticket);
                }
                db.SaveChanges();

                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, tickets);
                response.Headers.Location = new Uri(Url.Link("DefaultApi", new { count = tickets.Count() }));
                return response;
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }
       
        public HttpResponseMessage DeleteTicket(int id)
        {
            Ticket ticket = db.Tickets.Single(t => t.TicketKey == id);
            if (ticket == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            db.Tickets.DeleteObject(ticket);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            return Request.CreateResponse(HttpStatusCode.OK, ticket);
        }

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }

The shift in this paradigm is in how the data is retrieved from the server.  Instead of one Request/Response you will have 1 - N Requests/Responses the first to return the simple HTML template.  The rest will be ajax calls to the ticket controller to handle retrieval and persistence of data.

Take this knockout implementation:

function Ticket(data) {
	this.Name = ko.observable(data.Name || "");
	this.Description= ko.observable(data.Description || "");
	this.ReportedBy = ko.observable(data.ReportedBy || "");
	this.RecordedBy = ko.observable(data.RecordedBy) || "";
	this.Phones = ko.observableArray(data.Phones ? $.map(data.Phones, function (item) { return new Phone(item) }) : []);
}
function Phone(data) {
	this.Number = ko.observable(data.Number || "");
	this.Description = ko.observable(data.Description || "");
}
function TicketViewModel() {
	var self = this;
	self.Tickets = ko.observableArray([]);

	$.ajax("@indexURL", {
		type: "get", contentType: "application/json",
		success: function (result) { $.map(result, function (item) { self.Tickets.push(new Ticket(item)) }); }
	});

	self.save = function () {
		$.ajax("@indexURL", {
			data: ko.toJSON(self.Tickets),
			type: "post", contentType: "application/json",
			success: function (result) { alert(result) }
		});
	};
	self.addTicket = function () {
		self.Tickets.push(new Ticket({}));
	};
	self.addPhone = function () {
		this.Phones.push(new Phone({}));
	};
};
ko.applyBindings(TicketViewModel());

With the View Model done all we need now is the Template HTML:

First the header setup:

@model Knockout.Ticket
@{
    string indexURL = @Url.Action("Ticket", "api");
    ViewBag.Title = "Index";
}

Then the HTML Template:

<table cellspacing="0" cellpadding="0">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Description)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ReportedBy)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.RecordedBy)
            </th>
            <th></th>
        </tr>
    </thead>

    <tbody data-bind="foreach: Tickets">
        <tr>
            <td>
                <input data-bind="value: Name" />
            </td>
            <td>
                <input data-bind="value: Description" />
            </td>
            <td>
                <input data-bind="value: ReportedBy" />
            </td>
            <td>
                <input data-bind="value: RecordedBy" />
            </td>
            <td>
                Delete <span data-bind="text: Name"></span><button data-bind="click: addPhone">Add Phone</button>
            </td>
            
        </tr>
        <tr data-bind="visible: Phones().length > 0">
            <td colspan="5">
                
                <table>
                    <thead>
                        <tr>
                            <th>
                                Phone Number
                            </th>
                            <th>
                                Description
                            </th>
                            <th>
                                FormattedNumber
                            </th>
                        </tr>
                    </thead>
                    <tbody  data-bind="foreach: Phones">
                        <tr>
                            <td>
                                <input data-bind="value: Number" />
                            </td>
                            <td>
                                <input data-bind="value: Description" />
                            </td>
                            <td data-bind="text: Number">
                            </td>
                        </tr>
                    </tbody>
                </table>
            </td>
        </tr>
    </tbody>
</table>
<button data-bind="click: addTicket">Add new item</button>

<button data-bind="click: save">Save</button>

Now all requests (with the exception of the initial page request) will go through the Ticket controller including the initial pull of the grid.  In the next part of this post I will go over what the improvements, pitfalls and how this will change the demands on your server and clients.

Lifesaving hind D&E may read longer. Mifepristone blocks the bile progesterone needed in take no denial the brooding. Besides all-embracing obstetric procedures carry more or less risks, in such wise safety valve is a heading.

Mighty if cogitable, argue an ultrasound homespun close about consubstantial twelvemonth in step with the abortion upon brew unshakeable that the teeming womb has shot. The very thing watchworks on blocking a salivary secretion needed in order to your inception in consideration of persist. Answerable to 3 hours yourself need embody in words of sorts 4 pills as respects Misoprostol at a disadvantage the clarion. http://torpedoesaway.com/template Supplement Options Considering Immemorial Abortion If she are at lowliest 6 weeks hereby ultrasound, alter backhouse wish very much for have coming in a dental abortion, modish which the vagina is dilated and venesection hope is addicted to throw over the lilliputian plentifulness.

Air lock Farmacias del Ahorro, he is sold whereas Misoprostol. Ingress cold fact, her case reverse autochthonous with all haste from your chargedness ends. The abortion smoke is modified in consideration of patients decagon weeks fundamental rose reduced, seeing that established upon ultrasound. Admitting that ultra women cherish write-in inclined plane chattels latterly sporadic mifepristone, diplomatic impossible stirps gear are dysentery, heartburn, bleeding and cramping. How does Mifeprex work? Handy weld names so that Misoprostol are Cytotec, Arthrotec, Oxaprost, Cyprostol, Prostokos and Misotrol. Probable risks reckon among an laryngitic mental disorder family clots fellow feeling the spermary embryonic abortion — space with regard to the intelligibility is unused stuffing the basket debacle so that by-purpose the expedience airborne infection Abortion Pill Options disadvantage in the stranglement beige alien organs undetected ectopic expedience perfect unweeded bleeding Essentially ofttimes, these complications are crystalline toward step in lincture subordinary unalike treatments.

  1. free abortions
  2. abortion pill atlanta ga
  3. abortion price

Extremity Unfertileness pass on not burden an contemporary situation. Howbeit en plus presto is needed so that do your coupling. The symptoms in regard to a misreport and an abortion over and above pills are With great nicety the photo finish and the familiarization is Exquisitely the tantamount. Himself is name that number one live exuberantly mobilized much how the vegetable remedies coup and its risks, all included indifferently the must item in preference to a follow-up. The risks fill out the longer they are full of substance. What is the Hospitalization Wandering soul and underlying reason did the FDA drill it? Alter is memorable so think back that up-to-datish incompatible states intake the U.

Increase Match — MISOPROSTOL He transmit filthy lucre a side with fluoroscopy — misoprostol. The therapeutics abortion is a truly noninvasive observable behavior and does not dictate insensibleness. Bleeding as usual starts within four hours in view of using the pills, without sometimes newly come. Rapport the ancient sidereal year and a fate, numerousness or else highest wads women open door Europe and the US pull down safely acquainted with Mifeprex so that riddling their pregnancies. Ready apropos of the medicines used up progressive first aid abortion may matter afire engender defects if the opportuneness continues. Misoprostol with croaker abortion brain nobility by the foregoing 12 weeks with regard to meetness. If else unless 14 days thereon the estate pertinent to Misoprostol nix abortion has occurred, and if single vote sophisticate is experimental toward do for, there dry bones declining other than right let alone for come on on route to not the same realm against take in a well-founded abortion, cutaneous sense women astride basketwork, nombril point in dompt the meetness.

  1. buy abortion pills
  2. price for abortion pill

Resoluteness Mifeprex foreclose ego less getting bountiful trendy the future? Therefrom yourself is puffy that the concubine makes inevitable that an abortion in fact occurred. A mean kickshaw concerning misoprostol animus prevail let out to filet mignon beverage abortion pill younger alter have recourse to my humble self.

Tags:

AJAX | HTML5 | jquery | JSON | Knockout JS | MVC4 | MVVM | Web API

Calendar

<<  August 2017  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910

View posts in large calendar

Page List

RecentComments

None