Click here to Skip to main content
15,867,568 members
Articles / Web Development / ASP.NET

A Silverlight Expense Report Module using View Model (MVVM)

Rate me:
Please Sign up or sign in to vote.
4.82/5 (18 votes)
30 Aug 2010Ms-PL6 min read 64.5K   2.5K   58   17
This module allows you to easily gather, and process Expense Reports, from users in your DotNetNuke portal

Image 1

Live example at this: Link (registration and login required)

Note: Make sure you install, and run LinqPrep before installing the module. For directions on installing DotNetNuke modules, see the example at this link.

Silverlight View Model Series:

A Silverlight Expense Report Module

This module allows you to easily gather, and process Expense Reports, from users in your DotNetNuke portal.

Let's first look at the application:

Image 2

When portal users access the module, they will enter a name and a description for each of their Expense Reports.

Image 3

They will enter the report details in the bottom section.

Image 4

Because this is a Silverlight application, they can attach a scan of their receipt, that can be any size.

Image 5

They simply click, Insert to add the detail item.

Image 6

  • The line items show up in a sortable Grid.
  • Items can be deleted by clicking the "X" next to each line (a confirmation box will show).
  • The scans can be viewed by clicking the paper clip icon.
  •  A summary and total is displayed on the right side.

Image 7

The Expense Report can then be Printed.

Image 8

The Expense Report, is printed, using a template that can easily be altered using Microsoft Expression Blend.

The default template provides a line to sign, and date the report.

Image 9

For some organizations, it is convenient to have the users scan in their signed Expense Report.

Image 10

When an Administrator logs in, they will see a dropdown that shows the users that have submitted an Expense Report. They can select the user to see all their reports.

Image 11

The Administrator will usually Lock the report while they are processing it.

Image 12

The original user, will still be able to see the report, but they will not be able to make any changes unless the Administrator unlocks it.

Image 13

The Administrator will usually mark it Approved after reviewing all the scanned receipts. This allows the accounting personnel to know when they should reimburse the user.

After accounting has reimbursed the user, the Expense Report is then marked Completed.

Advantages of Using Silverlight

  • It is faster - When viewing the Expense Reports for a single user, there are no post-backs. The application moves considerably faster than a normal web application.
  • It will not time-out - Normal web applications require you to input something every 20 minutes, or you will be timed-out, and will lose any un-saved information.
  • Large file uploads - Users can upload scans of any size.
  • It can be re-designed with no code - This allows a designer, to completely redesign this application using Microsoft Expression Blend, with no code changes. Simply open the source up and make changes. When you compile the changes, it will create a "ExpenseReports.xap" file. Simply replace the file in the "DesktopModules\ExpenseReports\ClientBin" directory and you're done!

View Model / MVVM

Image 14

This application is designed using View Model (MVVM) structure.

Image 15

The biggest benefit, is that it allows a Developer to create an application with no User Interface (UI). A Designer is then able to create the entire UI using Microsoft Expression Blend 4+, without writing a single line of code.

If you are new to View Model Style it is suggested that you read Silverlight View Model Style : An (Overly) Simplified Explanation for an introduction.

Image 16

Above, is an overview of the Database, and the Web Service.

Image 17

Above, is the overview of the classes for the View Model and the Model.

Image 18

Above, shows the Website, and Silverlight project files.

Walk-Thru Of The Application Starting

To give you an idea of the flow of the application, we will look at how the application starts.

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
id="silverlightControl">
<param name="source" value="<%=SilverlightSourceParams %>" />
<param name="InitParams" value="<%=SilverlightInitParams %>" />
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.41108.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.41108.0" style="text-decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight"
style="border-style: none" />
</a>
</object>

The first thing to load is the Silverlight Control code. The markup is coded to allow SilverlightSourceParams, and SilverlightInitParams, to be set at run-time.

 SilverlightSourceParams = this.TemplateSourceDirectory + "/ClientBin/ExpenseReports.xap";
SilverlightInitParams = string.Format("UserId={0},RIAKey={1},PortalId={2},IPAddress={3},IsAdmin={4}," +
     "AuthUserId={5},CurrentUser={6}",
     UserIdToAdminister.ToString(), strRIAKey, PortalId.ToString(), this.Request.UserHostAddress,
     boolIsAdmin.ToString(), UserId.ToString(), UserToAdminister);

The values are set using the code above. A RIAKey is created for the current user and placed in the ExpenseReports_RIAUser table. This RIAKey will be used on all subsequent requests from the Silverlight application.

The App.xaml.cs file, in the Silverlight application, receives all the parameters and stores them in the Resources of the application, under a key called "RIAAuthenticationHeader":

private void Application_Startup(object sender, StartupEventArgs e)
{
    // Create RIAAuthenticationHeader
    RIAAuthenticationHeader RIAAH = new RIAAuthenticationHeader();
    RIAAH.UserID = Convert.ToInt32(e.InitParams["UserId"]);
    RIAAH.Password = Convert.ToString(e.InitParams["RIAKey"]);
    RIAAH.PortalID = Convert.ToInt32(e.InitParams["PortalId"]);
    RIAAH.IPAddress = Convert.ToString(e.InitParams["IPAddress"]);
    RIAAH.IsAdmin = Convert.ToBoolean(e.InitParams["IsAdmin"]);
    RIAAH.AuthUserID = Convert.ToInt32(e.InitParams["AuthUserId"]);
    RIAAH.Username = Convert.ToString(e.InitParams["CurrentUser"]);

    // Add RIAAuthenticationHeader to Application Resources
    Application.Current.Resources.Add("RIAAuthenticationHeader", RIAAH);

    this.RootVisual = new MainPage();
}

Next, the MainPage.xaml file (the View), contains the code, that specifies that the MainPageModel class is its View Model:

<UserControl.DataContext>
<local:MainPageModel/>
</UserControl.DataContext>

the MainPageModel class calls GetReportsFromModel in it's constructor:

#region GetReportsFromModel
private void GetReportsFromModel()
{
    // Clear the Report Collection
    colReports.Clear();

    // Call the Model to get the Reports
    Model.GetReports((sender, EventArgs) =>
    {
        if (EventArgs.Error == null)
        {
            // loop thru each item
            foreach (var Report in EventArgs.Result)
            {
                // Add to the Reports collection
                colReports.Add(Report);
            }

            // If we have any Reports...
            if (colReports.Count > 0)
            {
                //set the selected item value to the first one
                SelectedReportIndex = 0;

                // Set the Current Report
                GetReportFromModel(EventArgs.Result[0].ID);
            }
            else
            {
                // There are no Reports
                SetToNewReport();
            }

            #region Process Any Errors
            // Clear any Errors
            colErrors.Clear();

            // Show any errors
            foreach (var Report in EventArgs.Result)
            {
                if (Report.Errors.Count > 0)
                {
                    foreach (var item in Report.Errors)
                    {
                        colErrors.Add(item);
                    }
                }
            }

            // Set the visibility of the Message ListBox
            ErrorsVisibility = (colErrors.Count > 0) ? Visibility.Visible : Visibility.Collapsed;
            #endregion
        }
    });
}
#endregion

It calls the GetReports method in the Model, and processes the result, and fills the colReports collection, that is displayed in the drop down at the top of the page in the View.

The GetReports method, like all the methods in the Model is very simple. Note that it calls the GetAuth method, that gets the contents of the RIAAuthenticationHeader class and passes it's parameters to the web service:

#region GetReports
public static void GetReports(EventHandler<GetReportsCompletedEventArgs> eh)
{
    // Set up web service call
    WebServiceSoapClient WS = new WebServiceSoapClient();

    // Set the EndpointAddress
    WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());

    WS.GetReportsCompleted += eh;
    WS.GetReportsAsync(GetAuth());
}
#endregion

It calls a slightly more complicated web method, that is complicated mostly because it has to perform security:

#region GetReports
[WebMethod]
public List<ExpenseReports_Report> GetReports(RIAAuthenticationHeader Auth)
{
    // Make a empty collection so that something will always be returned
    List<ExpenseReports_Report> colExpenseReports_Report = new List<ExpenseReports_Report>();

    // See if the user is authorized
    RIAAuthentication RIAAuth = new RIAAuthentication(Auth);
    if (RIAAuth.IsUserValid())
    {
        try
        {
            ExpenseReportsDBDataContext db = new ExpenseReportsDBDataContext();

            // Unless a User is an Administrator, set the UserID to AuthUserID
            // to prevent a non Administrator from getting somone elses Reports
            if (!RIAAuth.IsAdmin())
            {
                Auth.UserID = Auth.AuthUserID;
            }

            // Get The Reports
            var reports = from ExpenseReports_Reports in db.ExpenseReports_Reports
                            where ExpenseReports_Reports.UserID == Auth.UserID
                            orderby ExpenseReports_Reports.InsertDate descending
                            select ExpenseReports_Reports;

            foreach (var item in reports)
            {
                // Set the ExpenseReports_Details to null
                // To prevent it from being returned (because it is not needed)
                // Also to prevent the web service call from failing when trying
                // To return a complex type
                item.ExpenseReports_Details = null;
                colExpenseReports_Report.Add(item);
            }
        }
        catch (System.Exception ex)
        {
            // If there are errors, add them to the response
            ExpenseReports_Report ER = new ExpenseReports_Report();

            ER.ID = -1;
            ER.Errors.Add(ex.Message);

            colExpenseReports_Report.Add(ER);
        }
    }

    // Return the response
    return colExpenseReports_Report;
}
#endregion

Image 19

The colReports collection (in the View Model), is bound to the drop down (in the View), and it automatically fills when the web service returns its response.

Pointers To Others Parts Of The Code

I have written a number of blogs and tutorials over the past few weeks that cover many of the techniques and code used.

Behaviors To Eliminate Code Behind

The only reason we want to eliminate code behind (and put all code in the View Model and supporting classes), is that our Designers may not be programmers. If we have any code behind, and the Designer accidently deletes something in the UI (the View), they will need a programmer to put things back together!

Usually in my projects, after the initial version, I do not touch any file with a .xaml extensions, and the Designers do not touch anything with a .cs extension. This works if I do not use any code behind.

As Ian Lackey told me once, 99% of the time you think you need code behind, you can use a Behavior. This project uses the following Behaviors:

DataGrid Helper Class

I wrote the following Blog explaining how the delete button works on the DataGrid:

Validation

I wrote the following CodeProject article to explain how the server side validation works:

File Upload

The code for the File Upload is covered in this CodeProject article:

Properties, Collections, And ICommands (and maybe Behaviors and Value Converters)

Thee are many variations to the View Model / MVVM pattern, but, when starting out, it is sometimes helpful to try to limit yourself to using just Properties, Collections, And ICommands. You may also need to use Behaviors and Value Converters. I have found that this is all that is needed, even in my most complex applications.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer (Senior) http://ADefWebserver.com
United States United States
Michael Washington is a Microsoft MVP. He is a ASP.NET and
C# programmer.
He is the founder of
AiHelpWebsite.com,
LightSwitchHelpWebsite.com, and
HoloLensHelpWebsite.com.

He has a son, Zachary and resides in Los Angeles with his wife Valerie.

He is the Author of:

Comments and Discussions

 
QuestionWould It be possible to apply this to LightSwitch? Pin
garthhenderson10-Sep-10 8:12
garthhenderson10-Sep-10 8:12 
AnswerRe: Would It be possible to apply this to LightSwitch? Pin
defwebserver10-Sep-10 9:49
defwebserver10-Sep-10 9:49 
GeneralAwesome sir! Pin
Sushant Joshi8-Sep-10 17:45
Sushant Joshi8-Sep-10 17:45 
GeneralRe: Awesome sir! Pin
defwebserver8-Sep-10 19:56
defwebserver8-Sep-10 19:56 
GeneralMy vote of 5 Pin
Richard Waddell7-Sep-10 3:57
Richard Waddell7-Sep-10 3:57 
GeneralRe: My vote of 5 Pin
defwebserver8-Sep-10 19:57
defwebserver8-Sep-10 19:57 
GeneralMy vote of 5 Pin
atmeginnis31-Aug-10 3:56
atmeginnis31-Aug-10 3:56 
GeneralRe: My vote of 5 Pin
defwebserver31-Aug-10 4:20
defwebserver31-Aug-10 4:20 
GeneralExcellent Pin
Marcelo Ricardo de Oliveira31-Aug-10 3:21
mvaMarcelo Ricardo de Oliveira31-Aug-10 3:21 
GeneralRe: Excellent Pin
defwebserver31-Aug-10 4:20
defwebserver31-Aug-10 4:20 
GeneralAt first there was only grey... And then the explosion of colour!!!! Pin
Alan Beasley30-Aug-10 22:20
Alan Beasley30-Aug-10 22:20 
GeneralRe: At first there was only grey... And then the explosion of colour!!!! Pin
defwebserver31-Aug-10 2:30
defwebserver31-Aug-10 2:30 
GeneralMy vote of 5 Pin
linuxjr30-Aug-10 21:35
professionallinuxjr30-Aug-10 21:35 
GeneralRe: My vote of 5 Pin
defwebserver31-Aug-10 2:23
defwebserver31-Aug-10 2:23 
GeneralMy vote of 5 Pin
Kunal Chowdhury «IN»30-Aug-10 19:47
professionalKunal Chowdhury «IN»30-Aug-10 19:47 
NewsRe: My vote of 5 Pin
Kunal Chowdhury «IN»30-Aug-10 19:49
professionalKunal Chowdhury «IN»30-Aug-10 19:49 
GeneralRe: My vote of 5 Pin
defwebserver30-Aug-10 19:49
defwebserver30-Aug-10 19:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.