Quantcast
Channel: eXpandFramework RSS
Viewing all 861 articles
Browse latest View live

BLOG by Martynas Dauciunas: Co2 Emission calculator

$
0
0
This is a small tool that I did according specific client requirements.
Tool is based on the "Carbon Footprint" method, which gives magnitudes, not the exact figures, of the carbon emission linked to the activity of a company, taking into account electricity production, transportation, raw material production, waste treatment, etc.
The objective was to supply the company with recommendations to mitigate Greenhouse Gas emissions at lowest cost.


Key features of the software:
  • Portable (single executable)
    • Doesn't require .Net framework 3.5 or SQL engine to be installed on the client machine, though both are being used by the software (which is fantastic for the developer and the customer)
    • Can be run by lowest privilege user on a Windows PC (doesn't require elevated privileges)
  • Lightweight (all the software with SQL and .Net 3.5 framework embedded inside the executable - weighs only ~60Mb)
  • Rich and Intuitive User Experience (DevExpress components)
  • MultiLanguage interface
Technology used:
  • ORM (XPO and Entity Framework)
  • Application Virtualization (XenoCode)
ScreenShots:








BLOG by Martynas Dauciunas: TP.Shell.XAF - ERP for manufacturing facilities

$
0
0
TP.Shell.XAF - ERP application, with unique capabilities such like integrated ACad, Visio, Intergraph drawing Viewer, predefined custom industry specific data engineering data classes and other.

Few screenshots of the application:




Leak Detection and Repair Dashboard:



Risk Based Inspection (interactive questionnaire and Risk Matrix)


Torque Calculator


Industry specific Valve Standard object

BLOG by Martynas Dauciunas: MultiEdit component for DevExpress Express Application Framework (XAF)

$
0
0
It is a common situation, that You want to update multiple objects.
DevExpress recommends writing a controller for each and every time You want to do so.

Well, i don't think so. I've implemented a "MultiEdit" controller, so one can easily update a list of objects.


Say You have a list of objects - like this :

and You want to update Fields like "Design pressure" for all selected objects.
Of course You can open each of them, and change them one by one. But :)

Select "MultiEdit" - that shows a Detail View for a objects selected, change the property You want, and press OK



It then Show's You what will be changed, and the progress of operation if very many objects are being updated.


BLOG by Martynas Dauciunas: Excel Importer for Express Application Framework (XAF)

$
0
0
One more common situation when working with XAF is that in many cases You usually get a lot of data in Excel SpreadSheets,You need to import to Your New Application.

So instead of writing a separate controller for each different excel file, o forcing people to use some predefined template, here what I came up with.


Wizard Page 1 lets You select Excell 2007 or newer file, select a Sheet that You want to import data from and see a PreView of the Data:


Next You can Map Excel columns to Your object Properties (or Let the Import Wizard try to Guess the mappings)


And See the progress of data import that is done in a separate thread.

BLOG by Martynas Dauciunas: Merge similar referenced objects controller for XAF

$
0
0
I've noticed that modern application usually have Dirty dictionaries
and there are no means of cleaning those dictionaries.

What do i mean by Dirty dictionaries ? here's a sample:

You have a list of whatever type of Objects, and You what to group them by one of the fields.
In this situation We're trying to group data object by property "Class". Here's what we get:




































 If we take a look at the "Dictionary", here's what we get:

Look's like that classe`es are identical... but.
What You'd what in such situation is somehow "Join" the 2 instances. An inexperienced user would simply delete one of them. But that doesn't solve the problem.

A clever SQL script here is not really possible. Why ?
Because it's after 5 years of working with SQL I have no idea how to write simple a script to find all objects using this dictionary value. It is possible of course. but.. I've found a better way :)







Here's how it works.
1. You go the the dictionary. Select object's that You want to Join and click Merge:























Then You Get a screen that Show's what will be merged, and asks You to select one that You want to stay:
Click OK, and The Controller collects (finds) ALL referencing objects that use one of the values being merged (Joined), and replaces them with the correct value.

BLOG by Martynas Dauciunas: TimeSpan Editor for XAF

$
0
0
Here's a simple solution for entering TimeSpan data in a casual style : " 1 day 39 hours 78 min"


The Solutions is to create a prpoperty editor that hosts simple Text editor, with some custom logic undeneath, that parses the string and converts it to TimeSpan after entering the string. And also Convert the timespan form the database to string, to be displayed in the casual format.


using System;
using System.Text.RegularExpressions;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Model;
using DevExpress.ExpressApp.Win.Editors;
using DevExpress.XtraEditors.Mask;
using DevExpress.XtraEditors.Repository;

namespace Solution2.Module.Win
{
[PropertyEditor(typeof(TimeSpan))]
public class DurationPropertyEditor : DXPropertyEditor
{
public DurationPropertyEditor(Type objectType, IModelMemberViewItem model)
: base(objectType, model)
{

}

protected override object CreateControlCore()
{
return new StringEdit();
}

protected override void SetupRepositoryItem(RepositoryItem item)
{
base.SetupRepositoryItem(item);

((RepositoryItemStringEdit)item).Mask.MaskType = MaskType.RegEx;
((RepositoryItemStringEdit)item).Mask.EditMask
= @"\s*((\d?\d?\d?\s*(d(ays?)?)))?\s*((\d?\d?\s*(h(ours)?)?))?\s*(\d?\d?\s*(m(in(utes)?)?)?)?";

if (Control == null) return;

Control.ShowToolTips = true;
Control.ToolTip =
" Examples: " + Environment.NewLine +
" 1d = 1 Day" + Environment.NewLine +
" 1 day = 1 Day" + Environment.NewLine +
" 2d 5h 45 m = 2 Days 5 Hours 45 minutes" + Environment.NewLine +
" 2 days 4 hours 25 min = 2 Days 4 Hours 25 minutes" + Environment.NewLine
;
Control.EditValueChanged += Control_EditValueChanged;
}

void Control_EditValueChanged(object sender, EventArgs e)
{
WriteValue();
OnControlValueChanged();
}

protected override object GetControlValueCore()
{
return ParseTimeSpan(Control.Text);
}

protected override void ReadValueCore()
{
Control.EditValue = DecodeTimeSpan((TimeSpan) PropertyValue);

}


public static TimeSpan ParseTimeSpan(string s)
{
const string Quantity = "quantity";
const string Unit = "unit";

const string Days = @"(d(ays?)?)";
const string Hours = @"(h((ours?)|(rs?))?)";
const string Minutes = @"(m((inutes?)|(ins?))?)";
const string Seconds = @"(s((econds?)|(ecs?))?)";

var timeSpanRegex = new Regex(
string.Format(@"\s*(?<{0}>\d+)\s*(?<{1}>({2}|{3}|{4}|{5}|\Z))",
Quantity, Unit, Days, Hours, Minutes, Seconds),
RegexOptions.IgnoreCase);
var matches = timeSpanRegex.Matches(s);

var ts = new TimeSpan();
foreach (Match match in matches)
{
if (Regex.IsMatch(match.Groups[Unit].Value, @"\A" + Days))
{
ts = ts.Add(TimeSpan.FromDays(double.Parse(match.Groups[Quantity].Value)));
}
else if (Regex.IsMatch(match.Groups[Unit].Value, Hours))
{
ts = ts.Add(TimeSpan.FromHours(double.Parse(match.Groups[Quantity].Value)));
}
else if (Regex.IsMatch(match.Groups[Unit].Value, Minutes))
{
ts = ts.Add(TimeSpan.FromMinutes(double.Parse(match.Groups[Quantity].Value)));
}
else if (Regex.IsMatch(match.Groups[Unit].Value, Seconds))
{
ts = ts.Add(TimeSpan.FromSeconds(double.Parse(match.Groups[Quantity].Value)));
}
else
{
// Quantity given but no unit, default to Hours
ts = ts.Add(TimeSpan.FromHours(double.Parse(match.Groups[Quantity].Value)));
}
}
return ts;
}

public static string DecodeTimeSpan(TimeSpan timeSpan)
{

var time = string.Empty;

if (timeSpan.Days > 0)
time = timeSpan.Days + " Days";


if (timeSpan.Hours > 0)
time += (time != string.Empty ? " " : "") + timeSpan.Hours + " Hours";


if (timeSpan.Minutes > 0)
time += (time != string.Empty ? " " : "") + timeSpan.Minutes + " Minutes";

return time;
}
}
}



BLOG by Martynas Dauciunas: Excel Import Wizard Demo from eXpandFramework

$
0
0
Here's a short video on how Excel import wizard that I've recently published to eXpandFramework works :)

BLOG by Martynas Dauciunas: How to Include eXpandFramework module into existing XAF application

$
0
0

 

This post describes how to add a feature from eXpandFramework into an existing XAF application.


1. Download the latest sources or binaries from here

Make sure that DevExpress engine that You're using matches with the one that eXpand was built on

 
2. In Case You've decided to download Source's - You'll need to build them first.

First make sure thet DX version matches (if not run the DX Project converter tool)

Then run buildall32bit.cmd or buildall64bit.cmd depending on Your system.

You'll find your build results  in Xpand.DLL folder

 
3. Add necessary references to Your project.

In this case : How to Add Excel import Wizard module to an Existing XAF application.
Add references, in Your main module, to:
  • Xpand.ExpressApp.dll
  • Xpand.ExpressApp.ImportWiz.dll
   Add reference, in Your Win module, to:
  • Xpand.ExpressApp.dll
  • Xpand.ExpressApp.Win.dll
  • Xpand.ExpressApp.ImportWiz.Win.dl


Add following code to Module.Designer.cs 
//
this.RequiredModuleTypes.Add(typeof(Xpand.ExpressApp.ImportWiz.ImportWizModule))
//
Add folowing code to WinModule.Designer.cs

//
this.RequiredModuleTypes.Add(typeof(ImportWizWinModule));
//


4. Run the application and use the New Module :)



Here's a blog post with video on How it Works
and a sample Solution that You can Download

BLOG by Manuel Grundner: Real MTier with DevExpress (Part 1)

$
0
0

Technologie overview

First of all one thing:

I like DevExpress and their controls, but i'm dissatisfied with the speed of the development on the mobile sector with large scaling XAF applications and the way they are managing their APIs. The options for developers are very restrictive in a manor of extensibility (thanks to VB.NET customers, i think. We develop C#. WE know what we shall or not shall do!). Why the fuck is every second method we like to use is fucking Browsable(false) or internal or is only reachable with real reflection pain? "For internal use only"? Are you fucking kidding me? DevExpress, please think of us developers when designing your APIs, not business analysts and script kiddies :(

Phew, that has done well. :)

Our main product is published to a wide range of clients, from small size scaling up to the enterprise sector.

One thing i really miss in our Portfolio are native apps for all kind of customers mobile devices. One real need for native apps is offline availability of the clients data (enterprise customers need to access their data anytime, regardless of connectionstate). So a Web solution will NOT meet our customers needs. Gladly we found Mono For Android and the MonoTouch framework from the Xamarin guys.

Technologie decision

But what data transport protocol should we use to support all upcomming platforms? WCF? SOAP? OData?

I think pure WCF is a overkill for most of our needs, and there is no tooling on all of our planned supported platforms (IOS for example).

SOAP has the problem that the effort to implement and extend a service for all our needs will take too long, and is a horror in maintainability across our customer needs. There is so much work on the meta-model before we get anything out/back from/to our database model.

DevExpress and OData

Then, all of a sudden, DevExpress announced support for OData in combination with XPO. Hurray! Getting the hands on the first versions and the result was sobering. :(

We have a huge XPO-Model (800+ PeristentClasses) with a legacy Database (migrating from our delphi application) and a legacy XPO-Model (many many many rookie mistakes was made in the beginning of migration).

Our Namespace model looks like something like this:

  • OurProduct.Model.Products

    • Product.cs
    • ProductCategory.cs
  • OurProduct.Model.Customer

    • Customer.cs
    • ContactInformation.cs

The problem here is there is no way to tell the XPO-Context-Driver to export complex types across different namespaces. That means we have to fetch up our data clientside with multiple queries to the OData-Service which is annoying and not very performant.

The second thing: The documentation was terrible. I don't know if there was a ability to filter out some of our classes from the ResourceSets.

So we decided to wait until DevExpress brings us new features with the next releases.

Starting with DXperience-12.1 they did a great job of cleaning the direct dependency of XAF on XPO. Nice stuff. And tadaaa there was the direct integration of OData into XPO!

And yes in this version the filtering of the ResourceSets is integrated! Also the Namespace is included in the Classname (sure not pretty, but hey it works!). Now we can start using this stuff and do some really cool things.

The Code

The code is pretty strate forward:

using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using DevExpress.Xpo.DB;
using DevExpress.Xpo;
using System.ServiceModel;
using DevExpress.Persistent.BaseImpl;

namespace MultitierSolution.OData
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class MultitierSolutionODataService : XpoDataService
    {

        public MultitierSolutionODataService() : base(new MultitierSolutionContext("XpoContext", "MultitierSolutionModel", CreateDataLayer())) { }

        static IDataLayer CreateDataLayer()
        {
            string conn = MySqlConnectionProvider.GetConnectionString("servername", "user", "password", "database");
            DevExpress.Xpo.Metadata.XPDictionary dict = new DevExpress.Xpo.Metadata.ReflectionDictionary();
            // Initialize the XPO dictionary. 
            dict.GetDataStoreSchema(typeof(Event).Assembly);
            IDataStore store = XpoDefault.GetConnectionProvider(conn, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists);
            return new ThreadSafeDataLayer(dict, store);
        }
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            config.DataServiceBehavior.AcceptProjectionRequests = true;
        }
    }

    public class MultitierSolutionContext : XpoContext
    {
        public MultitierSolutionContext(string containerName, string namespaceName, IDataLayer dataLayer)
            : base(containerName, namespaceName, dataLayer) { }

        public override bool HideMetaDataResourceProperty(Type classType, string propertyName)
        {
            if (classType == typeof(Event) && propertyName == "resourceIds")
                return true;
            return false;
        }

        public override bool HideMetaDataResourceSet(Type classType)
        {
            if (classType == typeof(Event))
                return false;

            return true;
        }
    }
}

Project overview of ODataService

Now whe have what we want. Only events are publised to the OData-Serivce:

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>    <service xml:base="http://localhost/MultitierSolution.OData/ODataDemoService.svc/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns="http://www.w3.org/2007/app"><workspace><atom:title>Default</atom:title><collection href="DevExpress_Persistent_BaseImpl_Event"><atom:title>DevExpress_Persistent_BaseImpl_Event</atom:title></collection></workspace></service>

And the result:

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?><feed xml:base="http://localhost/MultitierSolution.OData/ODataDemoService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"><title type="text">DevExpress_Persistent_BaseImpl_Event</title><id>http://localhost/MultitierSolution.OData/ODataDemoService.svc/DevExpress_Persistent_BaseImpl_Event</id><updated>2012-07-21T22:58:09Z</updated><link rel="self" title="DevExpress_Persistent_BaseImpl_Event" href="DevExpress_Persistent_BaseImpl_Event" /><entry><id>http://localhost/MultitierSolution.OData/ODataDemoService.svc/DevExpress_Persistent_BaseImpl_Event(guid'61bf9c11-a05e-46fd-9a82-4eb91dabb1a2')</id><title type="text"></title><updated>2012-07-21T22:58:09Z</updated><author><name /></author><link rel="edit" title="DevExpress_Persistent_BaseImpl_Event" href="DevExpress_Persistent_BaseImpl_Event(guid'61bf9c11-a05e-46fd-9a82-4eb91dabb1a2')" /><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/recurrencePattern" type="application/atom+xml;type=entry" title="recurrencePattern" href="DevExpress_Persistent_BaseImpl_Event(guid'61bf9c11-a05e-46fd-9a82-4eb91dabb1a2')/recurrencePattern" /><category term="MultitierSolutionModel.DevExpress_Persistent_BaseImpl_Event" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><content type="application/xml"><m:properties><d:oid m:type="Edm.Guid">61bf9c11-a05e-46fd-9a82-4eb91dabb1a2</d:oid><d:Subject>test3</d:Subject><d:Description></d:Description><d:StartOn m:type="Edm.DateTime">2012-07-21T14:00:00</d:StartOn><d:EndOn m:type="Edm.DateTime">2012-07-21T15:30:00</d:EndOn><d:AllDay m:type="Edm.Boolean">false</d:AllDay><d:Location></d:Location><d:Label m:type="Edm.Int32">0</d:Label><d:Status m:type="Edm.Int32">2</d:Status><d:Type m:type="Edm.Int32">0</d:Type><d:RecurrenceInfoXml m:null="true" /></m:properties></content></entry><entry><id>http://localhost/MultitierSolution.OData/ODataDemoService.svc/DevExpress_Persistent_BaseImpl_Event(guid'af62e758-f181-4702-8712-76111fb4705f')</id><title type="text"></title><updated>2012-07-21T22:58:09Z</updated><author><name /></author><link rel="edit" title="DevExpress_Persistent_BaseImpl_Event" href="DevExpress_Persistent_BaseImpl_Event(guid'af62e758-f181-4702-8712-76111fb4705f')" /><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/recurrencePattern" type="application/atom+xml;type=entry" title="recurrencePattern" href="DevExpress_Persistent_BaseImpl_Event(guid'af62e758-f181-4702-8712-76111fb4705f')/recurrencePattern" /><category term="MultitierSolutionModel.DevExpress_Persistent_BaseImpl_Event" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><content type="application/xml"><m:properties><d:oid m:type="Edm.Guid">af62e758-f181-4702-8712-76111fb4705f</d:oid><d:Subject>some stuff</d:Subject><d:Description></d:Description><d:StartOn m:type="Edm.DateTime">2012-07-21T07:30:00</d:StartOn><d:EndOn m:type="Edm.DateTime">2012-07-21T12:30:00</d:EndOn><d:AllDay m:type="Edm.Boolean">false</d:AllDay><d:Location></d:Location><d:Label m:type="Edm.Int32">3</d:Label><d:Status m:type="Edm.Int32">2</d:Status><d:Type m:type="Edm.Int32">0</d:Type><d:RecurrenceInfoXml m:null="true" /></m:properties></content></entry><entry><id>http://localhost/MultitierSolution.OData/ODataDemoService.svc/DevExpress_Persistent_BaseImpl_Event(guid'e7d053b8-1970-4c0d-b6a3-c6c3dd3ca83a')</id><title type="text"></title><updated>2012-07-21T22:58:09Z</updated><author><name /></author><link rel="edit" title="DevExpress_Persistent_BaseImpl_Event" href="DevExpress_Persistent_BaseImpl_Event(guid'e7d053b8-1970-4c0d-b6a3-c6c3dd3ca83a')" /><link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/recurrencePattern" type="application/atom+xml;type=entry" title="recurrencePattern" href="DevExpress_Persistent_BaseImpl_Event(guid'e7d053b8-1970-4c0d-b6a3-c6c3dd3ca83a')/recurrencePattern" /><category term="MultitierSolutionModel.DevExpress_Persistent_BaseImpl_Event" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><content type="application/xml"><m:properties><d:oid m:type="Edm.Guid">e7d053b8-1970-4c0d-b6a3-c6c3dd3ca83a</d:oid><d:Subject>test</d:Subject><d:Description></d:Description><d:StartOn m:type="Edm.DateTime">2012-07-21T01:00:00</d:StartOn><d:EndOn m:type="Edm.DateTime">2012-07-21T01:30:00</d:EndOn><d:AllDay m:type="Edm.Boolean">true</d:AllDay><d:Location></d:Location><d:Label m:type="Edm.Int32">0</d:Label><d:Status m:type="Edm.Int32">2</d:Status><d:Type m:type="Edm.Int32">0</d:Type><d:RecurrenceInfoXml m:null="true" /></m:properties></content></entry></feed>

WinRT Client Consumption

This task is like the use of a regular WCF-Service. Use the Add Service Reference command in Visual Studio:

Add Service Reference for OData in Visual Studio

Rebuild. Booom! What the heck!? It's not compiling anymore. According to a bug in the code generation of VS12 in the xaml designers we cannot fix this error now :(

But you catch the idea, right?

Shame on you Microsoft! :>

Mono For Android Client Consumption

This task is a little bit trickier (for sure, its not MS technologie). But hey, we've got still tools to help us here.

I've found a blog post to create the client proxy on a Mono based platform.

Create a custom tool entry under Visual Studio Tools/External Tools... to make this task a little bit more comfortable.

Tools/External Tools... Property Window

C:\Windows\Microsoft.NET\Framework\v4.0.30319\DataSvcUtil.exe
/out:Client.cs /version:2.0 /uri:http://localhost/sampleservice/peoplefeed.svc
$(ProjectDir)

Run the command with our Argument

/out:Client.cs /version:2.0 /uri:http://localhost/MultitierSolution.OData/MultitierSolutionODataService.svc

Add the generated Client.cs file to the Mono For android project and add a reference to System.Data.Services.Client.

I don't know why this is no where documented. Neither on the Mono Documentation nor the Mono for Android documentation.

Further steps

In our next post we implement the android client to see some action!

This blog post is part of the real mtier with devexpress series

Demo Source

The source can be found at Bitbucket

BLOG by Manuel Grundner: FastSchemaProvider

$
0
0

I just released beta (hmm let me think..., okay V0.5) of FastSchemaProvider

Indoduction

We are having a huge defeat regarding upgrading consumer databases by XPO.
XPO isn't capable of extending column widths (varchars..), neither altering foreign keys, primary keys, defaults...

even XPObjectTypes (oh well, this should be possible trough orm, ...)

Goal

This project is intended to perform any schema transitions as fast as possible, taking account the underling database engine and the remote (provided) schema. This tool will to supersede any xpo (entity framework as will) change, regarding your database.

State

Its not yet ready for full productive use, but it will soon (for sql anywhere about Aug 15 2012).

Some parts are still missing:

  • Altering columns taking place in indexes, would cause an error on most databases
  • Same for foreign keys
  • solutons/projects
  • property string length definition => transfer them to staging schema
  • provide general attribute for foreignkeys (sealed i don't care (╯°□°)╯︵ ┻━┻)

i'am at least sorry for bad english ^^

BLOG by Manuel Grundner: FastSchemaProvider V0.6

$
0
0

I've just updated FastSchemaProvider

Next internal steps was to attach a XPO/XAF-Application (internal needs ;-)

So I was refactoring the DBChanger => DVDiff, and sic adding otpions to compare...

Behaves well (but slowly, anybody an speed up option)?

next update in a few days

BlOG by Mike Calvert: .NET Applications on Linux

$
0
0
Well, what a touchy subject this might be to some people.  I have always seen the battle go back and forth between Windows, Macintosh and Linux.  Windows being a middle-tier price range which excellent performance, Macintosh being the high end simply from marketing and Linux being the low end cost point which the most potential. [...]

BLOG by Marcelo Paiva: DevExpress XAF – Introdução

$
0
0

Constantemente encontro com amigos que desejam criar um produto rapidamente e logo sair ganhando dinheiro. Muitos deles querem implementar algo para atender pontualmente certo mercado ou necessidade das pessoas, outros mais enfáticos planejam criar um “facebook 2″.

Sei que muitos não falam mas quando pensam também em qual ferramenta irá utilizar para criar o “inovador” produto para ficar “rico”, querem algo que lhe traga produtividade, até porque é preciso construir rapidamente antes que outro em algum lugar no mundo crie algo parecido.

Vou então fazer hoje uma sugestão de ferramenta e ainda fazer uma pequena introdução sobre ela, no entanto vou dizendo logo que é uma ferramenta paga, sei que praticamente todo mundo na área de desenvolvimento sai da faculdade querendo ter acesso a todas ferramentas gratuitamente, melhor dizendo, pensando assim: “quero usar somente produtos e ferramentas gratuitas e todo que eu fizer com elas vou cobrar”. Não vou entrar muito no mérito da questão pois seria outro post.

O que é bom destacar aqui é o fato de que você precisa antes de empreender qualquer negócio, pode ser ele uma loja de roupas e também a criação de um software, avaliar o investimento necessário é de fundamental importância. Assim como ao abrir uma loja você deverá investir, este mesmo raciocínio serve no momento de criar um produto.

Neste cenário sei que muitos procuram alguma ferramenta de geração de código/produto, eu particularmente no meu histórico profissional já me deparei com algumas como: Genexus (http://www.genexus.com/global/inicio?pt), GAS (http://www.gasweb.com.br), Maker (http://www.softwell.com.br). No entanto hoje estou usando em alguns projetos o XAF (http://www.devexpress.com/Products/NET/Application_Framework/) da Devexpress.

Sei perfeitamente que o XAF não atende em alguns casos e também pode não ser o melhor para algumas pessoas, mas tenho certeza que está atendendo nossa empresa em projetos importantes e trazendo uma produtividade sem igual.

Antes de começar a mostrar um pouco sobre ele, você pode baixar uma versão Trial no link: http://www.devexpress.com/Home/Try.xml

Vamos ao nosso “inovador” cadastro de pessoas usando o XAF. Estamos utilizando o Visual Studio 2012 e após instalar o produto são inseridos vários templates de projetos, dentre eles um grupo do eXpressApp Framework (XAF) que apresenta a tela abaixo (clique para ampliar).


Criaremos um projeto simples chamado “CadastroPessoas” a partir do template “Cross-Plataform Application” que permite que nossa aplicação tenha uma interface Windows e Web ao mesmo tempo.

Veja como fica a solução no VS2012, podemos perceber que o XAF trabalha de forma modular no entanto não se preocupe com os detalhes agora.

A ideia da ferramenta é que você se preocupe com as regras de negócio e ela cuida dos demais detalhes, então  vamos logo criar uma definição de objeto de negócio chamada “Pessoa”. Vamos fazer isso no projeto “CadastroPessoas.Module” na pasta “BusinessObjects” de acordo com a tela abaixo.

Iremos incluir algumas propriedades básicas como Nome, Endereço e Telefone para nossa Classe de negócio de acordo com a tela a seguir.

 

Com isso você já pode executar sua aplicação e poderá fazer o cadastramento das pessoas.

 

Bom aí você poderá me perguntar que “mágica” é essa?  Onde está sendo persistido os objetos ??

Sei que já temos até aqui algumas perguntas interessantes, então vamos apresentando algumas considerações sobre o “jeito” de pensar do XAF.

1 – Por default o XAF irá usar o SQL Express com sua instância padrão e vai criar automaticamente um banco de dados com o nome da solução.

 

2 – Ele cria uma interface padrão de navegação usando (é claro) os componentes do DevExpress que pode ser customizado.

3 – Como ele trabalha de forma modular, neste momento são incluídos somente os módulos básicos. No entanto podemos incluir validações, controle de aparência, relatórios e gráficos.

Vamos criar outros posts cada módulo separadamente incrementando nosso projeto “inovador”.

Espero que você possa entender mais sobre o XAF e considerar o uso desta poderosa ferramenta.

Até mais!

 

BLOG by Marcelo Paiva: DevExpress XAF – Validação

$
0
0

Olá pessoal, vamos continuar nossa séria sobre o XAF.

Se não leu ainda, veja nosso artigo anterior para melhor entender o que estamos falando no link: XAF – Introdução

Neste artigo vamos tratar do assunto validação, como nós criamos um projeto bem simples no artigo anterior que é um cadastro de pessoas e neste caso precisaremos de indicar campos obrigatórios como por exemplo o ‘Nome’ da pessoa.

Vamos ver como funciona isso no XAF!?

Em cada módulo do XAF existe um arquivo ‘Module.cs’ (isto no projeto base .Module, nos demais há uma sigla anterior ao nome) que é usado para fazer a configuração do módulo em questão, aqui então já vale entender porque em nosso exemplo foi criada 1 (uma) solução com 5 (cinco) projetos, no gráfico abaixo explicamos os relacionamentos entre eles e o que é cada um.

  • Projeto.Module = Contém a implementação as entidades de negócio e suas regras gerais
  • Projeto.Module.Web = Mantêm a implementação das regras específicas para o projeto Web
  • Projeto.Module.Win = Mantêm a implementação das regras específicas para o projeto Windows
  • Projeto.Web = Projeto final em Asp.Net
  • Projeto.Win = Projeto final em WindowsForms

Voltando a questão, você deve abrir o arquivo ‘Module.cs’ no projeto base .Module, e através da toolbox incluir o módulo ‘ValitadionModule’ conforme a imagem a seguir:

Com este módulo adicionado podemos então criar nossas regras de validação. Isso pode ser feito de duas maneiras, através do código e também com o editor de modelo (no arquivo Model.xafml), no entanto apresentar aqui como fazer através do código que a meu ver fica mais organizado.

Então ao expandir o arquivo Pessoa.bo temos o arquivo ‘Pessoa.bo.designer.cs’ com a especificação dos atributos da classe e precisaremos adicionar então a validação. Veja abaixo o que precisamos fazer no código para incluir o que precisamos.

Após implementar os passos anteriores e executar sua aplicação já teremos a regra de validação aplicada para os módulos Web e Windows. Veja abaixo como fica a interface validando o atributo ‘Nome’ na aplicação.

Isso é só um pequeno exemplo do que é possível fazer com este módulo de validação. É bastante simples escrever uma regra e indicar onde e quando ela deve ser aplicada. Também podemos colocar controle de aparência condicionado a um valor de atributo qualquer, iremos ver isso no próximo artigo.

Até a próxima.

BlOG by Mike Calvert: How to use the XPCursor

$
0
0
If you are familiar with the DevExpress eXpressApp Framework, than you may have already run into this issue and this will be a great tool for you.  If not, the DevExpress eXpressApp Framework provides a great tool for modeling and opening up your data.  Whether in-house or on a consultant level you will likely benefit [...]

BLOG by Robert Anderson: Installing Ruby with Homebrew and rbenv on Mac OS X Mountain Lion

$
0
0

Install Ruby with rbenv

I decided to setup Octopress on my Mac so that I can publish blog posts from either Windows or MacOS. I’m on OS X 10.8.2.

I tried to follow the Octopress instructions for installing Ruby but ran into a few problems.

Install Homebrew

Homebrew is a package manager for OS X. Open Terminal and install Homebrew with:

$ ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"

Once the installation is successful, you can run the following command to check your environment.

$ brew doctor

Apparently, you should see:

Your system is raring to brew

But instead I had 3 warnings.

Warning: /usr/bin occurs before /usr/local/bin
This means that system-provided programs will be used instead of those
provided by Homebrew. The following tools exist at both paths:

    gcov-4.2

Consider amending your PATH so that /usr/local/bin
occurs before /usr/bin in your PATH.

This one can be fixed by modifying your .profile file. Create it if it doesn’t already exist. Use nano ~/.profile if you don’t have a preferred editor.

.profile
123
# Fix $PATH for homebrewhomebrew=/usr/local/bin:/usr/local/sbinexport PATH=$homebrew:$PATH

Google tells me the other two warnings are related to Mono being installed and can be ignored.

Warning: /Library/Frameworks/Mono.framework detected
This can be picked up by CMake's build system and likely cause the build to
fail. You may need to move this file out of the way to compile CMake.Warning: You have a non-Homebrew 'pkg-config' in your PATH:
  /usr/bin/pkg-config => /Library/Frameworks/Mono.framework/Versions/2.10.9/bin/pkg-config

This was most likely created by the Mono installer. `./configure` may
have problems finding brew-installed packages using this other pkg-config.

Ploughing on…

Install rbenv

Rbenv handles the installation of multiple Ruby environments.

$ brew update
$ brew install rbenv
$ brew install ruby-build

Install gcc

If I try to install Ruby immediately, I get

ERROR: This package must be compiled with GCC, but ruby-build
couldn't find a suitable `gcc` executable on your system.
Please install GCC and try again.
...

XCode used to ship with a compatible gcc, but no longer does. We can install it with Homebrew.

$ brew update
$ brew tap homebrew/dupes
$ brew install autoconf automake apple-gcc42

The new gcc will coexist happily alongside the default one.

Install Ruby 1.9.3

Now we can install Ruby.

$ rbenv install 1.9.3-p194
$ rbenv rehash

Next run

$ ruby --version

Hmm… I get

ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0]

Shouldn’t it say ruby 1.9.3? It turns out you need to add the following to the end of your .profile.

.profile
12
# Initialize rbenvif which rbenv > /dev/null; then eval"$(rbenv init -)"; fi

Now quit and restart Terminal.

$ rbenv global 1.9.3-p194
$ ruby --version
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin12.2.1]

Ruby 1.9.3 is installed correctly. If I quit and restart Terminal, ruby --version is still 1.9.3.

BLOG by Manuel Grundner: How to use Dependency Injection in XAF

$
0
0

Dependency injection

XAF has the ability to provide DI over Domain-Componants, we use XPO so we don't have a chance to use this feature (and yes, i hate static methods! (testing testing testing))

http://en.wikipedia.org/wiki/Dependency_injection

Why?

It's simple. We have a legacy model with a lot of customers, and can't affort to recreate the model all and all over.

Testing abilities are also a huge factor for our development.

How?

It was a really tricky task to tell XAF & XPO the trick of DI (or IOC http://en.wikipedia.org/wiki/Inversionofcontrol)

Okay Let's start

First of all: The sample uses Unity (http://unity.codeplex.com) cause it's well known and supported from microsoft (and fits our needs perfect), but it's also possible to extract this hard dependency through the Service Locator Pattern if you like to. (http://en.wikipedia.org/wiki/Servicelocatorpattern)

The key interfaces!

First we need two simple interface's we can talk to:

public interface IUnityContainerProvider
{
    IUnityContainer UnityContainer { get; set; }
}

public interface IUnityModule
{
    void InitUnityContainer(IUnityContainer unityContainer);
    void UnityContainerInitialized(IUnityContainer unityContainer);
}

The IUnityContainerProvider is used for any class resolved by the UnityContainer to inject himself (we prefer PropertyInjection cause of the Session constructor forced by XPO.

The IUnityModule is intended to be implemented by a DevExpress.ExpressApp.ModuleBase derived type.

The InitUnityContainer is inteded to be called after the Application.Setup() method for each module loaded. The UnityContainerInitialized is called after the InitUnityContainer for each module. So we can override behavior provided by other modules.

How the hell can this work with XAF?

I've played with this really long and still facing out some problems i've seen with our implementation, but this works for almost 2 years now. So i can say it works almost with no problems so long. Till the next XAF update ;)

Children

Unity has the ability to create ChildContainer's from parent containers. This is nice cause the Frame concept in XAF is almost the same for the view handling.

We could reuse this for the domain-logic so we can simply write domain code without having to deal with different Sessions/UnitOfWorks/ObjectSpaces.

Code it please!

Okay okay, dont hustle...

UnityUnitOfWork

First of all we need a UnityUnitOfWork. This Class provides a UnityContainer and stores itself as a instance of type Session and UnitOfWork.

public class UnityUnitOfWork : UnitOfWork, IUnityContainerProvider
{
    public UnityUnitOfWork() { }
    public UnityUnitOfWork(DevExpress.Xpo.Metadata.XPDictionary dictionary) : base(dictionary) { }
    public UnityUnitOfWork(IDataLayer layer, params IDisposable[] disposeOnDisconnect) : base(layer, disposeOnDisconnect) { }
    public UnityUnitOfWork(IObjectLayer layer, params IDisposable[] disposeOnDisconnect) : base(layer, disposeOnDisconnect) { }

    private IUnityContainer _UnityContainer;
    public IUnityContainer UnityContainer
    {
        get
        {
            return _UnityContainer;
        }
        set
        {
            value.RegisterInstance<UnitOfWork>(this, new HierarchicalLifetimeManager());
            value.RegisterInstance<Session>(this, new HierarchicalLifetimeManager());
            _UnityContainer = value;
        }
    }

    protected override NestedUnitOfWork CreateNestedUnitOfWork()
    {
        return new NestedUnityUnitOfWork(this);
    }
}
NestedUnityUnitOfWork

Cause XPO supports nested transactions we shouldn't miss the NestedUnitOfWork who is also a fullUnityOfWork.

public class NestedUnityUnitOfWork : NestedUnitOfWork, IUnityContainerProvider
{
    protected internal NestedUnityUnitOfWork(Session parent)
        : base(parent)
    {
        UnityContainer = (parent as IUnityContainerProvider).UnityContainer.CreateChildContainer();
        UnityContainer.RegisterInstance<NestedUnitOfWork>(this, new HierarchicalLifetimeManager());
        UnityContainer.RegisterInstance<UnitOfWork>(this, new HierarchicalLifetimeManager());
        UnityContainer.RegisterInstance<Session>(this, new HierarchicalLifetimeManager());
    }

    public IUnityContainer UnityContainer { get; set; }

    protected override NestedUnitOfWork CreateNestedUnitOfWork()
    {
        return new NestedUnityUnitOfWork(this);
    }
}

But what about XAF?

We need to provide the same functionality to the XPObjectSpace as well to the XPNestedObjectSpace.

ObjectSpaces

UnityObjectSpace
public class UnityObjectSpace : XPObjectSpace, IUnityContainerProvider, IUnityObjectSpace
{
    public UnityObjectSpace(UnitOfWork unitOfWork) : base(unitOfWork) { }

    public UnityObjectSpace(ITypesInfo typesInfo, XpoTypeInfoSource xpoTypeInfoSource, CreateUnitOfWorkHandler createUnitOfWorkDelegate) : base(typesInfo, xpoTypeInfoSource, createUnitOfWorkDelegate) { }

    public IUnityContainer UnityContainer
    {
        get
        {
            if (Session is UnityUnitOfWork)
                return (Session as UnityUnitOfWork).UnityContainer;
            return null;
        }
        set { }
    }

    protected override UnitOfWork RecreateUnitOfWork()
    {
        var uow = base.RecreateUnitOfWork();
        if (uow is UnityUnitOfWork)
            (uow as UnityUnitOfWork).UnityContainer.RegisterInstance<IObjectSpace>(this, new HierarchicalLifetimeManager());
        return uow;
    }

    public override IObjectSpace CreateNestedObjectSpace()
    {
        var os = new UnityNestedObjectSpace(this);
        (os.Session as IUnityContainerProvider).UnityContainer.RegisterInstance<IObjectSpace>(os, new HierarchicalLifetimeManager());
        return os;
    }
}
UnityNestedObjectSpace
public class UnityNestedObjectSpace : XPNestedObjectSpace, IUnityContainerProvider
{
    public UnityNestedObjectSpace(IObjectSpace parentObjectSpace)
        : base(parentObjectSpace) {}

    public IUnityContainer UnityContainer
    {
        get
        {
            return (Session as IUnityContainerProvider).UnityContainer;
        }
        set {}
    }

    public override IObjectSpace CreateNestedObjectSpace()
    {
        var nestedOS = new UnityNestedObjectSpace(this);
        nestedOS.AsyncServerModeSourceResolveSession = AsyncServerModeSourceResolveSession;
        nestedOS.AsyncServerModeSourceDismissSession = AsyncServerModeSourceDismissSession;

        (nestedOS.Session as IUnityContainerProvider).UnityContainer.RegisterInstance<IObjectSpace>(nestedOS, new HierarchicalLifetimeManager());

        return nestedOS;
    }


    protected override UnitOfWork RecreateUnitOfWork()
    {
        var Result = base.RecreateUnitOfWork();
        (Result as IUnityContainerProvider).UnityContainer.RegisterInstance<IObjectSpace>(this, new HierarchicalLifetimeManager());
        return Result;
    }
}

Okay we have almost all we need, hurry up!

There are only 2 things missing. The infrastrucure for the ObjectSpaceProviders and the XAFApplication.

ObjectSpaceProviders & Application

There are 2 versions of the ObjectSpaceProvider: Secured and Unsecured.

First the unsecured version:

public class UnityObjectSpaceProvider : XPObjectSpaceProvider, IUnityContainerProvider
{
    public IUnityContainer UnityContainer { get; set; }

    public UnityObjectSpaceProvider(string connectionString, IDbConnection connection, IUnityContainer unityContainer) : base(connectionString, connection)
    {
        UnityContainer = unityContainer;
        unityContainer.RegisterInstance(typeof(IObjectSpaceProvider), this, new ContainerControlledLifetimeManager());
    }

    public UnityObjectSpaceProvider(IXpoDataStoreProvider dataStoreProvider, IUnityContainer unityContainer)
        : base(dataStoreProvider)
    {
        UnityContainer = unityContainer;
        unityContainer.RegisterInstance(typeof(IObjectSpaceProvider), this, new ContainerControlledLifetimeManager());
    }

    public UnityObjectSpaceProvider(IXpoDataStoreProvider dataStoreProvider, ITypesInfo typesInfo, XpoTypeInfoSource xpoTypeInfoSource, IUnityContainer unityContainer)
        : base(dataStoreProvider, typesInfo, xpoTypeInfoSource)
    {
        UnityContainer = unityContainer;
        unityContainer.RegisterInstance(typeof(IObjectSpaceProvider), this, new ContainerControlledLifetimeManager());
    }

    protected override IDataLayer CreateDataLayer(IDataStore dataStore)
    {
        var dataLayer = new SimpleDataLayer(this.XPDictionary, dataStore);

        return dataLayer;
    }

    protected override IObjectSpace CreateObjectSpaceCore()
    {
        var os = new UnityObjectSpace(TypesInfo, XpoTypeInfoSource, CreateUnitOfWorkDelegate);

        os.UnityContainer.RegisterInstance<IObjectSpace>(os, new HierarchicalLifetimeManager());

        return os;
    }

    protected override UnitOfWork CreateUnitOfWork(IDataLayer dataLayer)
    {
        var uow = new UnityUnitOfWork(dataLayer, null)
                  {
                      UnityContainer = UnityContainer.CreateChildContainer()
                  };

        return uow;
    }
}

And Secured:

public class SecureUnityObjectSpaceProvider : XPObjectSpaceProvider, IUnityContainerProvider
{
    private ISelectDataSecurityProvider SelectDataSecurityProvider;

    public bool AllowICommandChannelDoWithSecurityContext { get; set; }

    public SecureUnityObjectSpaceProvider(ISelectDataSecurityProvider selectDataSecurityProvider, IXpoDataStoreProvider dataStoreProvider, ITypesInfo typesInfo, XpoTypeInfoSource xpoTypeInfoSource, IUnityContainer unityContainer)
        : base(dataStoreProvider, typesInfo, xpoTypeInfoSource)
    {
        UnityContainer = unityContainer;
        SelectDataSecurityProvider = selectDataSecurityProvider;
        AllowICommandChannelDoWithSecurityContext = true;
    }

    public SecureUnityObjectSpaceProvider(ISelectDataSecurityProvider selectDataSecurityProvider, IXpoDataStoreProvider dataStoreProvider, IUnityContainer unityContainer)
        : base(dataStoreProvider)
    {
        UnityContainer = unityContainer;
        SelectDataSecurityProvider = selectDataSecurityProvider;
        AllowICommandChannelDoWithSecurityContext = true;
    }

    public SecureUnityObjectSpaceProvider(ISelectDataSecurityProvider selectDataSecurityProvider, string databaseConnectionString, IDbConnection connection, IUnityContainer unityContainer)
        : base(databaseConnectionString, connection)
    {
        UnityContainer = unityContainer;
        SelectDataSecurityProvider = selectDataSecurityProvider;
        AllowICommandChannelDoWithSecurityContext = true;
    }

    public IUnityContainer UnityContainer { get; set; }

    protected override IDataLayer CreateDataLayer(IDataStore dataStore)
    {
        var datalayer = new SimpleDataLayer(dataStore);

        return datalayer;
    }


    protected override IObjectSpace CreateObjectSpaceCore()
    {
        var os = new UnityObjectSpace(TypesInfo, XpoTypeInfoSource, CreateUnitOfWorkDelegate);

        os.UnityContainer.RegisterInstance<IObjectSpace>(os, new HierarchicalLifetimeManager());

        return os;
    }

    protected override UnitOfWork CreateUnitOfWork(IDataLayer dataLayer)
    {
        UnityUnitOfWork uow = new UnityUnitOfWork(dataLayer, null);

        uow.UnityContainer = UnityContainer.CreateChildContainer();

        SessionObjectLayer currentObjectLayer = new SecuredSessionObjectLayer(AllowICommandChannelDoWithSecurityContext, uow, true, null, new SecurityRuleProvider(XPDictionary, SelectDataSecurityProvider.CreateSelectDataSecurity()), null);

        var secureUnitOfWork = new UnityUnitOfWork(currentObjectLayer, uow);

        secureUnitOfWork.UnityContainer = uow.UnityContainer;

        return secureUnitOfWork;
    }
}

Note: The second one is almost a clone of the SecuredObjectSpaceProvider provided by DevExpress but we didn't want to intercept this class with reflection so we made a clone to inject our needs.

Application & Bootstrapping

public class UnityModuleInitializer
{
    public void InitUnityModules(IUnityContainer container, IEnumerable<IUnityModule> modules)
    {
        foreach (var module in modules)
            module.InitUnityContainer(container);

        foreach (var module in modules)
            module.UnityContainerInitialized(container);
    }
}

public class UnityWinApplication : WinApplication, IUnityContainerProvider
{
    public IUnityContainer UnityContainer { get; set; }

    public UnityWinApplication() : this(new UnityContainer()) { }

    public UnityWinApplication(IUnityContainer container)
    {
        UnityContainer = container;
        UnityContainer.RegisterInstance<XafApplication>(this, new ContainerControlledLifetimeManager());

        SettingUp += ParaXAFApplication_SettingUp;
    }

    protected override void CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args)
    {
        args.ObjectSpaceProvider = CreateUnityObjectSpaceProvider(args);
    }

    public XPObjectSpaceProvider CreateUnityObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs e)
    {
        return new UnityObjectSpaceProvider(e.ConnectionString, e.Connection, UnityContainer);
    }

    void ParaXAFApplication_SettingUp(object sender, SetupEventArgs e)
    {
        new UnityModuleInitializer().InitUnityModules(UnityContainer, Modules.OfType<IUnityModule>());
    }
}

Bring the stuff together

The Application:

public partial class XAFDISolutionWindowsFormsApplication : UnityWinApplication
{
    public XAFDISolutionWindowsFormsApplication(IUnityContainer container)
        : base(container)
    {
        InitializeComponent();
        DelayedViewItemsInitialization = true;
    }

    public XAFDISolutionWindowsFormsApplication() : this(new UnityContainer()) { }

    private void XAFDISolutionWindowsFormsApplication_DatabaseVersionMismatch(object sender, DevExpress.ExpressApp.DatabaseVersionMismatchEventArgs e)
    {
        if (System.Diagnostics.Debugger.IsAttached)
        {
            e.Updater.Update();
            e.Handled = true;
        }
        else
        {
            throw new InvalidOperationException(
                "The application cannot connect to the specified database, because the latter doesn't exist or its version is older than that of the application.\r\n" +
                "This error occurred  because the automatic database update was disabled when the application was started without debugging.\r\n" +
                "To avoid this error, you should either start the application under Visual Studio in debug mode, or modify the " +
                "source code of the 'DatabaseVersionMismatch' event handler to enable automatic database update, " +
                "or manually create a database using the 'DBUpdater' tool.\r\n" +
                "Anyway, refer to the 'Update Application and Database Versions' help topic at http://www.devexpress.com/Help/?document=ExpressApp/CustomDocument2795.htm " +
                "for more detailed information. If this doesn't help, please contact our Support Team at http://www.devexpress.com/Support/Center/");
        }
    }

    private void XAFDISolutionWindowsFormsApplication_CustomizeLanguagesList(object sender, CustomizeLanguagesListEventArgs e)
    {
        string userLanguageName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
        if (userLanguageName != "en-US" && e.Languages.IndexOf(userLanguageName) == -1)
        {
            e.Languages.Add(userLanguageName);
        }
    }
}

And Program.cs

static class Program
{
    [STAThread]
    static void Main()
    {
        var unityContainer = new UnityContainer();

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        EditModelPermission.AlwaysGranted = System.Diagnostics.Debugger.IsAttached;

        string connectionString = null;

        if (ConfigurationManager.ConnectionStrings["ConnectionString"] != null)
            connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

        var winApplication = new XAFDISolutionWindowsFormsApplication(unityContainer);
        winApplication.ConnectionString = connectionString;

        try
        {
            winApplication.Setup();
            winApplication.Start();
        }
        catch (Exception e)
        {
            winApplication.HandleException(e);
        }
    }
}

Lets rock!

In our platform agnostic module we create a simple BO:

[Persistent]
[DefaultClassOptions]
public class MyBo1 : XPObject
{
    public MyBo1()
    {
    }

    public MyBo1(Session session) : base(session)
    {
    }

    public MyBo1(Session session, XPClassInfo classInfo) : base(session, classInfo)
    {
    }

    [NonPersistent]
    [MemberDesignTimeVisibility(false)]
    public IUnityContainer UnityContainer
    {
        get { return (Session as IUnityContainerProvider).UnityContainer; }
    }

    private string _MyName;
    [Size(SizeAttribute.Unlimited)]
    [Persistent]
    public string MyName
    {
        get { return _MyName; }
        set { SetPropertyValue("MyName", ref _MyName, value); }
    }

    [DevExpress.Persistent.Base.Action(Caption = "Rename Me!!!")]
    public void RenameMe()
    {
        UnityContainer.Resolve<IRenamer>().RenameMe(this);
    }
}

Notice there is a MethodAction that pulls out the dependency of `IRenamer'

public interface IRenamer
{
    void RenameMe(MyBo1 myBo1);
}

And a NullImplementation

public class NullRenamer : IRenamer
{
    [Dependency]
    public IObjectSpace OS { get; set; }

    public void RenameMe(MyBo1 myBo1)
    {
        //I should never be called.
    }
}

So we have a nice NullImplementation and we don't have to check allways if the dependency is already registered (performance).

In the Module we implement the interface IUnityModule and register the type of the NullRenamer

public sealed partial class XAFDISolutionModule : ModuleBase, IUnityModule
{
    public XAFDISolutionModule()
    {
        InitializeComponent();
    }

    public override IEnumerable<ModuleUpdater> GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB)
    {
        ModuleUpdater updater = new DatabaseUpdate.Updater(objectSpace, versionFromDB);
        return new ModuleUpdater[] { updater };
    }

    public void InitUnityContainer(Microsoft.Practices.Unity.IUnityContainer unityContainer)
    {
        unityContainer.RegisterType<IRenamer, NullRenamer>();
    }

    public void UnityContainerInitialized(Microsoft.Practices.Unity.IUnityContainer unityContainer)
    {

    }
}

In the WinProject we create a new DomainLogic class called WinRenamer

public class WinRenamer : IRenamer
{
    [Dependency]
    public IObjectSpace OS { get; set; }

    public void RenameMe(MyBo1 myBo1)
    {
        //I should be be called.

        myBo1.MyName = "Hello from the Win Project: My Dependencies: " + GetType().FullName + " " + OS + "Session id:" + (OS as XPObjectSpace).Session;
    }
}

And the WinModule need's to overwrite the IRenamer registration

[ToolboxItemFilter("Xaf.Platform.Win")]
public sealed partial class XAFDISolutionWindowsFormsModule : ModuleBase, IUnityModule
{
    public XAFDISolutionWindowsFormsModule()
    {
        InitializeComponent();
    }
    public override IEnumerable<ModuleUpdater> GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB)
    {
        return ModuleUpdater.EmptyModuleUpdaters;
    }

    public void InitUnityContainer(IUnityContainer unityContainer)
    {
        unityContainer.RegisterType<IRenamer, WinRenamer>();
    }

    public void UnityContainerInitialized(IUnityContainer unityContainer)
    {

    }
}

Thats it!

Check out the video on Screencast And the source-code on Bitbucket

BLOG by Manuel Grundner: How to use Dependency Injection in XAF (UnitTesting) Part 2

$
0
0

How to UnitTest a XAF/XPO application?

It's not a really easy task to correctly UnitTest a XAF/XPO application, but as we all know, UnitTesting is a very important part of our work today.

Why?

Cause nobody can know all impacts a change can make in an application.

How?

I've written a bunch of little helpers to accomplish this tasks:

  • XpoTest
  • XafTest
  • XafModelTestHelper

XpoTest

I prefer this kind of test cause it is horrible fast and only tests model classes. Perfect to test DomainLogic.

This baseclass provides all you need to test a business object in memory (and of course with Unity)

public abstract class XpoTest
{
    private TestHelper _Helper;
    protected TestHelper GetHelper()
    {
        if (_Helper == null)
        {
            _Helper = GetHelperCore();
            InitUnityContainer(_Helper.UnityContainer);
        }
        return _Helper;
    }

    protected virtual void InitUnityContainer(IUnityContainer unityContainer)
    {

    }

    protected virtual TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo();
    }

    protected Session CreateSession()
    {
        return GetHelper().GetNewObjectSpace().GetSession();
    }
}

There are currently 5 generic versions of this class to provide a easy to read, and convention based approach to unit-test our classes:

public abstract class XpoTest<T, T2, T3, T4, T5> : XpoTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo<T, T2, T3, T4, T5>();
    }
}

public abstract class XpoTest<T, T2, T3, T4> : XpoTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo<T, T2, T3, T4>();
    }
}

public abstract class XpoTest<T, T2, T3> : XpoTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo<T, T2, T3>();
    }
}

public abstract class XpoTest<T, T2> : XpoTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo<T, T2>();
    }
}

public abstract class XpoTest<T> : XpoTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo<T>();
    }
}

It uses the TestHelper class which does all the bootstrapping for you:

public class TestHelper
{
    public IUnityContainer UnityContainer { get; set; }

    private IObjectSpaceProvider ObjectSpaceProvider;

    public static TestHelper NewInstance()
    {
        return new TestHelper();
    }

    public static TestHelper NewInstanceXpo(params Type[] types)
    {
        return new TestHelper();
    }

    public static TestHelper NewInstanceXpo<T>()
    {
        return NewInstanceXaf(typeof(T));
    }

    public static TestHelper NewInstanceXpo<T, T2>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2));
    }

    public static TestHelper NewInstanceXpo<T, T2, T3>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2), typeof(T3));
    }

    public static TestHelper NewInstanceXpo<T, T2, T3, T4>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2), typeof(T3), typeof(T4));
    }

    public static TestHelper NewInstanceXpo<T, T2, T3, T4, T5>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
    }

    public static TestHelper NewInstanceXaf(params Type[] types)
    {
        XafTypesInfo.Reset();

        if (XafTypesInfo.PersistentEntityStore == null)
            XafTypesInfo.SetPersistentEntityStore(new XpoTypeInfoSource(XafTypesInfo.Instance as TypesInfo));

        foreach (var typeToRegister in types)
            XafTypesInfo.Instance.RegisterEntity(typeToRegister);

        return new TestHelper();
    }

    public static TestHelper NewInstanceXaf<T>()
    {
        return NewInstanceXaf(typeof(T));
    }

    public static TestHelper NewInstanceXaf<T, T2>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2));
    }

    public static TestHelper NewInstanceXaf<T, T2, T3>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2), typeof(T3));
    }

    public static TestHelper NewInstanceXaf<T, T2, T3, T4>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2), typeof(T3), typeof(T4));
    }

    public static TestHelper NewInstanceXaf<T, T2, T3, T4, T5>()
    {
        return NewInstanceXaf(typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
    }

    public TestHelper AutoMock()
    {
        UnityContainer.AddNewExtension<AutoMockingContainerExtension>();
        return this;
    }

    public TestHelper()
    {
        UnityContainer = new UnityContainer();
    }

    public IObjectSpace GetNewObjectSpace(bool generateIds = true)
    {
        ObjectSpaceProvider = new UnityObjectSpaceProvider(new MemoryDataStoreProvider(), UnityContainer);
        var OS = ObjectSpaceProvider.CreateObjectSpace();

        if (generateIds)
            (OS as UnityObjectSpace).ObjectNewInObjectSpace += ChannelTestsHelper_ObjectNewInObjectSpace;

        return OS;
    }

    private static void ChannelTestsHelper_ObjectNewInObjectSpace(object sender, ObjectNewInObjectSpaceEventArgs e)
    {
        var classInfo = (e.Object as IXPObject).ClassInfo;
        if (!classInfo.IsPersistent)
            return;

        if (classInfo.KeyProperty.MemberType.IsAssignableFrom(typeof(int)))
        {
            classInfo.KeyProperty.SetValue(e.Object, IDGenerator.NextInt());
        }

        if (classInfo.KeyProperty.MemberType.IsAssignableFrom(typeof(Guid)))
        {
            classInfo.KeyProperty.SetValue(e.Object, IDGenerator.NextGuid());
        }
    }
}

static class TestExtensions
{
    internal static Session GetSession(this IObjectSpace os)
    {
        return (os as XPObjectSpace).Session;
    }
}

With memory-datastores there is no auto-incrementing key, so we have to mimic this behaviour.

public static class IDGenerator
{
    private static int Current = 0;

    public static int NextInt()
    {
        Current++;
        return Current;
    }

    public static Guid NextGuid()
    {
        return Guid.NewGuid();
    }
}

It also provides support for Moq, and Unity based AutoMoq.

public static class MoqExtensions
{
    public static Mock<T> RegisterMock<T>(this IUnityContainer container, MockBehavior behavior = MockBehavior.Strict) where T : class
    {
        var mock = new Mock<T>(behavior);

        container.RegisterInstance<Mock<T>>(mock);
        container.RegisterInstance<T>(mock.Object);

        return mock;
    }

    /// <summary>
    /// Use this to add additional setups for a mock that is already registered
    /// </summary>
    public static Mock<T> ConfigureMockFor<T>(this IUnityContainer container) where T : class
    {
        return container.Resolve<Mock<T>>();
    }

    public static void VerifyMockFor<T>(this IUnityContainer container) where T : class
    {
        container.Resolve<Mock<T>>().VerifyAll();
    }
}

public class AutoMockingContainerExtension : UnityContainerExtension
{
    protected override void Initialize()
    {
        var strategy = new AutoMockingBuilderStrategy(Container);

        Context.Strategies.Add(strategy, UnityBuildStage.PreCreation);
    }

    class AutoMockingBuilderStrategy : BuilderStrategy
    {
        private readonly IUnityContainer container;

        public AutoMockingBuilderStrategy(IUnityContainer container)
        {
            this.container = container;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            var key = context.OriginalBuildKey;

            if (key.Type.IsInterface && !container.IsRegistered(key.Type))
            {
                context.Existing = CreateDynamicMock(key.Type);
            }
        }

        private static object CreateDynamicMock(Type type)
        {
            var genericMockType = typeof(Mock<>).MakeGenericType(type);
            var mock = (Mock)Activator.CreateInstance(genericMockType);
            return mock.Object;
        }
    }
}

I have borrowed this classes from a blogpost i can't remember (sorry).

XafTest

Is intended to test all kinds of DomainLogic that interacts with XafTypesInfo and ObjectSpace relevant tests. It is much slower than the XpoTest cause the XafTypesInfo has to reset and needs to be repopulated.

The only difference in the usage of XpoTest and XafTest is that the XafTest provides a additional CreateObjectSpace method:

public abstract class XafTest : XpoTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstance();
    }

    protected virtual IObjectSpace CreateObjectSpace()
    {
        return GetHelper().GetNewObjectSpace();
    }
}

public abstract class XafTest<T, T2> : XafTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXaf<T, T2>();
    }
}

public abstract class XafTest<T, T2, T3> : XafTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXaf<T, T2, T3>();
    }
}

public abstract class XafTest<T, T2, T3, T4> : XafTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXaf<T, T2, T3, T4>();
    }
}

public abstract class XafTest<T, T2, T3, T4, T5> : XafTest
{
    protected override TestHelper GetHelperCore()
    {
        return TestHelper.NewInstanceXpo<T, T2, T3, T4, T5>();
    }
}

XafModelTestHelper

Can be used to load a ApplicationModel and test it's properties. You can test orders and columns of listviews or PropertyEditors of DetailViews ect. This is in fact the slowest guy in this round. But we have a lot of problems with small refactorings that can happen in huge view problems :(

public static class XafModelTestHelper
{
    public static IModelApplication LoadApplicationModel(this ModuleBase module)
    {
        var manager = new ApplicationModulesManager();

        manager.AddModule(module);

        manager.Load(XafTypesInfo.Instance, true);

        return new DesignerModelFactory().CreateApplicationModel(module,
                                                           manager,
                                                           ModelStoreBase.Empty);
    }
}

Go and test!

For simplicity i only used one UnitTest project to test platform agnostic and winforms code.

[TestFixture]
[Category("CI")]
public class XpoTest_MyBo1 : XpoTest<MyBo1>
{
    [Test]
    public void Test_Ctor()
    {
        var session = CreateSession();

        var myBo = new MyBo1(session);

        Assert.That(myBo, Is.AssignableFrom<MyBo1>());
    }

    private MyBo1 CreateEmptyTestObject()
    {
        var session = CreateSession();

        return new MyBo1(session);
    }

    [Test]
    public void Test_MyName_Stores_Value_Correctly()
    {
        var bo = CreateEmptyTestObject();

        const string expected = "My String";
        bo.MyName = "My String";

        Assert.That(bo.MyName, Is.EqualTo(expected));
    }

    [Test]
    public void Test_MyName_Stores_And_Resets_Value_Correctly()
    {
        var bo = CreateEmptyTestObject();

        const string expected = "My String";
        bo.MyName = "My String";
        bo.MyName = null;
        Assert.That(bo.MyName, Is.Null);
    }
}

This tests are simple property tests, nothing really special here..

But now the interessting things come:

[TestFixture]
[Category("CI")]
public class XpoTest_Manually_Mock_MyBo1 : XpoTest<MyBo1>
{
    public class Mock_Renamer : IRenamer
    {
        public void RenameMe(MyBo1 myBo1)
        {
            myBo1.MyName = typeof (Mock_Renamer).FullName;
        }
    }

    protected override void InitUnityContainer(IUnityContainer unityContainer)
    {
        base.InitUnityContainer(unityContainer);
        unityContainer.RegisterType<IRenamer, Mock_Renamer>();
    }

    [Test]
    public void Test_Rename_Works()
    {
        var bo = new MyBo1(CreateSession());

        bo.RenameMe();

        Assert.That(bo.MyName, Is.EqualTo(typeof(Mock_Renamer).FullName));
    }
}

As you can see we have a successfull test! And decoupled from the original dependency. But it can be really annoying to write a mock class for every interface we use. So get Moq!

[TestFixture]
[Category("CI")]
public class XpoTest_Moq_Mock_MyBo1 : XpoTest<MyBo1>
{
    [Test]
    public void Test_Rename_Works_Mocked()
    {
        var session = CreateSession();

        var mock = session.GetUnityContainer().RegisterMock<IRenamer>();

        mock.Setup(m => m.RenameMe(It.IsAny<MyBo1>()));

        var bo = new MyBo1(session);

        bo.RenameMe();

        mock.Verify(m => m.RenameMe(bo), Times.AtLeastOnce());
    }

We don't use a expected value here, cause we only want to know that this interface is resolved and test only the existence of the call.

Now want to test the XAF part:

[TestFixture]
[Category("CI")]
public class XafTest_MyBo1 : XafTest<MyBo1>
{
    [Test]
    public void Test_Ctor_With_OS()
    {
        var os = CreateObjectSpace();

        var myBo = os.CreateObject<MyBo1>();

        Assert.That(myBo, Is.AssignableFrom<MyBo1>());
    }

    private MyBo1 CreateEmptyTestObject()
    {
        var os = CreateObjectSpace();

        return os.CreateObject<MyBo1>();
    }

    [Test]
    public void Test_MyName_Stores_Value_Correctly()
    {
        var bo = CreateEmptyTestObject();

        const string expected = "My String";
        bo.MyName = "My String";

        Assert.That(bo.MyName, Is.EqualTo(expected));
    }

    [Test]
    public void Test_MyName_Stores_And_Resets_Value_Correctly()
    {
        var bo = CreateEmptyTestObject();

        const string expected = "My String";
        bo.MyName = "My String";
        bo.MyName = null;
        Assert.That(bo.MyName, Is.Null);
    }
}

Boring. Nothing special here. but a lot slower:

Now the XafApplicationModel part:

[TestFixture]
[Category("CI")]
public class XafApplicationModelTest_MyBo1
{
    [Test]
    public void Test_ListView_Should_Only_Contain_1_Column()
    {
        var model = XafModelTestHelper.LoadApplicationModel(new XAFDISolutionModule());

        var view = model.Views.OfType<IModelListView>().FirstOrDefault(m => m.Id == "MyBo1_ListView");

        Assert.That(view, Is.Not.Null);

        Assert.That(view.Columns.Count, Is.EqualTo(2));
        // Two is not correct, but i have no idea why the UnityContainer Column is generated??? :(
    }
}

This is almost the slowest of all tests, so i recommend you init the ApplicationModel only once per TestCase:

[TestFixture]
[Category("CI")]
public class XafApplicationModelTest_MyBo1
{
    private IModelApplication _Model;

    [TestFixtureSetUp]
    public void SetUp()
    {
        _Model = XafModelTestHelper.LoadApplicationModel(new XAFDISolutionModule());
    }

    [Test]
    public void Test_ListView_Should_Only_Contain_1_Column()
    {
        var view = _Model.Views.OfType<IModelListView>().FirstOrDefault(m => m.Id == "MyBo1_ListView");

        Assert.That(view, Is.Not.Null);

        Assert.That(view.Columns.Count, Is.EqualTo(2));
        // Two is not correct, but i have no idea why the UnityContainer Column is generated??? :(
    }

    [Test]
    public void Test_Caption_Of_MyName_Is_My_Name()
    {
        var view = _Model.Views.OfType<IModelListView>().FirstOrDefault(m => m.Id == "MyBo1_ListView");

        var column = view.Columns.FirstOrDefault(m => m.Id == "MyName");

        Assert.That(column, Is.Not.Null);

        Assert.That(column.Caption, Is.EqualTo("My Name"));
    }
}

The last one is the Application-Based Test:

[TestFixture]
[Category("CI")]
public class ApplicationTest_MyBo1
{
    private WinApplication TestApplication;
    [TestFixtureSetUp]
    public void SetUp()
    {
        var unityContainer = new UnityContainer();

        TestApplication = new TestApplication(unityContainer);

        var objectSpaceProvider = new UnityObjectSpaceProvider(new MemoryDataStoreProvider(), unityContainer);

        var testModule = new ModuleBase();

        testModule.AdditionalExportedTypes.Add(typeof(MyBo1));

        TestApplication.Modules.Add(new SystemModule());
        TestApplication.Modules.Add(testModule);

        TestApplication.Setup("TestApplication", objectSpaceProvider);

        TestApplication.CustomCheckCompatibility += (o, args) => args.Handled = true;
    }

    [Test]
    public void Test_Action_WorksCorrectly()
    {
        var os = TestApplication.CreateObjectSpace();

        var bo = os.CreateObject<MyBo1>();

        var detailView = TestApplication.CreateDetailView(os, bo);

        var controller = TestApplication.CreateController<DevExpress.ExpressApp.SystemModule.ObjectMethodActionsViewController>();

        controller.SetView(detailView);

        Assert.That(controller.Actions.Count, Is.GreaterThan(0));
    }
}

Note that the last test fails, because i have no idea how to instanciate the ObjectMethodActionsViewController correctly to test my action :(

But i think you get the point :)

Source

Update: Thanks to Robert Anderson the ControllerTest is now working correctly :)

BLOG by Manuel Grundner: How to use Dependency Injection in XAF (ASP.NET WebApi/WebMvc) Part 3

$
0
0

How How to use Dependency Injection in XAF (ASP.Net WebApi / MVC4) Part 3

How to add support for ASP.NET WebAPI / MVC4 in XAF?

It is not quite complicated, but took me also some hours of work to get it running.

Why?

We'd like to provide a WebAPI to other companies to reduce our amount of work, when handling with other companies data.

Microsoft's WebAPI/MVC4 are great frameworks to easily write such platforms. XPO and XAF are great products. So let's combine them.

How?

First of all we need 3 new projects in our solution. The first one is the WebApi project. The second one is the WebMvc Project. The third one will contain our Datatransfer objects to support strong typing. This one will be a portable class library.

WebApi

In this project goes the whole big stuff (domain logic & co). Let's start:

First of all we need to install the Unity.WebAPI nuget package. (Attention: if your project is strong signed, this will fail, but the source is not that hard ;))

We get this nice little bootstrapper class that allowes us to configure our UnityContainer.

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();

        // register all your components with the container here
        // e.g. container.RegisterType<ITestService, TestService>();            

        return container;
    }
}

The only thing we need to do is call our Bootstrapper in our Global.asax file:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        Bootstrapper.Initialise();
    }
}

Nothing special so far. This is default ASP.NET WebApi.

How to deal now with XPO? We need to bootstrap the Datalayer and the Session:

The interface IDataLayerHelper is used to abstract the procress of creating an DataLayer:

public interface IDataLayerHelper
{
    IDataLayer DataLayer { get; }
}

The implementation is pretty straight forward. Notice that nothing is static here, except of the XPDictionary (cause this is the only thing that is very time consuming, and dosn't change at all, when the application is running)

public class DataLayerHelper : IDataLayerHelper
{
    private IDataLayer _DataLayer;
    public IDataLayer DataLayer
    {
        get
        {
            if (_DataLayer == null)
            {
                var connectionString = ConnectionString;
                XpoDefault.Session = null;

                _DataLayer = new ThreadSafeDataLayer(XpDictionary, GetConnectionProvider(connectionString));
            }

            return _DataLayer;
        }

    }

    private readonly static object lockObjectXpDictionary = new object();

    private volatile static XPDictionary _XpDictionary;
    private static XPDictionary XpDictionary
    {
        get
        {
            if (_XpDictionary == null)
            {
                lock (lockObjectXpDictionary)
                {
                    if (_XpDictionary == null)
                    {
                        _XpDictionary = new ReflectionDictionary();

                        _XpDictionary.GetDataStoreSchema(Assembly.GetExecutingAssembly(), typeof(XAFDISolutionModule).Assembly);
                    }
                }
            }
            return _XpDictionary;
        }
    }

    protected internal string _ConnectionString;

    protected internal AutoCreateOption _DefaultAutoCreateOption = AutoCreateOption.None;

    protected virtual string ConnectionString
    {
        get
        {
            if (string.IsNullOrEmpty(_ConnectionString) && ConfigurationManager.ConnectionStrings["ConnectionString"] != null)
                _ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

            return _ConnectionString;
        }
    }

    protected virtual IDataStore GetConnectionProvider(string connectionString)
    {
        return XpoDefault.GetConnectionProvider(connectionString, _DefaultAutoCreateOption);
    }
}

Now we need to register this Type with the correct LifeTimeManager:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var unityContainer = new UnityContainer();

        unityContainer.RegisterType<IDataLayerHelper, DataLayerHelper>(new ContainerControlledLifetimeManager());

        return unityContainer;
    }
}

In this case we need a ContainerControlledLifetimeManager so the instance will live for every ChildUnityContainer that is created.

Of course we write a UnitTest for this:

[TestFixture]
[Category("CI")]
public class DataLayerHelperTest
{
    [Test]
    public void DataLayerHelper_Ctor_DoesNotThrowAnException()
    {
        Assert.DoesNotThrow(() =>
        {
            var helper = new DataLayerHelper();

            Assert.That(helper, Is.InstanceOf<DataLayerHelper>());
        });
    }

    [Test]
    public void DataLayerHelper_Can_Load_DataLayer_With_InMemoryConnectionString()
    {
        //Arrange
        var helper = new DataLayerHelper
            {
                _ConnectionString = InMemoryDataStore.GetConnectionStringInMemory(true),
                _DefaultAutoCreateOption = AutoCreateOption.DatabaseAndSchema
            };

        //Act
        var session = new UnitOfWork(helper.DataLayer);
        var bo = new MyBo1(session)
            {
                MyName = "test"
            };

        session.CommitTransaction();

        //Assert
        var itemToAssert = session.FindObject<MyBo1>(null);

        Assert.That(itemToAssert.MyName, Is.EqualTo("test"));
    }
}

Now we need a little interface that abstracts the creation of a new Session aka UnityUnitOfWork:

public interface IXpoHelper
{
    Session GetNewSession();
}

The implementation is easy:

public class XpoHelper : IXpoHelper
{
    private readonly IDataLayerHelper _DataLayerHelper;

    internal readonly IUnityContainer _UnityContainer;

    public XpoHelper(IUnityContainer unityContainer, IDataLayerHelper dataLayerHelper)
    {
        _DataLayerHelper = dataLayerHelper;
        _UnityContainer = unityContainer;
    }

    public Session GetNewSession()
    {
        return GetNewUnitOfWork();
    }

    UnityUnitOfWork GetNewUnitOfWork()
    {
        var uow = new UnityUnitOfWork(_DataLayerHelper.DataLayer)
        {
            UnityContainer = _UnityContainer
        };
        return uow;
    }
}

Notice that we use here ConstructorInjection

The UnitTest:

[TestFixture]
[Category("CI")]
public class XpoHelperTest
{
    [Test]
    public void Ctor_OfXpoHelperTest_DoesNotThrowAnException()
    {
        //Arrange
        var dataLayerHelperMock = new Moq.Mock<IDataLayerHelper>();
        var unityContainerMock = new Moq.Mock<IUnityContainer>();

        //Act & Assert
        Assert.DoesNotThrow(() =>
        {
            var xpoHelper = new XpoHelper(unityContainerMock.Object, dataLayerHelperMock.Object);
            Assert.That(xpoHelper, Is.InstanceOf<XpoHelper>());
        });
    }

    [Test]
    public void GetNewSession_Creates_A_New_Session()
    {
        //Arrange
        var dataLayerHelperMock = new Moq.Mock<IDataLayerHelper>();
        dataLayerHelperMock.SetupGet(m => m.DataLayer).Returns(new SimpleDataLayer(new InMemoryDataStore()));

        var unityContainerMock = new Moq.Mock<IUnityContainer>();
        var sessionHelper = new XpoHelper(unityContainerMock.Object, dataLayerHelperMock.Object);

        //Act 
        var session = sessionHelper.GetNewSession();

        //Assert
        Assert.That(session, Is.Not.Null);
        Assert.That(session, Is.InstanceOf<Session>());
    }
}

Now we also must register this guy in the Bootstrapper:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var unityContainer = new UnityContainer();

        unityContainer.RegisterType<IDataLayerHelper, DataLayerHelper>(new ContainerControlledLifetimeManager());
        unityContainer.RegisterType<IXpoHelper, XpoHelper>();

        return unityContainer;
    }
}

The portable assembly

Note that the WebApi and WebMvc are using .Net 4.5 to use the nice async/await featues we build the protable assembly with support for .Net 4.0 support to use the calls from our legacy XAFSolution.

Portable Lib

Here is the very simple MyBo1-DTO

public class MyBo1
{
    public int Oid { get; set; }
    public string MyName { get; set; }
}

Back to the future, ahm WebApi

We know use a simple pattern called the RepositoryPattern to access our Database via XPO and keep testablility and Seperation of Conserns:

public interface IBusinessObjectRepository
{
    Task<IEnumerable<MyBo1>> GetBusinessObjects();

    Task<MyBo1> GetBusinessObjectById(int id);

    Task<MyBo1> Save(MyBo1 bo);

    Task<MyBo1> Delete(int id);

    Task<MyBo1> Save(int id, MyBo1 value);
}

A nice an clean interface, isn't it? :)

The implementation is also not that complicated:

public class MyBo1Repository : IBusinessObjectRepository
{
    internal readonly IXpoHelper _Helper;

    public MyBo1Repository(IXpoHelper helper)
    {
        _Helper = helper;
    }

    MyBo1 CreateBusinessObject(XAFDISolution.Module.BusinessObjects.MyBo1 boXPO)
    {
        if(boXPO == null)
            return null;

        return new MyBo1()
            {
                Oid = boXPO.Oid,
                MyName = boXPO.MyName,
            };
    }

    XAFDISolution.Module.BusinessObjects.MyBo1 CreateBusinessObject(MyBo1 bo, Session session)
    {
        return MapBusinessObject(bo, new XAFDISolution.Module.BusinessObjects.MyBo1(session));
    }

    XAFDISolution.Module.BusinessObjects.MyBo1 MapBusinessObject(MyBo1 bo, XAFDISolution.Module.BusinessObjects.MyBo1 boXPO)
    {
        boXPO.MyName = bo.MyName;

        return boXPO;
    }

    public async Task<IEnumerable<MyBo1>> GetBusinessObjects()
    {
        return await Task<IEnumerable<MyBo1>>.Run(() => BusinessObjectsXPO.Select(CreateBusinessObject));
    }

    IEnumerable<XAFDISolution.Module.BusinessObjects.MyBo1> BusinessObjectsXPO
    {
        get
        {
            return new XPQuery<XAFDISolution.Module.BusinessObjects.MyBo1>(_Helper.GetNewSession());
        }
    }

    public async Task<MyBo1> GetBusinessObjectById(int id)
    {
        return CreateBusinessObject(_Helper.GetNewSession().GetObjectByKey<XAFDISolution.Module.BusinessObjects.MyBo1>(id));
    }

    public async Task<MyBo1> Save(MyBo1 bo)
    {
        return await Task<MyBo1>.Run(() =>
            {
                var session = _Helper.GetNewSession();
                var boToReturn = CreateBusinessObject(bo, session);
                session.CommitTransaction();

                return CreateBusinessObject(boToReturn);
            });
    }

    public async Task<MyBo1> Delete(int id)
    {
        return await Task<MyBo1>.Run(() =>
            {
                var session = _Helper.GetNewSession();
                var boXpo = session.GetObjectByKey<XAFDISolution.Module.BusinessObjects.MyBo1>(id);
                if (boXpo == null)
                    return CreateBusinessObject(null);

                session.Delete(boXpo);
                session.CommitTransaction();
                boXpo.Oid = -1;

                return CreateBusinessObject(boXpo);
            });
    }

    public async Task<MyBo1> Save(int id, MyBo1 bo)
    {
        return await Task<MyBo1>.Run(() =>
            {
                var session = _Helper.GetNewSession();
                var boXpo = session.GetObjectByKey<XAFDISolution.Module.BusinessObjects.MyBo1>(id);

                MapBusinessObject(bo, boXpo);

                session.CommitTransaction();

                return CreateBusinessObject(boXpo);
            });
    }
}

What we do here is basicly a proxy arround our XPO-Database-Objects and wrap the whole thing in async/await Tasks. The most complex thing that can happen are in the CreateBusinessObject and MapBusinessObject methods. Here is good testing absolutly necessary. If you miss a property the whole thing is unstable. The other side of the medal is that we can provide a clear interface to the database, and can decide how to serialize/deserialize data to the database.

The tests are also very interessting:

[TestFixture]
[Category("CI")]
public class MyBo1RepositoryTests
{
    [Test]
    public void Ctor_Is_Not_Throwing_An_Exception()
    {
        //Arrange
        var mockXpoHelper = new Mock<IXpoHelper>();

        //Act & Assert
        Assert.DoesNotThrow(() =>
        {
            var repo = new MyBo1Repository(mockXpoHelper.Object);

            Assert.That(repo, Is.InstanceOf<MyBo1Repository>());
        });
    }

    private IDataLayerHelper CreateDataLayer()
    {
        //Arrange
        var dataLayerHelperMock = new Moq.Mock<IDataLayerHelper>();
        dataLayerHelperMock.SetupGet(m => m.DataLayer).Returns(new SimpleDataLayer(new InMemoryDataStore()));
        return dataLayerHelperMock.Object;
    }

    [Test]
    public async void Save_A_New_Object_Will_Go_To_Database()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var repo = new MyBo1Repository(xpoHelper);

        //Act
        var boToTest = new XAFDiSolution.DTO.MyBo1()
            {
                MyName = "Test Name"
            };

        await repo.Save(boToTest);

        //Assert
        var myBo1Expected = xpoHelper.GetNewSession().FindObject<XAFDISolution.Module.BusinessObjects.MyBo1>(null);

        Assert.That(myBo1Expected, Is.Not.Null);

        Assert.That(myBo1Expected.MyName, Is.EqualTo("Test Name"));
    }

    [Test]
    public async void Save_An_Existing_Object_Will_Modify_The_XPO_Object()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var session = xpoHelper.GetNewSession();

        var existingXpoBo = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
            {
                MyName = "Test existing",
            };

        session.CommitTransaction();

        var oid = session.FindObject<XAFDISolution.Module.BusinessObjects.MyBo1>(null).Oid;

        var repo = new MyBo1Repository(xpoHelper);

        //Act
        var boToTest = new XAFDiSolution.DTO.MyBo1()
        {
            MyName = "Test Name"
        };

        await repo.Save(oid, boToTest);

        //Assert
        var assertSession = xpoHelper.GetNewSession();
        var myBo1ExpectedCollection = assertSession.GetObjects(assertSession.GetClassInfo<XAFDISolution.Module.BusinessObjects.MyBo1>(), null, new SortingCollection(), 0, Int32.MaxValue, true, true);

        Assert.That(myBo1ExpectedCollection, Is.Not.Null);
        Assert.That(myBo1ExpectedCollection.Count, Is.EqualTo(1));

        Assert.That(myBo1ExpectedCollection.OfType<XAFDISolution.Module.BusinessObjects.MyBo1>().ElementAt(0).MyName, Is.EqualTo("Test Name"));
    }

    [Test]
    public async void GetBusinessObjects_With_Empty_Database_Does_Not_Return_Null()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var session = xpoHelper.GetNewSession();

        var repo = new MyBo1Repository(xpoHelper);

        //Act
        var result = await repo.GetBusinessObjects();

        //Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Count(), Is.EqualTo(0));
    }

    [Test]
    public async void GetBusinessObjects_With_Filled_Database_Returns_All_BusinessObjects_Correct()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var session = xpoHelper.GetNewSession();

        var existingXpoBo1 = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
        {
            MyName = "Test existing1",
        };

        var existingXpoBo2 = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
        {
            MyName = "Test existing2",
        };

        session.CommitTransaction();

        var repo = new MyBo1Repository(xpoHelper);

        //Act

        var result = (await repo.GetBusinessObjects()).OrderBy(m => m.Oid);

        //Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Count(), Is.EqualTo(2));

        Assert.That(result.ElementAt(0).MyName, Is.EqualTo("Test existing1"));
        Assert.That(result.ElementAt(1).MyName, Is.EqualTo("Test existing2"));
    }

    [Test]
    public async void GetBusinessObjectById_With_Filled_Database_Returns_Correct_BusinessObject_With_Exisiting_Key_Property()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var session = xpoHelper.GetNewSession();

        var existingXpoBo1 = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
        {
            MyName = "Test existing1",
        };

        var existingXpoBo2 = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
        {
            MyName = "Test existing2",
        };

        session.CommitTransaction();

        var oid = session.FindObject<Module.BusinessObjects.MyBo1>(CriteriaOperator.Parse("MyName = ?", "Test existing2")).Oid;

        var repo = new MyBo1Repository(xpoHelper);

        //Act

        var result = await repo.GetBusinessObjectById(oid);

        //Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.MyName, Is.EqualTo("Test existing2"));
    }

    [Test]
    public async void GetBusinessObjectById_With_Empty_Database_Returns_Null_With_Non_Exisiting_Key_Property()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var session = xpoHelper.GetNewSession();

        session.CommitTransaction();

        var repo = new MyBo1Repository(xpoHelper);

        //Act
        var result = await repo.GetBusinessObjectById(1);

        //Assert
        Assert.That(result, Is.Null);
    }

    [Test]
    public async void Delete_With_Filled_Database_Returns_Correct_BusinessObject_With_Exisiting_Key_Property()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var session = xpoHelper.GetNewSession();

        var existingXpoBo1 = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
        {
            MyName = "Test existing1",
        };

        var existingXpoBo2 = new XAFDISolution.Module.BusinessObjects.MyBo1(session)
        {
            MyName = "Test existing2",
        };

        session.CommitTransaction();

        var oid = session.FindObject<Module.BusinessObjects.MyBo1>(CriteriaOperator.Parse("MyName = ?", "Test existing2")).Oid;

        var repo = new MyBo1Repository(xpoHelper);

        //Act

        var result = await repo.Delete(oid);
        var collectionResult = await repo.GetBusinessObjects();

        //Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Oid, Is.EqualTo(-1));
        Assert.That(result.MyName, Is.EqualTo("Test existing2"));

        Assert.That(collectionResult, Is.Not.Null);
        Assert.That(collectionResult.Count(), Is.EqualTo(1));

        Assert.That(collectionResult.ElementAt(0).MyName, Is.EqualTo("Test existing1"));
    }

    [Test]
    public async void Delete_With_Empty_Database_Returns_Null()
    {
        //Arrange
        var xpoHelper = new XpoHelper(new Mock<IUnityContainer>().Object, CreateDataLayer());

        var repo = new MyBo1Repository(xpoHelper);

        //Act

        var result = await repo.Delete(1);
        var collectionResult = await repo.GetBusinessObjects();

        //Assert
        Assert.That(result, Is.Null);

        Assert.That(collectionResult, Is.Not.Null);
        Assert.That(collectionResult.Count(), Is.EqualTo(0));
    }
}

Do not forget to add the Repository to the UnityContainer:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var unityContainer = new UnityContainer();

        unityContainer.RegisterType<IDataLayerHelper, DataLayerHelper>(new ContainerControlledLifetimeManager());
        unityContainer.RegisterType<IXpoHelper, XpoHelper>();
        unityContainer.RegisterType<IBusinessObjectRepository, MyBo1Repository>();

        return unityContainer;
    }
}

For now the data access aspect is finished. How to get the data now to the client?

We need to implement a new MyBusinessObjectController derived from ApiController;

public class MyBusinessObjectController : ApiController
{
    private readonly IBusinessObjectRepository _repository;

    public MyBusinessObjectController(IBusinessObjectRepository repository)
    {
        _repository = repository;
    }

    // GET api/MyBusinessObject
    public async Task<IEnumerable<MyBo1>> Get()
    {
        return await _repository.GetBusinessObjects();
    }

    // GET api/MyBusinessObject/5
    public async Task<MyBo1> Get(int id)
    {
        return await _repository.GetBusinessObjectById(id);
    }

    // POST api/MyBusinessObject
    public async Task<MyBo1> Post([FromBody]MyBo1 value)
    {
        return await _repository.Save(value);
    }

    // PUT api/MyBusinessObject/5
    public async Task<MyBo1> Put(int id, [FromBody]MyBo1 value)
    {
        return await _repository.Save(id, value);
    }

    // DELETE api/MyBusinessObject/5
    public async Task<MyBo1> Delete(int id)
    {
        return await _repository.Delete(id);
    }
}

Here we need no registration to create the controller, cause of the Unity.WebApi package we've imported via nuget.

The tests are not that clear cause we have to return tasks for now, but the async/await in the unittests helps us to write quite straight tests:

[TestFixture]
[Category("CI")]
public class MyBusinessObjectControllerTests
{
    [Test]
    public void Ctor_Does_Not_Throw_Exception()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();

        //Act & Assert
        Assert.DoesNotThrow(() =>
        {
            var controller = new MyBusinessObjectController(mockRepo.Object);
            Assert.That(controller, Is.InstanceOf<MyBusinessObjectController>());
        });
    }

    [Test]
    public async void Get_Will_Call_Repo()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();
        mockRepo.Setup(m => m.GetBusinessObjects()).Returns(() => Task.Run(() => new List<MyBo1>().AsEnumerable()));

        var controller = new MyBusinessObjectController(mockRepo.Object);

        //Act 
        var result = await controller.Get();

        //Assert
        mockRepo.Verify(m => m.GetBusinessObjects(), Times.Exactly(1));
    }

    [Test]
    public async void Get_Will_Return_2_Bo()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();
        mockRepo.Setup(m => m.GetBusinessObjects()).Returns(() => Task.Run(() => new List<MyBo1>() { new MyBo1() { MyName = "Test" }, new MyBo1() {MyName = "Test2"} }.AsEnumerable()));

        var controller = new MyBusinessObjectController(mockRepo.Object);

        //Act 
        var result = (await controller.Get()).OrderBy(m => m.Oid);

        //Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Count(), Is.EqualTo(2));
        Assert.That(result.ElementAt(0).MyName, Is.EqualTo("Test"));
        Assert.That(result.ElementAt(1).MyName, Is.EqualTo("Test2"));
    }

    [Test]
    public async void Get_Will_Return_1_Bo()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();
        mockRepo.Setup(m => m.GetBusinessObjectById(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1() { MyName = "Test" }));

        var controller = new MyBusinessObjectController(mockRepo.Object);

        //Act 
        var result = await controller.Get(1);

        //Assert
        Assert.That(result, Is.Not.Null);
        Assert.That(result.MyName, Is.EqualTo("Test"));
    }

    [Test]
    public async void Post_Will_Set_Something_Up_In_The_Database()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();
        mockRepo.Setup(m => m.Save(It.IsAny<MyBo1>())).Returns(Task.Run(() => new MyBo1() { Oid = 1, MyName = "Test" }));

        var controller = new MyBusinessObjectController(mockRepo.Object);

        //Act 
        var bo = new MyBo1() { MyName = "Test" };
        var result = await controller.Post(bo);

        //Assert
        mockRepo.Verify(m => m.Save(bo), Times.Exactly(1));
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Oid, Is.EqualTo(1));
        Assert.That(result.MyName, Is.EqualTo("Test"));
    }

    [Test]
    public async void Put_Will_Set_Something_Up_In_The_Database()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();
        mockRepo.Setup(m => m.Save(It.IsAny<int>(), It.IsAny<MyBo1>())).Returns(Task.Run(() => new MyBo1() { Oid = 1, MyName = "Test" }));

        var controller = new MyBusinessObjectController(mockRepo.Object);

        //Act 
        var bo = new MyBo1() { MyName = "Test" };
        var result = await controller.Put(1, bo);

        //Assert
        mockRepo.Verify(m => m.Save(1, bo), Times.Exactly(1));
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Oid, Is.EqualTo(1));
        Assert.That(result.MyName, Is.EqualTo("Test"));
    }

    [Test]
    public async void Delete_Will_Set_Something_Up_In_The_Database()
    {
        //Arrage
        var mockRepo = new Mock<IBusinessObjectRepository>();
        mockRepo.Setup(m => m.Delete(It.IsAny<int>())).Returns(Task.Run(() => new MyBo1() { Oid = -1, MyName = "Test" }));

        var controller = new MyBusinessObjectController(mockRepo.Object);

        //Act 
        var result = await controller.Delete(1);

        //Assert
        mockRepo.Verify(m => m.Delete(1), Times.Exactly(1));
        Assert.That(result, Is.Not.Null);
        Assert.That(result.Oid, Is.EqualTo(-1));
        Assert.That(result.MyName, Is.EqualTo("Test"));
    }
}

Ready to rock!

Starting up our projects.aspx) and see something in action.

Hit the page from our controller (http://localhost:3786/api/MyBusinessObject) we get this result:

<ArrayOfMyBo1 xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/XAFDiSolution.DTO"><MyBo1><MyName>Hello from the Win Project: My Dependencies: XAFDISolution.Module.Win.DomainLogic.WinRenamer XAFDISolution.Core.UnityObjectSpaceSession id:XAFDISolution.Core.UnityUnitOfWork(13)</MyName><Oid>2</Oid></MyBo1><MyBo1><MyName>Hello from the Win Project: My Dependencies: XAFDISolution.Module.Win.DomainLogic.WinRenamer XAFDISolution.Core.UnityObjectSpaceSession id:XAFDISolution.Core.UnityUnitOfWork(5)</MyName><Oid>0</Oid></MyBo1><MyBo1><MyName>Hello from the Win Project: My Dependencies: XAFDISolution.Module.Win.DomainLogic.WinRenamer XAFDISolution.Core.UnityObjectSpaceSession id:XAFDISolution.Core.UnityUnitOfWork(12)</MyName><Oid>1</Oid></MyBo1></ArrayOfMyBo1>

Nice or is it? :)

Let's check out fiddler an see if we can get some JSON from our api.

Fiddler input

Outputs:

Fiddler output

Nice! Lets input some data:

Post from fiddler

XAF Post from fiddler

Update some data:

Update from Fiddler

XAF Update from fiddler

And delete it:

Delete from fiddler

XAF Delete from fiddler

As you maybe noticed, i've never startet the application so far. The unit-testing works like a charm.

WebMvc

Here we need a little different package from nuget. Install Unity.WebMvc3 This will also work in Mvc4.

Our little friend the Bootstrapper is also present here:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();

        // register all your components with the container here
        // it is NOT necessary to register your controllers

        // e.g. container.RegisterType<ITestService, TestService>();            

        return container;
    }
}

Same thing here with the Global.asax file. We need to call the Bootstrapper once to get the nice DI-Thing:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterAuth();

        Bootstrapper.Initialise();
    }
}

Notice: The only thing we need in this assembly is the XAFDISolution.DTO Reference. Don't reference any XPO-specific here!!

Unfortunately the portable lib doesn't provide support for the Task class we used so far, so we have to recreate the IBusinessObjectRepository. But i don't like that. So i link the files via Add Existing Item feature from VisualStudio.

Now we need a new Repository. I call this one WebApiBoRepository:

public class WebApiBoRepository : IBusinessObjectRepository
{
    private readonly IEndpointProvider _endpointProvider;

    public WebApiBoRepository(IEndpointProvider endpointProvider)
    {
        _endpointProvider = endpointProvider;
    }

    private HttpClient CreateHttpClient()
    {
        var client = new HttpClient();

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        client.BaseAddress = new Uri(_endpointProvider.WebApiEndpoint);
        return client;
    }

    public async Task<IEnumerable<MyBo1>> GetBusinessObjects()
    {
        var client = CreateHttpClient();

        var response = await client.GetAsync("api/MyBusinessObject");

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsAsync<IEnumerable<MyBo1>>();
    }

    public async Task<MyBo1> GetBusinessObjectById(int id)
    {
        var client = CreateHttpClient();

        var response = await client.GetAsync("api/MyBusinessObject/" + id);

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsAsync<MyBo1>();
    }

    public async Task<MyBo1> Save(MyBo1 bo)
    {
        var client = CreateHttpClient();

        var response = await client.PostAsync("api/MyBusinessObject/", bo, new JsonMediaTypeFormatter());

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsAsync<MyBo1>();
    }

    public async Task<MyBo1> Delete(int id)
    {
        var client = CreateHttpClient();

        var response = await client.DeleteAsync("api/MyBusinessObject/" + id);

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsAsync<MyBo1>();
    }

    public async Task<MyBo1> Save(int id, MyBo1 bo)
    {
        var client = CreateHttpClient();

        var response = await client.PutAsync("api/MyBusinessObject/" + id, bo, new JsonMediaTypeFormatter());

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsAsync<MyBo1>();
    }
}

To avoid hard references to a singe WebApi-Endpoint we use a IEndpointProvider

public interface IEndpointProvider
{
    string WebApiEndpoint { get; }
}

And implement this really silly (but important) class:

public class HTTPEndpointProvider : IEndpointProvider
{
    public string WebApiEndpoint
    {
        get { return "http://localhost:3786/"; }
    }
}

If you need to implement SSL this post from Scott Hanselmann will help you, and you can use a SSLEndpointProvider:

public class SSLEndpointProvider : IEndpointProvider
{
    public string WebApiEndpoint
    {
        get { return "https://localhost:8443/"; }
    }
}

Don't forget to register the IEndpointProvider and the IBusinessObjectRepository

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var unityContainer = new UnityContainer();

        // register all your components with the container here
        // it is NOT necessary to register your controllers

        unityContainer.RegisterType<IEndpointProvider, HTTPEndpointProvider>();            

        unityContainer.RegisterType<IBusinessObjectRepository, WebApiBoRepository>();            

        return unityContainer;
    }
}

So far i found no easy way to test this Repository without providing a WebApi. Maybe someone will provide this functionality :).

Now we need a simple AsyncController called BOController:

public class BOController : AsyncController
{
    private readonly IBusinessObjectRepository _repository;

    public BOController(IBusinessObjectRepository repository)
    {
        _repository = repository;
    }

    //
    // GET: /BO/
    public async Task<ActionResult> Index()
    {
        return View(await _repository.GetBusinessObjects());
    }

    //
    // GET: /BO/Details/5

    public async Task<ActionResult> Details(int id)
    {
        return View(await _repository.GetBusinessObjectById(id));
    }

    //
    // GET: /BO/Create

    public ActionResult Create()
    {
        return View();
    }

    //
    // POST: /BO/Create

    [HttpPost]
    public async Task<ActionResult> Create(MyBo1 bo)
    {
        if (ModelState.IsValid)
        {
            try
            {
                await _repository.Save(bo);

                return RedirectToAction("Index");
            }
            catch
            {
                return View(bo);
            }
        }
        return View(bo);
    }

    //
    // GET: /BO/Edit/5

    public async Task<ActionResult> Edit(int id)
    {
        return View(await _repository.GetBusinessObjectById(id));
    }

    //
    // POST: /BO/Edit/5

    [HttpPost]
    public async Task<ActionResult> Edit(int id, MyBo1 bo)
    {
        if (ModelState.IsValid)
        {
            try
            {
                _repository.Save(id, bo);

                return RedirectToAction("Index");
            }
            catch
            {
                return View(bo);
            }
        }
        return View(bo);
    }

    //
    // GET: /BO/Delete/5

    public async Task<ActionResult> Delete(int id)
    {
        return View(await _repository.GetBusinessObjectById(id));
    }

    //
    // POST: /BO/Delete/5

    [HttpPost]
    public async Task<ActionResult> Delete(int id, MyBo1 bo)
    {
        if (ModelState.IsValid)
        {
            try
            {
                bo = await _repository.Delete(id);

                return RedirectToAction("Index");
            }
            catch
            {
                return View(bo);
            }
        }
        return View(bo);
    }

}

The unit tests are simple:

[TestFixture]
[Category("CI")]
public class BOControllerTest
{
    [Test]
    public void Ctor_Does_Not_Throw_Exception()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();

        //Arrange & Assert
        Assert.DoesNotThrow(() =>
        {
            var controller = new BOController(mockRepository.Object);
            Assert.That(controller, Is.InstanceOf<BOController>());
        });
    }

    [Test]
    public async void Index_Does_Call_Repo_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.GetBusinessObjects())
                      .Returns(() => Task.Run(() => new List<MyBo1>().AsEnumerable()));
        var controller = new BOController(mockRepository.Object);

        //Act
        var result = (ViewResult) await controller.Index();

        //Assert
        Assert.That(result.Model, Is.Not.EqualTo(null));
        Assert.That(result.Model, Is.InstanceOf<IEnumerable<MyBo1>>());

        mockRepository.Verify(m => m.GetBusinessObjects(), Times.Exactly(1));
    }

    [Test]
    public async void Details_Does_Call_Repo_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.GetBusinessObjectById(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1{Oid = 1, MyName = "Test"}));
        var controller = new BOController(mockRepository.Object);

        //Act
        var result = (ViewResult)await controller.Details(1);

        //Assert
        Assert.That(result.Model, Is.Not.EqualTo(null));
        Assert.That(result.Model, Is.InstanceOf<MyBo1>());
        Assert.That((result.Model as MyBo1).Oid, Is.EqualTo(1));
        Assert.That((result.Model as MyBo1).MyName, Is.EqualTo("Test"));
        mockRepository.Verify(m => m.GetBusinessObjectById(1), Times.Exactly(1));
    }

    [Test]
    public  void Create_Does_Not_Call_Repo()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        var controller = new BOController(mockRepository.Object);

        //Act
        var result = (ViewResult) controller.Create();

        //Assert
        mockRepository.Verify(m => m.GetBusinessObjectById(1), Times.Exactly(0));
    }

    [Test]
    public async void Create_Does_Call_Repo_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.Save(It.IsAny<MyBo1>()));
        var controller = new BOController(mockRepository.Object);

        var expected = new MyBo1() {MyName = "Test"};
        //Act
        var result = (ViewResult)await controller.Create(expected);

        //Assert
        mockRepository.Verify(m => m.Save(expected), Times.Exactly(1));
    }

    [Test]
    public async void Edit_Does_Call_Repo_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.GetBusinessObjectById(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1 { Oid = 1, MyName = "Test" }));
        var controller = new BOController(mockRepository.Object);

        //Act
        var result = (ViewResult)await controller.Edit(1);

        //Assert
        Assert.That(result.Model, Is.Not.EqualTo(null));
        Assert.That(result.Model, Is.InstanceOf<MyBo1>());
        Assert.That((result.Model as MyBo1).Oid, Is.EqualTo(1));
        Assert.That((result.Model as MyBo1).MyName, Is.EqualTo("Test"));
        mockRepository.Verify(m => m.GetBusinessObjectById(1), Times.Exactly(1));
    }

    [Test]
    public async void Edit_Will_Update_Call_Repo_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.Save(It.IsAny<int>(), It.IsAny<MyBo1>()));

        var controller = new BOController(mockRepository.Object);

        var expected = new MyBo1() {MyName = "Update"};
        //Act
        var result = (RedirectToRouteResult)await controller.Edit(1, expected);

        //Assert
        mockRepository.Verify(m => m.Save(1, expected), Times.Exactly(1));
    }

    [Test]
    public async void Delete_Will_Call_Repo_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.GetBusinessObjectById(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1 { Oid = 1, MyName = "Test" }));

        var controller = new BOController(mockRepository.Object);

        //Act
        var result = (ViewResult)await controller.Delete(1);

        //Assert
        mockRepository.Verify(m => m.GetBusinessObjectById(1), Times.Exactly(1));
    }

    [Test]
    public async void Delete_Will_Call_Repo_Delete_Correct()
    {
        //Arrange
        var mockRepository = new Mock<IBusinessObjectRepository>();
        mockRepository.Setup(m => m.Delete(It.IsAny<int>()));

        var controller = new BOController(mockRepository.Object);

        var expected = new MyBo1() { MyName = "Update" };
        //Act
        var result = (ViewResult)await controller.Delete(1, expected);

        //Assert
        mockRepository.Verify(m => m.Delete(1), Times.Exactly(1));
    }
}

Views

Let's modify the scaffolded views:

Create.cshtml

@model XAFDISolution.DTO.MyBo1

@{
    ViewBag.Title = "View2";
}

<h2>View2</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset><legend>MyBo1</legend><div class="editor-label">
            @Html.LabelFor(model => model.MyName)</div><div class="editor-field">
            @Html.EditorFor(model => model.MyName)
            @Html.ValidationMessageFor(model => model.MyName)</div><p><input type="submit" value="Create" /></p></fieldset>
}<div>
    @Html.ActionLink("Back to List", "Index")</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Delete.cshtml

@model XAFDISolution.DTO.MyBo1

@{
    ViewBag.Title = "Delete";
}

<h2>Delete</h2><h3>Are you sure you want to delete this?</h3><fieldset><legend>MyBo1</legend><div class="display-label">
         @Html.DisplayNameFor(model => model.Oid)</div><div class="display-field">
        @Html.DisplayFor(model => model.Oid)</div><div class="display-label">
         @Html.DisplayNameFor(model => model.MyName)</div><div class="display-field">
        @Html.DisplayFor(model => model.MyName)</div></fieldset>
@using (Html.BeginForm()) {<p><input type="submit" value="Delete" /> |
        @Html.ActionLink("Back to List", "Index")</p>
}

Details.cshtml

@model XAFDISolution.DTO.MyBo1

@{
    ViewBag.Title = "Details";
}

<h2>Details</h2><fieldset><legend>MyBo1</legend><div class="display-label">
         @Html.DisplayNameFor(model => model.Oid)</div><div class="display-field">
        @Html.DisplayFor(model => model.Oid)</div><div class="display-label">
         @Html.DisplayNameFor(model => model.MyName)</div><div class="display-field">
        @Html.DisplayFor(model => model.MyName)</div></fieldset><p>
    @Html.ActionLink("Edit", "Edit", new { id=Model.Oid  }) |
    @Html.ActionLink("Back to List", "Index")</p>

Edit.cshtml

@model XAFDISolution.DTO.MyBo1

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset><legend>MyBo1</legend><div class="editor-label">
            @Model.Oid</div><div class="editor-label">
            @Html.LabelFor(model => model.MyName)</div><div class="editor-field">
            @Html.EditorFor(model => model.MyName)
            @Html.ValidationMessageFor(model => model.MyName)</div><p><input type="submit" value="Save" /></p></fieldset>
}<div>
    @Html.ActionLink("Back to List", "Index")</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Index.cshtml

@model IEnumerable<XAFDISolution.DTO.MyBo1>

@{
    ViewBag.Title = "View1";
}

<h2>View1</h2><p>
    @Html.ActionLink("Create New", "Create")</p><table><tr><th>
            @Html.DisplayNameFor(model => model.Oid)</th><th>
            @Html.DisplayNameFor(model => model.MyName)</th><th></th></tr>

@foreach (var item in Model) {
    <tr><td>
            @Html.DisplayFor(modelItem => item.Oid)</td><td>
            @Html.DisplayFor(modelItem => item.MyName)</td><td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Oid }) |
            @Html.ActionLink("Details", "Details", new { id=item.Oid }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Oid })</td></tr>
}</table>

Action!

List:

List MVC

Create:

MVC Create

Result:MVC Create Result

Details:

MVC Details

Edit:

MVC Edit

Result:

MVC Edit Result

Delete:

MVC Delete

Result:

MVC Delete Result

Missing Parts

The one missing part is how to apply custom business logic for the XPO Object (Rename me, remember?). This will be covered in a future blog post.

Check out the sources

BLOG by Manuel Grundner: How to use Dependency Injection in XAF (ASP.NET Custom Actions) Part 4

$
0
0

How to use Dependency Injection in XAF (ASP.NET Custom Actions) Part 4

Now we get to the interesting part. Dependency Injection for an ApiAction.

The interface of the first blog postIRenamer looks like this:

public interface IRenamer
{
    void RenameMe(MyBo1 myBo1);
}

Our implementation from WebApi looks like this

public class WebApiRenamer : IRenamer
{
    public void RenameMe(MyBo1 myBo1)
    {
        myBo1.MyName = string.Format("I was renamed by '{0}'. Original Name '{1}'", typeof (WebApiRenamer).FullName, myBo1.MyName);
    }
}

Now we should register our Renamer in the Bootstrapper:

public static class Bootstrapper
{
    public static void Initialise()
    {
        var container = BuildUnityContainer();

        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var unityContainer = new UnityContainer();

        unityContainer.RegisterType<IDataLayerHelper, DataLayerHelper>(new ContainerControlledLifetimeManager());
        unityContainer.RegisterType<IXpoHelper, XpoHelper>();
        unityContainer.RegisterType<IBusinessObjectRepository, MyBo1Repository>();
        unityContainer.RegisterType<IRenamer, WebApiRenamer>();

        return unityContainer;
    }
}

The WebApiRepository

We need to extend the IBusinessObjectRepository with the method Rename that takes the id of an BusinessObject and returns it's id:

public interface IBusinessObjectRepository
{
    Task<IEnumerable<MyBo1>> GetBusinessObjects();

    Task<MyBo1> GetBusinessObjectById(int id);

    Task<MyBo1> Save(MyBo1 bo);

    Task<MyBo1> Delete(int id);

    Task<MyBo1> Save(int id, MyBo1 bo);

    Task<int> Rename(int id);
}

The implementation can look like this:

public class MyBo1Repository : IBusinessObjectRepository
{
    //...

    public Task<MyBo1> Rename(int id)
    {
        return Task.Run(() =>
        {
            var bo = this.BusinessObjectsXPO.FirstOrDefault(m => m.Oid == id);

            if (bo != null)
            {
                bo.RenameMe();

                bo.Session.CommitTransaction();

                return CreateBusinessObject(bo);
            }
            return null;
        });
    }
}

Let's test this:

[Test]
public async void Rename_Will_Be_Called_Correctly()
{
    var unityContainer = new UnityContainer();

    var mockRenamer = new Mock<IRenamer>();
    mockRenamer.Setup(m => m.RenameMe(It.IsAny<Module.BusinessObjects.MyBo1>()));

    unityContainer.RegisterInstance<IRenamer>(mockRenamer.Object);

    //Arrange
    var xpoHelper = new XpoHelper(unityContainer, CreateDataLayer());

    var session = xpoHelper.GetNewSession();

    var bo = new Module.BusinessObjects.MyBo1(session);

    bo.MyName = "TestName";

    session.CommitTransaction();

    var oid = session.FindObject<Module.BusinessObjects.MyBo1>(null).Oid;

    var repo = new MyBo1Repository(xpoHelper);

    //Act

    var result = await repo.Rename(oid);

    //Assert
    Assert.That(result, Is.Not.Null);
    Assert.That(result.Oid, Is.EqualTo(1));
    Assert.That(result.MyName, Is.EqualTo("TestName"));

    mockRenamer.Verify(m => m.RenameMe(It.IsAny<Module.BusinessObjects.MyBo1>()), Times.Exactly(1));
}

Also test the Real implementation:

[TestFixture]
[Category("CI")]
public class WebApiRenamerTest
{
    [Test]
    public void Ctor_Does_Not_Throw_An_Exception()
    {
        //Arrange / Act / Assert
        Assert.DoesNotThrow(() =>
        {
            var renamer = new WebApiRenamer();

            Assert.That(renamer, Is.InstanceOf<WebApiRenamer>());
        });
    }

    [Test]
    public void Rename_Does_Rename_Object()
    {
        //Arrange
        var renamer = new WebApiRenamer();

        var session = new Session(new SimpleDataLayer(new InMemoryDataStore()));
        var bo = new MyBo1(session);
        bo.MyName = "Test";

        //Act
        renamer.RenameMe(bo);

        //Assert
        Assert.That(bo.MyName, Is.EqualTo("I was renamed by 'XAFDISolution.WebApi.Domain.WebApiRenamer'. Original Name 'Test'"));
    }
}

The WebApiController

Extend the MyBusinessObjectController with the Rename method:

// PUT api/MyBusinessObject/5
[HttpPut]
public async Task<MyBo1> Rename(int id)
{
    return await _repository.Rename(id);
}

The HttpPutAttribute tells the framework that this will be a Put-Http-Request.

Test it:

[Test]
public async void Rename_Will_Call_Rename_And_Return_TheObject()
{
    //Arrange
    var mockRepo = new Mock<IBusinessObjectRepository>();
    mockRepo.Setup(m => m.Rename(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1(){MyName = "Renamed"}));

    var controller = new MyBusinessObjectController(mockRepo.Object);

    //Act
    var actual = await controller.Rename(1);

    //Assert
    Assert.That(actual, Is.Not.Null);
    Assert.That(actual.MyName, Is.EqualTo("Renamed"));
}

Fiddler!

One thing we need to tell WebApi how to handle routes with a custom pattern:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapHttpRoute(
            name: "DefaultApi2",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

The DefaultApi2 will handle our custom actions that will accept a id in the url.

Then we can call fiddler:

Call Fiddler

Result:

Result from fiddler

WebMvc

Repository

We need to extend the WebApiBoRepository:

public async Task<MyBo1> Rename(int id)
{
    var client = CreateHttpClient();

    var response = await client.PutAsync("api/MyBusinessObject/Rename/" + id, new MyBo1(), new JsonMediaTypeFormatter());

    response.EnsureSuccessStatusCode();

    return await response.Content.ReadAsAsync<MyBo1>();
}

Controller

The BOController must be extended:

//
// POST: /BO/Rename/5
[HttpPut]
public async Task<ActionResult> Rename(int id)
{
    if (ModelState.IsValid)
    {
        try
        {
            var bo = await _repository.Rename(id);

            return RedirectToAction("Details", bo);
        }
        catch
        {
            return View("Index");
        }
    }
    return View("Index");
}

Test it:

[Test]
public async void Rename_Will_Call_Repo_Rename_Correct()
{
    //Arrange
    var mockRepository = new Mock<IBusinessObjectRepository>();
    mockRepository.Setup(m => m.Rename(It.IsAny<int>())).Returns(() => Task.Run(() => new MyBo1() { Oid = 1}));

    var controller = new BOController(mockRepository.Object);


    //Act
    var result = (RedirectToRouteResult)await controller.Rename(1);

    //Assert
    mockRepository.Verify(m => m.Rename(1), Times.Exactly(1));
}

Extent the Index.cshtml:

@model IEnumerable<XAFDISolution.DTO.MyBo1>

@{
    ViewBag.Title = "View1";
}

<h2>View1</h2><p>
    @Html.ActionLink("Create New", "Create")</p><table><tr><th>
            @Html.DisplayNameFor(model => model.Oid)</th><th>
            @Html.DisplayNameFor(model => model.MyName)</th><th></th></tr>

@foreach (var item in Model) {
    <tr><td>
            @Html.DisplayFor(modelItem => item.Oid)</td><td>
            @Html.DisplayFor(modelItem => item.MyName)</td><td>
            @Html.ActionLink("Edit", "Edit", new { id=item.Oid }) |
            @Html.ActionLink("Details", "Details", new { id=item.Oid }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.Oid })
            @Html.ActionLink("Rename", "Rename", new { id=item.Oid })</td></tr>
}</table>

Action!

Call it

See the action!

Source is updated:

See here.

Viewing all 861 articles
Browse latest View live