Erchi
Erchi

Reputation: 139

Asynchronous call to webservice in MVC 4 web application

I am building my first real MVC4 application and I have run into following issue.

I have a model for "User" class. Data for it are obtained through asynchronous call to webservice:

public sealed class AdminDMSEntities
{
    public List<User> UserList { get; private set; }

    public AdminDMSEntities()
    {
        this.UserList = new List<User>(0);
        ServiceClient client = new ServiceClient();
        client.GetUsersCompleted += (s, e) => 
        {
            if (e.Result == null)
                throw new ArgumentNullException("No users were retrieved");

            UserList = new List<User>(0);
            e.Result.ForEach(w => this.UserList.Add(new User(w.Guid, w.TrusteeType, w.Username, w.Email, w.LastLogin, w.PasswordChanged, w.IsUsingTempPassword)));               
        };

        client.GetUsersAsync();
    }        
}

I intend to use this class as I would use class derived from DbContext (if I could use Entity Framework which I cant). So far I have only users in the class.

I am using tis class in UsersController like this:

public class UsersController : Controller
{
    private AdminDMSEntities adminEntities = new AdminDMSEntities();
    //
    // GET: /User/

    public ActionResult Index()
    {
        return View(adminEntities.UserList);
    }
}

The problem is that I will end up with InvalidOperationException, because controller is not waiting for async call completion and passes UserList to the view before it is properly filled with users.

I can have the call synchronous for the time being, but it is very likely I will be forced to use asynchronous calls later, so I would like to know how to ensure, that controller will wait for async call to be completed before UserList is passed to view...

Thanks in advance

EDIT: I have tried the approach with AsyncController as listed below, currently I have added this to AdminDMS entities class:

public static async Task<AdminDMSEntities> AdminDMSEntitiesAsync()
    {
        AdminDMSEntities result = null;
        Task<AdminDMSEntities> getUsersAsyncTask = Task.Factory.StartNew(() => new AdminDMSEntities());
        await getUsersAsyncTask;
        return result;
    }

and this is the change to the controller:

public class UsersController : AsyncController
{
    private AdminDMSEntities adminEntities = null;
    //
    // GET: /User/

    public async Task<ActionResult> Index()
    {
        if (adminEntities == null)
        {
            adminEntities = await AdminDMSEntities.AdminDMSEntitiesAsync();
        }
        return View(adminEntities.UserList);
    }
}

The result is that adminEntities are containing an instance of the class, but there are no users in the list (there should be 11).

EDIT2: Since i was told that creating new task is not the right thing to do, I went with the first suggested approach removin AdminDMSEntities class from the code. My thanks to Darin for helping me out :)

Upvotes: 1

Views: 4015

Answers (1)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038730

You could use an asynchronous controller. The idea is to have your controller derive from the AsyncController class instead of the Controller class. This class provides methods that allow you to perform asynchronous operations.

For example:

public class MyController: AsyncController
{
    public void IndexAsync()
    {
        AsyncManager.OutstandingOperations.Increment();
        var client = new SomeClient();
        client.GetUsersCompleted += (s, e) => 
        {
            UserList = new List<User>();
            AsyncManager.Parameters["users"] = e.Result.Select(
                w => new User(
                    w.Guid, 
                    w.TrusteeType, 
                    w.Username, 
                    w.Email, 
                    w.LastLogin, 
                    w.PasswordChanged, 
                    w.IsUsingTempPassword
                )
            )
            .ToList();
            AsyncManager.OutstandingOperations.Decrement();            
        };
        client.GetUsersAsync();
    }

    public ActionResult IndexCompleted(IEnumerable<User> users)
    {
        return View(users);
    }
}

and if you are using .NET 4.5 you could even take advantage of the new async keyword simplifying the asynchronous code even further. This is possible if you refactor your data access layer to the new pattern (i.e. return Tasks):

public class MyController: AsyncController
{
    public async Task<ActionResult> Index()
    {
        var client = new SomeClient();
        var users = await client.GetUsersAsync().Select(
            w => new User(
                w.Guid, 
                w.TrusteeType, 
                w.Username, 
                w.Email, 
                w.LastLogin, 
                w.PasswordChanged, 
                w.IsUsingTempPassword
            )
        )
        .ToList();
        return View(users);
    }
}

Upvotes: 1

Related Questions