Get Reference Assemblies Dashboard (V2)

Previous version:

I redid this dashboard to clean it up and add the ability to download multiple assemblies in one zip file.

For this dashboard to work, requires one layer, one function library, and one bpm.

The bpm is on Ice.Lib.FileTransfer.DownloadFile.
It is necessary because I do a little Tom Foolery on the file-transfer-kinetic widget. (use the serverpath to pass some data that we use to make this work)

Everything is in here → GetReferenceAssemblies.zip (18.8 KB)

Pics:

The relevant magic in the app..


Code:

BPM: Ice.Lib.FileTransfer.DownloadFile → Pre → GetReferenceAssemblies

 /*
  * ==========================================================================================
  * AUTHOR:    Kevin Lincecum
  * COPYRIGHT: Kevin Lincecum 2026
  * LICENSE:   MIT
  * ==========================================================================================
  * Directive Type:  Method -> Pre Processing
  * Directive BO:    Ice.Lib.FileTransfer.DownloadFile
  * Directive Name:  GetReferenceAssemblies
  * Directive Desc:  Intercepts the call to download a file if a custom url is used, and routes
  *                  it appropriately.
  * Directive Group: GetReferenceAssemblies
  * ==========================================================================================
  * 
  * Hi Mom!
  *
  * CHANGELOG:
  * 04/02/2026 | klincecum | Kevin Lincecum | Initial Implementation
  *
  * ==========================================================================================
  */
  
  //using Newtonsoft.Json;
  
  //We are only interested in requests that the server path starts with "GRA://"
  if(serverPath.StartsWith("GRA://"))
  {
      try
      {
          string json = serverPath.Replace("GRA://", String.Empty); //Pull off our trigger phrase
          json = $"{{\"Assemblies\":{json}}}"; //Massage it into a DataSet
      
          var ds = JsonConvert.DeserializeObject<DataSet>(json);
      
          //Call the plugin function
          var response = InvokeFunction("References", "DownloadAssemblies", ds);
          
          //So y'all can see what was returned better
          var responseUnwrapped = new
          {
              Success   = (bool)  response[0],
              Message   = (string)response[1],
              ZipBase64 = (string)response[2]
          };
      
      
          //If we succeeded, we are done. Convert the Base64 Data from the function to a byte array and return it. 
          if( responseUnwrapped.Success ) //Success
          {
              if(!String.IsNullOrEmpty(responseUnwrapped.ZipBase64))
              {
                result = Convert.FromBase64String(responseUnwrapped.ZipBase64);
                
                MarkCallCompleted();
              }
              
              return; //Done. Stop Processing.
          }
          
          //Failure
          
          //Create an exception
          var exception = new BLException( "Unable to download file." );
          
          if(!String.IsNullOrEmpty(responseUnwrapped.Message))
          {
              //If we have errors, pass them along with the exception.
              exception.Data.Add("Message", responseUnwrapped.Message);
          }
          
          //Bam!
          throw exception;
              
      }    
      catch (Exception ex)
      {
          //We will just rethrow anything. Catches our exceptions above, as well as unknowns.
          throw new BLException(ex.ToString());
      }
  }

Functions:


Function: GetAvailableReferences

var FunctionID = this.ToString().Split(".").Last().Replace("Impl", "");
var Messages = new List<string>(); Messages.Add($"Begin {FunctionID} ->{Environment.NewLine}");
Action<string> AddMessage = (s) => {if(!(String.IsNullOrEmpty(s) || String.IsNullOrWhiteSpace(s))) {Messages.Add( $"  {FunctionID}:{Environment.NewLine}" + string.Join(Environment.NewLine, s.Split(Environment.NewLine).Select(x => $"    {x}")) + Environment.NewLine);}};
Func<object, string> Pretty = (o) => Newtonsoft.Json.JsonConvert.SerializeObject(o, Newtonsoft.Json.Formatting.Indented);  
    
try
{
    Assemblies = new DataSet();
    var asmTable = Assemblies.Tables.Add("Assemblies");
    asmTable.Columns.Add("AssemblyName", typeof(string));
    asmTable.Columns.Add("FileName", typeof(string));
    asmTable.Columns.Add("Version", typeof(string));


    //Epicor.System missing, but available, wtf lol..
    asmTable.Rows.Add("Epicor.System", "Epicor.System.dll", "");
    
    CallService<Ice.Contracts.BpMethodSvcContract>(bpMethod =>
    {
        var bpMethodList = bpMethod.GetAvailableReferences("Assemblies");
        
        //Message = Pretty(bpMethodList);

        bpMethodList.ForEach(x =>
        {
            asmTable.Rows.Add(x.Name, x.FileName, x.Version);
        });        
    });
   
    Success = true;    
}
catch (Exception ex)
{
    AddMessage(ex.Message);
    AddMessage(Pretty(ex));
}
finally
{
    Messages.Add($"<- End {FunctionID}");
    Message = String.Join(Environment.NewLine, Messages);
}

Function: DownloadAssemblies

var FunctionID = this.ToString().Split(".").Last().Replace("Impl", "");
var Messages = new List<string>(); Messages.Add($"Begin {FunctionID} ->{Environment.NewLine}");
Action<string> AddMessage = (s) => {if(!(String.IsNullOrEmpty(s) || String.IsNullOrWhiteSpace(s))) {Messages.Add( $"  {FunctionID}:{Environment.NewLine}" + string.Join(Environment.NewLine, s.Split(Environment.NewLine).Select(x => $"    {x}")) + Environment.NewLine);}};
Func<object, string> Pretty = (o) => Newtonsoft.Json.JsonConvert.SerializeObject(o, Newtonsoft.Json.Formatting.Indented);  
    
try
{
 
  Func<Dictionary<string, byte[]>, byte[]> ZipDictionary = (dicFiles) =>
  {
      byte[] retBytes = null;
      
      using (MemoryStream zipMS = new MemoryStream())
      {
      
          using (ZipArchive zipArchive = new ZipArchive(zipMS, ZipArchiveMode.Create, true))
          {
              dicFiles.Keys.ToList().ForEach(df =>
              {
                  var zipArchiveEntry = zipArchive.CreateEntry(df, CompressionLevel.Fastest);
              
                  using (var zipStream = zipArchiveEntry.Open())
                  {
                      zipStream.Write(dicFiles[df], 0, dicFiles[df].Length);
                  }
              });
          }
          
          zipMS.Flush();
          retBytes = zipMS.ToArray();
      };
      
      return retBytes;    
  };
    
    var fileDictionary = new Dictionary<string, byte[]>();
    CallService<Ice.Contracts.EcfToolsSvcContract>(ecf =>
    {
        Assemblies.Tables["Assemblies"].AsEnumerable().ToList().ForEach(x =>
        {
            var data = ecf.GetAssemblyBytes(x.Field<string>("AssemblyName"));
            fileDictionary.Add(x.Field<string>("FileName"), data);
        });
    });

    if(fileDictionary.Count == 0 ) return; //exit early
    
    ZipBase64 = Convert.ToBase64String( ZipDictionary(fileDictionary) );

    Success = true;    
}
catch (Exception ex)
{
    AddMessage(ex.Message);
    AddMessage(Pretty(ex));
}
finally
{
    Messages.Add($"<- End {FunctionID}");
    Message = String.Join(Environment.NewLine, Messages);
}
9 Likes

That is pretty slick! Thank you for doing this and sharing it.

1 Like

:fire:

Why is there no GOAT emoji response?
Thanks again.

I will accept a jackass. :donkey:

1 Like