Blog Posts Tagged ASP.Net
Using Ext JS Grids with ASP.Net MVC - Basic Example
Following my post "Ext JS JsonStore and Linq to JSON Gotcha" I've had a few people asking for a more complete example of how to use Ext JS Grids and data stores (in particular the JsonStore) with ASP.Net MVC and LINQ to SQL. Since writing that blog post I've learnt a lot more about the ASP.Net MVC framework and the Ext JS framework so I thought it would be helpful to publish a complete working demo. So, here's an example that displays a simple Ext JS Grid populated from the Northwind database Products table. The example shows how to use an Ext JS Grid with a JsonStore that supports server side paging and sorting.
Download this example as a working Visual Studio 2008 MVC (Version 1.0) application.
Requirements
- ASP.Net MVC Version 1.0
- Ext JS version 3.0 (works with version 2.x also) - to keep things simple I'm using standalone Ext JS and ext-all.js library.
- Dynamic LINQ Query Library - to keep things simple I've included the source code in the project under App_Code
- Northwind sample Microsoft SQL Server database
Overview
So take a look at the code. It's very simple and straightforward! Here's what you should see when you run the project.
In brief the Index action method of the Products controller handles a vanilla HTTP GET request for the Products index page (/Products/). The view it returns is an almost empty page that simply exposes an element that we can render the Ext JS GridPanel to.
When the index page loads, the load event creates the Ext JS components needed including the JsonStore. It then renders the grid and loads the data store thus triggering an AJAX request to the same URL (this time as a POST, which is the only method supported by the JsonStore component). A different Index action method in the controller handles the POST and if it's an Ajax request it pulls the products from the database and returns JSON in a format expected by the JsonStore.
[AcceptVerbs(HttpVerbs.Post)] public ActionResult Index(int Skip, int Take, string SortDir, string Sort) { if (Request.IsAjaxRequest()) { var products = productsRepository.GetAllProducts(); var results = (new { totalRecords = products.Count(), products = products.OrderBy(Sort + " " + SortDir).Skip(Skip).Take(Take) }); return Json(results); } else { return RedirectToAction("Index"); } }
Some Points of Interest
Where I define the JsonStore component in the ext-lib.js file you will notice I have re-defined the default paging parameter names to match my own code behind. Also notice I am redefining some JsonReader parameters (totalProperty and root) to match the JSON data returned by the controller action method. These steps aren't strictly necessary for this example but I have included it for reference. Alternatively you could use the default Ext JS parameter names and ensure your c# code matches.
Northwind.ProductStore = function(config) {
Northwind.ProductStore.superclass.constructor.call(this, Ext.apply({
paramNames: {
"start": "Skip",
"limit": "Take",
"sort": "Sort",
"dir": "SortDir"
},
totalProperty: 'totalRecords',
root: 'products',
remoteSort: true,
autoLoad: false,
id: "ProductID",
method: "POST"
}, config));
};
The config parameter of the ProductStore constructor above is used to allow us to pass in additional, or override the existing, configuration parameters of the component.
Also in ext-lib.js file you will notice a column renderer method. This is a helper function called during grid rendering to format the data in certain columns. When the columns are specified in the ProductColumnModel you will see I have configured the Stock column to use the stockRenderer method to render the column data.
The stockRenderer method shows how you can reference the data row currently being rendered by the grid and manipulate the HTML rendered in the grid cell. Here I simply add a style to any products that have a stock level less than the reorder level and that are also discontinued.
The column renderer for the Price column uses a built in formatter to render the data as a currency.
The Discontinued column uses a special Ext JS column type to render the boolean value.
Summary
Well, I hope you have found this interesting and helpful. I'm hoping to expand on some of the concepts here and provide examples of some more advanced capabilities and features I have implemented using the ASP.Net MVC and Ext JS frameworks.
Comments: 8 Comments
Telerik RadAjaxmanager and IE Conditional Comments Gotcha
Recently I was working on a project that extensively used Telerik RadControls for ASP.Net AJAX and we were experiencing some Internet Explorer CSS rendering weirdness across versions 6,7 and 8 whenever an AJAX call was made. My immediate thought was the usual IE float clearing bug, a double margin bug or, let's be honest, one of any number of irritating IE CSS bugs . The strange thing was each IE version exhibited slightly different rendering issues which, after some thought, made me think it was something else.
After some rummaging around with the wonderful Firebug I noticed that all of the style sheets within the IE conditional comments were being attached after each AJAX callback. So the style sheet for IE6 was being attached when viewed in IE7 and IE8 and so on. Hmmm.
It seems that the RadAjaxManager doesn't handle IE conditional comments. After an Ajax request the RadAjaxManager attaches all style sheets referenced within IE conditional comments mark-up and the CSS is applied.
Luckily there are some workarounds which we were able to use. In my case I was able to set the EnablePageHeadUpdate property to false so the head content was not updated for each Ajax callback.
Comments: 0 Comments
For the last time. Panels Render Mark-up!
If I had a penny for each time I encounter this kind of mark-up in an ASP.Net Web Form...
<asp:Panel runat="server" ID="PanelFooContainer" Visible="true"> <div id="FooContainer"> Foo bar baz </div> </asp:Panel>
...I'd be a few quid better off. I've also seen the following a few times which makes me feel a little unwell.
<table>
<tr>
<th>Foo</th>
</tr>
<asp:Panel runat="server" ID="PanelFooContainer" Visible="true">
<tr>
<td>bar baz</td>
</tr>
</asp:Panel>
</table>
What's wrong with this? Well, this mark-up renders the following HTML to the browser:
<table>
<tr>
<th>Foo</th>
</tr>
<div id="PanelFooContainer">
<tr>
<td>bar baz</td>
</tr>
</div>
</table>
Which, I am sure you will agree, is a pigs ear. Oh, and it's invalid HTML.
I'm sure most developers guilty of this do not intend to render that additional mark-up - they just want a control which they can show or hide easily from the code. But if all you require is a control that you can work with in your code then simply use a PlaceHolder control that doesn't render any superfluous mark-up.
Your mark-up will be valid, you will feel warm and cosy and your code will probably remain exactly the same.
Comments: 0 Comments
Ext JS JsonStore and Linq to JSON Gotcha
Here's a little gotcha that had be scratching my head for a moment. I was testing the Grid component of the excellent Ext JS framework when I ran into a problem loading the JSON result into the grid.
It seemed simple enough - populate the grid from JSON data returned by a controller action method. When I ran the code the grid wasn't populated: the Ext JSONReader was throwing an error "root is undefined". I checked and double checked everything but could not see what the problem was.
Here's the JavaScript to create the JsonStore for the grid:
var ds = new Ext.data.JsonStore({ url: '/Home/GetData', totalProperty: 'totalRecords', root: 'requests', id: "ReqNo", fields: ['Division', 'Department'], listeners: { loadexception: function(proxy, store, response, e) { alert(e.message); } } });
Here's the controller method that returns the JSON:
public JsonResult GetData(int? start, int? limit) { limit = limit ?? 25; start = start ?? 0; string dataPath = HttpContext.Server.MapPath("~/Data/TestData.xml"); XDocument xDoc = XDocument.Load(dataPath); var results = from requests in xDoc.Descendants("requests") select new { totalRecords = requests.Elements("request").Count(), requests = (from request in requests.Elements("request") select new NBWeb.Models.Request { Division = request.Element("Division").Value, Department = request.Element("Department").Value }).Skip((int)start).Take((int)limit) }; return this.Json(results); }
And here's an example of the JSON it emits:
[{
"totalRecords":200,
"requests":[
{"Division":"Division 1","Department":"Department 5",},
{"Division":"Division 2","Department":"Department 4",},
{"Division":"Division 3","Department":"Department 5",},
{"Division":"Division 4","Department":"Department 6",},
]
}]
Eventually, upon examining the JSON for the tenth time, it dawned on me - the entire JSON data was nested in a redundant outer array. So the JsonStore root property was not being found - it was looking for:
obj["requests"]
but would need to look for:
obj[0]["requests"]
So it was the controller returning slightly malformed JSON. It's a bug in my code - I need to return the inner result of the Linq select rather than the entire result. So I need to take the FirstOrDefault record from the results, like so:
public JsonResult GetData(int? start, int? limit) { limit = limit ?? 25; start = start ?? 0; string dataPath = HttpContext.Server.MapPath("~/Data/TestRequests2500.xml"); XDocument xDoc = XDocument.Load(dataPath); var results = (from requests in xDoc.Descendants("requests") select new { totalRecords = requests.Elements("request").Count(), requests = (from request in requests.Elements("request") select new NBWeb.Models.Request { Division = request.Element("Division").Value, Department = request.Element("Department").Value, }).Skip((int)start).Take((int)limit) }).FirstOrDefault(); return this.Json(results); }
And here's the corrected JSON it emits (note the absence of the outer square brackets):
{
"totalRecords":200,
"requests":[
{"Division":"Division 1","Department":"Department 5",},
{"Division":"Division 2","Department":"Department 4",},
{"Division":"Division 3","Department":"Department 5",},
{"Division":"Division 4","Department":"Department 6",},
]
}
Seems like a dumb mistake to make but it took a bit of debugging to find it - hopefully this post will save someone out there some time.
Comments: 5 Comments
Integrating a Blogger Atom Feed into an ASP.Net MVC Application
Part One: Configure your Blogger Settings, Consuming Atom feeds with ASP.Net
Part Two: Configure your MVC Application - Routes, Controllers... ACTION!
Part Three: Create a Tag Cloud (Display all current labels with post counts)
Part Four: Create Archive Links
Part Five: Add Blog Search Capabilities
Part Six: Integrating Comments
Part Seven: Disadvantages to this approach and how to improve things
In this series of posts I'm going to describe how to integrate an atom feed into an ASP.Net MVC application based on my experience integrating my Blogger blog into the www.iqode.com site.
In this example I'm going to publish the blog to my server using the Blogger FTP publishing feature. You could simply consume a remote atom feed and display it in your MVC application but I want to create a much tighter integration. FTP Publishing throws up some interesting challenges when integrating into an MVC application and I'm going to elaborate more on this in my next post.
Configure your Blogger Settings
I'll assume you already have a Blogger blog, if not then it only takes a few minutes to sign up to Blogger and make your first post.
OK, now you're signed into Blogger you need to go to your Blogger Settings tab aand follow these instructions on setting up FTP Publishing.
Now go to your Archiving tab and, for now, set Archiving setting to No Archive. Scroll down and make sure Enable Post Pages is set to "Yes". Save your settings and move on to the Site Feed tab and complete the relevant Site Feed values. You also need to set the Blog Posts Feed drop down to "Full" then save your settings.
Now you have your blog set up and you're publishing your blog files and full feeds to your own server via FTP. Now open your MVC application in Visual Studio.
Create Your Model Object
An examination of the Atom feed xml will reveal the following structure:
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'> <id>tag:blogger.com,1999:blog-8097474</id> <updated>2008-09-16T16:53:51.743+01:00</updated> <title type='text'>IQODE Limited</title> <subtitle type='html'>ATTRACTIVE, INTUITIVE AND ACCESSIBLE WEBSITES, BUILT WITH WEB STANDARDS</subtitle> <link rel='alternate' type='text/html' href='http://www.iqode.com/Blog/Browse/'/> <link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8097474/posts/default'/> <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.iqode.com/Feeds/atom.xml'/> <author> <name>Steve</name> <uri>http://www.blogger.com/profile/15887521996067704936</uri> <email>noreply@blogger.com</email> </author> <generator version='7.00' uri='http://www.blogger.com'>Blogger</generator> <openSearch:totalResults>2</openSearch:totalResults> <openSearch:startIndex>1</openSearch:startIndex> <openSearch:itemsPerPage>25</openSearch:itemsPerPage> <entry> <id>tag:blogger.com,1999:blog-8097474.post-2242729009616470352</id> <published>2008-08-21T18:09:00.002+01:00</published> <updated>2008-09-15T14:29:52.307+01:00</updated> <category scheme='http://www.blogger.com/atom/ns#' term='News'/> <title type='text'>New IQODE Website Goes Live</title> <content type='html'><h4>"Sometimes you've just got to let it go and get it out there"</h4><p>A comment from the audience during the Q&amp;A session after <a class="external" href="http://skillswap-brighton.org/2008/07/28/skillswap-goes-pretty/">the latest Brighton Skillswap</a> persuaded me to finally publish the latest IQODE website.</p><p>So without further ado I give you... oh, you're already here.</p></content> <link rel='alternate' type='text/html' href='http://www.iqode.com/Blog/Browse/2008/08/new-iqode-website-goes-live.html' title='New IQODE Website Goes Live'/> <link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=8097474&postID=2242729009616470352&isPopup=true' title='0 Comments'/> <link rel='replies' type='application/atom+xml' href='http://www.iqode.com/Feeds/atom.xml' title='Post Comments'/> <link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8097474/posts/default/2242729009616470352'/> <link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8097474/posts/default/2242729009616470352'/> <author> <name>Steve</name> <uri>http://www.blogger.com/profile/15887521996067704936</uri> <email>noreply@blogger.com</email> </author> </entry> </feed>
So to model a blog post I'm going to need the following Model Object so right click your Model folder and add a new class and the following class definition.
public class BlogPost { public string Title { get; set; } public string LinkTitle { get; set; } public DateTime Published { get; set; } public string Url { get; set; } public List<string> Tags { get; set; } public string Content { get; set; } public string Author { get; set; } }
Read the Feed Using Linq to XML
Next, Create a method to read in the feed xml, transform it into model objects and return a generic list of BlogPost objects. Linq is an excellent tool to achieve this in a very few lines of code...
protected IEnumerable<BlogPost> GetPosts() { var posts = HttpRuntime.Cache["IQODEFeed"] as IEnumerable<BlogPost>; if (posts == null) { string feedPath = Server.MapPath(IQODEWebLib.Config.Current.BlogFeedURL); XDocument rssFeed = XDocument.Load(feedPath); XNamespace atomNS = "http://www.w3.org/2005/Atom"; posts = from item in rssFeed.Root.Descendants(atomNS + "entry") select new BlogPost { Title = item.Element(atomNS + "title").Value, LinkTitle = GetLinkTitle(item.Element(atomNS + "title").Value), Published = DateTime.Parse(item.Element(atomNS + "published").Value), Content = item.Element(atomNS + "content").Value, Author = item.Element(atomNS + "author").Element(atomNS + "name").Value, Tags = (from category in item.Elements(atomNS + "category") orderby category.Value select category.Attribute("term").Value).ToList() }; // cache and set dependency on the local feed file HttpRuntime.Cache.Insert("IQODEFeed", posts, new CacheDependency(feedPath),DateTime.MaxValue, System.Web.Caching.Cache.NoSlidingExpiration); } return posts; }
Two things to note here:
- To read the atom feed I need to combine the atom namespace with the local name when constructing the Linq statement.
- I'm caching this list and setting a cache dependency on the actual feed xml file. This way if the feed file changes the cache item is automatically removed and my code reloads the feed xml.
Next create a new controller for your blog index page and drop in the code blow.
public ActionResult Index() { var posts = GetPosts(); ViewData.Model = posts.ToList(); return View(); }
Make sure your View page is strongly typed, for example...
public partial class Posts : System.Web.Mvc.ViewPage<List<BlogPost>> { }
...and then you can MVC magically reference the Posts Model in your code
<% foreach (var BlogPost in ViewData.Model) {%> <h3><strong><%=Html.RouteLink(BlogPost.Title, "Blog", new { id = BlogPost.Published.Year, month = BlogPost.Published.Month, title = BlogPost.LinkTitle }, null)%></strong></h3> <div class="postBody"> <%=BlogPost.Content %> </div> <p class="author"><strong>Published:</strong> <%=BlogPost.Published.ToString("dddd, dd MMMM yyyy hh:mm tt")%> by <%= BlogPost.Author %></p> <p class="tags"><strong>Tags:</strong> <% foreach (var tag in BlogPost.Tags) { Response.Write(string.Format("<span title='{0}'>{1} </span>", "See all posts tagged " + tag, Html.ActionLink(tag, tag, "Blog/Tags"))); } %> </p> <p class="comments"><strong>Comments:</strong> <a href="<%=BlogPost.CommentsInfo.Link%>" class="external-blog"><%=BlogPost.CommentsInfo.Title %></a></p> <% } %>
You'll notice some extra cruft in the code above which I will be covering in later posts but I'm sure you get the idea. Stay tuned for Part Two where I'll be covering in more details the controller actions, routing and rendering within the application.
Comments: 0 Comments
CSS to jQuery ASP.Net HTTPHandler
In this post I'm going to explain why and how to automatically turn css into jQuery code that is executed on page load. The solution is written for ASP.Net but in theory it could be ported to any other framework.
It's nice to add various visual enhancements and tweaks to websites I develop and I do that using the jQuery Javascript library. These enhancments, in my mind, are not necessary but nice to haves. However, it's important to ensure that web pages are still accessible and render well when Javascript is disabled. So, according to the principles of Progressive Enhancement, I layer the effects on top of the "vanilla" page using jQuery. A simple example is my About page. Try turning Javascript on and off then reloading. Without Javascript each section is displayed inline. With Javascript turned on some jQuery voodoo shows and hides the sections with some nice fade transition effects .
Where am I going with this? Well I find myself writing a lot of jQuery in order to apply some CSS to various elements in order to switch them from vanilla mode into singing and dancing mode. Which means I need to write script to run as the page loads to override the vanilla CSS rules.
And?
I thought it would be nice to author these styles in a way I'm familiar with - style sheets. So I wrote a simple ASP.Net HTTP Handler to parse certain CSS files (I chose the .jqcss file extension) and spit out jQuery instead.
For example:
.test {
background-color: red;
padding: 10px;
background-position: top left;
margin: 5px;
}
h2:first {
color: red;
}
h2[id="3"] {
color: blue;
}
#blue {
background-color: blue;
color: #fff;
padding: 5px;
}
Becomes:
$(document).ready(function(){
$('.test').css({backgroundColor:'red',padding:'10px',backgroundPosition:'top left',margin:'5px'});
$('h2:first').css({color:'red'});
$('h2[id="3"]').css({color:'blue'});
$('#blue').css({backgroundColor:'blue',color:'#fff',padding:'5px'});
});
To use the HTTP Handler you need to add a reference to your handler in your web.config httpHandlers section and tell it to handle .jqcss files, as follows:
<httpHandlers> <add verb="*" path="*.jqcss" type="IQODE.WebLib.Handlers.JQCSSHandler" /> </httpHandlers>
Then create your .jqcss file containing the style rules you want to be applied on load. Finally you need to add a link to your .jqcss file from your page:
<script type="text/javascript" src="test.jqcss"></script>
I'm not absolutely convinced there's a huge need for this technique. It simplified the develoment of my site, iqode.com, and keeps my pages tidier - I'm not a massive fan of having a load of inline Javascript cluttering up my pages. More importantly, by placing this script in an external file I can also manage how the file gets cached by my application and the browser - in ASP.Net I can set a cache dependancy on the .jqcss file itself and only refresh the cache if the file changes. This means the handler only needs to parse the file occasionally.
Here's the entire source code for the HTTPHandler - I removed the cache control code etc. for brevity. One point of interest is the use of an arbitrary Web Control (a button in this case) - so I don't need to parse all the style rules for each selector I load the entire rules string into the Web Controls CssStyleCollection object. This object parses it for me and I simply iterate of the key collection pulling out each rule/value pair to build the jQuery statement.
using System; using System.Collections; using System.Web; using System.Text.RegularExpressions; using System.Text; using System.Web.UI; using System.Web.UI.WebControls; namespace IQODE.WebLib.Handlers { public class JQCSSHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { // Get the physical path of the file being processed string File = context.Request.PhysicalPath; string css = string.Empty; StringBuilder sb = new StringBuilder(); Button btn = new Button(); // Open the file, read css using (System.IO.StreamReader reader = new System.IO.StreamReader(File)) { css = reader.ReadToEnd(); } css = RemoveWhitespace(css); string[] cssRules = css.Split('}'); sb.AppendLine("$(document).ready(function(){"); foreach (string cssRule in cssRules) { if (!string.IsNullOrEmpty(cssRule)) { string selector = cssRule.Split('{').GetValue(0).ToString().Trim(); string rules = cssRule.Split('{').GetValue(1).ToString(); CssStyleCollection sc = btn.Style; sc.Value = rules; rules = ""; foreach (string key in btn.Style.Keys) { rules += ConvertToCamel(key) + ":'" + btn.Style[key].Replace("'", "\"") + "',"; } sb.AppendLine(" $('" + selector + "').css({" + rules.TrimEnd(',') + "});"); } } sb.AppendLine("});"); context.Response.ContentType = "text/css"; context.Response.ContentType = "text/javascript"; context.Response.Write(sb.ToString()); } protected string ConvertToCamel(string s) { try { if (s.IndexOf('-') < 0) { return s; } string[] words = s.Split('-'); string word1 = words[0]; string word2 = words[1]; string lttr = word2.Substring(0, 1).ToUpper(); return word1 + lttr + word2.Remove(0, 1); } catch (Exception) { throw new Exception("Error Parsing jQcss file - malformed css attribute found in stylesheet: " + s); } } public bool IsReusable { get { return false; } } public static string RemoveWhitespace(string str) { try { if (string.IsNullOrEmpty(str)) { return str; } return new Regex(@"[\t\r\n]").Replace(str, string.Empty); } catch (Exception) { return str; } } } }
And that's it... I hope someone finds this useful. You could simply put all our jQuery css stuff in a separate script file but I found it easier and quicker to do it this way.
Drop me a line if you have any questions, spot a bug or have suggestions for improvement.
Comments: 2 Comments