views:

486

answers:

4

I have a custom ASP.NET solution deployed to the ISV directory of an MS Dynamics CRM 4.0 application. We have created a custom entity, whose data entry requires more dynamism than is possible through the form builder within CRM. To accomplish this, we have created an ASP.NET form surfaced through an IFRAME on the entity's main form.

Here is how the saving functionality is currently laid out:

  1. There is an ASP.NET button control on the page that saves the entity when clicked. This button is hidden by CSS.
  2. The button click event is triggered from the CRM OnSave javascript event.
  3. The event fires and the entity is saved.

Here are the issues:

  1. When the app pool is recycled, the first save (or few) may:
    1. not save
    2. be delayed (ie. the interface doesn't show an update, until the page has been refreshed after a few sec)
  2. Save and Close/New may not save

For issue 1.1 and 2, what seems to be happening is that while the save event is fired for the custom ASP.NET page, the browser has moved on/refreshed the page, invalidating the request to the server. This results in the save not actually completing.

Right now, this is mitigated with a kludge javascript delay loop for a few seconds after calling the button save event, inside the entity OnSave event.

Is there any way to have the OnSave event wait for a callback from the IFRAME?

A: 

One thing that would help is to increase the app pool timeout in IIS. Its default is 20 minutes so if nobody uses your IFrame for 20 minutes, it will recycle the app pool and result in a long delay the next time it's fired up.

Focus
I think that the main problem is when the save is done... if the underlying application is slow or is not responding, the CRM form close and drop the connection to the Custom App...
Mercure Integration
A: 

One solution in that case is to hook on the IFrame State or Events, and continue the CRM save action only when the IFrame is reloaded after the postback. In that way, if the custom app fails or is slow, you can notify the user and cancel the save process.

Mercure Integration
I have tried this approach, but all the examples that I have seen and attempted were async, which doesn't lend itself to being caught in the OnSave event. Do you have an example?
Forgotten Semicolon
The process should be synchronous inside the save button event handler. The base concept is to loop, check the IFrame readyState property and validate that it gets thru the loading and complete states.
Mercure Integration
+1  A: 

My preferred method of handling the saving of values in an iframe is via webservice. Have the CRM form call a function on your iframe that collects all of the fields and synchronously posts them. When the webservice returns a response and your save function returns, the CRM form will then perform its save.

Here's what your save method on your iframe might look like, using jQuery:

function save() {
    var saveResult = false;

    $.ajax({
        async: false,
        type: "POST",
        url: "/YourWebservice.asmx/SaveIframeValues",
        contentType: 'application/json; charset=utf-8',
        dataType: "json"
        data: '{ paramname : "paramvalue" }',
        success: function(response) {
            if (response.d === true) // assuming your webservice returns true upon success
            {
                saveResult = true;
            }
        }
    });

    if (saveResult !== true) {
        // alert user, perhaps

        // and stop the CRM form from saving
        event.returnValue = false;
        return false;
    } else {
        // iframe save was successful, time for a nap
    }
}
Polshgiant
A: 

The solution that I've implemented (very recently) to overcome the problem is a little complicated, but let me try to explain:

Essentially - On the page with the iFrame, you're going to track (in Session) the save event and expose the results through a WebMethod that the CRM page will call (using the jquery .ajax() functionality)

Best way to explain is with sample code (Just create a new Website Project with Default.aspx & IFramePage.aspx):

IFramePage.aspx ->

    <script type="text/javascript">

    SaveStuff = function() {            
        __doPostBack('SaveButton', '');
    }

</script>    
    Hello - I am an iFrame!<br />
    <asp:linkbutton id="SaveButton" runat="server" style="display:none;" onclick="SaveButton_Click" text="Save" />
    <asp:hiddenfield id="RandomGuidHiddenField" runat="server" />
</div>

IFramePage.aspx.cs ->

    public static object locker = new object();

/// <summary>
/// Gets the statuses.
/// </summary>
/// <value>The statuses.</value>
public static Dictionary<Guid, bool> Statuses
{
    get 
    {
        lock (locker)
        {
            if (HttpContext.Current.Session["RandomGuid"] == null)
            {
                HttpContext.Current.Session["RandomGuid"] = new Dictionary<Guid, bool>();
            }

            return (Dictionary<Guid, bool>) HttpContext.Current.Session["RandomGuid"];
        }
    }
}

[WebMethod]
public static bool CheckSaveComplete(string randomGuid)
{
    var currentGuid = new Guid(randomGuid);
    var originalTime = DateTime.Now;

    if (!Statuses.ContainsKey(currentGuid))
    {
        Statuses.Add(currentGuid, false);
    }

    while (!Statuses[currentGuid])
    {
        if (DateTime.Now.Subtract(originalTime) > new TimeSpan(0, 0, 0, 1))
        {
            return false;
        }

        Thread.Sleep(1000);
    }

    return true;
}

/// <summary>
/// Handles the Load event of the Page control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        this.RandomGuidHiddenField.Value = Guid.NewGuid().ToString();            
    }
}

/// <summary>
/// Handles the Click event of the SaveButton control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected void SaveButton_Click(object sender, EventArgs e)
{
    var randomGuid = new Guid(this.RandomGuidHiddenField.Value);

    if (!Statuses.ContainsKey(randomGuid))
    {
        Statuses.Add(randomGuid, false);
    }

    Thread.Sleep(10000);

    Statuses[randomGuid] = true;
}

In my Default.aspx page (to simulate the CRM Form Entity page) ->

   <script type="text/javascript">
    function OnSave() {
        var saveResult = false;
        var randomGuid = window.frames[$("#daIframe")[0].id].$("input[id$=RandomGuidHiddenField]").val();

        window.frames[$("#daIframe")[0].id].SaveStuff();


        $.ajax(
        {
            async: false,
            type: "POST",
            url: "IFramePage.aspx/CheckSaveComplete",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            data: '{"randomGuid" : "' + randomGuid + '"}',
            success: function(response) {
                if (response.d === true) {
                    alert(response.d);
                    saveResult = true;
                }
            }
        }
        );

    if (saveResult !== true) {
        alert("Oh No!");
    }
    else {
        alert("way to go!");
    }
}

</script>
<iframe name="daIframe" id="daIframe" src="IFramePage.aspx"></iframe>
<asp:textbox id="lsc_paymentinfo" runat="server" text="1000" /><br />
<input type="button" value="Save" onclick="OnSave();" />

Let me know if you have any questions!

Rasto