Category Archives: Uncategorized

The native System.DirectoryService can’t recursively retrieve groups, so I created this enchanced one.


    public class ActiveDirectoryService
    {
        private readonly string _gusetUserid;
        private readonly string _guestPasswd;
        private readonly DirectoryEntry _ldapEntry;

        public enum FilterType
        {
            CN,             // subGroup
            SAMAccountName  // user
        }

        public ActiveDirectoryService(string host, string baseDN, string guestUserId, string guestPasswd)
        {
            _gusetUserid = guestUserId;
            _guestPasswd = guestPasswd;

            _ldapEntry = new DirectoryEntry("LDAP://" + host + "/" + baseDN, _gusetUserid, _guestPasswd);
        }

        public IEnumerable<string> GetADUserGroups(string username)
        {
            return GetADUserGroups(username, true);
        }

        public IEnumerable<string> GetADUserGroups(string username, bool recursive)
        {
            return GetParentGroups(username, new List<string>(), FilterType.SAMAccountName, recursive);
        }

        private IList<string> GetParentGroups(string childName,
            IList<string> originalGroups,
            FilterType filterType,
            bool recursive)
        {
            var search = new DirectorySearcher(_ldapEntry)
                             {
                                 Filter = string.Format("({0}={1})", filterType, childName)
                             };
            search.PropertiesToLoad.Add("memberOf");
            SearchResult result = search.FindOne();

            if (result != null)
            {
                int propertyCount = result.Properties["memberOf"].Count;

                string dn;

                for (int i = 0; i < propertyCount; i++)
                {
                    dn = result.Properties["memberOf"][i].ToString();
                    int equalsIndex = dn.IndexOf("=", 1);
                    int commaIndex = dn.IndexOf(",", 1);
                    if (equalsIndex != -1)
                    {
                        string groupName = dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1);
                        if (!originalGroups.Contains(groupName))
                        {
                            originalGroups.Add(groupName);

                            if (recursive) originalGroups = GetParentGroups(groupName, originalGroups, FilterType.CN, true);
                        }
                    }
                }
            }

            return originalGroups;
        }
    }
namespace ActiveDirectoryServiceSpecs
{
    [TestFixture]
    public class ActiveDirectoryServiceSpecs
    {
        const string LDAP_HOST = "Dcxx";
        const string LDAP_BASE_DN = "DC=xxxx,DC=xxx,DC=xx";
        const string LDAP_GUEST_USERID = "guest";
        const string LDAP_GUEST_PASSWD = "hello";

        private ActiveDirectoryService _SUT;
        [SetUp]
        public void Setup()
        {
            _SUT = new ActiveDirectoryService(LDAP_HOST, LDAP_BASE_DN, LDAP_GUEST_USERID, LDAP_GUEST_PASSWD);
        }

        [Test]
        public void should_get_immediate_user_ad_groups()
        {
            IEnumerable<string> adUserGroups = _SUT.GetADUserGroups("fmao", false);
            Assert.That(adUserGroups.Count() > 0);

            Assert.IsFalse(adUserGroups.Contains("Information Systems"));

            foreach (var s in adUserGroups)
            {
                Console.WriteLine(s);
            }
        }   

        [Test]
        public void should_get_recursive_user_ad_groups()
        {
            IEnumerable<string> adUserGroups = _SUT.GetADUserGroups("fmao", true);
            Assert.That(adUserGroups.Count() > 20);

            Assert.That(adUserGroups.Contains("Information Systems"));
            foreach (var s in adUserGroups)
            {
                Console.WriteLine(s);
            }
        }
    }
}

I thought NCover is completely commercial, until yesterday I found NCover commnity version (1.5.8beta) on their website. With a little bit effort to enable it running on our CI server, we got a very simple version of code coverage report.

Here is the batch job I created to run NCover:

SET NCOVER=”c:\program files\ncover\ncover.console”
SET NUNIT=”C:\Work_Dir\StakeholderDemo2009\tools\nunit\nunit-console.exe”
SET WORK_DIR=”C:\Work_Dir\StakeholderDemo2009\compile”
SET ASSEMBLY=StakeholderDemo.Layers.Tests.dll

%NCOVER% //w %WORK_DIR%  %NUNIT%  “%WORK_DIR%\%ASSEMBLY%” //reg //x “%WORK_DIR%\Coverage.Xml” //l “%WORK_DIR%\Coverage.Log”

I’ve seen the report generated by commercial version, much better and detailed. According the feature comparison table, integration with NUnit will be simpler.

I currently added this NCover batch as a Publisher executable task in my CI project, because I want NCover run after every build no matter green or red.

One challenge left to me is, my NAnt build script should run NCover instead of NUnit on CI Server, otherwise all unit-tests will run twice.

When using UserName instead of the default Windows authentication, the idea of using Custom username validator can only do the authentication part, while the authorization always needs more info besides the username, usually it’s a list of roles. We could create a CustomAuthorizationPolicy : IAuthorizationPolicy, then fill out the evalute method something like this:


         <behavior name="customAuthPolicy">
            <serviceMetadata httpGetEnabled="true"/>
            <serviceDebug includeExceptionDetailInFaults="true"/>

            <serviceAuthorization principalPermissionMode ="Custom" >
                <authorizationPolicies>
                  <add policyType="Premotion.Services.CustomAuthorizationPolicy, App_Code" />
                </authorizationPolicies>
            </serviceAuthorization>
            <serviceCredentials>
                <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
                <userNameAuthentication userNamePasswordValidationMode="Custom"
                                        customUserNamePasswordValidatorType="Premotion.Services.UsernameValidator, App_Code" />
            </serviceCredentials>
        </behavior>

    public class CustomAuthorizationPolicy : IAuthorizationPolicy
    {
        public string Id
        {
            get { return new Guid().ToString(); }
        }

        public bool Evaluate(EvaluationContext evaluationContext, ref object state)
        {
            // get the authenticated client identity
            IIdentity client = GetClientIdentity(evaluationContext);

            List<string> roles = new List<string>();
            if (client.Name == "frank") roles.Add("Admins");

            evaluationContext.Properties["Principal"] = new CustomPrincipal(client, roles.ToArray());

            return true;
        }

        public ClaimSet Issuer
        {
            get { return ClaimSet.System; }
        }

        private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
        {
            object obj;
            if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
                throw new Exception("No Identity found");

            IList<IIdentity> identities = obj as IList<IIdentity>;
            if (obj == null || identities.Count <= 0)
                throw new Exception("No Identity found");

            return identities[0];
        }
    }

Not only the relection code to get identity from  evalution context,  to create our own CustomPriciple and CustomIdentiy class is also very annoying, even it’s simple, but, can we make it simpler. What we need just a list of roles based on the username.

    /// <summary>
    /// Make sure to generate the test certificate
    /// C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin
    /// makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyServerCert -sky exchange –pe
    /// </summary>
    [TestFixture]
    public class UsernameWcfSpecs
    {
        private ServiceHost _host;
        private const string _url = "http://localhost:9000/TestUsernameAuthentication";

        private void SetupWcfHostTakingWindowsLogin()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows; // default

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);
            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private void SetupWcfHostTakingUsernameButValidateItAgainstWindows()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message; // default

            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; // default is windows

            // protect message sent from client and certificate host
            _host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                X509FindType.FindBySubjectName, "MyServerCert");

            _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                UserNamePasswordValidationMode.Windows; // default

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);

            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private void SetupWcfHostTakingUsernameAndDoCustomValidation()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message; // default

            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; // default is windows

            // protect message sent from client and certificate host
            _host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                X509FindType.FindBySubjectName, "MyServerCert");

            _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                UserNamePasswordValidationMode.Custom; // default is windows

            _host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.None; // default is UseWindowGroups
            _host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator =
                new CustomUsernameValidator(new MyActiveDirectoryService());

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);

            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private void SetupWcfHostUsingUsernameAuthCustomValidationWithAuthorization()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message; // default

            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; // default is windows

            // protect message sent from client and certificate host
            _host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                X509FindType.FindBySubjectName, "MyServerCert");

            _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                UserNamePasswordValidationMode.Custom; // default is windows
            var policies = new List<IAuthorizationPolicy>();
            policies.Add(new CustomAuthorizationPolicy(new MyActiveDirectoryService()));
            _host.Authorization.ExternalAuthorizationPolicies = policies.AsReadOnly();
            _host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom; // default is UseWindowGroups

            _host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator =
                new CustomUsernameValidator(new MyActiveDirectoryService());

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);

            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private ChannelFactory<ITestService> _channelFactory;
        private bool _success;

        [TearDown]
        public void CleanUpChannelFacotry()
        {
            if (_success)
            {
                _channelFactory.Close();
            }
            else
            {
                _channelFactory.Abort();
            }

            _host.Close();
            Console.WriteLine("Wcf host stopped.");
        }

        [Test]
        public void should_connect_to_host_taking_windows_auth()
        {
            SetupWcfHostTakingWindowsLogin();

            var binding = new WSHttpBinding();

            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(_url));

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            _success = true;
        }

        [Test]
        public void should_connect_to_host_taking_username_auth_windows_login()
        {
            SetupWcfHostTakingUsernameButValidateItAgainstWindows();

            var binding = new WSHttpBinding();
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Mode = SecurityMode.Message;

            // usually certificate should be same as the domain name of service host, so set identity at client to bypass this check.
            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(new Uri(_url), EndpointIdentity.CreateDnsIdentity("MyServerCert")));

            // Ignore certificate, call could be redirected to a malicious service (through client address resolving)
            _channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            _channelFactory.Credentials.UserName.UserName = "guest";
            _channelFactory.Credentials.UserName.Password = "hello";

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            _success = true;
        }

        [Test]
        public void should_connect_to_host_takeing_username_validation()
        {
            SetupWcfHostTakingUsernameAndDoCutomValidation();

            var binding = new WSHttpBinding();

            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

            // usually certificate should be same as the domain name of service host, so set identity at client to bypass this check.
            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(new Uri(_url), EndpointIdentity.CreateDnsIdentity("MyServerCert")));

            // Ignore certificate, call could be redirected to a malicious service (through client address resolving)
            _channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            _channelFactory.Credentials.UserName.UserName = "guest";
            _channelFactory.Credentials.UserName.Password = "hello";

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            bool noAccess = false;
            try
            {
                proxy.CmisUsersOnly();
            }
            catch (SecurityAccessDeniedException)
            {
                noAccess = true;
            }
            Assert.That(noAccess);

            _success = true;
        }

        [Test]
        public void should_connect_to_host_takeing_username_validation_with_authorization()
        {
            SetupWcfHostUsingUsernameAuthCustomValidationWithAuthorization();

            var binding = new WSHttpBinding();

            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

            // usually certificate should be same as the domain name of service host, so set identity at client to bypass this check.
            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(new Uri(_url), EndpointIdentity.CreateDnsIdentity("MyServerCert")));

            // Ignore certificate, call could be redirected to a malicious service (through client address resolving)
            _channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            _channelFactory.Credentials.UserName.UserName = "guest";
            _channelFactory.Credentials.UserName.Password = "hello";

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            proxy.CmisUsersOnly();
            _success = true;
        }

AspSqlMembership

In fact, MS provide a default Role-based AspSqlMembership Provider for WCF, with a pre-defined database and whole set of membership provider / role manager classes. Fortunately, We can easily override the classes to let WCF to go against our own credential store.


        <behavior name="AglcSqlMembership">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceAuthorization principalPermissionMode ="UseAspNetRoles" />
          <serviceCredentials>
            <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
            <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" />
          </serviceCredentials>
        </behavior>

...

    <system.web>
    <membership defaultProvider ="MySqlMembershipProvider" >
<providers>
        <add name ="MySqlMembershipProvider"
             type ="Premotion.Services.MySqlMembershipProvider"
             connectionStringName ="WcfUsers"
          />
      </providers>
     </membership>

    <roleManager enabled="true" defaultProvider ="MySqlRoleManager">
<providers>
        <add name ="MySqlRoleManager"
             type ="Premotion.Services.MySqlRoleManager"
             connectionStringName ="WcfUsers"
             />
      </providers>
    </roleManager>
  </system.web>
  <connectionStrings>
    <add name="WcfUsers"  connectionString ="data souce=.\SQLEXPRESS; Integrated Security=SSPI; Initial Catalog=WcfUsers" />
  </connectionStrings>

    public class MySqlMembershipProvider : SqlMembershipProvider
    {
        public override bool ValidateUser(string username, string password)
        {
            // check if the user is not test
            if (username == "test1" && password == "test1") return true;
            return false;
        }

    public class MySqlRoleManager : SqlRoleProvider
    {
        public override bool IsUserInRole(string username, string roleName)
        {
            if (username == "test1" && roleName == "Admin") return true;
            return false;
        }
    }  

  }

public class CoreService: ICoreServiceContract
{
        [PrincipalPermission(SecurityAction.Demand, Role="Admin")]
        public void AdminsOnly()
        {

        }
}

public string Hello()
{
   OperationContext context = OperationContext.Current;

   MessageProperties messageProperties = context.IncomingMessageProperties;

   RemoteEndpointMessageProperty endpointProperty =
     messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

   return string.Format("Hello {0}! Your IP address is {1} and your port is {2}",
      ServiceSecurityContext.Current.PrimaryIdentity.Name, endpointProperty.Address, endpointProperty.Port);
   // If it's Windows authorization
   // return "How are you ! " + Thread.CurrentPrincipal.Identity.Name;
 }

I had to add a connectionStringName in those xml definition part, even I’m not using it at all.

Some interesting differences,

security mode UserNamePassword Validation Mode Authorization PrincipalPermissionMode Authentication Type principle type
Windows Kerberos WindowsPrinciple
UserName Windows anyone but Custom NTLM WindowsPrinciple
UserName Custom (must apply CutomAuthorziationPolicy, otherwise the Thread.CurrentPrincipal is not authenticated while ServiceSecurityContext is ) Custom CustomUsernameValidator
UserName MembershipProvider UseAspNetRoles MembershipProviderValidator GenricPrinciple

Updates: The WsHttpBinding using Windows auth which sendng Kerberos auth will fail on Vista client, the error message is “The message or signature supplied for verification has been altered”. Microsoft admit it’s a bug.

Hide menustrip in child form.

From MSDN How to: Set Up Automatic Menu Merging for MDI Applications

Step 5, Set the Visible property of the target MenuStrip to false.

Otherwise there will be a gap left by this empty menuStrip.

Merge index can’t jump

MSDN How to: Insert a MenuStrip into an MDI Drop-Down Menu (Windows Forms) (MatchOnly for menu (root), and Insert for menu items.)

See the example, no gap between each menu items merge index. No Gap! There is different than PB and VB.

No menustrip inheritance?

MS forces you to use merge menus between mdi parent and child.

When databinding IList to  listbox, the setter won’t be triggered when calling IList.Add(). I had to use a local variable, then set it back to view property to trigger the setter.

The listbox won’t automaticlly refresh/update the changes in IList, the stupid way of working around is set datasource to null, then set it to new value.

Looks like using CuccencyManager looks more professional? Here is MS’s instruction for CurrencyManager.

We spent almost 3 hours today on trying to figure a weird problem about databinding combo box. The error just doesn’t make sense at all. What we wanted to do is very general and simple,  showing text in display area, but save key value back to object’s property.

We kept getting the selected index back to -1 when tabbing out from combo box control, debugged into combobox_selectIndexChanged method, tried to do a cast to int from comboBox.SelectedValue. The comipler didn’t allow us to do that, complained data should be less than infinity.

What? We then added a intermediate cast, first cast to int, then cast this int to short. The problem went away.

That why, short and int just don’t match automatically when databinding.

Another example of telling me that UI work is hard.

JP provides a great solution to warp log4net, textwritter and other logging service, the basic idea is to create a common interface and a common Log class used by application directly.

In Application:

Log.For(this).Info(…)

And Log class initialize different logging service might be passed in.


public class Log
{
private static ILogFactory logFactory;

public static void InitializeLogFactory(ILogFactory logFactory)
{
Log.logFactory = logFactory;
}

public static ILog For(object itemThatRequiresLoggingServices)
{
return For(itemThatRequiresLoggingServices.GetType());
}

public static ILog For(Type type){

// This is IOC style, then we should not use logFactory field which needs to be passed in when using log.
//            return DependencyResolver.GetImplementationOf().CreateFor(type);

return logFactory.CreateFor(type); ;
}
}

If it’s a simple console logging service, the implementation will be like this:


    public class ConsoleLogFactory : ILogFactory
    {
        public ILog CreateFor(Type type)
        {
            return new ConsoleLogger();
        }

        private class ConsoleLogger : ILog
        {
            public void Informational(string message)
            {
                System.Console.Out.WriteLine(message);
            }

            public void Informational(string messageFormat, params object[] args)
            {
                System.Console.Out.WriteLine(messageFormat, args);
            }

And the log4net factory will be this, in which the WireUpConfiguration() method is using singleton pattern.

    public class Log4NetLogFactory : ILogFactory
    {
        private ILog4NetInitialization initialization;
        private bool initialized;

        public Log4NetLogFactory(): this(new Log4NetInitialization())
        {
        }

        public Log4NetLogFactory(string filename): this(new Log4NetInitialization(filename))
        {
        }

        // using ILog4NetInitialization can enable logFactory read from different app config file.
        public Log4NetLogFactory(ILog4NetInitialization initialization)
        {
            this.initialization = initialization;
        }

        public ILog CreateFor(Type typeThatRequiresLoggingServices)
        {
            WireUpConfiguration();
            return new Log4NetLog(LogManager.GetLogger(typeThatRequiresLoggingServices));
        }

        private void WireUpConfiguration()
        {
            if (initialized) return;

            initialization.Execute();
            initialized = true;
        }
    }

Finally, the log4netINitialization is this: (I don’t think ILog4NetInitialization is necessary, in fact the whole Log4NetInitialization can also be moved into log4net Factory class even.)

    public class Log4NetInitialization : ILog4NetInitialization
    {
        private readonly XmlElement configuration;
        public Log4NetInitialization(){}

        public Log4NetInitialization(string filename)
        {
            XmlDocument document = new XmlDocument();
            document.Load(filename);
            configuration = document.DocumentElement; 

        }
        public Log4NetInitialization(XmlElement configuration)
        {
            this.configuration = configuration;
        }

        public void Execute()
        {
            if (configuration == null) XmlConfigurator.Configure();
            else XmlConfigurator.Configure(configuration);
        }
    }

The benefit of all those work is, the front end is same based on different logging service implementation,

Log.For(this).Info(…)

Log.For(this).Debug(…)

Log.For(this).Fatal(…)

Log.For(this).Warn(…)

Log.For(this).Error(…)

To test CSLA BO’s authorization settings, I have to mock the IPrincipal.IsInRole method, RhinoMock example like this:

        [Test]
        [ExpectedException("System.Security.SecurityException")]
        public void CanHideForUnAuthorizedUser()
        {
            MockRepository mocks = new MockRepository();
            IPrincipal mockPrincipal = (IPrincipal)mocks.CreateMock<IPrincipal>();
            Csla.ApplicationContext.User = mockPrincipal;

            using (mocks.Record())
            {

                Expect.Call(mockPrincipal.IsInRole("user")).Return(false);

            }// Replay and validate interaction
            Order obj = null;
            using (mocks.Playback())
            {
                obj = Order.GetOrder(new Guid("c512e473-19fd-401c-80f6-055cf239e461"));

            }// Post-interaction assertions
            Assert.IsNull(obj);

        }

         [Test]
        public void CanGetOrderUsingTypeMock()
        {

            // Setup Stud Data to be returned from mocked repository.
            // This code could be moved into record expectation block
            // but we assume everything inside that block is mocked.
            // to reduce confusion, just leave it here.
            // In fact , this mode is faster comparing the other one.
            OrderDTO stubOrderDTO = new OrderDTO();
            stubOrderDTO._OrderNumber = "12345";

            // Everything inside recorder block will be mocked.
            // expect the settng return value line: recorder.Return()
            using (RecordExpectations recorder = RecorderManager.StartRecording())
            {
                // Don't have to mock the IPricipal object "User"
                // Here TypeMock is too powerful. no need to think.
                Csla.ApplicationContext.User.IsInRole("user");
                recorder.Return(true);

                OrderRepository mockRepository = new OrderRepository();
                mockRepository.GetById(Guid.Empty);
                recorder.Return(stubOrderDTO);
            }

            Order p = Order.GetOrder(Guid.Empty);
            Assert.IsNotNull(p);
            Assert.AreEqual("12345", p.OrderNumber);
        }

After using typemock for a while, I suddenly tired of Rhino’s record-play syntax, looking into MoQ’s doc, it’s very similar to TypeMock, but it’s LinQ syntax (var and Lamba) are only available on 3.5 framework.

If using MoQ, the Rhino example will shorten to:


       [Test]
        [ExpectedException("System.Security.SecurityException")]
        public void CanHideForUnAuthorizedUser()
        {
            var mock = new Mock<IPrincipal>();
            mock.Expect(x => x.IsInRole("user")).Returns(true);
            Csla.ApplicationContext.User = mock;

            var obj = Order.GetOrder(new Guid("c512e473-19fd-401c-80f6-055cf239e461"));
        }

I like this drag-and-drop data binding feature. Detail banding! All the controls with labels can be dragged from Data->Datasource view to form. What developer has to do is just to set the datasource on the form.

If it’s MVP pattern. The presenter should be able to set the datasource object on form directly. I will investigate this later. Right now I just want relax and enjoy this fantastic drag-and-drop for a while.

The next beauty is, by using errorProvider control, all the textboxs can link to the validation rule in BO! With that nice exclamation mark blinking! This is even better than PFC!

For update, I hate wrapping thousands of parameters, then I tried CSLA 3.5 hoping LInQ feature can save me out. But, the SP parm warpper in LinQ to SQL GUI design is not used by CSLA. LinQ is System.Data.Linq.Table, different than CSLA’s base BO.

Can I do partial class to merge those two together? Or how can I take advantage of LinQ to Sql’s custom behavior design?

One problem I encountered is original value in SP. LinQ to Sql can handle this easily, but CSLA’s BO doesn’t have ‘Original’ storage. I had to add a cloned object in retrieve which is used later by update. But, what if user update without read first? When should I take snapshot of all the original values? Right before update?

The update process is very different in CSLA, I start missing NHibernate and pure LinQ to SQL.

What’s next?

- DeepData using LinQ: I really don’t like this mix-ActiveRecord pattern. It also makes mock/test data access impossible.

- After fetch, do we have to do property loop to set each one from fetch result to BO? The problem back to different object in CSLA BO and LinQ. How about define a internal dataobject inside BO, then just pass the fetched data result into this? Obvious, by doing this, all the fields will be one level deeper.


Spent 2 days on installing Dreamweaver CS3, some problems occurred during installation.

Followed the instruction on kb.adobe.com, typed “eventvwr” in run command window, looked into event viewer, the problem is:

Product: Adobe Flash Player 9 Plugin — Error 1714.The older version of Adobe Flash Player 9 Plugin cannot be removed. Contact your technical support group. System Error 1612.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

Product: Adobe Flash Player 9 ActiveX — Error 1904.Module C:\WINDOWS\system32\Macromed\Flash\FlDbg9c.ocx failed to register. HRESULT -2147220473. Contact your support personnel.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

Another tip from adobe suggests to download uninstall_flash_player.exe to delete old version flash player on my machine. I did that, but it seems this tool can only remove flash9 player. I then used another tool from microsoft msicuu2.exe (Windows Install Clean up) to remove the flash8 player.

After a re-install, the problem was fixed. This solution is different than those being talked in hostingforum.ca which needs to disconnect from network and disable the firewall.