Wednesday, August 28, 2019

update record from c# Linq to Ax with impersonation runAs as a Active Directory Windows User

when you update a record you might want to store who made the last change

image

when you are within ax this is done automatically as long as you have set on your table the ModifyBy property to yes.

if you use Linq to Ax using .Net interop to X++ it becomes a bit more complex.

on the example you find here https://docs.microsoft.com/en-us/dynamicsax-2012/developer/code-example-linq-to-ax-from-csharp
they connect using this code

         // Logon to Dynamics AX.
         U23.Session axSession = new U23.Session();
         axSession.Logon(null, null, null, null);

which will cause that it will always mark the modifiedBy user to the AOT user.

there is a different way to open the session in C# which is axSession.LogonAs
so you can do:

U23.Session axSession = new U23.Session();
System.Net.NetworkCredential nc = new System.Net.NetworkCredential("ProxyUserID", "password");
var strUserName = nc.UserName;
axSession.LogonAs(strUserName.Trim(), "yourDomain.com", nc, "fch", "en-GB", null, null);

which works perfectly fine if you have the Active Directory username and password.

If you authenticate your client web site using forms authentication and the user enters his username and password you are fine, you have them in clear (just don’t tell it to a security expert…). Otherwise if you use for example windows authentication you need to start messing up with impersonation to pass the network credentials to you the API.

here an introduction: How To: Use Impersonation and Delegation in ASP.NET 2.0

it is quite a complex subject, so I decided to use a different approach and leverage the runAs method.

here the class I created:

//This class is useful when doing a record update from Linq to Ax. otherwsie it would take the AOS user as the user making the change.
class MYD_UpdateImpersonated
{
    Common common;
    UserId userId;
}
private void new()
{
}
public static MYD_UpdateImpersonated construct()
{
    return new MYD_UpdateImpersonated();
}
protected Common parmCommon(Common _common = common)
{
    common = _common;

    return common;
}
protected UserId parmUserId(UserId _userId = userId)
{
    userId = _userId;

    return userId;
}
public RecId update()
{
    container args;
    container result;
    container recordBuf;
    TableId tableId;
    RecId recId;

    RunAsPermission permission;

    recordBuf = buf2Con(common);
    args = [common.TableId, recordBuf, common.RecId];

    permission = new RunAsPermission(userId);
    permission.assert();

    // Need use the runas method; we want to run as the user to update records
    // BP deviation documented
    result = runAs(userId, classNum(MYD_UpdateImpersonated), staticMethodStr(MYD_UpdateImpersonated, updateAsUser), args);
    CodeAccessPermission::revertAssert();

    [recId] = result;
    return recId;
}
private static container updateAsUser(container _args)
{
    DictTable dictTable, dictTableBuf;
    Common common, commonBuf;
    TableId tableId;
    container recordBuf;
    RecId recId;

    [tableId, recordBuf, recId] = _args;

    dictTable = new DictTable(tableId);
    dictTableBuf = new DictTable(tableId);

    common = dictTable.makeRecord();
    commonBuf = dictTableBuf.makeRecord();

    con2Buf(recordBuf, commonBuf);

    ttsBegin;
    common.selectForUpdate(true);
    select common where common.RecId == recId;
    buf2Buf(commonBuf, common);
    common.update();
    ttsCommit;

    return [common.RecId];
}
public static server MYD_UpdateImpersonated newFromCommon(Common _common, UserId _userId)
{
    MYD_UpdateImpersonated updateImpersonated;

    updateImpersonated = MYD_UpdateImpersonated::construct();
    updateImpersonated.parmCommon(_common);
    updateImpersonated.parmUserId(_userId);

    return updateImpersonated;
}

now in c# you can create an extension class like this:

using System;
using System.Collections.Generic;
using Microsoft.Dynamics.AX.Framework.Linq.Data;
using U23 = Microsoft.Dynamics.AX.ManagedInterop;
using U22 = Microsoft.Dynamics.AX.Framework.Linq.Data;

namespace MydDynamicsIntegration.DynamicsCommon
{
    public static class GenericRecord
    {
        public static long UpdateRecord<T>(this T axTable, string windowsUser) where T : Common
        {
            var userInfo = AifPortUser::getAxaptaUser(windowsUser);// in the format  DOMAIN\USERNAME
            //if (!userInfo.isActiveDirecroyUser()) return 0;//if you prefer rather that an error is thrown from MYD_UpdateImpersonated class if the user is invalid
            var updateImpersonated = MYD_UpdateImpersonated.newFromCommon(axTable, userInfo.getUserId());
            return updateImpersonated.update();
        }
}

now we create our repository class like this:

public void UpdateDiscussion(Discussion discussion, string windowsUser)
{
    //see: https://enricoariel.blogspot.com/2019/08/proxy-classes-for-net-interop-to-x.html
    //on how to open a session inside a using statement
    using (var axSession = new AxSessionManager())
    {
        axSession.OpenConnection();

        var workerRecId = HcmWorker.findByPersonnelNumber(discussion.Worker).RecId;
        var hcmDiscussion = HcmDiscussion.findByDiscussionWorker(discussion.DiscussionId, workerRecId);

        hcmDiscussion.status = (HcmDiscussionStatus) discussion.Status.Value;
        hcmDiscussion.UpdateRecord(windowsUser);  
    }
}

finally here my discussion dto Poco classes

namespace MydDynamicsIntegration.Models
{
    public class Discussion
    {
        public string DiscussionId { get; set; }
        public AxEnumDefinition Status { get; set; }
        public string Worker { get; set; }
    }
}

namespace MydDynamicsIntegration.Models
{
    public class AxEnumDefinition
    {
        public int Value { get; set; }
        public string Name { get; set; }
        public string Label { get; set; }
    }
}

No comments:

Post a Comment