MVC Not the Magic Bullet

by Admin 1. July 2012 08:31

I have been working with ASP.MVC 3 fairly heavily for the past year and I am anxiously awaiting the production release of VS 11 and MVC 4.0.  I think ASP.MVC  is a fantastic platform and solves many of the issues that exist with ASP.Net webforms.  The issue that I have notice though is many people in the development community are touting it as a “Magic Bullet” of sorts.  I have heard “Development times will shrink when we move to MVC” or “This would be so much easier in MVC” or “You won’t have to worry about page bloat in MVC.”  These comments are really only half truths.  

Will development time speed up under MVC?  In the long run with competent architects, developers and designers timelines should shrink a small amount (I need more data to give exact numbers) in MVC. Developers can spend their time writing business logic without having worry about the impact to the UI (with some considerations).  Designers can focus on making the UI look good without worrying about to much ASP gobbledygook (with some considerations). In addition SEO competent designers will have a much easier time adding SEO compliance to the site (if that is your goal).

Will development get easier in MVC? Yes and no.  I would like to discuss this particular topic in three parts. First, there are many seasoned ASP.NET Web Form developers out there that are well versed in the page life cycle, web controls and the ins and outs of ASP.NET in general, and can deliver excellent web sites working in that framework.  MVC (razor in particular) will be a complete paradigm shift for them.  I have heard several developers compare it to classic ASP and they are a bit annoyed they are being pushed back in that direction.  This could not be further from the truth, but a cursory glance at MVC (especially non-razor) it is easy to see how you could jump to that conclusion.  The removal of view-state and control-state presents a new set of challenges when working with pages.  The heavy dependency on JavaScript and JQuery on validations, AJAX and UI will be a bit of a learning curve for web developers that have only had to deal with that on a cursory level.  It is now, in my opinion, impossible to keep developers shielded from JavaScript and to a point even CSS.  Most developers are already comfortable in these areas but I know of several that only use the two when absolutely necessary.

Secondly, MVC (and HTML 5) blurs the lines between behavior, formatting and content which traditionally follows this model:

Web Block

The new model looks something like this:

Web Venn

Leading to questions of what should I use and when?  There really is no hard and fast answer to that question.  I usually use the following as a guide line in order of importance?  

Target Browser Compatible > Robustness of Functionality > Ease of Implementation > Maintainability

This wasn’t as much of a concern in ASP.NET Webforms as many of the web controls used handled all three aspects in their server side properties.  Which brings me to my third topic; the lack of standardized custom controls with ASP.NET MVC.  With ASP.NET MVC there is no such beast as a “Web Control”, there are only HTML helpers, JavaScript and JQuery libraries.  The big component players (Telerik, Infragistics and ComponentOne) are providing some JQuery libraries and MVC libraries but not much that competes with the free items already out there also JQuery changes so often that using version based component specific JavaScript and CSS really limits your ability to keep up to date.  I found out the hard way trying to go with the old model of “buy rather than build” can severely limit your options.  Recently I have been going the way of “find and tweak rather than build from scratch.”  GitHub and CodePlex are becoming the new source of third party components.  These controls however are purely an implementation of HTML, JavaScript/JQuery and CSS.  They are not server code specific.  So throw out the idea of setting some properties in the designer and it just working.

Lastly, can developers be completely oblivious to page bloat?  Absolutely not, page bloat has just shifted from viewstate to JavaScript includes and CSS.  Finding all of the controls to replace existing libraries comes with the price of including more and more .JS files.  So if you think you are trading you 500k viewstate for 0k MVC, guess again.  If you are not careful it could be 500k viewstate for 1 meg of added script.  Luckily MVC 4 is working on combatting that issue with bundling, but that isn’t a magic bullet either.

The long and short; if you were slashing timelines and removing servers from you web farms because you thought all of your problems were solved.  You are gonna find yourself up “bit” creek without a paddle.

 

Tags: , , ,

ASP.NET | CodePlex | GitHub | HTML5 | MVC3

Update Panel: The Devil's toolkit

by Admin 1. April 2012 06:27

I am preparing to take my Microsoft Web Developer certification and in doing so I am forced to relearn one of (in my opinion) the biggest missteps in ASP.NET history; the “Update Panel”.  Now I realize many developers feel the “Update Panel” is a great shortcut, I am not one of them.  While it may look as though you are “Ajaxafying” your web page you are actually just prepping your web server for an eventual crash(maybe a bit over dramatic).  Rather than pontificate on the whys and hows I think it would be more effective to give a demonstration.  I will start from best to worst; I will use a simple example of an ajax zip code lookup:


Example 1:

Let’s look at the numbers for an pure JSON/AJAX implementation of the lookup, using ASP.MVC :

Initial Page Size:

4513 Bytes

Lookup Request Length:

15 bytes

Request Body: 

{ 'zip': '08021'}

Lookup Response Length:

52 bytes

Response Body: 

{"Zip":"08021","City":"Laurel Springs","State":"NJ"}

All and all this is a very small amount of data being sent back and forth.  There is very little in the way of overhead in these calls.  The implementation is as follows:

Important Controls:

<div class="editor-label">
        @Html.LabelFor(model => model.City)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.City)
        @Html.ValidationMessageFor(model => model.City)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.State)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.State)
        @Html.ValidationMessageFor(model => model.State)
    </div>
    <div class="editor-label">
        @Html.LabelFor(model => model.Zip)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Zip)
        @Html.ValidationMessageFor(model => model.Zip)
        <input type="button" onclick="return getData();" value="Update City/State" />
    </div>

Script:

function getData() {
        var zip = $("#Zip").val();
        $.ajax(
            { type: "POST",
            url: "@(Url.Content("~/Contact/_getZipData"))",
            data: "{'zip':'" + zip + "'}",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(data){
                    $("#City").val(data.City);
                    $("#State").val(data.State);
                },
            async: true,
            error: function(jqXHR, textStatus, errorThrown){
                alert("something bad happened.");
            }
        });
        return false;
    }

Controller:

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

        [HttpPost]
        public ActionResult _getZipData(string zip)
        {
            ZipCode zipCodeData = zip.GetCityAndState();
            return Json(zipCodeData);
        }
    }

Example 2:

The following is a WCF AJAX enabled service.  In the world of ASP.NET this is the optimal method for the backend of an Ajax service.  I am using Microsoft Ajax functions to perform the communication to the server (you can use the same JQuery method of calling the service as in the previous example).  This is where we will notice some inefficiencies occur:

Initial Page Size:

5697 Bytes

Lookup Request Length:

15 bytes

Request Body: 

{"zip":"08021"}

Lookup Response Length:

100 bytes

Response Body: 

{"d":{"__type":"ZipCode:#MvcApplication1.Cache","City":"Laurel Springs","State":"NJ","Zip":"08021"}}

Important Controls:

<div class="editor-label">
            <asp:Label ID="lbCity" runat="server" Text="City" AssociatedControlID="tbCity"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbCity" ClientIDMode="Static" runat="server"></asp:TextBox>
        </div>
        <div class="editor-label">
            <asp:Label ID="lbState" runat="server" Text="State" AssociatedControlID="tbState"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbState" ClientIDMode="Static" runat="server"></asp:TextBox>
        </div>
        <div class="editor-label">
            <asp:Label ID="lbZip" runat="server" Text="Zip" AssociatedControlID="tbZip"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbZip" ClientIDMode="Static" runat="server"></asp:TextBox>
            <input type="button" onclick="return getData();" value="Update City/State" />
        </div>

Script:

function getData() {
    var zip = $("#tbZip").val();
    GetZipData.GetZipInfo(zip, onSucceed, onError);
    return false;
}

function onSucceed(result) {
    $("#tbCity").val(result.City);
    $("#tbState").val(result.State);
}

function onError(result) {
     alert("something bad happened.");
}

Code Behind (I added the debug writes to show the page is not calling events in the Ajax call):

public partial class WcfMethodTest : System.Web.UI.Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Page Init");
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Page Load");
        }
        protected void Page_PreRender(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Page PreRender");
        }
        protected void Page_Unload(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Page Unload");
        }
    }

Service:

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class GetZipData
{
    [OperationContract]
    public ZipCode GetZipInfo(string zip)
    {
        ZipCode zipCodeData = zip.GetCityAndState();
        return zipCodeData;
    }
}

Example 3:

This is using the Microsoft AJAX JS libraries to call a Page method. You will notice there is very little difference between this and the WCF implementation with the exception of initial page size.

Initial Page Size:

9337 Bytes (a lot of scaffolding needed for this implementation)

Lookup Request Length:

15 bytes

Request Body: 

{"zip":"08021"}

Lookup Response Length:

99 bytes

Response Body: 

{"d":{"__type":"MvcApplication1.Cache.ZipCode","Zip":"08021","City":"Laurel Springs","State":"NJ"}}

Important Controls:

        <div class="editor-field">
            <asp:TextBox ID="tbCity" ClientIDMode="Static" runat="server"></asp:TextBox>
        </div>
        <div class="editor-label">
            <asp:Label ID="lbState" runat="server" Text="State" AssociatedControlID="tbState"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbState" ClientIDMode="Static" runat="server"></asp:TextBox>
        </div>
        <div class="editor-label">
            <asp:Label ID="lbZip" runat="server" Text="Zip" AssociatedControlID="tbZip"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbZip" ClientIDMode="Static" runat="server"></asp:TextBox>
            <input type="button" onclick="return getData();" value="Update City/State" />
        </div>

Script:

function getData() {
    var zip = $("#tbZip").val();
    PageMethods.GetZipData(zip, onSucceed, onError);
    return false;
}

function onSucceed(result) {
    $("#tbCity").val(result.City);
    $("#tbState").val(result.State);
}

function onError(result) {
    alert("something bad happened.");
}

Code Behind (I added the debug writes to show the page is not calling events in the Ajax call):

public partial class PageMethodTest : System.Web.UI.Page
{
    protected void Page_Init(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page Init");
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page Load");
    }

    protected void Page_PreRender(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page PreRender");
    }

    protected void Page_Unload(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page Unload");
    }

    [WebMethod()]
    public static ZipCode GetZipData(string zip)
    {
        ZipCode zipCodeData = zip.GetCityAndState();
        return zipCodeData;
    }
}

Example 4:

This is using the Update Panel to update the controls that should be updated in the same manner as the previous examples. This is by far the worst of all of the examples posted

Initial Page Size:

5538 Bytes (less than the previous example; less scaffolding required)

Lookup Request Length:

638 bytes (over a 4000% increase over previous examples!!!)

Request Body:

ctl00%24body%24smUpdate=ctl00%24body%24smUpdate%7Cctl00%24body%24lbUpdateCityState&__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUKLTE0ODYwOTIxMWRkXgPL66ERIqtx3gVubvTQQrkVqfAVcjgDjOwCDEs4Z8k%3D&__EVENTVALIDATION=%2FwEWCwKC9ZT3DAK59tu9AgL%2F1ZrTAgLIuYntCwKZ9eKsCgKp742yBwLVs6yZCQKqxfb2DALOw%2ByZAwKu2fbCAQL%2FlNACnJEvyD3m7WcENitn%2FCghxG8SBhyLx5ZsHpmAJLC57q4%3D&ctl00%24body%24tbFirstName=&ctl00%24body%24tbLastName=&ctl00%24body%24tbStreetAddress1=&ctl00%24body%24tbStreetAddress2=&ctl00%24body%24tbCity=&ctl00%24body%24tbState=&ctl00%24body%24tbZip=08021&ctl00%24body%24tbPhone1=&ctl00%24body%24tbPhone2=&__ASYNCPOST=true&ctl00%24body%24lbUpdateCityState=Update%20City%2FState

Lookup Response Length:

1307 bytes (over a 2500% increase over the pure JSON implementation and over a 1300% increase over the WCF and Page Method examples!!!)

Response Body:

1|#||4|647|updatePanel|body_upUpdateCityState|
                <div class="editor-label">
                    <label for="body_tbCity" id="body_lbCity">City</label>
                </div>
                <div class="editor-field">
                    <input name="ctl00$body$tbCity" type="text" value="Laurel Springs" id="body_tbCity" />
                </div>
                <div class="editor-label">
                    <label for="body_tbState" id="body_lbState">State</label>
                </div>
                <div class="editor-field">
                    <input name="ctl00$body$tbState" type="text" value="NJ" id="body_tbState" />
                </div>
            |68|hiddenField|__VIEWSTATE|/wEPDwUKLTE0ODYwOTIxMWRkXgPL66ERIqtx3gVubvTQQrkVqfAVcjgDjOwCDEs4Z8k=|136|hiddenField| __EVENTVALIDATION|/wEWCwKC9ZT3DAKp742yBwLVs6yZCQK59tu9AgL/1ZrTAgLIuYntCwKZ9eKsCgKqxfb2DALOw+yZAwKu2fbCAQL/lNACjqb cQj2LhjBHPYGxcc/Oy+TYZxbWVj4w/u27ZVUFn84=|51|asyncPostBackControlIDs||ctl00$body$lbUpdateCityState,body_lbUpdateCityState|0|postBackControlIDs|||52|updatePanelIDs ||fctl00$body$upUpdateCityState,body_upUpdateCityState|0|childUpdatePanelIDs|||51|panelsToRefreshIDs ||ctl00$body$upUpdateCityState,body_upUpdateCityState|2|asyncPostBackTimeout||90|11|formAction ||UpdatePanel|4|pageTitle||Test|

As a side note: There is also a hidden issue to the Update Panel, the page must go through the entire page life cycle.  Meaning every event in the page is fired including all of the rendering and child creation events, even if the control is not updated.

Important Controls:

<asp:UpdatePanel ID="upUpdateCityState" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="false">
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="lbUpdateCityState" EventName="Click"/>
    </Triggers>
    <ContentTemplate>
        <div class="editor-label">
            <asp:Label ID="lbCity" runat="server" Text="City" AssociatedControlID="tbCity"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbCity" runat="server"></asp:TextBox>
        </div>
        <div class="editor-label">
            <asp:Label ID="lbState" runat="server" Text="State" AssociatedControlID="tbState"></asp:Label>
        </div>
        <div class="editor-field">
            <asp:TextBox ID="tbState" runat="server"></asp:TextBox>
        </div>
    </ContentTemplate>
</asp:UpdatePanel>
<div class="editor-label">
    <asp:Label ID="lbZip" runat="server" Text="Zip" AssociatedControlID="tbZip"></asp:Label>
</div>
<div class="editor-field">
    <asp:TextBox ID="tbZip" runat="server"></asp:TextBox>
    <asp:Button ID="lbUpdateCityState" runat="server" Text="Update City/State" onclick="lbUpdateCityState_Click"></asp:Button>
</div>

Code Behind (I added the debug writes to show the page is calling every event in the Update Panel call):

public partial class UpdatePanelTest : System.Web.UI.Page
{
    protected void Page_Init(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page Init");
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page Load");
    }
    protected void Page_PreRender(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page PreRender");
    }
    protected void Page_Unload(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Page Unload");
    }

    protected void lbUpdateCityState_Click(object sender, EventArgs e)
    {
        string zip = tbZip.Text;
        ZipCode zipCodeData = zip.GetCityAndState();
        tbCity.Text = zipCodeData.City;
        tbState.Text = zipCodeData.State;
    }
}

While this example is fairly simplistic it illustrates a very important point, the update panel adds a tremendous amount of overhead for a minor saving on code.  Is it more maintainable?  Slightly; as new developers only need to know ASP.NET and not javascript (IMO not an options for web developers today).

So let’s look at the numbers:

 

Pure Ajax Json

WCF ASP.NET

Page Method ASP.NET

Update Panel

Request Length

15

15

15

638

Response Length

52

100

99

1307

Lines of Script

18

12

12

0

Lines of Markup

21

18

18

25

Lines of Code (just the update method)

5

10

5

7


I did not add execution times because as can vary, but the raw numbers speak for themselves.  There is very little value add in using the Update panel over standard AJAX calls.  I would go as far as saying there is no room for them in any web application/site in which load based performance is any kind of consideration.

UpdatePanelComparisons.zip (2.49 mb)

Tags:

AJAX | ASP.NET | jquery | JSON | MVC3

JQuery Tooltips with MVC3 and CSS 3.0 (Part 2)

by Admin 25. March 2012 02:56

So in my previous post I explained all of the steps required to create a simple but effective CSS 3 JQuery tooltip implementation.  This was a good start, I had all I needed to fulfill my requirement of a consistent tooltip in my application.  But I needed it to be a little easier to use throughout the application.  A custom Html Helper Extension to the rescue. Creating an HTML Helper is a pretty straight forward task this one, however, needed a bit of special sauce to make it work the way I wanted.

First I wanted to make sure I could bind it to my model, in case in the future I wanted to default it to the [Display] property on the model.  Second I wanted to be able to use the @<text></text> syntax for the contents of the tooltip.  It made sense to work backwards I wanted the control to work like this: 

@Html.InfoTooltipFor(model => model.ToolTipTitle,
	@<text>
	<h4>@Model.ToolTipTitle</h4>
	<ul>
   		<li>
			<b>Scott Guthrie</b> – 
			<a href="http://weblogs.asp.net/scottgu/">
				ScottGu's Blog
			</a> One of the best resources for all things Microsoft Web
                </li>
		<li>
			<b>Scott Hanselman</b> – 
				<a href="http://www.hanselman.com/blog/">
					Scott Hanselman's Computer Zen
				</a> Another great resource very current and very informative.
		</li>
		<li>
			<b>ASP.NET</b> – 
				<a href="http://www.asp.net/mvc">
					ASP.NET MVC
				</a> The mother of all things ASP.NET MVC
		</li>
     </ul>
</text>)

 

Seemed simple enough so at first I create the Helper as follows:

 

public static MvcHtmlString InfoTooltipFor<TModel, TValue>(
    this HtmlHelper<TModel> html, 
    Expression<Func<TModel, TValue>> expression, 
    string tooltip)
{
...
}

 

Nope that won't work... 

 

And once I thought about it makes sense that a string won't work.  It isn't anything remotely like a string it contains functions, delegates and properties. I know this type of implementation exists I have seen it in other controls.  So how is it done?  After an exhaustive google search I found this article by Phil Haack.  It was close to what I needed, so, after some trial and error I came up with this:

public static MvcHtmlString InfoTooltipFor<TModel, TValue>(this HtmlHelper<TModel> html, 
		Expression<Func<TModel, TValue>> expression, 
		Func<object, HelperResult> tooltip)
{
	//Get the Model metadata
	ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
	string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
	string tooltipId = metadata.PropertyName ? ? htmlFieldName.Split('.').Last();

	if (String.IsNullOrEmpty(tooltipId))
	{
		return MvcHtmlString.Empty;
	}
	//creates the span tag for the image
	TagBuilder spanTag = new TagBuilder("span");
	spanTag.MergeAttribute("class", "tooltipItem");
	spanTag.MergeAttribute("id", tooltipId);
	spanTag.MergeAttribute("data-tooltip", "tt" + tooltipId);

	TagBuilder imgTag = new TagBuilder("img");
	imgTag.MergeAttributes(new RouteValueDictionary(
		new
		{
			@class = "infoimage",
			src = UrlHelper.GenerateContentUrl("~/Content/info.png", html.ViewContext.HttpContext),
			alt = "info"
		}
		));
	spanTag.InnerHtml = imgTag.ToString(TagRenderMode.Normal);

	//creates the tooltip contents
	TagBuilder tooltipTag = new TagBuilder("div");
	tooltipTag.MergeAttribute("class", "hidden");
	tooltipTag.MergeAttribute("id", "tt" + tooltipId);
	tooltipTag.MergeAttribute("name", "tt" + tooltipId);
	tooltipTag.MergeAttribute("data-tooltip", "tt" + tooltipId);
	tooltipTag.InnerHtml = tooltip(null).ToString();

	return MvcHtmlString.Create(spanTag.ToString(TagRenderMode.Normal) + tooltipTag.ToString(TagRenderMode.Normal));
}

The key is Func<object, HelperResult> this allows templated items to be passed into helper method.  This was exactly what I needed and poof like magic it worked.  MVC is awesome, creating a control like that in standard ASP.NET would have been a major pain. 

The entire solution is in the attached zip...

EasyTooltips.zip (2.46 mb)

Tags: , , , ,

AJAX | css | jquery | MVC3

MVC Makes AJAX easier

by Admin 12. February 2012 19:07

MVC is a pretty new paridigm to me, I just started using it about 5 months ago, but one of the first things I noticed is how much easier it has made AJAX.  This is coming from someone who has had a very long career in ASP.NET.  There were a few different ways to provide Ajax functionality in ASP.NET the evil update panel being the easiest to implement.  My preference and, IMO, the more correct approach was to use the AJAX framework in conjunction with AJAX Aware WCF/Web Services.  The low framework overhead (no Eventing Model) in conjuction with the relatively small request/response package made these fairly easy to implement and perform very well.  The problem with these services is they were still tied up in all sorts of framework gook.

With MVC, all of that framework gook is gone, it is a much simpler PHP style implementation of AJAX.  There are several ways to utilize AJAX in the MVC world, there are the AJAX MVC Helper Class which is very easy to work with, but I perfer a pure JQuery ajax approach.

Enough talk here is an example of a simple implementation of ajax using 4 commonly used methods:

First the Controller (in this case I am using the Products table of the Northwind Database (kickin' it old school)):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
 
namespace JqueryAjax.Controllers
{
    public class ProductsController : Controller
    {
        //
        // GET: /Products/
 
        public ActionResult Index()
        {
 
            List<Product> prodList = null;
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prodList = ctx.Products.OrderBy(p => p.ProductName).ToList();
 
            }
            return View(prodList);
        }
 
        public ActionResult _productInfoJSON(int? id)
        {
            Product prod = null;
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prod = ctx.Products.Single(p => p.ProductID == id);
 
            }
            return Json(prod, JsonRequestBehavior.AllowGet);
        }
 
        public ActionResult _productInfo(int? id)
        {
            Product prod = null;
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prod = ctx.Products.Single(p => p.ProductID == id);
 
            }
            return PartialView(prod);
        }
 
        [HttpPost]
        public ActionResult _productInfoJSONParams()
        {
            Product prod = null;
            int prodId = int.Parse(this.ValueProvider.GetValue("id").AttemptedValue);
            using (NorthwindEntities ctx = new NorthwindEntities())
            {
                prod = ctx.Products.Single(p => p.ProductID == prodId);
 
            }
            return Json(new { prod.ProductName, prod.UnitPrice });
        }
 
    }
}

I use the "_" syntax to denote partial actions and views.  So looking at the above code you will notice there is only one full action "Index", so lets look at its view:

@model IEnumerable<JqueryAjax.Product>
 
@{
    ViewBag.Title = "Products List";
}
 
@section script{
    <script src="@Url.Content("~/Scripts/Info.js")" type="text/javascript"></script>
}
<h2>Index</h2>
<table>
    <tr>
        <th>
 
        </th>
        <th>
            ProductName
        </th>
    </tr>
 
@foreach (var item in Model) {
    <tr>
        <td>
            <a class="prodInfo" href="#" data-product-id="@item.ProductID">
                <img src="../../Content/Images/info.jpg" width="20" height="20" alt="info" border="0" />
            </a>
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ProductName)
            <div id="product-@item.ProductID" style="display:none;">
            </div>
        </td>
    </tr>
}
 
</table>
So in this view I am simply building a Table of product names with an info image, easy peasy.  The Ajax parts of the html are housed in the:
 
<div id="product-@item.ProductID" style="display:none;">
And the:
<a class="prodInfo" href="#" data-product-id="@item.ProductID">

The former is the place holder for our dynamic content and the latter is the link to our dynamic content.  Now that we have all of the page scaffolding in place we need to hook up the pieces.  There are several ways we can retrive the content.  We could retrieve a standard JSON object from a controller _productInfoJSON and _productInfoJSONParams or you can return a partial view.  This is where the power of MVC really hits home.  You can craft a fully functional page and return it to your AJAX container.  That is pretty sweet...  But first lets look at the JSON example:

I created 2 a JSON Get (useful for RESTful services) and a JSON Post (prefered for internal web site calls):

Here is the Java script for calling the JSON get in conjuntion with the previously crafted view and controller:

/// <reference path="jquery-1.7.1.min.js" />
$(document).ready(function () {
    $(".prodInfo").click(function (e) {
        //stop the href="#" from firing
        e.preventDefault();
 
        var prodId = $(this).data("productId");
        jsonGet(prodId);
 
    });
});

function jsonGet(prodId) {
    $.ajax({
        url: "/Products/_productInfoJSON/" + prodId,
        success: function (data) {
            $("#product-" + prodId).toggle();
            $("#product-" + prodId).html("<span> price:"+ data.UnitPrice +"</span>");
        }
    });
}
and the JSON parameterized post:

jsonPost(prodId);

function jsonPost(prodId) {
    $.ajax({
        url: "/Products/_productInfoJSONParams",
        type: "POST",
        data: { id: prodId },
        success: function (data) {
            $("#product-" + prodId).toggle();
            $("#product-" + prodId).html("<span> price:" + data.UnitPrice + "</span>");
        }
    });
}

This will get you a simple return value that you can format in JQuery.

Pretty simple right?  Well its about to get easier.  Now lets say we want to return a significant amount of data to the client and we don't want to do that messy JavaScript HTML formatting.  Partial Views to the rescue.  Create the following partial view from the _productInfo action:

@model JqueryAjax.Product
 
<fieldset>
    <legend>Product Info</legend>
 
    <div class="display-label"></div>
    <div class="display-field">
        QuantityPerUnit: @Html.DisplayFor(model => model.QuantityPerUnit)
    </div>
 
    <div class="display-field">
        UnitPrice: @Html.DisplayFor(model => model.UnitPrice)
    </div>
 
    <div class="display-field">
        UnitsInStock: @Html.DisplayFor(model => model.UnitsInStock)
    </div>
 
    <div class="display-field">
        UnitsOnOrder: @Html.DisplayFor(model => model.UnitsOnOrder)
    </div>
 
    <div class="display-field">
        ReorderLevel: @Html.DisplayFor(model => model.ReorderLevel)
    </div>
 
    <div class="display-field">
        Discontinued: @Html.DisplayFor(model => model.Discontinued)
    </div>
</fieldset>

Now change the JS click event to fire:

partialJqueryGet(prodId);

with the function definition of:

function partialJqueryGet(prodId) {
    $.get("/Products/_productInfo/" + prodId,
    function (data) {
        $("#product-" + prodId).toggle();
        $("#product-" + prodId).html(data);
    });
}

BA BAM done.  Now you are returning a partial view from an ajax call.  You can also use a pure jquery ajax implementation as opposed to the Get helper class:

function partialAjaxGet(prodId) 
{
    $.ajax({
        url: "/Products/_productInfo/" + prodId,
        success: function (data) {
            $("#product-" + prodId).toggle();
            $("#product-" + prodId).html(data);
        }
    });
 
}

 

All of the code and the northwind database can be found here:

JqueryAjax.zip (2.67 mb)

http://www.microsoft.com/download/en/details.aspx?id=23654

 

Tags: , , , , , ,

AJAX | ASP.NET | jquery | JSON | MVC3 | Razor

Calendar

<<  June 2017  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

View posts in large calendar

Page List

RecentComments

None