Philippsen's Blog

Everyday findings in my world of .net and related stuff

Archive for October, 2014

Multiselection checkboxes in an AxGridview

Posted by Torben M. Philippsen on October 30, 2014

For a customer Ihad to fix a very non user friendly behavier in an AxGridview control.

The scenario was that they had an AxGridview control containing attachments in an expence module. For a single Expence you would one or more attachments – those attachments would be shown in the AxGridview when viewin the given expence post. In the gridview you would be able to select and deselect attachments but in the standard behavior the user flow would be something like this:

  1. Mark a row in the gridview – wait for postback to finish
  2. now the row is in edit mode – check the combobox – wait for postback
  3. click the update command button in the row – wait for poatback to finish

That’s a lot of waiting and posting back just to check a combobox. Ofcourse the customer wanted a more smooth solution.

My workaround was to try and create comboboxes that would be checkable but without posting back all the time. Doing this I had to consider how I would read the checked state from the comboboxes and persist the state to Ax/the underlying dataset. Here’s what I ended up with…

The markup

In order to have a combobox that is checkable at all times – not just in editmode, you will have to create a template field
EP_Multiselect_markup
Please notice that the AxGridview doesn’t allow paging and editing. Also you should notice that the combox doesn’t postback.

The first time the page loads checkboxes should be checked according to values from AX. Notice that the checked propety is handled by the Marked method

private Hashtable checkbox_values = new Hashtable();

    /// <summary>

    /// Method used in when each row is databound (See markup)

    /// </summary>

    /// <param name="dataItem"></param>

    /// <param name="container"></param>

    /// <returns></returns>

    protected bool Marked(object dataItem, object container)

    {

        AxGridViewRow gridViewRow = (AxGridViewRow)container;

        //CheckBox checkbox = (CheckBox)gridViewRow.FindControl("markCheckbox");

        DataSetViewRow row = dataItem as DataSetViewRow;

        if (row != null)

        {

            //string hashcode = checkbox.UniqueID;

            string hashcode = gridViewRow.RowIndex.ToString();

            if (this.checkbox_values.ContainsKey(hashcode))

            {

                //return row.Mark((bool)this.checkbox_values[hashcode]);

                return (bool)checkbox_values[hashcode];

            }           

        }

        return false;

    }

You will notice that the checked state depends on whether the current rows combobox has been added to a hashtable. The hashtable contains a boolean value for each rows combobox indicating whether it should be checked or not. The hashtable is populated on every postback like this:

 

   protected void Page_Load(object sender, EventArgs e)

    {

        getChecked(false);

 

 

//EG/2014.10.28/tomph, 00392460 XpenseAttachments –>

    /// <summary>

    /// Method used to fetch all marked checkboxes from the grid.

    /// This approach is used because we use a classic .net combobox with no postback (not an axboundfield)

    /// </summary>

    /// <param name="_saveToDataset">indicates whether this method is triggered from the save button click event or is just a normal postback</param>

    protected void getChecked(bool _saveToDataset)

    {

        foreach (GridViewRow row in AxGridView1.Rows)

        {

            if (row.RowType == DataControlRowType.DataRow)

            {

                CheckBox chk = (CheckBox)row.FindControl("markCheckbox");

                string hashcode = row.RowIndex.ToString();

                    if (chk.Checked)

                    {

                        if (!checkbox_values.ContainsKey(hashcode))

                        {

                            //only add if it does not exist

                            checkbox_values.Add(hashcode, true);

                        }

                        else

                        {

                            //update value if exists

                            checkbox_values[hashcode] = false;

                        }

                        if (_saveToDataset)

                        {      

                               //save the checked state to the datasat

                               DataSetView dsv = this.ImageDS.GetDataSourceView(this.AxGridView1.DataMember).DataSetView;

                               dsv.SetCurrent(row.RowIndex);

                               DataSetViewRow dsvr = dsv.GetCurrent();

                               dsvr.BeginEdit();

                               dsvr.SetFieldValue("selection**", 1);

                               dsvr.EndEdit();

                               this.AxGridView1.UpdateRow(row.RowIndex, false);

                        }

                    }

                    else

                    {

                        if (!checkbox_values.ContainsKey(hashcode))

                        {

                            //only add if it does not exist

                            checkbox_values.Add(hashcode, false);

                        }

                        else

                        {

                            //update value if exists

                            checkbox_values[hashcode] = false;

                        }

                        if (_saveToDataset)

                        {

                            //save the unchecked state to the datasat

                            DataSetView dsv = this.ImageDS.GetDataSourceView(this.AxGridView1.DataMember).DataSetView;

                            dsv.SetCurrent(row.RowIndex);

                            DataSetViewRow dsvr = dsv.GetCurrent();

                            dsvr.BeginEdit();

                            dsvr.SetFieldValue("selection**", 0);

                            dsvr.EndEdit();

                            this.AxGridView1.UpdateRow(row.RowIndex, false);

                        }

                    }               

            }

        }

    }

The only thing we will have to do know is to persist the checked comboboxes to the data, when clicking a “save/update” button. The save button click event calls the same method as above. Only difference is that the boolean value true is passed which again causes each row to be updated.

    protected void UpdateButton_Click(object sender, EventArgs e)

    {

        getChecked(true);

        this.ImageDS.GetDataSet().DataSetRun.AxaptaObjectAdapter.Call("updateSelections");

    }

Advertisements

Posted in Enterprise Portal Development | Tagged: , , , | Comments Off on Multiselection checkboxes in an AxGridview

Handling timeout issues on the EnterPrise Portal

Posted by Torben M. Philippsen on October 29, 2014

Lately I have been struggling with a timeout issue for a customer on the Enterprise Portal for Dynamics AX 2009.

I ended up doing two things – here are my experiences:

The problem:

If You have been working with EP, You have probably seen something like this on occasion
EP_TimeOut

The solution:

I will not claim to have a bulletproof solution but there are things that You might want to try

  1. In the AX client navigate to administration—>setup—>internet—>Enterprise Portal—>Parameters
    EP_parameters
    You could try to increase the values for timeout and history and see how that plays out for you.
  2. Another approach could be to increase the session timeout for the AX website in IIS. In order to do that you will have to do two things…
    Find the web.config file for your ax site. The path may look something similar to this C:\inetpub\wwwroot\wss\VirtualDirectories\80 – but ofcourse this may vary depending on your installation structure.
    In the configsections (in the top section of the web.config) you will have to add

    <sectionGroup name="Microsoft.Dynamics">
          <section name="Session" type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral,PublicKeyToken=b77a5c561934e089" />
    </sectionGroup>

    EP_webConfig1
    Now scroll down and place the pointer after <system.web/> and before <runtime> and add

    <Microsoft.Dynamics>
        <Session Timeout="120" />
    </Microsoft.Dynamics>

    EP_webConfig2
    The session timeout is set in seconds and according to your needs.

    Good luck…

 

 

Thanks to my collegue Martin Lindhoff Lysgaard for input and assistance.

Posted in Enterprise Portal Development | Tagged: , , | Comments Off on Handling timeout issues on the EnterPrise Portal

AX2012 AIF – CallContext

Posted by Torben M. Philippsen on October 8, 2014

Working with AIF on the Dynamics AX 2009 platform you had to create the SoapHeader manually. In that you had to specify the destination endpoint and the source endpoint user in order to being able to target a specific company using and maybe using a specific AX user. It could look like something similar to this:

 

public static class SoapHeader

    {

 

        /// <summary>

        /// Helper method – adds a SOAP Header defining the destination endpoint (local endpoint) in Dynamics AX

        /// </summary>

        /// <param name="nameOfEndpoint">The name of the local endpoint</param>

        public static void SetDestinationEndpoint(string _nameOfEndpoint)

        {

            OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("DestinationEndpoint", "http://schemas.microsoft.com/dynamics/2008/01/services&quot;, _nameOfEndpoint));

        }

 

        /// <summary>

        /// Helper method – adds a SOAP Header defining the source endpoint name and the source endpoint user to use

        /// </summary>

        /// <param name="sourceEndpointName">the name of the source endpoint</param>

        public static void SetSourceEndpointAndUser(string _sourceEndpointName, string _userName)

        {

            //string userName = HttpContext.Current.User.Identity.Name.ToString(); //returns the current user and domian – eg. egdk\tomph

            var addressHeader = AddressHeader.CreateAddressHeader("SourceEndpointUser", "http://schemas.microsoft.com/dynamics/2008/01/services&quot;, _userName);

            var addressBuilder = new EndpointAddressBuilder(

            new EndpointAddress(new Uri("urn:" + _sourceEndpointName), addressHeader));

            var endpointAddress = addressBuilder.ToEndpointAddress();

            OperationContext.Current.OutgoingMessageHeaders.From = endpointAddress;

        }

 

 

    }

            //call the webservice’s find method

            try

            {

                //SOAP header info

                using (new OperationContextScope(client.InnerChannel))

                {

                    //CREATE HEADER TO SET SOURCE ENDPOINT

       //this assumes that a endpoint (inside AX) with the selected (dataareaid in this case) name has
       been created for all companies

                    SoapHeader.SetDestinationEndpoint(ddlDataAraeId.SelectedValue);

                    //CREATE HEADER TO SET TARGET COMPANY

       //this assumes that a local endpoint (inside ax) with the name [ddlDataAraeId.SelectedValue] has
       been configured and is associated with a company that exists in dynamics ax

                    SoapHeader.SetSourceEndpointAndUser("Default", Helper.GetCurrentUser());

                    //submit the request and retrieve the respons

                }   

                response = client.find(qc);

                enumerEmplTable = response.EmplTable.GetEnumerator();

            }

 

 

Now in AX 2012 AIF you can simply specify the CallContext – which I may say is a h… of a lot easier…

 

            //create the AX call context in order to being able to define Ax company and submitting user

            CallContext axContext = new CallContext();

            axContext.Company = ddlDataAraeId.SelectedValue;

            axContext.LogonAsUser = Helper.GetCurrentUser();

 

            try

            {

                #if DEBUG

                CreateXmlMessageTextFileFromCreate(axdEGF_HRMWebRecruitment, Guid.NewGuid().ToString());

                #endif

                keys = client.create(axContext, axdEGF_HRMWebRecruitment);

                txtResult.Text = "SUCCESS: " + keys[0].KeyData[0].Field + " = " + keys[0].KeyData[0].Value;

            }   

Thank You MS for making my life just a little bit easier:-)

You may find it relevant to look at the this peace of documention on technet.

Posted in AIF | Tagged: , , , , | Comments Off on AX2012 AIF – CallContext

AIF–Invalid data container type

Posted by Torben M. Philippsen on October 8, 2014

Recently Ive been developping a AIF document service. After days of testing and customizing my requests suddenly started to fail and I would receive an AIF error in the Exception log in AX stating that “Invalid data container type”. Tome, this came out of the blue, since everything up to taht point worked just fine. I can’t explain the error, but the sollution to me was to:

  • update the document service using the AIF wizard with the switch to update AxBC classes (not regenerate)
  • Recompile the entire private service project.
  • Perform incremental CIL
  • Unpublish an publish the service.

I’m not sure whart part actually solved the problem – but the main goal was to get everything working again – and it did.

Posted in AIF | Tagged: , , | Comments Off on AIF–Invalid data container type

AIF–post processing of incomming record

Posted by Torben M. Philippsen on October 7, 2014

In a recent task for a customer I created a document service based on customized datamodel (build by myself). The datamodel consisted of two related tables. In these two tables I would collect incomming records from whicj I had to create CirParty entities, HRM applicants and HRM applications. This means that my datamodel would only serve the purpose of collecting the data. Then I would create some business logic to process the incoming logic. In Dynamics AX 2012 I searched for the right hookin and found that the UpdateNow method on the Axd class to fully fulfill my requirements.

I ended up with this peace of code…

ClassDeclaration:

class Axdegf_HRMWebRecruitment extends AxdBase

{

    #define.WebRecruitment_DataSourceName(‘WebRecruitment’)

    #define.Attachments_DataSourceName(‘Attachments’)

   

 

      //EG/2014.10.07/TOMPH, 00328407_3 HRMWebRecruitment—>

    AxEGF_HRMWebRecruitment            axbc_WebRecruitment;

    AxEGF_HRMWebRecruitmentAttachments axbc_Attachments;

    //EG/2014.10.07/TOMPH, 00328407_3 HRMWebRecruitment <–

}

PrepareForSaveExtended method (where the reference for the current record is created – marked in red):

public boolean prepareForSaveExtended(

    AxdStack                    _axBcStack,

    str                         _dataSourceName,

    AxdRecordProcessingContext  _recordProcessingContext,

    AxInternalBase              _childRecord)

{

    //TODO: Add code here to ensure that required fields specified in the initMandatoryFieldsMap method are sent in by the service caller.

    switch (_dataSourceName)

    {

        // ———————————————————————-

        // Process WebRecruitment records

        // ———————————————————————-

        case #WebRecruitment_DataSourceName:

 

            axbc_WebRecruitment = _axBcStack.top();

            switch (_recordProcessingContext)

            {

                //EG/2014.10.01/TOMPH, 00328407_3 HRMWebRecruitment –>

                case AxdRecordProcessingContext::BeforeChildRecordProcessed:

                    switch(_childRecord.dataSourceName())

                    {

                        case #Attachments_DataSourceName:

                            if(!axbc_WebRecruitment.isProcessed())

                            {

                                return true;

                            }

                            break;

                    }

                    return false;

                    //EG/2014.10.01/TOMPH, 00328407_3 HRMWebRecruitment <–

                // Ensure WebRecruitment record is saved

                case AxdRecordProcessingContext::AfterAllChildRecordsProcessed:

                    if (!axbc_WebRecruitment.isProcessed())

                    {

                        return true;

                    }

                    return false;

            }

—-code left out—

UpdateNow method:

//EG/2014.09.21/TOMPH, 00328407_3 HRMWebRecruitment

//Meethod used to run busines logig before AIF ends its lifecycle

//Here we process the incoming record

public void updateNow()

{

    EGF_HRMWebRecruitment           egf_HRMWebRecruitment;   

    ;

    super();

    //get the current incoming record

    //egf_HRMWebRecruitment = axegf_HRMWebRecruitment.eGF_HRMWebRecruitment();

    egf_HRMWebRecruitment = axbc_WebRecruitment.eGF_HRMWebRecruitment();

    //having he incoming record – process it

    egf_HRMWebRecruitment.ProcessIncomingRecord();

}

Posted in Books and reading stuff | Comments Off on AIF–post processing of incomming record

AIF–handling related incoming records

Posted by Torben M. Philippsen on October 7, 2014

In a recent task for a customer I had to being able to handle HRM applicants and applications from a corparate website. In basics the customer wanted to have a webform where the applicant could enter his/her personal data and attach files like eg. a CV, the application or any other relevant stuff.

To handle that scenario I created two tables in AX – one to handle the applicant and application related stuff and another to handle the file attachments. One application should be able to have many attachments. Therefore the first attachment should relate to one application and the second attachment to the same application and so on…

Having the datamodel set, I created a document service.

I created a simple webform in c# that consumed the AIF service and I was soon able to send data into AX. Then it occured to me, that I wasn’t able to create the relation between attachments and applications from the webclient because I needed the RecID og the Application in order to put in onto the attachments.

Inside ax i found the PrepareForSaveExtended method of my AXD class to be useful. Here I could make sure that applications would be processed before attachments. And once the applications were processed I could fetch the RecId and put in on each attachment.

I ended up with this:

Class declaration:

 

class Axdegf_HRMWebRecruitment extends AxdBase

{

    #define.WebRecruitment_DataSourceName(‘WebRecruitment’)

    #define.Attachments_DataSourceName(‘Attachments’)

   

    //EG/2014.10.07/TOMPH, 00328407_3 HRMWebRecruitment –>   

   AxEGF_HRMWebRecruitment            axbc_WebRecruitment;

    AxEGF_HRMWebRecruitmentAttachments axbc_Attachments;

 //EG/2014.10.07/TOMPH, 00328407_3 HRMWebRecruitment <–

}

PrepareForSaveExtended:

public boolean prepareForSaveExtended(

    AxdStack                    _axBcStack,

    str                         _dataSourceName,

    AxdRecordProcessingContext  _recordProcessingContext,

    AxInternalBase              _childRecord)

{

    //TODO: Add code here to ensure that required fields specified in the initMandatoryFieldsMap method are sent in by the service caller.

    switch (_dataSourceName)

    {

        // ———————————————————————-

        // Process WebRecruitment records

        // ———————————————————————-

        case #WebRecruitment_DataSourceName:

 

            axbc_WebRecruitment = _axBcStack.top();

            switch (_recordProcessingContext)

            {

                //EG/2014.10.01/TOMPH, 00328407_3 HRMWebRecruitment –>

               case AxdRecordProcessingContext::BeforeChildRecordProcessed:

                    switch(_childRecord.dataSourceName())

                    {

                        case #Attachments_DataSourceName:

                            if(!axbc_WebRecruitment.isProcessed())

                            {

                                return true;

                            }

                            break;

                   }

                    return false;

                    //EG/2014.10.01/TOMPH, 00328407_3 HRMWebRecruitment <–

                // Ensure WebRecruitment record is saved

                case AxdRecordProcessingContext::AfterAllChildRecordsProcessed:

                    if (!axbc_WebRecruitment.isProcessed())

                    {

                        return true;

                    }

                    return false;

            }

            return false;

        // ———————————————————————-

        // Process Attachments records

        // ———————————————————————-

        case #Attachments_DataSourceName:

 

            axbc_Attachments = _axBcStack.top();

            //axbc_Attachments.parmDescription("dudelidut");

            switch (_recordProcessingContext)

            {

                //EG/2014.10.01/TOMPH, 00328407_3 HRMWebRecruitment –>

                // Propagate parent’s key

                case AxdRecordProcessingContext::BeforeAnyChildRecordsProcessed:

                        axbc_WebRecruitment = axbc_Attachments.parentAxBC();

                        axbc_Attachments.parmHRMWebRecruitmentRecIdRef(axbc_WebRecruitment.currentRecord().RecId);

                        return false;

                //EG/2014.10.01/TOMPH, 00328407_3 HRMWebRecruitment <–

 

                // Ensure Attachments record is saved

                case AxdRecordProcessingContext::AfterAllChildRecordsProcessed:

                    if (!axbc_Attachments.isProcessed())

                    {

 

                        return true;

                    }

 

                    return false;

            }

            return false;

 

        // ———————————————————————-

        // Unsupported data sources

        // ———————————————————————-

        default:

            error(strfmt("@SYS88979",classId2Name(classidget(_axBcStack.top()))));

            return false;

    }

    return false;

}

 

Posted in AIF | Tagged: , , , | 2 Comments »

 
%d bloggers like this: