问题描述:

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?

网友答案:

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!

网友答案:

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
    }
}
网友答案:

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.

网友答案:

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.

相关阅读:
Top