Tuesday, November 25, 2014

Deploying a modern app to a distributed sales force in a not-so-modern industry

Earlier this year a great opportunity came my way: the proof of concept WinRT app created to showcase the possibilities for our sales force was received favorably and my team was given the green light to make it a reality. We started preliminary design work in June, finished the first release in October, and we're currently wrapping up the second release that includes order entry capabilities.

Project Goal

The goal was pretty ambitious given the short time frame and its impact on 150+ sales reps throughout North America:
  • Distribute new ThinkPad 10 devices running Win 8.1 Pro with WWAN service.
  • Convert the reps to Office 365 in the process.
  • Deploy an easy-to-use, touch-first sales intelligence app that provides far more actionable information about our customers than ever before available in the field.
Regarding the sales intelligence app, it certainly helped to have had previous experience creating an occasionally-connected Win 8 app. Here's an overview of the new sales app:

Functional highlights

  • Daily engagement list at the rep's fingertips for easy access to customer information.
  • Graphical sales trends, filterable by product segments.
  • Recent history of quotes and orders for a rep's customer base.
  • Rich customer profile info, including sales history and trends, AR and aging data, contacts, quote and order history, even recent history of customer phone interactions with our inside sales.
  • Real-time inventory and pricing data on all products.
  • Ordering capabilities.

Technical highlights

  • Lenovo's ThinkPad 10 device running Win 8.1 Pro, joined to the domain.
  • Designed as an occasionally-connected app using SQLite for caching data locally, with a variable refresh schedule so that more static data is cached for longer, while volatile data is refreshed more frequently.
  • WinRT (C# / XAML).
  • CSLA.NET framework for encapsulating all business logic, not to mention the built-in plumbing necessary to support our 4-tier architecture.
  • MVVM Light toolkit.
  • RayGun error and crash reporting service.
  • Telerik's UI controls for Windows Universal apps
  • SCCM for managing deployment, as this is not distributed through the Windows Store.

Other highlights

  • The ThinkPad 10 device replaces the sales reps' existing desktops and/or laptops. Given that it runs Win 8.1 Pro, we've even loaded some of our legacy apps onto the device, while other legacy apps are accessed via VMWare's VM View remote solution. It's going to be an adjustment for our sales reps...time will tell.
  • This project has certainly helped push our small development group further along to thinking about application design in a more modern way, using newer technologies, frameworks and toolkits.
  • The benefits of the CSLA .NET framework show yet again that our investment in learning and adopting the framework has proven valuable, as we can easily leverage our knowledge to build maintainable applications with lower cost.
  • RayGun, if you've not checked it out, is quite handy. It allows us to be notified of errors encountered by our distributed users before our help desk even hears about it. It pays to read Hanselman
  • This app provides a modern experience that parallels our 20 year old legacy order entry app (still VB6 to this day). It has been a great exercise for our IT group and users to be challenged to think outside of the way-we've-always-done-it box. Old paradigms didn't work with a touch-first app, so creativity was required. At the same time, 20 years of an actively developed application reveal that a lot of thought was put in to it by a lot of smart people during that time.

Thursday, June 26, 2014

IIS 7.5 + Windows Authentication: some users still getting prompted for credentials

It has been a while since I created an intranet web app that uses Windows Authentication. I recently ran into a problem where some users were able to authenticate correctly while others were prompted for credentials. I spent a lot of time checking and verifying a number of things in the process of trying to figure this out:

Settings Checked

IIS Authentication settings

Made sure Anonymous was disabled, Windows Authentication was enabled.
image
image
I saw a number of sites that said to bump up NTLM to the top of the Providers list…which isn’t recommended. (Didn’t work anyway.)

Web.config Settings - made sure the settings were correct there:

<authentication mode="Windows" />
<identity impersonate="false" />

Browser-based settings for users who were prompted for credentials:

image
image
Also added the app to the Trusted Sites just to make sure.

Solution

I realized that setting Windows Authentication in IIS causes the current user’s identity to be used when the site’s files are accessed on disk. In the case of the users who were prompted, they weren’t in an AD group that had permission to the root directory. But granting access to the root folder isn’t an option for security reasons, especially if you’re storing a database connection string in the web.config file.

Thanks to this post, I learned IIS 7.5 has a setting that forces IIS to utilize another identity for disk access even when Windows Authentication is utilized. The key is to set the authenticatedUserOverride option to use the “UseWorkerProcessUser” value. Of course, make sure the worker process has permissions to the app's directory.

image

image

image

One more thing

User.Identity.Name no longer works once the change is made. To compensate, you can use the altnerative: Request["LOGON_USER"].

Friday, June 20, 2014

No endpoint listening: WinRT calling a WCF service on the corporate network

Scenario: WinRT app interacting with CSLA business objects hosted in a WCF service on an app server in the corporate domain.

As is typical, early in development the data portal is hosted locally on my dev machine. And the transition to hosting the data portal on a separate app server is generally straight forward. But with this WinRT app, the first I've done for the corporate environment, I ran into this problem:








Drilling down, the inner-most exception message was actually this:
"An attempt was made to access a socket in a way forbidden by its access permissions."
But everything was set up the same way my recent MVC projects have been set up. All permissions in IIS were correct.

The problem, it turns out, was not with IIS or the remote data portal. This article pointed to the necessity of declaring the right capabilities in the WinRT app's package manifest.


Tuesday, March 25, 2014

Browser Link and Slow Page Loads while Debugging with VS 2013

I recently started noticing sluggishness in IE 11 when debugging a MVC 4.5 app with VS2013. Specifically, after a page would seemingly load, it wouldn't respond to my mouse movement until what seemed like a full second or two later. It was concerning. I had been making a lot of changes, especially around the inclusion of images, and I figured I'd done something less than ideal that was causing the page load time to increase. So I hit F12 and start capturing network traffic:


Besides a couple issues that I need to look at, one thing appeared odd to me: the second-to-last line with a URL of "/__browserLink/..."

I wasn't sure what it was, but it appeared to be the issue. I then find out that this is a new and pretty useful tool for testing with multiple browsers. And there is an easy way to disable it when not needed, which resolves the slowness issue.


IE seemed to suffer a little more from the Browser Link delay than did Chrome.

Thursday, March 6, 2014

Using Shared Scripts in Azure Mobile Services

Windows Azure is outstanding. If you haven't checked it out, do so: WindowsAzure.com/. They provide a free trial if you want to experiment. You also get free compute time if you have an MSDN account.

There are, of course, tasks in Azure that require know-how. One of those tasks that took me a good deal of time to get right is using shared scripts in Azure Mobile Services.

Background on Mobile Services

With mobile services, you're given an easy way to intercept and modify the calls from your apps before they perform database CRUD actions. You do this using Node.js scripts. The common examples I've seen shows how to compare the UserId of the user to that of individual records being affected by the request. For instance, in the Read script you might do this:

        query.where({ UserId: user.userId });
        request.execute();

This appends an additional where clause to your query so that it is filtered to return only records where the UserId field matches the requesting user's Id. (This assumes you've specified that the permissions for the particular table script is set to "Only Authenticated Users.")

But sometimes you want to perform other actions, such as looking up data from another table, logging activity, updating data, etc., prior to submitting the actual request passed by your app. If you need to perform these common tasks across a number of service calls, you have the option of writing that code once and sharing it across all service methods. But this is where I struggled...how to get it set up and working correctly.

Creating and Using Shared Scripts

By default, the Azure management tool provides an easy way to manage your table scripts. But the tool doesn't provide visibility (yet) to shared scripts...that is, scripts not tied to one particular table action (insert, update, delete, read). To do this, you have two options: (1) set up a Git repository and configure that in your mobile service or (2) use the Azure command-line tool to upload/manage shared scripts. I'm not using Git, so I opted for the command-line tool.

Task #1 - installing the command-line tool

The first problem I ran into was that, after installing the tool, I was confused on which tool to use. I had a few show up in my search from the Win 8 start screen:

Later, I found this useful tip from Chris Avis in a TechNet blog, which enabled me to find a helpful command line tool that for some reason isn't visible after installing from the above link:
"Interesting Tidbit – PowerShell ISE gets installed as a part of this process, but you won’t find it by searching on the Start screen of a Windows 8 machine. But it is there! You just have to dig for it.  It is actually located in the Control Panel –> Administrative Tools area"
This tool includes a nice utility to search through various commands.

Task #2 - managing shared scripts from the command line

From the command line, you can view and manage all of your scripts (table, shared and custom). Here's a helpful list of script commands. Before you do that, however, you need to link your Azure account to the command-line tool:

azure account download

That will open a browser prompting you to sign in, upon which it will download the publish file so that you can reference that with your next command:

azure account import <path-to-settings-file>

See the documentation which explains this process in more detail. Once you have your account set up, you can begin uploading your shared scripts. For example:

azure mobile script upload mytestservice shared/myscript.js -f c:\users\tim\documents\myscript.js

Note that I had trouble with the above command without specifying the script file location using the -f option.

Task #3 - using the shared script in your table scripts

Finally, here's an example of consuming a shared script from within a table script:
function read(query, user, request) {
    // retrieve the shared module for common code
    var shared = require('../shared/myscript'); //note the absence of the .js extension
     
    // Test for compliant app version
    if (!shared.isAppVersionValid(request.parameters.AppVersionNumber))
    {
        request.respond(statusCodes.BAD_REQUEST, 'The app version is not valid');
    }
 
    request.execute();
}
Carlos Figueira (MSFT) has produced a number of helpful posts on working with Mobile Service scripts. The Azure documentation also contains good information. I found the following pages to be the most helpful in getting a handle on mobile service scripts and shared scripts: