What's coming in CslaGenerator

Hi everybody. It's been a while. A lot has been going on lately in CslaGenerator Land and I wanted to post about what's coming. For background on what's been going on you can read this.

Let's start with something that has been needing some work for a while, rules:

I've done a lot of work here, adding a lot of flexibility. Here's the list of new properties for the rules:

  • ArgumentType: You can choose between RuleArgs and DecoratedRuleArgs. (Csla 3.0 and beyond).
  • DecoratorArgs: This is a collection where you can define the decorator arguments. It has 3 properties: Name, Value and ValueType.
  • DescriptionType: String/Code. The difference here is that if you choose code, it doesn't output quotes for the description, allowing you to get the string from a function, resource or whatever.
  • Mode: Generated/Custom/CustomGeneric. Generated is the standard way and it's what we've always had so far. Custom only does the AddRule() call without generating an actual rule method so that you can code it by hand or call an external validation routine. CustomeGeneric is the same as custom, the difference is that Custom uses "object" as the target type and custom generic uses the business object's type as the target type.

Other improvements to rules:
Description can now use a format string, where {0} will be passed the friendly name of the property.
Assert expression now also uses format strings, so you can use {0} for the field.
Also, for both assert expression and description, if you have decorator args, you'll get them as {1}...{n}.

Example for a string max length rule.



Here's the decorator args (only one argument here):




The result looks like this (reformatted a bit so that it's a bit more readable):

    Public Shared Function CustNameMaxLenth(ByVal target As Customer, _

        ByVal e As Csla.Validation.DecoratedRuleArgs) As Boolean

        Dim result As Boolean = _

                target.m_customerName.Length < DirectCast(e("MaxLength"), Int32)

        If result = False Then

            e.Description = String.Format( _

                  "{0} must be less than {1} characters.", _

                  "Customer Name", e("MaxLength").ToString())

            e.Severity = Csla.Validation.RuleSeverity.Error

        End If

        Return result

    End Function

 

 

    Protected Overrides Sub AddBusinessRules()

        'Rules for "CustomerName"

        Dim StrMaxLenthArgs As New DecoratedRuleArgs("CustomerName")

        StrMaxLenthArgs("MaxLength") = 10

        ValidationRules.AddRule(Of Customer, DecoratedRuleArgs)( _

           AddressOf CustomerRules.StrMaxLenth, StrMaxLenthArgs, 0)

    End Sub


Now, while this introduces a lot of flexibility and power to rule definitions in CslaGenerator, I bet the question will arise:
"Does it create some default rules automatically now?"

The short answer is NO. Rules are really very tied to your business, locale and other variables, and while the app could create string max length rules for example, you may not like the default message to name one thing.

Instead, there will be other ways to define rules automatically that will be entirely under your control. I'll explain this in another post, since it doesn't apply to rules exclusively. I'll give you one hint: PLUGINS.

Andrés

Csla 3.0 released

Rockford Lhotka has released version 3.0 of his awesome framework. Congratulations Rocky!!
Head over to his site and read about all the new cool stuff.

Hopefully, Cslagen will support this new stuff soon.

Validating date ranges in a collection.

Some time ago, I wrote some code to handle colliding date ranges inside a collection.
As simple as it may seem, it's not as straight forward as some may think.
At that time I used some active objects features and worked very well and contemplated many different scenarios.
Since then, I thought about doing the same for CSLA classes, this time in a reusable way. So, here we go:
   
What cases does this cover?
-Two single items inside a collection can't have an overlapping date range (start/end dates).
-Items may or may nor be groupped (we'll come back to this later).

What does this mean?
Well, for the dates themselves we have 3 basic scenarios (with their inverse variations):


That's pretty obvious. So far so good.

Now, what are the "issues"? By issues I don't necessarily mean problematic issues, but more like things you need to take into account.
Issue #1 is that items inside a collection don't have references to each other.
Issue #2 is that you're adding items to the collection.
Issue #3 is that you're removing items from the collection.
Issue #4 is that the data in the items can and probably will change.

Note: For issue #1, you could access the "Parent" property, do some casting and get a reference to every other item, but that's not ideal. In this demo, I'm splitting the responsability of the validation between the items and the collection. A good reason for that is that you don’t want to revalidate a child against all others every time the rules are checked, only when the items are inserted /removed or when they actually change, and not for instance, when you call ValidationRules.CheckRules.

I’ll spare us all from the implementation details and leave that to those of you who are really interested and want to look at the code itself.

It is worth noting that the child has a state variable that tells it whether it’s conflicting or not.
There are 2 props (StartDate and EndDate) that expose the text of both smartdates. Also, for the purposes of testing, you’ll notice that I’m exposing the smart dates as readonly properties too. This is only to make testing easier, and they should be removed. They should not be used directly. (Their main purpose is to make sorting in the sample grid easier)

Now, a last feature that’s really important in all of this is grouping. Most of the times you don’t need to validate an item against every other item. Sometimes, there are categories. Let’s see a project tracker like example:
A project has resource assignments (let’s forget about roles for a sec). Resources may appear more than once in the project, as long as they don’t appear more than once in the same time frame. So, let’s say you assign “Joe” to a project from 2007-01-01 to 2007-02-28, and then from 2007-04-01 to 2007-05-31. That’s a perfectly valid scenario. You have a resource assigned to a project twice but with different start / end dates. The validation should only be run for Joe, and not against all resources.

That’s where grouping comes into play.
The child item has function called “GetGroupIdValue()” that you can override and that returns a default value for all items in case you don’t need grouping.

So, if you were to do this sort of validation by resource, you would override that function and return the assignment’s id in this scenario.
The function returns an object, so you can return anything there. In the case where you have more than one grouping key, you could create an object or structure that contains all the appropriate values and that overrides “Equals()” to correctly handle comparisons.
If your grouping key is subject to change, (For instance, you group by resource and role and the role may change in the child) there’s a delegate that takes care of it. You just need to trigger that delegate in order to notify the collection that the grouping for that item has changed. All you need to do is call “OnGrouppingKeyChanged()” in the base class from your property set and that’s it.

The collection keeps a hashtable of these groups that uses the grouping key as key and contains a List(Of T) that contains all the items in that group. The hashtable is not serialized and is recreated upon deserialization.

The sample code demoes all of these features, so if you’re interested, download the solution and try it out.

Note that I rewrote this from scratch about a month ago, so I might have missed something, If you notice anything strange, let me know.
The demo app uses the error treeview to simplify the ui and to help you find the items with problems faster. All the references are included.
Again, remember that SDate and EDate are only there to simplify sorting, they’re not editable in the grid.

Well, that’s it!! I hope I got you interested!

You can get the code here.

Andrés

Custom Remoting DataPortal with compression sequel

Thanks to Rocky's feedback I reworked my initial solution to make it a little better.
This one doesn't involve reflection at all and it implements a custom interface that is used only between the proxy and the server component.

Also, I see I created some confusion with an ill choice of namespace naming. I changed them to be more Csla like. The assembly name also changed. Sorry if you had trouble with that.

Also, I separated the "dynamic proxy" part from the actual proxy that does compression so if you don't really need it, you can skip it. The new proxy is called "CompressedRemotingProxy" and the server component "CompressedRemotingPortal".

Here's a sample for the relevant web.config section:


    <system.runtime.remoting>

        <application>

            <service>

                <wellknown mode="SingleCall" objectUri="RemotingPortal.rem" type="Csla.Server.Hosts.RemotingPortal, Csla"/>

                <wellknown mode="SingleCall" objectUri="RemotingPortalCompressed.rem" type="Csla.Server.CompressedRemotingPortal, Csla.Compression"/>

            </service>

            <channels>

                <channel ref="http">

                    <serverProviders>

                        <provider ref="wsdl"/>

                        <formatter ref="soap" typeFilterLevel="Full"/>

                        <formatter ref="binary" typeFilterLevel="Full"/>

                    </serverProviders>

                </channel>

            </channels>

        </application>

    </system.runtime.remoting>



And here's the same for the client's app.config:

        <add key="CslaDataPortalProxy" value="Csla.DataPortalClient.CompressedRemotingProxy, Csla.Compression"/>

        <add key="CslaAuthentication" value="Csla"/>

        <add key="CompressedDataPortalUrl" value="http://localhost/CompressedPortal/RemotingPortalCompressed.rem"/>

        <!--<add key="CslaDataPortalUrl" value="http://localhost/CompressedPortal/RemotingPortalCompressed.rem"/>-->



I created a new parameter for passing the url: CompressedDataPortalUrl. If the parameter is not found, it falls back to CslaDataPortalUrl (notice it's commented out in the snippet).

Make sure you test the server component from the browser in case you have something wrong in your config. Paste this as the url in your browser:
http://localhost/CompressedPortal/RemotingPortalCompressed.rem?wsdl

If you get an xml result then you're good to go.

For those of you who are interested in using the dynamic changing of dataportal types, use "CustomDataPortalRemotingProxy" instead of CompressedRemotingProxy in your app.config.


You can get the source code here.
Well, that's it. Enjoy!


Andrés

Custom Remoting DataPortal with compression

To start things off, I thought of writing about something I've done very recently. Have you ever used csla's remoting portal over a slow network? Internet anyone? Not everybody has a super fast connection and even people that do have days where it seems like they're on 56k dialup...
I have a 1Mbps connection and even though it's pretty fast for download, it's upload speed sucks , so hosting a remoting portal here can get irritating!! (For the ones on the other side at least).

Yes, there's a performance hit because it adds more processing to the overall process, but the size of the transfers are dramatically reduced so that makes up for the processing time. Another thing to take into account is that you don't _have_ to use this dataportal OR the other. You can create both portals in one site and use one or the other depending on your needs, you just need to change your app config, or you could change the url in code on the fly.

So, let's dive into it.

First, for the anxious, how to use it. Assuming you compiled it, added the appropriate dlls to the web site's bin folder, here's the configuration:

    <system.runtime.remoting>
        <application>
            <service>
                <wellknown mode="SingleCall" objectUri="RemotingPortal.rem" type="Csla.Server.Hosts.RemotingPortal, Csla"/>
                <wellknown mode="SingleCall" objectUri="RemotingPortalCompressed.rem" type="Csla.DataPortal.Compressed.Server.RemotingPortalCompressed, Csla.DataPortal.Compressed"/>
            </service>
            <channels>
                <channel ref="http">
                    <serverProviders>
                        <provider ref="wsdl"/>
                        <formatter ref="soap" typeFilterLevel="Full"/>
                        <formatter ref="binary" typeFilterLevel="Full"/>
                    </serverProviders>
                </channel>
            </channels>
        </application>
    </system.runtime.remoting>

There isn't a single change from the regular config, other than the fact that I have 2 services a standard portal and a compressed one. You don't need both, so you can remove the first one if you don't plan to actually use it...

Now to the client's app.config:

<appSettings>
    <add key="CslaDataPortalProxy"
    value="Csla.DataPortalCompressed.CustomDataPortalRemotingProxy, Csla.DataPortal.Compressed"/>
    <add key="CslaAuthentication" value="Csla" />
    <add key="CslaDataPortalUrl"
    value="http://<portal's url>/RemotingPortalCompressed.rem"/>
</appSettings>

Simple uh? For those used to setting up a remoting portal, I highlighted the differences with what you'd normally do.
With these simple changes, I have been able to use the portal succesfully in an application that's been working for a long time. No code changes were needed (only config files), just add a reference in the main app to the compressed dataportal and that's it.

Now, how does it work?

The dataportal has a server side object (the portal itself) and a client side proxy. The server side component is simply an object with 4 functions: Create, Fetch, Update and Delete. They all return a DataPortalResult object.
If you look at the remoting portal in rocky's code, all these methods do is redirect those calls to a simple dataportal. My implementation does this exact same thing, although it alters the resulting DataPortalResult in order to compress the object being transported. Here's the Fetch method to give you an idea:

  Public Function Fetch(ByVal objectType As System.Type, _

  ByVal criteria As Object, ByVal context As DataPortalContext) _

  As DataPortalResult Implements IDataPortalServer.Fetch

      Dim dp As New Csla.Server.DataPortal

      Return Compression.GetCompressedResult( _

          dp.Fetch(objectType, criteria, context))

  End Function


As you can see, the only thing that changes here from the default implementation is that I call a function that gets a new compressed dataportal result.

The same process is used for create and update. Delete doesn't need it, since there is no object going back to the server.
Update has one more difference, since it's the only instance where you send an object to the dataportal, the client compresses the object before sending it, so it needs to be decompressed before calling dp.Update.

The client proxy behaves similarly, with the difference that it needs to get an uncompressed result:

    Public Function Fetch(ByVal objectType As System.Type, _

    ByVal criteria As Object, _

    ByVal context As Csla.Server.DataPortalContext) _

    As Csla.Server.DataPortalResult _

    Implements Csla.Server.IDataPortalServer.Fetch

        Return Compression.GetUncompressedResult(GetProxy().Fetch( _

        objectType, criteria, context))

    End Function



GetProxy only get's a cached instance of the proxy to be used.


You'll notice one more thing here. This proxy has a feature that let's you swap proxies on the fly.
You don't need to use it or even be aware that it exists. But if you're in a situation where you need to swap between a simple portal and a remoting portal on the fly, all you need to do is call:
Csla.DataPortalCompressed.CustomDataPortalRemotingProxy.DataPortalType = _
        Csla.DataPortalCompressed.DataPortalTypes.SimplePortal

This allows you to switch back and forth between remoting and simple dataportal (and even enterprise services) in case you ever need to.


The portal uses BZip2 compression provided by icsharpcode's #ziplib (it's opensource). You can get that here:
http://www.icsharpcode.com/OpenSource/SharpZipLib/Default.aspx

Well, I hope you enjoyed my first article and that you find it useful.

You can get the code for this article here.


Update:
The Code for this article has been updated. Take a look at this post.


Andrés

Yet another blogging attempt

Well, having found a new "suitable" place to maintain a blog, here I go again...
I'll try to keep a consistent post rate this time and hopefully write some useful articles. As for the aim, there isn't any for now, so I'll post on a variety of subjects and time will tell.

Stay tuned!
«September»
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789