Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Azure, PHP, OpenXML, VSTS, ...

About the author

Maarten Balliauw is currently employed as .NET Technical Consultant at RealDolmen. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
View Maarten Balliauw's MVP profile

Search

Latest Twitter

    Follow me on Twitter...

    My projects

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    © Copyright Maarten Balliauw 2010

    Running on Windows Azure - ChronoRace - Autoscaling

    image At RealDolmen, we had the luck of doing the first (known) project on Windows Azure in Belgium. Together with Microsoft, we had the opportunity to make the ChronoRace website robust enough to withstand large sports events like the 20km through Brussels.

    ChronoRace is a Belgian company based in Malmédy, specialised in electronic timing for large sports events (around 340 per year) troughout Europe in different disciplines like jogging, cycling, sailing, … Each participant is registered through the website, can consult their results and can view a high-definition video of their arrival at the finish line. Of course, these results and videos are also used to brag to co-workers and family members, resulting in about 10 result and video views per participant. Imagine 20.000 or more participants on large events… No wonder their current 1 webserver – 1 database server setup could not handle all load.

    Extra investments in hardware, WAN connection upgrades and video streaming were considered, however found too expensive to have these running for 365 days a year while on average this extra capacity would only be needed for 14 days a year. Ideal for cloud computing! Especially with an expected 30.000 participants for the 20km through Brussels... (which would mean 3 TB of data transfers for the videos on 1 day!!!)

    Microsoft selected RealDolmen as a partner for this project, mainly because of the knowledge we built over the past year, since the first Azure CTP. Together with ChronoRace, we gradually moved the existing SQL Server databse to SQL Azure. After that, we started moving the video streaming to blob storage, implemented extra caching and automatic scaling.

    You probably have seen the following slides in various presentations on cloud computing:

    Capacity cloud computing

    All marketing gibberish, right? Nope! We actually managed to get very close to this model using our custom autoscaling mechanism. Here are some figures we collected during the peak of the 20km through Brussels:

    Windows Azure Auto Scaling

    More information on the technical challenges we encountered can be found in my slide deck I presented at KAHOSL last week:

    If you want more information on scalability and automatic scaling, feel free to come to the Belgian Community Day 2010 where I will be presenting a session on this topic.

    Oh and for the record: I’m not planning on writing marketing posts. I just was so impressed by our actual autoscaling graph that I had to blog this :-)


    Introducing RealDolmenBlogs.com

    RealDolmenBlogs.com Here’s something I would like to share with you. A few months ago, our company (RealDolmen) started a new website, RealDolmenBlogs.com. This site syndicates content from employee blogs, people with lots of experience in their range of topics. These guys have lots of knowledge to share, but sometimes their blog does not have a lot of attention from, well, you. Since we would really love to share employee knowledge, RealDolmenBlogs.com was born.

    The following topics are covered:

    • .NET
    • Application Lifecycle Management
    • Architecture
    • ASP.NET
    • Biztalk
    • PHP
    • Sharepoint
    • Silverlight
    • Visual Studio

    Make sure to subscribe to the syndicated RSS feed and have quality content delivered to your RSS reader.

    The technical side

    Since I do not like to do blog posts on topic that do not have a technical touch, considered that the first few lines of text of this post are pure marketing in a sense, here’s the technical bit.

    RealDolmenBlogs.com is built on Windows Azure and SQL Azure. As a company we believe there is value in cloud computing, in this case we chose for cloud computing due to the fact that the setup costs for the website were very small (pay-per-use) and that we can easily scale-up the website if needed.

    The software behind the site is a customized version of BlogEngine.NET. It has been extended with a syndication feature, pulling content from employee blogs with a little help of the Argotic syndication framework. Running BlogEngine.NET on Windows Azure is not that hard, especially when you are using SQL Azure as well: the only thing to modify is the connection string to your database and you are done. Well… that is if you don’t care about images and attachments. We had to do some modifications to how BlogEngine.NET handles file uploads and made sure everything is now stored safe and sound in Windows Azure blob storage.

    That being said: enjoy the content that my colleagues are sharing, posts are definitely worth a read!


    Using Windows Azure Drive (aka X-Drive)

    Windows Azure X Drive With today’s release of the Windows Azure Tools and SDK version 1.1, also the Windows Azure Drive feature has been released. Announced at last year’s PDC as X-Drive, which has nothing to do with a well-known German car manufacturer, this new feature enables a Windows Azure application to use existing NTFS APIs to access a durable drive. This allows the Windows Azure application to mount a page blob as a drive letter, such as X:, and enables easily migration of existing NTFS applications to the cloud.

    This blog post will describe the necessary steps to create and/or mount a virtual hard disk on a Windows Azure role instance.

    kick it on DotNetKicks.com

    Using Windows Azure Drive

    In a new or existing cloud service, make sure you have a LocalStorage definition in ServiceDefinition.csdef. This local storage, defined with the name InstanceDriveCache below, will be used by the Windows Azure Drive API to cache virtual hard disks on the virtual machine that is running, enabling faster access times. Here’s the ServiceDefinition.csdef for my project:

    <?xml version="1.0" encoding="utf-8"?>
    <ServiceDefinition name="MyCloudService" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
      <WorkerRole name="MyWorkerRole" enableNativeCodeExecution="true">
        <LocalResources>
          <LocalStorage name="InstanceDriveCache"
                        cleanOnRoleRecycle="false"
                        sizeInMB="300" />
        </LocalResources>
        <ConfigurationSettings>
          <!-- … -->
       </ConfigurationSettings>
      </WorkerRole>
    </ServiceDefinition>

    Next, in code, fire up a CloudStorageAccount, I’m using development storage settings here:

    CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;

    After that, the Windows Azure Drive environment has to be initialized. Remember the LocalStorage definition we made earlier? This is where it comes into play:

    LocalResource localCache = RoleEnvironment.GetLocalResource("InstanceDriveCache");
    CloudDrive.InitializeCache(localCache.RootPath, localCache.MaximumSizeInMegabytes);

    Just to be sure, let’s create a blob storage container with any desired name, for instance “drives”:

    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    blobClient.GetContainerReference("drives").CreateIfNotExist();

    Ok, now we got that, it’s time to get a reference to a Windows Azure Drive. Here’s how:

    CloudDrive myCloudDrive = storageAccount.CreateCloudDrive(
        blobClient
            .GetContainerReference("drives")
            .GetPageBlobReference("mysupercooldrive.vhd")
            .Uri.ToString()
    );

    Our cloud drive will be stored in a page blob on the “drives” blob container, named “mysupercooldrive.vhd”. Note that when using development settings, the page blob will not be created on development storage. Instead, files will be located at C:\Users\<your.user.name>\AppData\Local\dftmp\wadd\devstoreaccount1.

    Next up: making sure our virtual hard disk exists.  Note that this should only be done once in a virtual disk’s lifetime. Let’s create a giant virtual disk of 64 MB:

    try
    {
        myCloudDrive.Create(64);
    }
    catch (CloudDriveException ex)
    {
        // handle exception here
        // exception is also thrown if all is well but the drive already exists
    }

    Great, our disk is created. Now let’s mount it, i.e. assign a drive letter to it. The drive letter can not be chosen, instead it is returned by the Mount() method. The 25 is the cache size that will be used on the virtual machine instance. The DriveMountOptions can be None, Force and FixFileSystemErrors.

    string driveLetter = myCloudDrive.Mount(25, DriveMountOptions.None);

    Great! Do whatever you like with your disk! For example, create some files:

    for (int i = 0; i < 1000; i++)
    {
        System.IO.File.WriteAllText(driveLetter + "\\" + i.ToString() + ".txt", "Test");
    }

    One thing left when the role instance is being shut down: unmounting the disk and makign sure all contents are on blob storage again:

    myCloudDrive.Unmount();

    Now, just for fun: you can also create a snapshot from a Windows Azure Drive by calling the Snapshot() method on it. A new Uri with the snapshot location will be returned.

    Full code sample

    The code sample described above looks like this when not going trough each line of code separately:

    public override void Run()

        CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;

        LocalResource localCache = RoleEnvironment.GetLocalResource("InstanceDriveCache");
        CloudDrive.InitializeCache(localCache.RootPath, localCache.MaximumSizeInMegabytes);

        // Just checking: make sure the container exists
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
        blobClient.GetContainerReference("drives").CreateIfNotExist();

        // Create cloud drive
        CloudDrive myCloudDrive = storageAccount.CreateCloudDrive(
            blobClient
            .GetContainerReference("drives")
            .GetPageBlobReference("mysupercooldrive.vhd")
            .Uri.ToString()
        );

        try
        {
            myCloudDrive.Create(64);
        }
        catch (CloudDriveException ex)
        {
            // handle exception here
            // exception is also thrown if all is well but the drive already exists
        }

        string driveLetter = myCloudDrive.Mount(25, DriveMountOptions.Force);

        for (int i = 0; i < 1000; i++)
        {
            System.IO.File.WriteAllText(driveLetter + "\\" + i.ToString() + ".txt", "Test");
        }

        myCloudDrive.Unmount();
    }

    Enjoy!

    kick it on DotNetKicks.com


    Categories: Azure | C# | General | Scalability

    Creating an external facing Azure Worker Role endpoint

    Internet facing Azure Worker Role When Windows Azure was first released, only Web Roles were able to have an externally facing endpoint. Since PDC 2009, Worker Roles can now also have an external facing endpoint, allowing for a custom application server to be hosted in a Worker Role. Another option would be to run your own WCF service and have it hosted in a Worker Role. Features like load balancing, multiple instances of the Worker are all available. Let’s see how you can create a simple TCP service that can display the current date and time.

    Here’s what I want to see when I connect to my Azure Worker Role using telnet (“telnet efwr.cloudapp.net 1234”):

    Telnet Azure Worker Role

    Let’s go ahead and build this thing. Example code can be downloaded here: EchoCloud.zip (9.92 kb)

    kick it on DotNetKicks.com

    Configuring the external endpoint

    Fire up your Visual Studio and create a new Cloud Service, named EchoCloud, with one Worker Role (named EchoWorker). After you complete this, you should have a Windows Azure solution containing one Worker Role. Right-click the worker role and select Properties. Browse to the Endpoints tab and add a new endpoint, like so:

    Configuring an external endpoint on a Windows Azure Worker Role

    This new endpoint (named EchoEndpoint) listens on an external TCP port with port number 1234. Note that you can also make this an internal endpoint, which is an endpoint that can only be reached within your Windows Azure solution and not from an external PC. This can be useful if you wan to host a custom application server in your project and make it available for other Web and Worker Roles in your solution.

    Building the worker role

    As you know, a Worker Role (in the WorkerRole.cs file in your newly created solution) consists of 3 methods that can be implemented: OnStart, Run and OnStop. There’s also an event handler RoleEnvironmentChanging available. The method names sort of speak for themselves, but allow me to explain quickly:

    • OnStart() is executed when the Worker Role is starting. Initializations and some checks can be done here.
    • Run() is the method which contains the actual Worker Role logic. The cool stuff goes in here :-)
    • OnStop() can be used to do things that should be done when the Worker Role is stopped.
    • RoleEnvironmentChanging() is the event handler that gets called when the environment changes: configuration changed, extra instances fired, … are possible triggers for this.

    Our stuff will go in the Run() method. We’ll be creating a new TcpListener which will sit and accept connections. Whenever a connection is available, it will be dispatched on a second thread that will be communicating with the client. Let’s see how we can start the TcpListener:

    public class WorkerRole : RoleEntryPoint
    {
        private AutoResetEvent connectionWaitHandle = new AutoResetEvent(false);

        public override void Run()
        {
            TcpListener listener = null;
            try
            {
                listener = new TcpListener(
                    RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["EchoEndpoint"].IPEndpoint);
                listener.ExclusiveAddressUse = false;
                listener.Start();
            }
            catch (SocketException)
            {
                Trace.Write("Echo server could not start.", "Error");
                return;
            }

            while (true)
            {
                IAsyncResult result = listener.BeginAcceptTcpClient(HandleAsyncConnection, listener);
                connectionWaitHandle.WaitOne();
            }
        }
    }

    First thing to notice is that the TcpListener is initialized using the IPEndpoint from the current Worker Role instance:

    listener = new TcpListener(
                    RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["EchoEndpoint"].IPEndpoint);

    We could have started the TcpListener using a static configuration telling it to listen on TCP port 1234, but that would be difficult for the Windows Azure platform. Instead, we start the TcpListener using the current IPEndpoint configuration that we set earlier in this blog post. This allows the application to run on the Windows Azure production environment, as well as on the development environment available from the Windows Azure SDK. Here’s how it would work if we had multiple Worker Roles hosting this application:

    Multiple worker roles running a custom TCP server 

    Second thing we are doing is starting the infinite loop that accepts connections and dispatches the connection to the HandleAsyncConnection method that will sit on another thread. This allows for having multiple connections into one Worker Role. Let’s have a look at the HandleAsyncConnection method:

    private void HandleAsyncConnection(IAsyncResult result)
    {
        // Accept connection
        TcpListener listener = (TcpListener)result.AsyncState;
        TcpClient client = listener.EndAcceptTcpClient(result);
        connectionWaitHandle.Set();

        // Accepted connection
        Guid clientId = Guid.NewGuid();
        Trace.WriteLine("Accepted connection with ID " + clientId.ToString(), "Information");

        // Setup reader/writer
        NetworkStream netStream = client.GetStream();
        StreamReader reader = new StreamReader(netStream);
        StreamWriter writer = new StreamWriter(netStream);
        writer.AutoFlush = true;

        // Show application
        string input = string.Empty;
        while (input != "9")
        {
            // Show menu
            writer.WriteLine("…");

            input = reader.ReadLine();
            writer.WriteLine();

            // Do something
            if (input == "1")
            {
                writer.WriteLine("Current date: " + DateTime.Now.ToShortDateString());
            }
            else if (input == "2")
            {
                writer.WriteLine("Current time: " + DateTime.Now.ToShortTimeString());
            }

            writer.WriteLine();
        }

        // Done!
        client.Close();
    }

    Code speaks for itself, no? One thing that you may find awkward is the connectionWaitHandle.Set();. In the previous code sample, we did connectionWaitHandle.WaitOne();. This means that we are not accepting any new connection until the current one is up and running. connectionWaitHandle.Set(); signals the original thread to start accepting new connections again.

    Running the worker role

    When running the application using the development fabric, you can fire up multiple instances. I fired up 4 Worker Roles that provide the simple TCP service that we just created. This means that my application will be load balanced, and every incoming connection will be distributed over these 4 Worker Role instances. Nifty!

    Here’s a screenshot of my development fabric with two Worker Roles that I crashed intentionally. The service is still available, thanks to the fabric controller dispatching connections only to available Worker Role instances.

    Development fabric with crashed worker roles

    Example code

    Example code can be downloaded here: EchoCloud.zip (9.92 kb)

    Just a quick note: the approach described here can also be used to run a custom WCF host that has other bindings than for example basicHttpBinding.

    kick it on DotNetKicks.com 


    Categories: Azure | C# | General | Scalability | Webfarm

    Microsoft PDC09 keynote highlights

    Finally found some time to write a short blog post on the announcements this morning at PDC 2009.Microsoft PDC keynote highlights Ray Ozzie started the keynote this morning, focusing on Microsoft’s “three-screen” vision for the future. There will be three screens connected to the cloud: TV, (handheld) devices and of course good old PC. This vision is driven by some key players: Windows 7, Internet Explorer, Silverlight and Windows Azure. Make sure to have a look at these four if you want to play in this future.

    Some announcements were made as well:

    Had a great day yesterday, driving trough the city of Los Angeles and looking at various places in town. Conference day one was also very interesting, lots of good sessions. Currently missing a session slot though, waiting for a Channel9 interview on the Windows Azure SDK for PHP. Stay tuned!


    Using the ASP.NET MVC Futures AsyncController

    Last week, I blogged about all stuff that is included in the ASP.NET MVC Futures assembly, which is an assembly available on CodePlex and contains possible future features (tonguetwister!) for the ASP.NET MVC framework. One of the comments asked for more information on the AsyncController that is introduced in the MVC Futures. So here goes!

    kick it on DotNetKicks.com

    Asynchronous pages

    The AsyncController is an experimental class to allow developers to write asynchronous action methods. But… why? And… what? I feel a little confusion there. Let’s first start with something completely different: how does ASP.NET handle requests? ASP.NET has 25 threads(*) by default to service all the incoming requests that it receives from IIS. This means that, if you have a page that requires to work 1 minute before returning a response, only 25 simultaneous users can access your web site. In most situations, this occupied thread will just be waiting on other resources such as databases or webservice, meaning it is actualy waiting without any use while it should be picking up new incoming requests.

    (* actually depending on number of CPU’s and some more stuff, but this is just to state an example…)

    In ASP.NET Webforms, the above limitation can be worked around by using the little-known feature of “asynchronous pages”. That <%@ Page Async="true" ... %> directive you can set in your page is not something that had to do with AJAX: it enables the asynchronous page processing feature of ASP.NET Webforms. More on how to use this in this article from MSDN magazine. Anyway, what basically happens when working with async pages, is that the ASP.NET worker thread fires up a new thread, assigns it the job to handle the long-running page stuff and tells it to yell when it’s done so that the worker thread can return a response to the user. Let me rephrase that in an image:

    Asynchronous page flow 

    I hope you see that this pattern really enables your web server to handle more requests simultaneously without having to tweak standard ASP.NET settings (which may be another performance tuning thing, but we will not be doing that in this post).

    AsyncController

    The ASP.NET MVC Futures assembly contains an AsyncController class, which actually mimics the pattern described above. It’s still experimental and subject to change (or to disappearing), but at the moment you can use it in your application. In general, the web server schedules a worker thread to handle an incoming request. This worker thread will start a new thread and call the action method on there. The worker thread is now immediately available to handle a new incoming request. Sound a bit the same as above, no? Here’s an image of the AsyncController flow:

    AsyncController thread flow

    Now you know the theory, let’s have a look at how to implement the AsyncController pattern…

    Preparing your project…

    Before you can use the AsyncController, some changes to the standard “File > New > MVC Web Application” are required. Since everything I’m talking about is in the MVC Futures assembly, first grab the Microsoft.Web.Mvc.dll at CodePlex. Next, edit Global.asax.cs and change the calls to MapRoute into MapAsyncRoute, like this:

    routes.MapAsyncRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = "" }
    );

    No need to worry: all existing synchronous controllers in your project will keep on working. The MapAsyncRoute automatically falls back to MapRoute when needed.

    Next, fire up a search-and-replace on your Web.config file, replacing

    <add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

    <add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

    with:

    <add verb="*" path="*.mvc" validate="false" type="Microsoft.Web.Mvc.MvcHttpAsyncHandler, Microsoft.Web.Mvc"/>

    <add name="MvcHttpHandler" preCondition="integratedMode" verb="*" path="*.mvc" type="Microsoft.Web.Mvc.MvcHttpAsyncHandler, Microsoft.Web.Mvc"/>

    If you now inherit all your controllers from AsyncController instead of Controller, you are officially ready to begin some asynchronous ASP.NET MVC development.

    Asynchronous patterns

    Ok, that was a lie. We are not ready yet to start asynchronous ASP.NET MVC development. There’s a choice to make: which asynchronous pattern are we going to implement?

    The AsyncController offers 3 distinct patterns to implement asynchronous action methods.. These patterns can be mixed within a single AsyncController, so you actually pick the one that is appropriate for your situation. I’m going trough all patterns in this blog post, starting with…

    The IAsyncResult pattern

    The IAsyncResult pattern is a well-known pattern in the .NET Framework and is well documened on MSDN. To implement this pattern, you should create two methods in your controller:

    public IAsyncResult BeginIndexIAsync(AsyncCallback callback, object state) { }
    public ActionResult EndIndexIAsync(IAsyncResult asyncResult) { }

    Let’s implement these methods. In the BeginIndexIAsync method, we start reading a file on a separate thread:

    public IAsyncResult BeginIndexIAsync(AsyncCallback callback, object state) {
        // Do some lengthy I/O processing... Can be a DB call, file I/O, custom, ...
        FileStream fs = new FileStream(@"C:\Windows\Installing.bmp",
            FileMode.Open, FileAccess.Read, FileShare.None, 1, true);
        byte[] data = new byte[1024]; // buffer

        return fs.BeginRead(data, 0, data.Length, callback, fs);
    }

    Now, in the EndIndexIAsync action method, we can fetch the results of that operation and return a view based on that:

    public ActionResult EndIndexIAsync(IAsyncResult asyncResult)
    {
        // Fetch the result of the lengthy operation
        FileStream fs = asyncResult.AsyncState as FileStream;
        int bytesRead = fs.EndRead(asyncResult);
        fs.Close();

        // ... do something with the file contents ...

        // Return the view
        ViewData["Message"] = "Welcome to ASP.NET MVC!";
        ViewData["MethodDescription"] = "This page has been rendered using an asynchronous action method (IAsyncResult pattern).";
        return View("Index");
    }

    Note that standard model binding takes place for the normal parameters of the BeginIndexIAsync method. Only filter attributes placed on the BeginIndexIAsync method are honored: placing these on the EndIndexIAsync method is of no use.

    The event pattern

    The event pattern is a different beast. It also consists of two methods that should be added to your controller:

    public void IndexEvent() { }
    public ActionResult IndexEventCompleted() { }

    Let’s implement these and have a look at the details. Here’s the IndexEvent method:

    public void IndexEvent() {
        // Eventually pass parameters to the IndexEventCompleted action method
        // ... AsyncManager.Parameters["contact"] = new Contact();

        // Add an asynchronous operation
        AsyncManager.OutstandingOperations.Increment();
        ThreadPool.QueueUserWorkItem(o =>
        {
            Thread.Sleep(2000);
            AsyncManager.OutstandingOperations.Decrement();
        }, null);
    }

    We’ve just seen how you can pass a parameter to the IndexEventCompleted action method. The next thing to do is tell the AsyncManager how many outstanding operations there are. If this count becomes zero, the IndexEventCompleted  action method is called.

    Next, we can consume the results just like we could do in a regular, synchronous controller:

    public ActionResult IndexEventCompleted() {
        // Return the view
        ViewData["Message"] = "Welcome to ASP.NET MVC!";
        ViewData["MethodDescription"] = "This page has been rendered using an asynchronous action method (Event pattern).";
        return View("Index");
    }

    I really think this is the easiest-to-implement asynchronous pattern available in the AsyncController.

    The delegate pattern

    The delegate pattern is the only pattern that requires only one method in the controller. It basically is a simplified version of the IAsyncResult pattern. Here’s a sample action method, no further comments:

    public ActionResult IndexDelegate()
    {
        // Perform asynchronous stuff
        AsyncManager.RegisterTask(
            callback => ...,
            asyncResult =>
            {
                ...
            });

        // Return the view
        ViewData["Message"] = "Welcome to ASP.NET MVC!";
        ViewData["MethodDescription"] = "This page has been rendered using an asynchronous action method (Delegate pattern).";
        return View("Index");
    }

    Conclusion

    First of all, you can download the sample code I have used here: MvcAsyncControllersExample.zip (64.24 kb)

    Next, I think this is really a feature that should be included in the next ASP.NET MVC release. It can really increase the number of simultaneous requests that can be processed by your web application if it requires some longer running I/O code that otherwise would block the ASP.NET worker thread. Development is a little bit more complex due to the nature of multithreading: you’ll have to do locking where needed, work with IAsyncResult or delegates, …

    In my opinion, the “event pattern” is the way to go for the ASP.NET MVC team, because it is the most readable. Also, there’s no need to implement IAsyncResult classes for your own long-running methods.

    Hope this clarified AsyncControllers a bit. Till next time!

    kick it on DotNetKicks.com


    Categories: ASP.NET | C# | General | MVC | Scalability