Friday, December 2, 2016

Consuming a WCF service using REST from Angular

I know this doesn't have much to do with WPF but I've been wanting to write RESTful WCF services for some time and consuming them from Angular was plain good fun!

You can click on any of the images below to see full size versions.

We're going to use the AdventureWorks2012 SQL Server sample database from Microsoft.

Start Visual 2015 and start a new WCF Service Application project and call it "products".



At this point Visual Studio has created a sample service called Service1 that contains examples of code. You can delete Service1 and IService1 or simply ignore them.

Our next step is to add our own service called Products. In the solution explorer, right-click on the Products project, select Add and then select New Item.


Select WCF Service and name it "Products". Click [Add]


Now your Solution Explorer looks like this and you can get a clean build.



We will write a Products service that returns a list of products. The first thing to do is to enhance our interface to include a definition of the product class and a definition of the products service. Lets' start with the product class. Open the IProducts class and remove the examples that Visual Studio put there for us. Replace the contents with the following class definition.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace Products
{
    [DataContract]
    public class Product
    {
        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string ProductNumber { get; set; }

        [DataMember]
        public string Color { get; set; }

        [DataMember]
        public decimal Price { get; set; }
    }
 }

Note the [DataContract] and [DataMember] attributes. They tell WCF how to send this class and its members across the wire to the client.

Now add the interface for the GetProductList method. Note it also has attributes that indicate how we will invoke it (with a GET) and how data will be returned (in Json format). If we wanted to be able to save a product list we would add a similar definition but with Method="POST" and referencing a different method (maybe SaveProductList). The UriTemplate would be the same.

    [ServiceContract]
    public interface IProducts
    {
        [OperationContract]
        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "ProductList/")]
        List<Product> GetProductList();
    }

To recap, this interface converts a request to http://.../ProductList/ using the GET method into a call to GetProductList() and converts the returned data into JSON which is attached to the response. In effect we have a layer that converts REST to SOAP. We will see below that we have the ability to hide or expose the underlying SOAP functionality.

At this point the IProducts.cs file has a "Products" namespace that contains a "Product" class and an IProducts interface.

Before we go any further we need to put our connection string into Web.config so we can access the database. Add the following to your Web.config file inside <configuration>.

  <connectionStrings>
    <add name="localhost" connectionString="Server=localhost;Database=AdventureWorks2012;Trusted_Connection=yes"/>
  </connectionStrings>

While we are here we can define our endpoint behaviors. Merge the following tags into <system.serviceModel> in your Web.config file. Some of these tags will appear to be invalid until they are all in the correct places. Two attributes we could change are httpGetEnabled which determines if SOAP behavior is exposed by the endpoint and includExceptionDetailInFaults which determines if detailed error messages are returned.

    <services>
      <service name="Products.Products" behaviorConfiguration="serviceBehavior">
        <endpoint address="" binding="webHttpBinding" contract="Products.IProducts" behaviorConfiguration="web"/>
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehavior">
          <serviceMetadata httpGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>

I am going to use a data extension to convert a datatable to a list of dictionaries which means I need to add a reference to System.Data.DataSetExtensions. In the Solution Explorer right-click on References and select Add Reference.

Scroll to System.Data.DataSetExtensions, click on it to make the checkbox visible, then check the checkbox and click [OK].


Now we can write the products class. Open the Products.svc.cs file. Remove all the example code and replace it with the following. If we had decided to support saving a product list we would write SaveProductList here (after defining the interface in IProducts).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace Products
{
    public class Products : IProducts
    {
        public List<Product> GetProductList() 
        {
            String SQL = "SELECT ProductID, Name, ProductNumber, Color, ListPrice FROM Production.Product ORDER BY ProductID";
            String ConnString = System.Configuration.ConfigurationManager.ConnectionStrings["localhost"].ConnectionString;
            DataTable dt = new DataTable();

            using (SqlConnection conn = new SqlConnection(ConnString))
            {
                conn.Open();
                using (SqlCommand comm = new SqlCommand(SQL, conn))
                {
                    using (SqlDataAdapter da = new SqlDataAdapter(comm))
                    {
                        da.Fill(dt);
                    }
                }
            }
            List<Product> ProductList = dt.AsEnumerable().Select(r => new Product()
            {
                ID = r.Field<int>("ProductID"),
                Name = r.Field<String>("Name"),
                ProductNumber = r.Field<String>("ProductNumber"),
                Color = r.Field<String>("Color"),
                Price = r.Field<Decimal>("ListPrice")
            }).ToList();
            return ProductList;
        }

    }
}

We define the GetProductList method that pulls the data from the database and converts it into a list of product objects. The list is automatically converted to JSON by the ResponseFormat = WebMessageFormat.Json clause in the interface's attribute.

At this point you can right-click on Products.svc and select "View in Browser". We disabled SOAP when we set httpGetEnabled="false"  above so we will see the message "Metadata publishing for this service is currently disabled". If we append "/ProductList/" to the URL we will get a JSON file back that represents all the products.

Now it is time to consume the data. Add an html page to the project called "Default" and set it as the start page. It will use some simple Angular to $http.get the JSON and render it in a basic table. Replace the contents of Default.html with this.

<!DOCTYPE html>
<html ng-app="DefaultApp">
<head>
    <title>products</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
    <script>
        angular.module("DefaultApp", [])
        .constant("productsUrl", "/Products.svc/ProductList/")
        .controller("DefaultCtrl",
        function ($scope,$http,productsUrl) {

            $scope.getData = function () {
                $http.get(productsUrl)
                .then
                (
                    function (result) { $scope.products = result.data; }
                    ,
                    function (result) { $scope.statusText = result.statusText; }
                )
            };

            $scope.getData();
        });
    </script>
</head>
<body ng-controller="DefaultCtrl">
    <div>{{statusText}}</div>
    <table>
        <tr ng-repeat="item in products">
            <td>{{item.ID}}</td>
            <td>{{item.Name}}</td>
            <td>{{item.ProductNumber}}</td>
            <td>{{item.Color}}</td>
            <td>{{item.Price | currency}}</td>
        </tr>
    </table>
</body>
</html>

Now run the project. The result will be a table of products.


Wednesday, November 2, 2016

Binding to a DataGrid's SelectedItems property

I have a requirement to only enable a button when there is at least one row selected in a DataGrid. My initial response was to attach the button to a routed command, bind the SelectedItems property of the DataGrid to a collection, and test the collection's count in the CanExecute method. However SelectedItems is not a dependency property for some reason so you cannot bind it in XAML.

We found two solutions to this problem so I will present them both.

1. Create a custom dependency property.

This solution is more work but has the advantage of being easier to consume. I.e. once the work is done, it can be used in many places easily. It is also more 'pure' MVVM.

a. Start by creating a custom class that derives from DataGrid and adds a custom dependency property that can be bound to. Whenever the SelectedItems property changes, the custom dependency property gets changed too.

using System.Windows;
public class CustomDataGrid : DataGrid
{
    public CustomDataGrid()
    {
        this.SelectionChanged += CustomDataGrid_SelectionChanged;
    }

    void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        this.SelectedItemsList = this.SelectedItems;
    }

    public IList SelectedItemsList
    {
        get { return (IList)GetValue(SelectedItemsListProperty); }
        set { SetValue(SelectedItemsListProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsListProperty =
                DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomDataGrid), new PropertyMetadata(null));
}

b. Now add a "local" reference to the XAML so we can use the custom data grid like this.

<Window x:Class="MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:local="clr-namespace:DataGridTesting"
            Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="Copy" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"></CommandBinding>
    </Window.CommandBindings>
    <DockPanel>
        <local:CustomDataGrid ItemsSource="{Binding Model}"
                              SelectionMode="Extended"
                              IsReadOnly="True"
                              AutoGenerateColumns="False"
                              SelectedItemsList="{Binding SelectedItemsList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding}" Width="100" Header="Name"></DataGridTextColumn>
            </DataGrid.Columns>
        </local:CustomDataGrid>
        <Button Content="How Many?" Command="Copy"></Button>
    </DockPanel>

</Window>

c. The code behind looks like this. Note I broke strict MVVM by using a routed command for simplicity. You won't do that, I'm sure.

using System;
using System.Windows;
using System.Windows.Input;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MyViewModel();
    }

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        if (this.DataContext != null)
            e.CanExecute = (((MyViewModel)this.DataContext).SelectedItemsList.Count > 0);
    }

    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("You selected " + ((MyViewModel)this.DataContext).SelectedItemsList.Count);
    }
}

d. And here is my ViewModel. Note I used a list of strings so I don't have a Model class. 

using System;
using System.Collections.Generic;

public class MyViewModel
{
    private List<String> _myModel = new List<String> {"Anne","Bob","Connie","David"};

    public IEnumerable<String> Model { get { return _myModel; } }

    private IList _selectedModels = new ArrayList();

    public IList SelectedItemsList
    {
        get { return _selectedModels; }
        set
        {
            _selectedModels = value;
        }
    }
}

2. Passing SelectedItems through the command parameter.

My colleague used the ability to bind the command parameter so that the command passes the DataGrid's SelectedItems property. It isn't perfect MVVM because the command has to bind to the DataGrid using it's name. I'll use Routed Commands to simplify this example too.

a. Start with the XAML this time. We don't need the local reference but we have to name the grid and reference it in the new CommandParameter of the button.

<Window x:Class="MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="Copy" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"></CommandBinding>
    </Window.CommandBindings>
    <DockPanel>
        <DataGrid x:Name="AvailableNames"
                              ItemsSource="{Binding Model}"
                              SelectionMode="Extended"
                              IsReadOnly="True"
                              AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding}" Width="100" Header="Name"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
        <Button Content="How Many?" Command="Copy" CommandParameter="{Binding ElementName=AvailableNames, Path=SelectedItems}"></Button>
    </DockPanel>
</Window>

b. The code behind looks like this. Note how the CanExecute and Executed methods use the command parameter.

using System;
using System.Windows;
using System.Windows.Input;
using System.Collections;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MyViewModel();
    }

    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        if (e.Parameter != null)
            e.CanExecute = ((e.Parameter as IList).Count > 0);
    }

    private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("You selected " + (e.Parameter as IList).Count);
    }
}

c. Lastly the ViewModel is much simpler now.

using System;
using System.Collections.Generic;

public class MyViewModel
{
    private List<String> _myModel = new List<String> { "Anne", "Bob", "Connie", "David" };

    public IEnumerable<String> Model { get { return _myModel; } }
}


Thursday, October 20, 2016

Two cool techniques

It's been a while since my last post. I guess I haven't been struggling to solve problems like I used to. Today I had a couple of problems to solve that have nothing to do with WPF.

The scenario is that I have log files accumulated on the middle tier and I want to be able to search them. I want to be able to give the user either a list of all log files that match a file name, or a list of log files that match a file name and contain a specified string.

There is a method called GetFiles on the DirectoryInfo class that returns a list of FileInfo objects for files that match a particular filter. I used the LINQ Select method to return the FullName property from each of those FileInfo objects into a collection of strings.

Imports System.Linq
Dim NameFilter as String = "*BOB*"
Dim LogPath as String = "C:\Logs"
Dim ReturnFiles As New Collections.Generic.List(Of String)
ReturnFiles.AddRange(New IO.DirectoryInfo(LogPath).GetFiles(NameFilter).Select(Function(t) t.FullName))

The other requirement is to select files that contain a specific string. Now I could do this by reading and searching each file, but the FindInFiles method is much faster as well as being cooler.

Imports System.Linq
Dim NameFilter as String = "*BOB*"
Dim LogPath as String = "C:\Logs"
Dim ContentFilter as String = "contains"
Dim ReturnFiles As New Collections.Generic.List(Of String)
ReturnFiles.AddRange(Microsoft.VisualBasic.FileIO.FileSystem.FindInFiles(LogPath, ContentFilter, True, Microsoft.VisualBasic.FileIO.SearchOption.SearchTopLevelOnly, NameFilter))

Monday, June 20, 2016

IsSynchronizedWithCurrentItem

I had an interesting problem today. Here's the scenario...

A user is in the search screen and the "Transaction Status" drop down list is set to "All".
The user performs a search, selects a transaction and goes to the edit screen.
The edit screen has a transaction status drop down list too, which is bound to the status of the selected transaction. Let's say the selected transaction has a status of "OPEN".
When the user returns to the search screen the Transaction Status drop down list now displays "OPEN" instead of "All".

Obviously the act of selecting "OPEN" in the edit page's drop down list is selecting "OPEN" in the search page's drop down list. This is commonly achieved by setting both drop down lists' IsSynchronizedWithCurrentItem="true" and sharing the same ItemsSource between both drop down lists.

Both drop down lists are bound to the same ItemsSource to improve performance. When I checked the default style for drop down lists I saw IsSynchronizedWithCurrentItem="true".

    <Style TargetType="{x:Type ComboBox}" x:Key="DropDownList" BasedOn="{StaticResource {x:Type ComboBox}}">
        <Setter Property="IsEditable" Value="False"/>
        <Setter Property="IsSynchronizedWithCurrentItem" Value="True"/>
        <Setter Property="HorizontalAlignment" Value="Stretch"/>
    </Style>

I have three possible solutions.

1. Bind each control to its own ItemsSource. This will slow the application down.
2. Change the default style. This may break something else.
3. Override the style with an explicit attribute.

<ComboBox Name="SearchGLDocumentStatusCombo" DisplayMemberPath="Description" SelectedValuePath="ID" Style="{StaticResource DropDownList}" IsSynchronizedWithCurrentItem="False"/>

Thursday, May 19, 2016

DataTrigger with a MultiBinding

Framework 3.5

My requirement is to tell the user when the one value in a data grid row is less than another value. I'm going to tell them by making the first cell's foreground red and adding a tooltip. So I will add a trigger that will use a multi value converter.

So step one is to write a multi value converter that will take two values which are passed by binding and an operator which is passed as the parameter and return true or false. It's a nice generic converter so I will be able to use it in many places.

Public Class CompareConverter
    Implements System.Windows.Data.IMultiValueConverter

    ' values(0) is decimal
    ' values(1) is decimal
    ' returns boolean
    Public Function Convert(values() As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert

        Dim LH As Decimal = 0
        Dim Op As String = "="
        Dim RH As Decimal = 0
        Dim Result As Boolean = False

        If values.Count > 0 Then Decimal.TryParse(values(0).ToString(), LH)
        Op = parameter.ToString()
        If values.Count > 1 Then Decimal.TryParse(values(1).ToString(), RH)

        Select Case Op
            Case "<" : Result = (LH < RH)
            Case ">" : Result = (LH > RH)
            Case "<>", "!=" : Result = (LH <> RH)
            Case "<=" : Result = (LH <= RH)
            Case ">=" : Result = (LH >= RH)
            Case Else : Result = (LH = RH)
        End Select

        Return Result

    End Function

    Public Function ConvertBack(value As Object, targetTypes() As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
        Throw New System.NotImplementedException
    End Function
End Class

Of course we will need to create a static resource so we can reference it.

<local:CompareConverter x:Key="CompareConverter"/>

Now we need to add a style to the column to define a trigger.

<DataGridTextColumn Header="Total Account Chgs" Binding="{Binding Path=Debit, StringFormat='{}{0:0.00}'}">
    <DataGridTextColumn.ElementStyle>
        <Style TargetType="TextBlock">
            <Setter Property="Foreground" Value="Black"></Setter>
            <Setter Property="TextBlock.ToolTip" Value=""></Setter>
            <Style.Triggers>
                <DataTrigger Value="True">
                    <DataTrigger.Binding>
                        <MultiBinding Converter="{StaticResource CompareConverter}" ConverterParameter="&lt;">
                            <Binding Path="Debit"></Binding>
                            <Binding Path="TotalPaid"></Binding>
                        </MultiBinding>
                    </DataTrigger.Binding>
                    <DataTrigger.Setters>
                        <Setter Property="Foreground" Value="Red"/>
                        <Setter Property="TextBlock.ToolTip" Value="The Total Account Charges are less than the Total Paid."/>
                    </DataTrigger.Setters>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>


Tuesday, May 17, 2016

Simple approach to dealing with software criticism

A colleague of mine recently sent me a flow diagram for dealing with software criticism which I have reproduced below without attribution.



I thought it was a bit too complex so I simplified it.


I didn't say it was effective!

Friday, May 6, 2016

Political stupidity

For years, like millions of other developers, we have used the term "Dirty Page" to describe a common feature whereby the page knows if the user has changed something and prompts the user to save if they try to exit the page before saving. We also use the page's dirty state to enable the save and cancel buttons.


Yesterday we were all informed by senior management that the term "Dirty Page" has negative connotations and cannot be used in future. We were not given an alternative term to use. So far we have...

"The page formerly known as dirty"
"Stinky, filthy, disgusting page"
"If you think this is dirty you should see the code"
"Preserve My Stuff"

Your tax payer dollars at work!

Tuesday, April 26, 2016

Control's IsEnabled is inherited from the parent control

Two posts in two days. Must be having a bad week.

I spent four hours trying to figure this one out. The problem was that the user is unable to select a row in a datagrid and the datagrid looked disabled. The datagrid is inside a user control.

I put a breakpoint in a routed command's can_execute method (very handy for debugging) and quickly determined that the datagrid's IsEnabled property is false. Then it gets weird - I set it to true in the watch window and it immediately gets set false again even though it is a read/write property.

So I added an IsEnabledChanged event handler in the initialization code. When the event handler was called to set the IsEnabled property false I looked at the call stack, but it just says "External Code". So my code isn't the problem.

The user control is inside an expander which is initially collapsed. I notice the datagrid is only being disabled as the expander expands, ie when the user control is initially rendered. I have no Loaded event handler.

I took all the styles off the datagrid in case I had some kind of trigger on a style but that didn't fix the problem. I don't have a default style for datagrids.

Then I went home because it was late and I was making no progress. Plus it was snowing at my house and I had to get home in my Prius.

The next morning I noticed the user control's IsEnabled property is false and I can't change it to true in the watch window. Same behaviour as the datagrid. I wondered if the datagrid's IsEnabled property is inherited from the user control.

Then I started to look for the code that was disabling the user control. Found it! When I modified the code to not disable the user control everything worked correctly.

It's amazing how sleeping on an intransigent problem can help you solve it. It's tempting to keep working on a problem but sometimes walking away from it for a while can be the right thing to do.

Monday, April 25, 2016

Suppressing dropdown on a combobox

Framework Version 4.0

I have a combo box that displays a list of values the user can chose from or they can enter their own reason. The combo box drop down is populated from a table that the users can maintain.

<ComboBox IsReadOnly="False" IsEditable="true" SelectedValue="{Binding Description}" DisplayMemberPath="Description" SelectedValuePath="Description" ItemsSource="{Binding Descriptions}"/>


Nothing too exciting here.

But sometimes the table is empty. Then we want the users to be able to enter text but they have nothing to chose from. When they drop the dropdown it looks nasty. Like this...


Combo box with a zero row itemssource

If I disable the combo box to prevent them dropping it then they can't enter a value either. The solution is to set MaxDropDownHeight = 0 either in code or using a converter. Then when the user tries to drop the dropdown nothing happens. Like this...

Same combo box with MaxDropdownHeight = 0

Tuesday, April 12, 2016

Extracting the real reason for a 500 error from Reporting Services

One of the things that makes our lives more difficult is when Microsoft returns generic error messages that simply mean "Something went wrong". In this case we find that Reporting Services returns error 500 when the report is missing, invalid, missing a data source, is called with bad parameters, or any of a hundred other possible problems.

Up until now I have simply pasted the URL into a web browser to see the real problem I really wanted something better.

The code is fairly simple. I create a web request and grab the response.

Dim URL As String = "http://MyReportServer/ReportServer?/REQUISITION&rs:Format=PDF&rs:Command=Render"
Dim Request As System.Net.HttpWebRequest = Nothing
Dim Response As System.Net.HttpWebResponse = Nothing
Dim Stream As System.IO.Stream = Nothing

Request = System.Net.WebRequest.Create(URL)
Request.UseDefaultCredentials = True
Request.PreAuthenticate = True
Request.Timeout = 300000
Response = Request.GetResponse()
Stream = Response.GetResponseStream()

If a problem occurs the call to GetResponse will throw an exception indicating the server returned a 500 server error. But the response stream actually contains the real error message embedded in an HTML page. So in the catch block try this code...

Dim ErrorMsg As String = ex.Message
If TypeOf ex Is System.Net.WebException Then
   Dim WE As System.Net.WebException = DirectCast(ex, System.Net.WebException)
   Dim Buffer(WE.Response.GetResponseStream().Length) As Byte
   Dim HTML As String
   Dim pLI As Integer, pA As Integer
   WE.Response.GetResponseStream().Read(Buffer, 0, WE.Response.GetResponseStream().Length)
   HTML = System.Text.Encoding.Default.GetString(Buffer)
   pLI = HTML.IndexOf("<li>") + 4
   pA = HTML.IndexOf("<a ", pLI)
   If pLI > -1 AndAlso pA > -1 Then
      ErrorMsg = HTML.Substring(pLI, pA - pLI)
   End If
End If
Throw new Exception(ErrorMsg)

Tuesday, March 15, 2016

Configuring Reporting Services to be run from a middle tier when not in a domain

Earlier I wrote a blog entry on how I configured Reporting Services to be run from a middle tier using NETWORK_SERVICE. Today I had to figure out how to do the same thing when the middle tier and the reporting server are not in a domain - they are in two separate work groups.

The obvious thing to do is to create an identical user locally on each server, same user - same password. Let's assume we created a non-administrator user called ReportServiceUser. We made the user non-administrator because we care about security.

Next, you logon to the middle tier, find the application pool that hosts your WCFService, and use ReportServiceUser as the identity.

Then you logon to the Report Server and give ReportServiceUser access to the site and the reports. This is all very similar to the procedure explained in the earlier blog entry.

Try to run the report through Report Manager from the middle tier and you may or may not succeed. But if you use your application to run the report from the WCFService you will fail. You will also see that the application pool has stopped. Open up event viewer on the middle tier and look at the System events...


First we got three warnings, then we got an error. The error tells us the services was stopped but it's the first warning that has the important error code.


Simply searching Google on the error code will normally tell you the next step to take. In this case we find that ReportServiceUser does not have the required roles to act as an application pool identity. You need to give the user the ability to logon as a batch job and the ability to logon as a service.

Browse to Administrative Tools -> Local Security Policy. Then choose User Rights Assignment.


You need to add ReportServiceUser to both the highlighted rights. To do this, right-click one of them and select "Properties". In the popup click on the [Add User or Group] button and add ReportServiceUser. Then repeat for the other.


Now recycle the application pool to use these new capabilities.