Archives for : August2014

Previous Post: MVC and Azure for the MVVM/WPF Dev, Part 1 – Getting Started

Up next in this series is covering the model, view and controller objects that were created in the tutorial, Deploy a Secure ASP.NET MVC 5 app with Membership, OAuth, and SQL Database to an Azure Web Site.

Getting There

How does all the MVC views and view controller work? Well, let’s start with getting to the index view from the main page. The tutorial originally told you to replace the home with a call to the Cm controller it had you create. As I mentioned previously it seemed odd to call it Cm and so I called mine ContactsController. And rather then replacing the main link to the home page I added a new link. My “/Views/Shared/_Layout.cshtml” ends up looking like:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("ExperimentalProducts", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                    <li>@Html.ActionLink("Contact Manager", "Index", "Contacts")</li>
                </ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>© @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

This makes it so I now have a “Contact Manager” link in my nav bar. We need to understand what is going on here. The @Html object is an MVC helper object to set up links and make your life easier when working with html. We’re creating a link that we want to perform some sort of action. In our case we want to go to the “ContactsController” and tell it we want it to perform an Index action. If you look at the third line in the navbar you can see that we have added a link that looks like:

<li>@Html.ActionLink("Contact Manager", "Index", "Contacts")</li>

The first parameter is the string to display for the link. The second parameter is the action we want to perform, in our case we want to display the index. The third parameter is the controller we want to call. Note that part of the name, Controller, is missing. I don’t know what would happen if you created a name without the Controller part appended.

MVC then takes care of wiring everything up so that when you click on the link we end up at “/Views/Contacts/Index.cshtml”.

The Model

The Contact model is straightforward and working with models is the same as if you were using models in MVVM. We’re using Entity Framework on the back-end and it all works almost the same as when I’ve used it in desktop applications. You’ll find the DbContext in “/Models/IdentityModels.cs”.

There are really only two differences from how I use the DbContext with desktop applications. The first is that when working with with desktop applications I tend to extend the DbContext to take a connection string. The auto-generated constructor is empty but will call base with connection string name “DefaultConnection”. I often like to expose this so I can allow my users to dynamically change the connection string dependent on the needs of the application. There may very well be the same need here but I don’t see the need in the near future. The second difference is that we’re obviously using a web.config instead of an app.config. I’m going to assume that you understand the differences between the web.config in the root of the project and an app.config. I will be covering “Views/Web.config” separately in a different post.

The Controller

Before I go into the views we need to take a look at the controller. But before we even do that we need to understand HTTP request methods. This is how the browser makes a request to the server. We’re primarily concerned with two methods, GET and POST. Generally GET is performed by clicking a link and POST is performed by submitting a form via a button at the bottom of the page. You don’t have to perform the request methods in this manner but it’s behavior users will expect.

It is important to take to heart, as a matter of convention, the purpose of GET and POST. GET should only be for getting data. POST, conversely, should be for making changes to data. This is incredibly important and stems from lessons learned in my java servlet/asp.net days. Like most conventions in software development, they are needed in order to make maintenance and the continued operation of an application (whether desktop or web) easier and with a minimal amount of defects.

[Authorize]
public class ContactsController : Controller
{
    private ApplicationDbContext db = new ApplicationDbContext();

    // GET: Contacts
    public ActionResult Index()
    {
        return View(db.Contacts.ToList());
    }

    // GET: Contacts/Details/5
    public ActionResult Details(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Contact contact = db.Contacts.Find(id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    // GET: Contacts/Create
    public ActionResult Create()
    {
        return View();
    }

    // POST: Contacts/Create
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "ContactId,Name,Address,City,State,Zip,Email")] Contact contact)
    {
        if (ModelState.IsValid)
        {
            db.Contacts.Add(contact);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(contact);
    }

    // GET: Contacts/Edit/5
    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Contact contact = db.Contacts.Find(id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    // POST: Contacts/Edit/5
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "ContactId,Name,Address,City,State,Zip,Email")] Contact contact)
    {
        if (ModelState.IsValid)
        {
            db.Entry(contact).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    // GET: Contacts/Delete/5
    public ActionResult Delete(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Contact contact = db.Contacts.Find(id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    // POST: Contacts/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        Contact contact = db.Contacts.Find(id);
        db.Contacts.Remove(contact);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

If you’re comparing along with your own code from the tutorial you’ll see that there is one difference. I added a “[Authorize]” tag at the top of my class. This makes it so that the only way to enter this controller is if the user is authorized. You can take this attribute a step further and specify specific roles that may access the controller. There are a few things to take note of:

  1. Each method ties to an action, which in turn ties to a view
  2. Each method returns an ActionResult
  3. Each method defaults to a get request
  4. You must explicitly identify post requests with the attribute, “HttpPost”
  5. All post requests have the attribute, “ValidateAntiForgeryToken”

The first point, “Each method ties to an action” is incredibly important. Remember a view ties to an action in the controller. What started this was we were trying to get to the Index action for the link we added in “/Views/Shared/_Layout.cshtml”.

The Views

When doing MVVM development there tends to be a one to one relationship between the view model and the view. This isn’t neccasarily true of models as they tend to get used as needed but of view models and views it is.

Take a look at “/Views/Contacts/” and you’ll see that there are 5 views. Each view relates to an action that can be performed on the model. Index lists all instances of the model, and the rest are obvious. This is in stark contrast to how I tend to do MVVM dev. Here is how I would do this in MVVM:

ContactsListView that contains a data context of a ContactsListViewModel. The view model would have a list of contacts I would bind to the list in the view. There would be an empty panel in the view for another view that would be for editing of contacts. At the top of the grid would be a “Create New” button. Selecting a row in the list would set a “SelectedContact” in the view model that would cause the empty panel to populate with a ContactView.

ContactView that contains a data context of a ContactViewModel that would contain the Contact model. It would contain all details on the Contact and would allow you to create, delete and edit the contact.

In MVVM for this operation you end up with two views and two view models. There are a few more “fancy” ways to do this but the above implementation I could probably do in a trivial amount of time.

But here in MVC world we have 5 views and only one controller. Could we do it the same way as I would do it with MVVM? I don’t know, probably something to look at.

So in our ContactsController we have “public ActionResult Index()” and in our views we have Index.cshtml.

@model IEnumerable<ExperimentalProducts.Models.Contact>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Address)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.City)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.State)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Zip)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Email)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Address)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.City)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.State)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Zip)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.ContactId }) |
            @Html.ActionLink("Details", "Details", new { id=item.ContactId }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.ContactId })
        </td>
    </tr>
}

</table>

I glossed over “@” before but I need to address it here. In the cshtml @ indicates a start of a code block. This is very similar to using <?php and ?> in php and <% and %> in asp.net. For more information on the syntax read Introduction to ASP.NET Web Programming Using the Razor Syntax (C#).

So let’s walk through this code.

  • Line 01: Declare the type for the WebViewPage<TModel> (which is what this view page inherits) is of type IEnumerable<ExperimentalProducts.Models.Contact>.
    • This is the type that will be used for “Model” below.
  • Line 04: Set the ViewBag.Title to “Index”.
  • Lines 13 – 33: Display the columns for each of the properties on the model.
  • Line 15: Use the Html helper object to display the name for the Name property of our model.
    • These are lambda expressions and could just as easily be “x => x.Name”.
      • Try it yourself.  Change all of your “model => model.Property” to “x => x.Property” and you’ll see that the page still loads/works just fine.
    • By default it will use the property name but you can specify your own name in the model by adding the attribute, “[Display(Name = “First/Last Name”)]”. This would therefore display “First/Last Name” instead of just “Name”.
  • Lines 35 – 61: Iterate over each item in Model displaying the values.
    • Model gets set with the results from the Index method in the controller and is a property of WebViewPage<TModel>. Rather like data binding to a data context.
    • Type of Model is determined by the “@model” type set at the top of the page.
    • Where does “modelItem” come from? This is a lambda express we’re passing into the “DisplayFor” method so what it’s name is irrelevant. It’s just like using “x => item.Name”.
  • Line 56: Finally build the links passing in an anonymous object with the property “id”.
    • The property name is important as it must match the parameter on the action method that is being called in the controller.

And that’s it. Just like working in MVVM there is quite a bit you just have to assume is going to get wired up correctly.  The first time I looked over this cshtml I was utterly confused.  Why have @model at the top and then use model in the code?  But once you understand that these are just lambdas being passed into a method everything gets a bit less murky.

Up next I’ll cover the edit and create actions of the controller and the views so we’ll have a better understanding of what is happening in the back-end when everything gets wired up.

Thanks for reading,
Brian

MVC and Azure for the MVVM/WPF Dev, Part 1 – Getting Started

I’ve done extensive web development prior to my current position (where I primarily do desktop applications dev), but it’s been a while. In fact its been almost 5 years since I’ve gotten my hands deep into CSS, javascript and ASP.NET. And 5 years is an eternity in this business. So where are things in the ASP.NET world now-a-days? Well, the big buzz is MVC. This post and any that follow on MVC are about me learning MVC as I go. While I feel I can confidently claim to be an MVVM expert, I know very little about MVC.

There will be bits in this series where I specifically target MVVM/WPF/XAML developers and how to transition knowledge and experience from those domains into MVC but that doesn’t mean that this wouldn’t be a descent guide for those learning MVC.  I’m just learning MVC and I hope to pass on some of that knowledge.

You probably know what MVC is but bear with me as I go into a brief explanation. MVVM is a platform-specific implementation of the application framework design pattern MVC. That’s a mouthful. Where as MVVM is Model-View-ViewModel, MVC is Model-View-Controller. Rather than coming up with another name for MVC in ASP.NET, Microsoft apparently decided to keep it easy and just use MVC. (If your curious you can google around for MVP (Model-View-Presenter), which is the WinForms version of MVC. It doesn’t have anything to do with this, just another version of MVC.)

So it’s all pretty simple, your controller sits in the middle of your view and your model, just like a view model. But as we all know the devil is in the details.

Okay, so how do we get started?

First, if you haven’t already, you need to create an Azure account. I mean, why go to all this trouble if you can’t show off the world what you’ve done? If you have an MSDN license you can create an Azure account, and at least at the time of this post, you get 10 free websites per region. The free sites will use the azurewebsites.net URL (for instance, I created my project with the name “ExperimentalProducts” and thus have the site at experimentalproducts.azurewebsites.net). I’m sure there are a bunch of restrictions. Confirm anything I say about Azure features with the Azure site and team and don’t consider me an authority in this regard. I may be wrong or things may have changed by the time you get around to reading this post. There is a lot more you get with your MSDN subscription for Azure but that is outside of the scope of this post.

After you have an Azure account I’d recommend you start with the tutorial, Deploy a Secure ASP.NET MVC 5 app with Membership, OAuth, and SQL Database to an Azure Web Site. I followed the tutorial as far as the section on OAuth and OpenID and then switched to the tutorial Code! MVC 5 App with Facebook, Twitter, LinkedIn and Google OAuth2 Sign-on (C#).

Okay, got all that done? No? Well, if you want to continue to read feel free but it probably won’t make a lot of sense without having your own code available.

If you’ve done the above steps you should be at the same place I am. The problem with some tutorials is that they don’t tell you the consequences of the action. They give a step-by-step process to do exactly what they say they will. This is great but you can do the two above tutorials and not understand anything about MVC. So I want to look at the solution and the state it’s in to try and explain the project and what it is that Microsoft and you created. Again, as mentioned above, I’m doing this so I can have a better understanding of what is happening and hopefully you’ll walk away with a better understanding as well.

As we’re going through this (and as mentioned earlier) I named my project “ExperimentalProducts”. As such a few of the names may be different. I also didn’t name my Contacts Controller “CmController” like the tutorial suggested. I named it “ContactsController”. This seemed more in-line with standard naming conventions.

Here is the project as it should be after the tutorials:

MVC Project

Expand out the references and let’s see what we have.

Libraries other than standard ASP.NET in the project:

Antlr3

WebGrease

These two libraries are used for bundling and minification of css and javascript files. In order to minimize traffic the project will bundle and compress css and javascript. To see how it is used look at /App_Start/BundleConfig.cs in the project.

EntityFramework

Entity Framework is Microsoft’s data access library. Allows you to easily create models and then map them back to the database in a “Code First” approach. You can also do a “Database First” approach where you create the tables in the database and have Entity Framework generate your models for you.

Owin

Microsoft’s implementation of the “Open Web Interface for .NET”. See the article, What is OWIN, and what is it doing in my ASP.NET MVC project? for more information.

Included css and javascript libraries in the project to make everything easier:

bootstrap.css

bootstrap is a css library for making your website work cleanly and easily with mobile browsers. If you shrink down your browser to what a user with a smart phone would see with the default build you will see that everything collapses nice and neat so it will look good on a smart phone.

jQuery

jQuery is the de facto javascript library for doing javascript development.

Modernizr

Modernizr is a javascript library that will determine what features (like css gradients) are available in the user’s browser. This way you can test in your css or javascript if a feature is available.

That’s it for now. Just wanted to cover setting up the projects and what was added. Next week I’ll get more in-depth on what the models, views and controllers are doing, how they parallel MVVM and how they’re different.

Thanks for reading,
Brian

Live Filter Design Pattern in XAML

GetTheCode

I decided I should start titling my posts with XAML instead of WPF as they are applicable regardless of whether you are developing in Windows 7 with WPF or Windows 8. I’m going to take the BreadCrumb design pattern sample and extend it to include the Live Filter design pattern.

The premise behind the Live Filter design pattern is pretty simple, show the user everything and then allow them to filter the results, immediately showing how the filter affects the results. As noted in the BreadCrumb post dense trees can easily become overwhelming to users. Providing a simple, easy to use live filter makes the experience significantly better.LiveFilter

In the above image you can see there we have a search text box with two filter parameters. As the user types into the search box the results are immediately filtered.

<Grid Grid.Row="0" Grid.ColumnSpan="3" Margin="5">
    <xctk:WatermarkTextBox Margin="2" Watermark="Search" Text="{Binding SearchValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <Button Margin="5 0 5 0" Width="16" Height="16" HorizontalAlignment="Right" FontFamily="Segoe UI Symbol" Opacity=".75" ToolTip="Clear" Command="{Binding ClearCommand}">
        <TextBlock Margin="0 -3.5 0 0" Foreground="#FF63A3B2">👽</TextBlock>
    </Button>
</Grid>
<StackPanel Grid.Row="1" Grid.ColumnSpan="3" Margin="5" Orientation="Horizontal">
    <CheckBox Margin="5" IsChecked="{Binding SearchProperty2, Mode=TwoWay}" Content="Include Property 2 In Search" />
    <CheckBox Margin="5" IsChecked="{Binding SearchProperty3, Mode=TwoWay}" Content="Include Property 3 In Search" />
</StackPanel>

Above is the XAML for the search controls. Most of it is standard MVVM binding. The problem with standard MVVM binding is that for textbox it only updates the underlying view model property on lost focus. That doesn’t help us as we want to update the results in real-time. To fix this the UpdateSourceTrigger is set to PropertyChanged. This way as soon as the user types a letter the property is changed and we can perform a search. (I’m actually using the nuget package for the Extended WPF Toolkit to provide a watermark textbox but it works like a regular textbox).

<TreeView Grid.Row="2" Grid.RowSpan="2" Margin="5" 
        ItemsSource="{Binding ChildNodes}"
        VirtualizingStackPanel.IsVirtualizing="True"
        VirtualizingStackPanel.VirtualizationMode="Recycling"
        SelectedItemChanged="TreeView_SelectedItemChanged">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="Visibility" Value="{Binding Visibility, Mode=TwoWay}" />
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <StackPanel>
                <TextBlock Text="{Binding Property1}" FontSize="18" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

The first important part is the TreeViewItemContainerStyle. The nodes implement the properties IsExpanded and Visibility. This is extremely important. We don’t want to have to deal with the ItemContainerGenerator. This can be a major pain the rear and will make the code extremely sluggish. If we’re going to go with binding the nodes in the tree let’s take full advantage of it. The other important part is setting up the VirtualizingStackPanel values. The search is really rather trivial and usually works extremely fast. The slow part is updating the UI. We leave that to the masters at Microsoft by binding everything and letting them figure out how to render it. But we can help things out a bit. By setting up the VirtualizingStackPanel the control doesn’t have to render all the children in the tree. Now use this with some caution. Because the nodes will only be updated as the user drags the scroll bar you can get some choppy and sluggish operations as the control updates.

string searchValue;
public string SearchValue
{
    get { return searchValue; }
    set
    {
        if (SetProperty(ref searchValue, value))
        {
            PerformSearch();
        }
    }
}

bool searchProperty2 = false;
public bool SearchProperty2
{
    get { return searchProperty2; }
    set 
    {
        if (SetProperty(ref searchProperty2, value))
        {
            PerformSearch();
        }
    }
}

bool searchProperty3 = false;
public bool SearchProperty3
{
    get { return searchProperty3; }
    set 
    { 
        if(SetProperty(ref searchProperty3, value))
        {
            PerformSearch();
        }
    }
}

These are the properties in the view model. We’re using BindableBase from Prism for the view model. Thus SetProperty returns a true if there was a change. We need to minimize extraneous calls to PerformSearch as much as possible.

private static object lockObj = new object();
private CancellationTokenSource CancellationTokenSource { get; set; }
private void PerformSearch()
{
    //if we're doing a search then we probably have some new search term
    clearCommand.RaiseCanExecuteChanged();

    lock (lockObj)
    {
        if (CancellationTokenSource != null)
            CancellationTokenSource.Cancel();

        CancellationTokenSource = new CancellationTokenSource();

        resultCount = 0;
    }

    Task.Run(() =>
    {
        DateTime now = DateTime.Now;
        try
        {
            if (string.IsNullOrEmpty(SearchValue))
            {
                ClearAllNodes(ChildNodes, Visibility.Visible, CancellationTokenSource.Token);
                return;
            }
            else
            {
                ClearAllNodes(ChildNodes, Visibility.Collapsed, CancellationTokenSource.Token);
            }

            var options = new ParallelOptions { CancellationToken = CancellationTokenSource.Token };
            try
            {
                Parallel.ForEach(ChildNodes, options, (childNode) =>
                {
                    PerformSearch(childNode, options);
                });
            }
            catch (OperationCanceledException)
            {
                //Noop
            }
        }
        finally
        {
            LastSearchTookInMilliseconds = (DateTime.Now - now).Milliseconds;
            OnPropertyChanged(() => ResultCount);
        }
    }, CancellationTokenSource.Token);
}

private void PerformSearch(RandomValueNode childNode, ParallelOptions options)
{
    if (options.CancellationToken.IsCancellationRequested)
        return;

    if (childNode.Property1.StartsWith(SearchValue) ||
        (SearchProperty2 && childNode.Property2.StartsWith(SearchValue)) ||
        (SearchProperty3 && childNode.Property3.StartsWith(SearchValue)))
    {
        Interlocked.Increment(ref resultCount);
        childNode.IsExpanded = true;
        childNode.Visibility = Visibility.Visible;
    }
    foreach (RandomValueNode node in childNode.Children)
    {
        if (options.CancellationToken.IsCancellationRequested)
            break;

        PerformSearch(node, options);
    }
}

And finally we do the search. To make this as responsive as possible we need to cancel the search each time as quickly as possible. Thus I’m using a CancellationToken in the Task.Run and the Parallel.ForEach to do the search. Remember that it is only effective to spin off threads from the thread pool if you can give them enough work. In my case there will only be ten nodes but I give each of those plenty to do. I’m passing the options into the PerformSearch so that we kill the search as quick as possible. As the code recurses I want to kill everything. It may seem like overkill to check for options.CancellationToken.IsCancellationRequested at the top of the method and in the foreach, and it may very well be.

So this pattern is pretty straightforward, simply update live results as the user changes the filter parameters. The key here is taking into account the result set size and to stop any existing search quickly, really try and take advantage of all that the TPL provides. As you can see in the image at the top I can iterate over 700,000 nodes pretty quickly and the UI is still pretty responsive. Of course that is really more because of the Task.Run and using VirtualizingStackPanel.

There is, of course, another way to do this. If you find that things are moving much too sluggish the other alternative is to show a minimal amount of results (like when doing a Google search and it begins to suggest terms). Then in the background have a thread that starts to update the results.

I would encourage you to get the code from the “GET THE CODE” button at the top.

Thanks for reading,
Brian