Philip Hendry's Blog

December 23, 2011

Create a contents page with page numbers from html input using Websupergoo Abcpdf

Filed under: ASP.NET — philiphendry @ 9:25 am

I’ve created a report pdf from an html page output from our ASP.NET based product but I needed to change a contents list that was rendered as a hyperlink list on the page into a list of section headings and page number since the pdf was primarily for printing. The solution wasn’t immediately obvious but I’ve come up with the following which I was running in a unit test for simple quick prototyping. The key is using the HtmlOptions.AddTags property in ABCpdf which allows areas of the HTML to be retrieve during pdf rendering and modified.

 

   1: using System.Diagnostics;

   2: using System.IO;

   3: using System.Collections.Generic;

   4: using System.Linq;

   5: using Microsoft.VisualStudio.TestTools.UnitTesting;

   6: using WebSupergoo.ABCpdf8;

   7:  

   8: namespace TestPdfFormFields

   9: {

  10:    [TestClass]

  11:    public class PdfPrototyping

  12:    {

  13:       struct Tag

  14:       {

  15:          public string name;

  16:          public string rectString;

  17:          public int pagenumber;

  18:       }

  19:  

  20:          

  21:       [TestMethod]

  22:       public void testCreatingAPdfWithContentsPageFromHtml()

  23:       {

  24:          // Create Websupergoo ABCpdf document and set up the page size

  25:          var theDoc = new Doc();

  26:          theDoc.Rect.Inset(100, 100);

  27:          theDoc.Rect.Top = 700;

  28:  

  29:          // The following HtmlOption instructs ABCpdf to interpret the style tags 'abcpdf-tag-visible' and

  30:          // create a array of the id's take from the html and the rectangles representing the size of the 

  31:          // html element. 

  32:          theDoc.HtmlOptions.AddTags = true;

  33:  

  34:          // The basis of this solution therefore is to create placeholders where page numbers will be rendered

  35:          // in the contents page and back-fill them once we know where the sections/chapters have been rendered

  36:          // since it won't be known until ABCpdf has chained pages together.

  37:          var theID = theDoc.AddImageHtml(

  38:                   @"

  39:                      <h1>Contents</h1>

  40:                      <ul>

  41:                         <li><span id='contents1' style='abcpdf-tag-visible: true; width: 20px;'></span>.......First paragraph</li>

  42:                         <li><span id='contents2' style='abcpdf-tag-visible: true; width: 20px;'></span>.......Second paragraph</li>

  43:                         <li><span id='contents3' style='abcpdf-tag-visible: true; width: 20px;'></span>.......Third paragraph</li>

  44:                      </ul>

  45:                      <h1 id='heading1' style='abcpdf-tag-visible: true; page-break-before:always;'>Section One</h1>

  46:                      <h1 id='heading2' style='abcpdf-tag-visible: true; page-break-before:always;'>Section Two</h1>

  47:                      <h1 id='heading3' style='abcpdf-tag-visible: true; page-break-before:always;'>Section Three</h1>

  48:                   ");

  49:  

  50:          var tagCache = new List<Tag>();

  51:          var pagenumber = 1;

  52:          while (true)

  53:          {

  54:             // Fetch all the tags and rectangles and add them to a tagCache for the current theID. Chaining

  55:             // creates a new theID which will contain more tags to add

  56:             var tags = theDoc.HtmlOptions.GetTagIDs(theID);

  57:             var tagRects = theDoc.HtmlOptions.GetTagRects(theID);

  58:             tagCache.AddRange(tags.Select((t, i) => new Tag {name = t, pagenumber = pagenumber, rectString = tagRects[i].String}));

  59:  

  60:             if (!theDoc.Chainable(theID))

  61:                break;

  62:  

  63:             theDoc.Page = theDoc.AddPage();

  64:             theID = theDoc.AddImageToChain(theID);

  65:             pagenumber++;

  66:          }

  67:  

  68:          // Now we have a cache of all contents and heading tags we can iterate through the

  69:          // contents tags, find the smallest page number of the corresponding section then

  70:          // render the page number into the contents.

  71:          theDoc.HPos = 1.0;   // Right justify

  72:          theDoc.VPos = 0.5;   // Centre vertically

  73:          theDoc.FontSize = 8;

  74:          foreach (var tag in tagCache.Where(t => t.name.StartsWith("contents")))

  75:          {

  76:             var paragraphName = "heading" + tag.name.Substring("contents".Length);

  77:             var paragraphPageNumber = tagCache.Where(t => t.name == paragraphName).Select(t => t.pagenumber).Min();

  78:  

  79:             theDoc.PageNumber = tag.pagenumber;

  80:             theDoc.Rect.String = tag.rectString;

  81:             theDoc.AddText(paragraphPageNumber.ToString());

  82:          }

  83:  

  84:          // Now iterate through all the pages, add page numbers and flatten the layers.

  85:          theDoc.Rect.String = "100 70 500 150";

  86:          var pageCount = theDoc.PageCount;

  87:          for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)

  88:          {

  89:             theDoc.PageNumber = pageNumber;

  90:             theDoc.AddText("Page " + pageNumber + " of " + pageCount);

  91:             theDoc.Flatten();

  92:          }

  93:  

  94:          const string testFilename = @"c:\temp\HtmlOptionsGetTagRects.pdf";

  95:          if (File.Exists(testFilename))

  96:             File.Delete(testFilename);

  97:          theDoc.Save(testFilename);

  98:          Process.Start(testFilename);

  99:       }

 100:    }

 101: }

March 23, 2011

Configuring Event Log Permission for Asp.net applications

Filed under: ASP.NET, Security — philiphendry @ 2:40 pm

I’ve been having some problems with writing to a custom event log as well as the standard ‘Application’ event log where it failed with the following error :

    Stack Trace: 
    
    
    [Win32Exception (0x80004005): Access is denied]
    
    [InvalidOperationException: Cannot open log for source 'Application'. You may not have write access.]
    System.Diagnostics.EventLog.OpenForWrite(String currentMachineName) +1008783
    System.Diagnostics.EventLog.InternalWriteEvent(UInt32 eventID, UInt16 category, EventLogEntryType type, String[] strings, Byte[] rawData, String currentMachineName) +216
    System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData) +264
    System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData) +87
    System.Diagnostics.EventLog.WriteEntry(String source, String message, EventLogEntryType type) +14
    ASP.test_testeventlogaccess_aspx.WriteToApplication(Object sender, EventArgs e) in c:\Inetpub\wwwroot\connect\Test\TestEventLogAccess.aspx:20
    System.Web.UI.WebControls.Button.OnClick(EventArgs e) +111
    System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +110
    System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10
    System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13
    System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +36
    System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1565

What made this harder to figure was it worked in one environment but not another! The difference was impersonation was turned on in the web.config for the failing web site :


  <system.web>
    <identity impersonate="true" />
  </system.web>

This meant that the user being used to access the event log was not the AppPool identity but rather the ASP.NET IUSR_ identity (which has not been overridden with the optional username/password attributes above in the config above.)

Microsoft has a knowledge base article describing how to configure event log permissions but there were two extra steps I had to perform before I could add the IUSR identity to the event log permissions :

Fetching the SID for an account

The SDDL string that needs to be added according to the knowledge base article referenced above requires the SID for the IUSR account. There are a couple of ways to do this listed below. Either script block should be saved to a .vbs file and run as a parameter to cscript.exe from a command line :

    strComputer = "."
    Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
    Set objAccount = objWMIService.Get("Win32_UserAccount.Name='IUSR_<COMPUTER NAME>',Domain='<COMPUTER NAME>'")
    Wscript.Echo objAccount.SID
    
    
    On Error Resume Next
    strComputer = "."
    Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
    Set colItems = objWMIService.ExecQuery("Select * from Win32_UserAccount",,48)
    For Each objItem in colItems
        Wscript.Echo "AccountType: " & objItem.AccountType
        Wscript.Echo "Caption: " & objItem.Caption
        Wscript.Echo "Description: " & objItem.Description
        Wscript.Echo "Disabled: " & objItem.Disabled
        Wscript.Echo "Domain: " & objItem.Domain
        Wscript.Echo "FullName: " & objItem.FullName
        Wscript.Echo "InstallDate: " & objItem.InstallDate
        Wscript.Echo "Lockout: " & objItem.Lockout
        Wscript.Echo "Name: " & objItem.Name
        Wscript.Echo "PasswordChangeable: " & objItem.PasswordChangeable
        Wscript.Echo "PasswordExpires: " & objItem.PasswordExpires
        Wscript.Echo "PasswordRequired: " & objItem.PasswordRequired
        Wscript.Echo "SID: " & objItem.SID
        Wscript.Echo "SIDType: " & objItem.SIDType
        Wscript.Echo "Status: " & objItem.Status
    Next

Remove the IUSR Identity from the Guest Users Group

The last modification which is not mentioned in the knowledge base article is that the Guest Users group is already explicitly denied access to the event log (at least on my Windows Server 2003 machine) and therefore adding the SID for this account will not have an account. To solve this I removed the IUSR account from the Guest Users group and everything worked.

Debugging

As an aside to be able to test and debug this on both a production and test server I created an .aspx file with no code-behind with the following code :

    <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Diagnostics"%>
    
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Test Event Log Access</title>
        
        <script runat="server">
        
        protected void WriteToMyEventLog(object sender, EventArgs e)
        {
            EventLog.WriteEntry("MyEventLog", "This is a test", EventLogEntryType.Error);
        }
    
        protected void WriteToApplication(object sender, EventArgs e)
        {
            EventLog.WriteEntry("Application", "This is a test", EventLogEntryType.Error);
        }        
        </script>
        
    </head>
    <body>
        <form id="form" runat="server">
        <div>
            <asp:Button runat="server" ID="btnWriteToMyEventLog" Text="Write to My Event Log" OnClick="WriteToMyEventLog" />
            <asp:Button runat="server" ID="btnWriteToApplication" Text="Write to Application" OnClick="WriteToApplication" />
        </div>
        </form>
    </body>
    </html>

February 10, 2011

Displaying ‘Unsaved Changes’ message for a web page using jQuery

Filed under: ASP.NET, jQuery, Web — philiphendry @ 10:48 am

I had a need to allow a user to cancel navigation to another page if changes to a form had not yet been saved – especially if they attempted to navigate to another page from the ever present menu. The code I used has a slight ‘hack’ which involves using the propertychange DOM event in IE rather than the change event which I would have usually expected.

Here’s the code that sets a global flag to indicate there’s an unsaved change :

var _changesMade = false;
$(document).ready(function() {

   $('form').bind($.browser.msie ? 'propertychange' : 'change', function() {
       _changesMade = true;
   });

   $(window).bind('beforeunload', function() {
       if (_changesMade)
           return 'There are unsaved changes which will be lost if you continue.';
   });
});

There are a couple of caveats. Any button that actually does the save will need a OnClientClick that sets the _changesMade to false to prevent the message from being displayed :

<asp:Button ID="btnSubmit" runat="server" Text="Submit" CssClass="button" OnClick="btnSubmit_Click" 
    OnClientClick="_changesMade=false; return true;" /> 

Also, if you have any code that causes the change event to fire (DOM manipulation after an Ajax call) then you might have to save the state of the _changesMade flag, make the DOM changes then reset the flag to it’s original value.

November 15, 2010

Creating a EqualityComparer on the fly with Lambdas

Filed under: .Net, Testing — philiphendry @ 11:12 am

When I’m writing unit tests I need the code to be a succinct as possible and one of the issues I’ve had is comparing objects sets/graphs to confirm expectations. Here’s a couple of the unit tests that test my solution and hopefully demonstrate what I’m after :

public class TestObject
{
   public string FirstProperty { get; set; }
   public string SecondProperty { get; set; }
}

[TestMethod]
public void EqualityCompareFactory_Should_DetectEquality_Given_TwoObjectInstancesWithTheSamePropertyValues()
{
  var comparer = EqualityComparerFactory<TestObject>.Create((x, y) => x.FirstProperty == y.FirstProperty);
  Assert.IsTrue(comparer.Equals(new TestObject { FirstProperty = "first" }, new TestObject { FirstProperty = "first"}));
}

[TestMethod]
public void EqualityCompareFactory_Should_DetectInequality_Given_TwoObjectInstancesWithTheDifferentPropertyValues()
{
  var comparer = EqualityComparerFactory<TestObject>.Create((x, y) => x.FirstProperty == y.FirstProperty);
  Assert.IsFalse(comparer.Equals(new TestObject { FirstProperty = "first" }, new TestObject { FirstProperty = "different" }));
}

The key here is that, rather than having to create a derived class of EqualityComparer<> I can simply use a factory and pass it a lambda that defines the comparison operation. In my business rule testing I can now write something like the following assertion :

var testResults = TimesheetItemBL.TestReturnSavePaymentFlags.ToList();
var expected = new List<TimesheetItemPaymentFlags>{
     new TimesheetItemPaymentFlags { AppliedRule = null, Date = testWeekStartDate, IsPaid = null },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(1), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(2), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(3), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(4), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.WaitingDay, Date = testWeekStartDate.AddDays(5), IsPaid = false },
     new TimesheetItemPaymentFlags { AppliedRule = AbsenceRules.Paid, Date = testWeekStartDate.AddDays(6), IsPaid = true },
 };
NUnit.Framework.Assert.That(testResults, new CollectionEquivalentConstraint(expected).Using<TimesheetItemPaymentFlags>(
     EqualityComparerFactory<TimesheetItemPaymentFlags>.Create((x, y) => x.AppliedRule == y.AppliedRule && x.Date.Equals(y.Date) && x.IsPaid == y.IsPaid, x => x.Date.GetHashCode())
 ));

Admittedly this is still pretty verbose and I might extract some helper functions to reduce the size of this but it does demonstrate the simplicity of the solution which, by the way, looks like this :

public class DynamicEqualityComparer<T> : EqualityComparer<T>
{
   private readonly Func<T, T, bool> _equalsFunction;
   private readonly Func<T, int> _hashCodeFunction;

   internal DynamicEqualityComparer(Func<T, T, bool> equalsFunction, Func<T, int> hashCodeFunction)
   {
       _equalsFunction = equalsFunction;
       _hashCodeFunction = hashCodeFunction;
   }

   public override bool Equals(T x, T y)
   {
       return _equalsFunction(x, y);
   }

   public override int GetHashCode(T obj)
   {
       return _hashCodeFunction(obj);
   }
}

public class EqualityComparerFactory<T> where T : class
{
   public static DynamicEqualityComparer<T> Create(Func<T, T, bool> equalsFunction)
   {
       return new DynamicEqualityComparer<T>(equalsFunction, x => x.GetHashCode());
   }

   public static DynamicEqualityComparer<T> Create(Func<T, T, bool> equalsFunction, Func<T, int> hashCodeFunction)
   {
       return new DynamicEqualityComparer<T>(equalsFunction, hashCodeFunction);
   }
}

There’s some complexity involved in providing a hasCode function that is meaningful to the comparison, but otherwise it’s fairly basic.

September 23, 2010

Strongly-typed ASP.NET Data-Binding

Filed under: ASP.NET — philiphendry @ 8:18 pm

Update : Just thought I’d better highlight something before I get shot down! I could, of course, early-bind to data by saying DataField=”<%#((Employee)Container.DataItem).Name%>” but below I was exploring ways to leverage code generation I had to make the syntax better – not sure I’ve done that of course, but the journey was fun!!

I was about to write the following code… again…

    <asp:GridView ID="grdSickness" runat="server" AutoGenerateColumns="false">
        <Columns>
            <asp:BoundField HeaderText="Name" DataField="EmployeeName" />
        </Columns>
    </asp:GridView>

…but it strikes me as very ‘yesterday’ to have an un-typed DataField property. If the entity changes that’s bound to this grid I’m not going to know about it until the grid is bound and that’s way too late for me!

What I really want to see is something like this :

      <asp:BoundField HeaderText="Name" DataField="<%=Entity.EmployeeName%>" />

Where the Entity object provides me with intellisense list of names. Now, this is by no means perfect and all I was looking for was a way to ‘inject’ the property name into the DataField attribute.

Code Generation

And the reason this comes so easily is because I’ve already written a code generator that creates a data access layer of strongly typed calls to stored procedures and uses the sql command ‘set fmtonly on’ to interpret the result sets of the stored procedures to generate entities that can be loaded into collections all fairly automatically. All I had to do was add four lines of code (and six more for the unit test) to pump out a static inner class into my entity that returns those property names. Here’s an example entity and one sproc wrapper that was generated :

    public partial class Employee : BaseEntity
    {
        private Employee() { }
        public String Name { get; private set; }
        public String Position { get; private set; }
        public static class ColumnNames
        {
            public static String Name { get { return "Name"; } }
            public static String Position { get { return "Position"; } }
        }
        public static Employee Load(IDataRecord record)
        {
            return new Employee
            {
                Name = GetNullableString(record, "EmployeeName"),
                Position = GetNullableString(record, "EmployeePosition")
            };
        }
        public static IEnumerable<Employee> LoadCollection(IDataReader reader)
        {
            return LoadCollection<Employee>(reader, Load);
        }
    }

    public partial class EmployeeDAL
    {
        public static DbDataReader spEmployeeSearch(bool? includeAuthorised)
        {
            Database db = DatabaseFactory.CreateDatabase("DefaultConnectionString");
            DbCommand command = db.GetStoredProcCommand("[spEmployeeSearch]");
            db.AddInParameter(command, "includeAuthorised", DbType.Boolean, includeAuthorised);
            return (DbDataReader)db.ExecuteReader(command);
        }
    }

The extra bit that I’ve added to this generated is the static inner class called ColumnNames. This, I thought meant I could write this :

<asp:BoundField HeaderText="Name" DataField="<%=Employee.ColumnNames.Name%>" />

Sideline : The code generator I’ve written uses a text template to integrate with Visual Studio which calls C# code I’ve written which then uses SQL Server SMO objects to connect to a database, iterate through the stored procedures and read extended properties which describe what to generate from them. I prefer keeping C# code in the text template to a minimum especially as I wanted the code generator to be fully unit tested!

Code Expressions

Whoops… but I forgot – you can’t but an expression in a WebControl attribute like that and I got this error at runtime :

{"A field or property with the name '<%Employee.ColumnNames.Name%>' was not found on the selected data source."}

Luck would have it I’ve already solved this one before. An extensibility point in ASP.NET allows me to create a new expression processor. It’s easier just to show the code and how to use it. Here’s the code :

namespace Utility.Web
{
    [ExpressionPrefix("Code")]
    public class CodeExpressionBuilder : ExpressionBuilder
    {
        public override CodeExpression GetCodeExpression(BoundPropertyEntry entry,
           object parsedData, ExpressionBuilderContext context)
        {
            return new CodeSnippetExpression(entry.Expression);
        }
    }
}

Which has to be registered in the web.config :

<?xml version="1.0"?>
<configuration>
    <system.web>
        <compilation>
            <expressionBuilders>
                <add expressionPrefix="Code" type="Utility.Web.CodeExpressionBuilder, Utility.Web"/>
            </expressionBuilders>
        </compilation>
    </system.web>
</configuration>

Now I can write the final version of my binding like this :

    <asp:BoundField HeaderText="Name" DataField="<%$ Code : Employee.ColumnNames.Name%>" />
    

That might seem like a lot of effort, but I now have a system whereby if I change any column in the result set of a stored procedure I’ll get a compile error where the entity is bound to a UI element and, to be honest, this blog post probably took longer to write!!

Oh, one caveat, I only bind directly to entities generated against a sproc for trivial CRUD like screens. Where it gets more complicated I would generate a model class and transform the entities into it in the same manner I would for ASP.NET MVC apps.

September 21, 2010

Always close Your HTML Elements Properly

Filed under: ASP.NET — philiphendry @ 9:16 am

I’ve just had a strange error trying to write a noddy ASP.NET page using Microsoft Ajax. The error itself was Microsoft JScript runtime error: Object required which basically tells you nothing. This error was being thrown in Sys$WebForms$PageRequestManager$_initializeInternal by the line trying to access this._form.onsubmit. It would appear _form is null! Looking up the call stack it originated from the Ajax initialize call : Sys.WebForms.PageRequestManager._initialize(‘scriptManager’, document.getElementById(‘form1′));. It would appear that the form element has not been closed yet and therefore the document.getElementById is searching for something that doesn’t exist.

Ok, that tells me nothing.

Thankfully I had an epiphany which worked (for once!) Spot the difference between this :

</title>
    <script type="text/javascript" language="javascript" src="GetResource.ashx?../lib/jquery/1.3.2/jquery-1.3.2.js" />
    <script type="text/javascript" language="javascript" src="GetResource.ashx?../lib/jqueryplugins/jquery.blockUI.js" />
    <script type="text/javascript" language="javascript" src="GetResource.ashx?../rms.utility.web.jqueryplugins/jquery.rms.ajaxprogress.js" />
</head>

and this :

<head runat="server">
    <title>jquery.rms.ajaxprogress.js Tests</title>
    <script type="text/javascript" language="javascript" src="GetResource.ashx?../lib/jquery/1.3.2/jquery-1.3.2.js"></script>
    <script type="text/javascript" language="javascript" src="GetResource.ashx?../lib/jqueryplugins/jquery.blockUI.js"></script>
    <script type="text/javascript" language="javascript" src="GetResource.ashx?../rms.utility.web.jqueryplugins/jquery.rms.ajaxprogress.js"></script>
</head>

In fact, it looks like my syntax highlighter I’m using in LiveWriter has spotted the difference too! Basically because I didn’t close the script element with a </script> IE only executed the first javascript fetch, Firefox wouldn’t execute any and Chrome worked!! Go figure!

September 14, 2010

Implementing a jQuery Browser history Plugin using microsoft Ajax History

Filed under: ASP.NET, jQuery, Uncategorized — philiphendry @ 8:31 pm

This article is a summary of the steps I took to produce a jQuery plugin that would create history points in the browsers history programmatically. In particular I wanted to create as simple an interface in the implementing page as possible and primarily needed to integrate with ASP.NET.

I will also describe my workaround for a bug in Microsoft Ajax ASP.NET 3.5 where the onNavigate event would not fire when the page was too large since the _onIFrameLoad code injected by the ScriptManager would execute before the ajax initialisation had time to run.

The first thing I did was consider the interface I wanted to write in each of the aspx pages that required history. In particular I wanted to :

  1. Only have to enter the code to achieve history in one location
  2. Encapsulate as much of the complexities of maintaining browser history as possible.
  3. Allow the plugin to be extendable without over-engineering it!

I decided that keeping it client-side and written in javascript would be the best approach and using my favoured library, jQuery, would simplify integration. And this is the interface I came up with :

<script type="text/javascript" language="javascript">
   $(document).ready(function() {
       $.fn.ajaxHistory.registerCallbacks(getHistory, setHistory, refreshHistory);
       $("#<%=ddlStore.ClientID%>").ajaxHistory();
       $("#<%=btnSearch.ClientID%>").ajaxHistory();
   });
   
   function getHistory() {
       return {
           "store": $("#<%=ddlStore.ClientID%>").val(),
           "surname": $("#<%=txbSurname.ClientID%>").val()
       };
   }

   function setHistory(state) {
       $("#<%=ddlStore.ClientID%>").val(state.store || "0");
       $("#<%=txbSurname.ClientID%>").val(state.surname || "");
   }

   function refreshHistory() {
       $("#<%=btnSearch.ClientID%>").click();
   }
</script>

This code his very basic indeed and doesn’t really include an functionality whatsoever. The ready() function registers three callback with the refreshHistory callback optional. getHistory() is called to return a javascript object containing all the data that must be stored in order to return the page back to a the current state. setHistory() performs the reverse of getHistory(). And refreshHistory() is called upon to refresh the page using the restored state and therefore fetch any updates from a server. In this particular example, the page is a search screen where the user can select a store from a drop down list (ddlStore) which is configured for AutoPostBack and refreshes the search results or they can enter a surname and click the search button (btnSearch.) In the ready() I therefore tell .ajaxHistory(), my plugin, the controls that, when clicked or changed, are required to mark a history point.

The plugin itself is written using a standard pattern and over all looks like this :

(function($) {
    var _elements = new Array();

    $.fn.ajaxHistory = function() {
        return this.each(function() {
            var element = "#" + this.id;
            bindEvent(element);
            _elements.push(element);
        });
    };

    $.fn.ajaxHistory.initialise = function() {
        Sys.Application.add_navigate(onNavigate);
        Sys.WebForms.PageRequestManager.getInstance().add_endRequest(onEndRequest);
    };


    // onEndRequest will be called each time an ajax call is completed and therefore we can rebind all the events.
    function onEndRequest(sender, e) {
        $.each(_elements, function(index, item) {
            bindEvent(item);
        });
    };

})(jQuery);

$(document).ready(function() {
    $.fn.ajaxHistory.initialise();
});

The plugin initialises a couple of Microsoft Ajax events in its own ready() method and the ajaxHistory() method binds the passed elements (specified by a selector) to events that inform the plug in when the element is clicked (buttons, links) or changed (textboxes, drop downs). Since my plugin is used within UpdatePanels the onEndRequest() will be fired at the end of each Ajax call and re-bind the controls (I would use the jQuery .live() binding however binding the change event in IE using jQuery 1.4.2 seems to be broken at the moment and therefore I have to revert to jQuery 1.3.2 instead.)

To remember history points I’m making use of the history functionality built into Microsoft Ajax. This requires that the ScriptManager control is set with the attribute EnableHistory turned on :

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="true" />

The Microsoft Ajax history functionality is then used in the plugin when the events bound to each of the elements is fired and simply adds a history point calling the getHistory() callback. This code store the state as a query string preceeded with a # which forces the browser to log it in it’s history and can even be bookmarked :

Sys.Application.addHistoryPoint(_getHistory(), document.title);

When the user clicks Back in the browser window, Microsoft Ajax will automatically detect the state after the # and raise the onNavigate event passing the state to it. The plugin simply passes that state to the setHistory() callback and then calls the refresh callback.

Microsoft Ajax Bug

Josh Close has already blogged about the bug I had whilst writing this plugin. My initial testing didn’t have any problems but when I tried to integrate my plugin to a larger page I suddenly found the onNavigate event would not fire and therefore my page would not return to a previous state.

My own debugging in the Microsoft Ajax javascript lead me to the _onIFrameLoad javascript that loads the state and it was Josh’s article that helped me understand that it was running too early. To solve this problem I made sure that the history was initialised as soon as possible rather than last in the page and therefore I made the call to _enableHistoryInScriptManager() myself immediately after the ScriptManager. Thankfully this method doesn’t do much anyway but ensures the write state is set up for the onNavigate event to be fired as expected.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="true" />
<script type="text/javascript" language="javascript">
    Sys.Application._enableHistoryInScriptManager();
</script>

Hopefully this problem will go away in ASP.NET 4!

August 9, 2010

Manually converting a Visual studio project to include asp.net mvc Context menus

Filed under: ASP.NET MVC, Visual Studio — philiphendry @ 2:26 pm

I’ve got a Visual Studio project that includes ASP.NET MVC controllers, models and view utilities but because it was not a Web Application I wasn’t getting any of the context menus for adding MVC elements. For example, ‘Controller’ was missing from the Add menu :

image

To fix this I unloaded the project, edited the csproj and added the following line :

  <PropertyGroup>
    ...
    <ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
    ...
  </PropertyGroup>

Using a list of known project type guids I could identify the second guid which is Windows (C#) whilst the first I found by googling and checking a test ASP.NET MVC application I created with Visual Studio.

January 6, 2010

IE8 Caches jQuery Ajax results

Filed under: ASP.NET MVC, jQuery — philiphendry @ 10:07 am

In my client code I have a small chunk of javascript that fetches the data for my form asynchronously using Ajax :

$.getJSON(_config.getUrl, null, function(data) {
    _shiftPattern = data;
    renderModel();
});

However, my server code was only being called on the first visit to the page and on subsequent attempts the browser seemed to be fetching cached results. Since my server side code is ASP.NET MVC the solution to this is very simple indeed and simply involves decorating the action method with a OutputCache attribute :

[OutputCache(Duration = 0, VaryByParam = "None")]
public ActionResult GetJson(int id)
{
    return Content(
        JsonHelper.Serialize(ShiftPatternBL.Get(id)), 
        "application/json; charset=utf-8"
        );
}

ASP.NET MVC is proving very elegant indeed :)

December 22, 2009

Sending and Receiving JSON between jQuery and ASP.NET MVC using Ajax.

Filed under: ASP.NET MVC, jQuery — philiphendry @ 9:17 pm

It’s taken me a while to get this working without any problems so it’s worth jotting down how it all works. I don’t think I was trying to do anything particularly difficult – just receive Json from an ASP.NET MVC application using jQuery then submitted but the APIs had to be coerced into just the correct way to get it functional.

Receiving Json from ASP.NET MVC using jQuery

First up, here’s the Json format I’m trying to send and receive :

var _shiftPattern = {
  'employee': {'name' : 'Jason Wright', 'number': 1234, 'contractedHours': 39.0 },
  'stepSize': 0.5,
  'days': [
      { 'day': 'Sunday',    'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'w', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'p', 'p', 'p', 'p', 'p', 'b', 'p', 'p', 'p', '', '', '', '', '', '', '', '', '', '', '' ] },
      { 'day': 'Monday',    'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'p', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'p', 'p', 'p', 'p', 'p', 'b', 'p', 'p', 'p', '', '', '', '', '', '', '', '', '', '', '' ] },
      { 'day': 'Tuesday',   'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'p', 'p', 'p', 'p', 'p', 'b', 'p', 'p', 'p', '', '', '', '', '', '', '', '', '', '', '' ] },
      { 'day': 'Wednesday', 'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'w', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'w', 'w', 'p', 'p', 'p', 'b', 'p', 'p', 'p', '', '', '', '', '', '', '', '', '', '', '' ] },
      { 'day': 'Thursday',  'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'p', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'w', 'w', 'p', 'p', 'p', 'b', 'p', 'p', 'p', '', '', '', '', '', '', '', '', '', '', '' ] },
      { 'day': 'Friday',    'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'w', 'w', 'p', 'p', 'p', 'b', 'p', 'p', 'p', '', '', '', '', '', '', '', '', '', '', '' ] },
      { 'day': 'Saturday',  'pattern': ['', '', '', '', '', '', '', 'p', 'b', 'w', 'w', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'w', 'w', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' ] }
  ]
};

Note that I’ve opted to maintain Javascript standards by using camel casing – that caused a bit of trouble and I’ll show the problem and work-around later.

The jQuery to fetch this Json is trivial :

$(document).ready(function() {
  $.getJSON("/willingtowork/shiftpattern/1234", null, function(data) {
      _shiftPattern = data;
      renderModel();
  });
});

…and ASP.NET MVC controller action looks like this :

public ActionResult ShiftPattern(int id)
{
    return Content(JsonHelper.Serialize(
        new WillingToWork
        {
            Employee = new Employee { Name = "Jason Wright", Number = id, ContractedHours = 39.0f },
            StepSize = 0.5f,
            Days = new List<Day> {
                new Day { DayName = "Sun", Pattern = new List<string> {"", "", "", "", "", "", "", "p", "p", "b", "p", "p", "p", "p", "w", "w", "", "", "", "", "", "", "", "", "p", "p", "p", "p", "p", "p", "p", "w", "w", "w", "w", "w", "", "", "", "", "", "", "", "", "", "", "", "" }},
                new Day { DayName = "Mon", Pattern = new List<string> {"", "", "", "", "", "", "", "p", "p", "b", "p", "p", "p", "p", "w", "w", "", "", "", "", "", "", "", "", "p", "p", "p", "p", "p", "p", "p", "b", "p", "p", "p", "p", "w", "w", "w", "", "", "", "", "", "", "", "", "" }},
                new Day { DayName = "Tue", Pattern = new List<string> {"", "", "", "", "", "", "", "p", "p", "b", "p", "p", "p", "p", "w", "w", "", "", "", "", "", "", "", "", "p", "p", "p", "p", "p", "p", "p", "b", "p", "p", "p", "p", "w", "w", "w", "", "", "", "", "", "", "", "", "" }},
                new Day { DayName = "Wed", Pattern = new List<string> {"", "", "", "", "", "", "", "p", "p", "b", "p", "p", "p", "p", "w", "w", "", "", "", "", "", "", "", "", "p", "p", "p", "p", "p", "p", "p", "b", "p", "p", "p", "p", "w", "w", "w", "", "", "", "", "", "", "", "", "" }},
                new Day { DayName = "Thu", Pattern = new List<string> {"", "", "", "", "", "", "", "p", "p", "b", "p", "p", "p", "p", "w", "w", "", "", "", "", "", "", "", "", "p", "p", "p", "p", "p", "p", "p", "b", "p", "p", "p", "p", "w", "w", "w", "", "", "", "", "", "", "", "", "" }},
                new Day { DayName = "Fri", Pattern = new List<string> {"", "", "", "", "", "", "", "p", "p", "", "", "", "w", "w", "w", "w", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }},
                new Day { DayName = "Sat", Pattern = new List<string> {"", "", "", "", "", "", "", "", "", "", "", "", "w", "w", "w", "w", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }}
            }
        }
    ), "application/json; charset=utf-8");
}

In this test code I’ve hard-coded some object initialisations but the real issue for me here was originally choosing to use a JsonResult by calling Json() on the Mvc Controller Base but I’ve replaced it with a ContentResult and serialised the Json manually – more on that later.

The code below defines the data transfer objects I’m using to strongly type the Json in C#. Note I’ve implemented IExtensibleDataObject so that any additional data added to the Json in the javascript that I haven’t accounted for is ignored but still maintained if necessary (you may choose to remove this if you want to be strict but any extra data will cause an exception during deserialising.) I might also implement all properties as virtual to enable mocking frameworks (an alternative to defining interfaces which would be a bit complicated for DTOs) but that’s not shown here.

The main point to notice with the DataContract below is the use of DataMemberAttribute and its ability to rename properties – this is how I’m maintaining Pascal casing in C# and Camel casing for the serialised Json.

[DataContract]
public class WillingToWork : IExtensibleDataObject
{
   [DataMember(Name="employee")] public Employee Employee { get; set; }
   [DataMember(Name="days")] public List<Day> Days { get; set; }
   [DataMember(Name="stepSize")] public float StepSize { get; set; }

   public virtual ExtensionDataObject ExtensionData { get; set; }
}

[DataContract]
public class Day : IExtensibleDataObject
{
   [DataMember(Name = "pattern")] public List<string> Pattern { get; set; }
   [DataMember(Name = "day")] public string DayName { get; set; }

   public virtual ExtensionDataObject ExtensionData { get; set; }
}

[DataContract]
public class Employee :IExtensibleDataObject
{
   [DataMember(Name="contractedHours")] public float ContractedHours { get; set; }
   [DataMember(Name="name")] public string Name { get; set; }
   [DataMember(Name="number")] public int Number { get; set; }

   public virtual ExtensionDataObject ExtensionData { get; set; }
}

… and here lies a problem. When I was using the ASP.NET MVC JsonResult, the Json simply would not serialise using the names I’d provided in the DataMember and instead used the property name.

I’d already seen how to use the DataContractJsonSerializer() to manually create test code which correctly used the DataMemberAttribute() name property so I was wondering why JsonResult() wasn’t doing the same. Taking a look with reflector showed the following code in the JsonResult():

public override void ExecuteResult(ControllerContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }
    HttpResponseBase response = context.HttpContext.Response;
    if (!string.IsNullOrEmpty(this.ContentType))
    {
        response.ContentType = this.ContentType;
    }
    else
    {
        response.ContentType = "application/json";
    }
    if (this.ContentEncoding != null)
    {
        response.ContentEncoding = this.ContentEncoding;
    }
    if (this.Data != null)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        response.Write(serializer.Serialize(this.Data));
    }
}

The problem in this code is the use of the JavaScriptSerializer() which is now marked obsolete (although that seems to be questionable) and doesn’t handle the DataMemberAttribute(). Rather than try to work around this too cleverly I’ve used the ContentResult() instead passing a Json string serialized from a helper using the DataContractJsonSerializer() and making sure to pass the content type. Here’s the serializer helper (not exactly rocket science):

public class JsonHelper
{
   public static string Serialize<T>(T obj)
   {
       var serializer = new DataContractJsonSerializer(obj.GetType());
       var ms = new MemoryStream();
       serializer.WriteObject(ms, obj);
       return Encoding.Default.GetString(ms.ToArray());
   }
}

Posting Json using jQuery back to an ASP.NET MVC Controller

The next step was finishing the loop and allowing the client to post back the Json data in order for it to be persisted. Here’s the jQuery in the client:

function saveModel() {
  $.ajax({
      url: "/willingtowork/save",
      type: "POST",
      data: JSON.stringify(_shiftPattern),
      dataType : "json",
      contentType: "application/json; charset=utf-8",
      success: function(data, textStatus) {
          alert(data.result);
      },
      error: function(request, textStatus, errorThrown) {
          alert(request.responseText);
      }
  });
}

And the controller action that receives this:

[JsonFilter(Param = "data", RootType = typeof(WillingToWork))]
public ActionResult Save(WillingToWork data)
{
  return Json(new {result = "Success" });
}

The key here is the JsonFilterAttribute()

public class JsonFilter : ActionFilterAttribute
{

   public string Param { get; set; }
   public Type RootType { get; set; }

   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
       if (!(filterContext.HttpContext.Request.ContentType ?? string.Empty).Contains("application/json")) return;
       filterContext.ActionParameters[Param] = JsonHelper.Deserialize<WillingToWork>(filterContext.HttpContext.Request.InputStream);
   }
}

public class JsonHelper
{
   public static T Deserialize<T>(Stream sr)
   {
       var serializer = new DataContractJsonSerializer(typeof (T));
       return (T) serializer.ReadObject(sr);
   }
}

I sourced the JsonFilter from an article online although I’ve lost the reference now but I’m very grateful to whoever it was!!! I’ve also renamed the filter itself and changed it to use a helper for the deserializing. However, that filter does turn a Json string into a typed object with very little plumbing.

And that’s it!! Phew, it took a while and in the end the code needed to complete it is fairly simple. At some point I’ll probably create a new ActionResult() that wraps up the serializing using DataContractJsonSerializer() so that it’s not necessary to remember to use a ContentResult(), serializer and content type!!! Yeuck!

Older Posts »

Theme: Shocking Blue Green. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.