Search code examples
c#asp.net-coreminio

How do I get a list of strings from an observable.Subscribe function


I got the C# .net core ListObjectsAsync example working for Minio: , but how do I capture the results into a List instead of publishing to a console?

using System;
using Minio.DataModel;

namespace Minio.Examples.Cases
{

    class ListObjects
    {
        // List objects matching optional prefix in a specified bucket.
        public static void Run(Minio.MinioClient minio,
                                     string bucketName = "my-bucket-name",
                                     string prefix = null,
                                     bool recursive = false)
        {
            try
            {
                Console.Out.WriteLine("Running example for API: ListObjectsAsync");
                IObservable<Item> observable = minio.ListObjectsAsync(bucketName, prefix, recursive);

                IDisposable subscription = observable.Subscribe(
                    item => Console.WriteLine("Object: {0}", item.Key),
                    ex => Console.WriteLine("OnError: {0}", ex),
                    () => Console.WriteLine("Listed all objects in bucket " + bucketName + "\n"));

                // subscription.Dispose();
            }
            catch (Exception e)
            {
                Console.WriteLine("[Bucket]  Exception: {0}", e);
            }
        }
    }
}

I tried modifying the Subscribe function so that it would add the names to a list instead of print them to a console:

IDisposable subscription = observable.Subscribe(
item => names.Add(item.Key), 
ex => Console.WriteLine("OnError: {0}", ex), 
() => Console.WriteLine("Listed all objects in bucket "  
+ bucketName + "\n" + "count:" + names.Count +"\n"));

it prints 7, but if I print names.Count outside of Subscribe it prints 0;

This is What I want to do:

List<Item> myList = new List<Item>(observable.ToList().Subscribe(x => Console.WriteLine("do something"),
  ex => Console.WriteLine("OnError: {0}", ex),
  () => Console.WriteLine("Done" + "\n"));

But observable returns an IDisposable so that's not possible. How do I go from an observable to a List ? or how do I collect the data so I can pass it into an asp.net core view?

This works - using Wait just like svek suggested. Didn't realize the syntax until I read this: waiting IObservable to get all elements error

List<Item> names = new List<Item>();

IDisposable subscription = observable.ToList().Subscribe(
  x => names.AddRange(x),
  ex => Console.WriteLine("OnError: {0}", ex), 
  () => Console.WriteLine("Done" + "\n") );
observable.Wait();
Console.WriteLine("out of subscribe count:" + names.Count + "\n");
subscription.Dispose();
return names;

Solution

  • Although I find your code a bit harder to read than what I am accustomed to. In regards to your topic, How do I get a list of strings from an observable.Subscribe function the code itself is valid

    The reason why you are getting 0 when you try and print names.Count is likely because you are not waiting for the asynchronous operation of the Observable <T>.Subscribe() method to complete.

    To help you understand the syntax of your code, see this example:

    IObservable<int> source = Observable.Range(1, 10);
    IDisposable subscription = source.Subscribe(
        x => Console.WriteLine("OnNext: {0}", x),
        ex => Console.WriteLine("OnError: {0}", ex.Message),
        () => Console.WriteLine("OnCompleted"));
    
    Console.WriteLine("Press ENTER to unsubscribe...");
    Console.ReadLine();
    subscription.Dispose();
    

    Which can be rewritten to use the Observer type like so:

    IObservable<int> source = Observable.Range(1, 10);
    IObserver<int> observer = Observer.Create<int>(
        x => Console.WriteLine("OnNext: {0}", x),
        ex => Console.WriteLine("OnError: {0}", ex.Message),
        () => Console.WriteLine("OnCompleted"));        
    IDisposable subscription = source.Subscribe(observer);
    
    Console.WriteLine("Press ENTER to unsubscribe...");
    Console.ReadLine();
    subscription.Dispose();
    

    The above refactoring will help clarify the solution far more. As you can see the parameters that were used in the first code block is actually calling the Observer.Create<T>() method.

    Observer.Create Method looks like this:

    public static IObserver<T> Create<T>(
        Action<T> onNext,
        Action<Exception> onError,
        Action onCompleted
    )
    

    That is why this will properly return the count when you use:

    // OnCompleted
    () => Console.WriteLine("Listed all objects in bucket " 
              + bucketName + "\n" + "count:" + names.Count +"\n"));
    

    because it is properly waiting for the sequence to complete before doing the action of displaying the message to the console.

    To solve your problem, you simply need to wait for your asynchronous Subscribe operation to complete first before trying to enumerate it.


    Note:
    The code examples from above were taken from MSDN and can be found here.