John님의 프로필John Wood's Blog사진블로그리스트 도구 도움말

Wood John

직업
사진(1/1)
다른 앨범(1)
1월 5일

Blog is moving!

I am moving to dotnetjunkies!

Please continue reading my blogs at http://dotnetjunkies.com/weblog/johnwood.

1월 3일

Something to help you sort arrays

Talking of simple classes that provide something useful, here's another class I use quite often. It's a generic comparer, letting you sort any array of objects by a specified property name, using reflection. It works with both fields and properties.

 

class GenericComparer : IComparer

{

            MemberInfo field;

            bool ascDesc;

 

            public GenericComparer(Type type, string propertyName, bool ascDesc)

            {

                        this.field = type.GetField(propertyName);

                        this.ascDesc = ascDesc;

            }

 

            object GetValue(object obj)

            {

                        if (field is FieldInfo)

                                    return ((FieldInfo)field).GetValue(obj);

                        else

                                    return ((PropertyInfo)field).GetValue(obj, new object[0]);

            }

           

            int IComparer.Compare(object obj1, object obj2)

            {

                        IComparable comparer = (IComparable)GetValue(obj1);

                        return comparer.CompareTo(GetValue(obj2)) * (ascDesc ? 1 : -1);

            }

}

 

To use it?

 

myarray = new ArrayList();

myarray.Add(new Person("Bob"));

myarray.Add(new Person("Ted"));

myarray.Add(new Person("Henry"));

myarray.Sort(new GenericComparer(typeof(Person), "Name", true));

 

Hopefully someone somewhere might find it useful…

1월 2일

Tail.net - The Unix Tail Command for Windows

So I spent a few minutes searching around for a utility like Unix's Tail utility: something that can view a text file, monitor the file for changes and display updates to the file as they happen.  For example, it's useful when you want to monitor log files for new entries.

Well anyway I only found a couple at best. One of them was in the Windows 2003 Resource Kit, which I didn't feel like installing for just such a simple utility. The second was a GUI version and that was pretty much overkill.

It's such a simple utility I decided to write it myself. In fact the executable was just 4k (!) and it took me all of 10 minutes.  The code is also a pretty good example of using the FileSystemWatcher class.

The code is as follows:

public class TailApp

{

            long filePos = 0;

            string filename = null;

            FileSystemWatcher watcher = null;

 

            public void ShowTail(string filename)

            {

                        this.filename = filename;

                        StreamReader reader = new StreamReader(filename);

                        reader.BaseStream.Seek(filePos, SeekOrigin.Begin);

                        filePos = reader.BaseStream.Length;

                        Console.Write(reader.ReadToEnd());

                        reader.Close();

            }

           

            void FileUpdated(object sender, FileSystemEventArgs args)

            {

                        ShowTail(filename);

            }          

           

            public TailApp(string filename)

            {

                        ShowTail(filename);

 

                        watcher = new FileSystemWatcher(Path.GetDirectoryName(filename), Path.GetFileName(filename));

                        watcher.NotifyFilter = NotifyFilters.LastWrite;

                        watcher.Changed += new FileSystemEventHandler(FileUpdated);

                        watcher.EnableRaisingEvents = true;

            }

           

            static void Main(string[] args)

            {

                        if (args.Length==0)

                                    Console.WriteLine("Tail.net v0.1.  Usage: tail filename");

                        else

                        {

                                    try

                                    {

                                                TailApp tail = new TailApp(args[0]);

                                                while (true) Console.ReadLine();

                                    }

                                    catch (Exception e)

                                    {

                                                Console.WriteLine("Error: " + e.Message);

                                    }

                        }

            }

}

Or you can download it here: http://www.serviceframework.com/services/tail.zip

Now you can run something like:  "tail c:\mylog.log"- and this will write out the content of the file, and then wait for further updates to the file, showing them as they occur.

The Unnecessary Complexity of Good Design

There's recently been a bit of banter (here and here) regarding the appropriateness of applying the MVC pattern to ASP.Net applications (and probably any application). I'm quite opinionated in this area so I felt I should chime in, from the "over-engineered vs. under-engineered" design perspectives.

I find that the more experience you get with application design, the more you uncover this inherent conflict that exists in every architecture. It's a balance between what you want to see today, and what you want to do tomorrow. There's really no avoiding the need for expansion in any program, and to ignore that is just going to mean a massive rewrite later on, and yet you don't want to bog down your first version with an insanely complex n-tier codebase from hell either.

I've seen it happen time and time again. There's really two scenarios that occur: under-engineered and over-engineered: Either the system is written with complete disregard for future expansion (interweaving domain and presentation logic, for example), or the system is subject to the mind of a naïve genius who single-handedly develops an uber-design based on what they think will happen to the business and the application and ultimately produces a system that is way over-engineered, and then (surprise surprise) the requirements change and the entire codebase has to be re-developed from scratch.

I don't think the problem is quite as difficult to solve as it may seem. As Sahil Malik pointed out in a blog on the subject (in so many words), the critical aspect is that you develop in such a way that your codebase remains simple to manage and written in terms of your immediate requirements, but sufficiently agile to be expanded in any one of a broad choice of directions, and fairly rapidly at that. (or, just keep MVC in mind!).

The key, I think, is a mixture of agile programming

and loose coupling, both of which are fairly trendy things. (even though loose coupling has been known as orthogonality for many years, it's nice to rediscover old ideas, give them a new name and claim a new revolution now and then).

Off the top of my head, here's a few key principles I've derived from this formula:

1. No matter where you business / domain logic is, identify logic that in itself is quite important to your business, and separate this in some way - even if it just means putting it in its own function. Refer to it as a service.

For example, rather than validating an order in the OKButton_Click event procedure in a form class, if the validation is quite complex then just put it into a separate function and pass the order details in as a parameter, with an intention of making it independent from the presentation layer code. It's a common sense and simple step to take, but it makes the code far more expandable and manageable if this approach permeates your entire application. It also opens the code up to refactoring, if you need to move the code out to another project later on.

2. Keep a list of the services you've developed and be prepared to refactor the services so they can be re-used.

It's important that you define the signature or interface on to your service function with a bit of thought. For example, think about the name of the service, what comes in and what goes out. Identify your services with the intention of re-use, and of centralizing the unit of business knowledge into one easily accessible method. Apply the DRY principle

.

If you set your code up appropriately, refactoring can be a very effective way of having your application evolve. Anything that you can do to make your code easier to refactor will make your life much easier when the time comes, and help you maintain maximum agility. Keeping your code simple, not interdependent, and maximizing your re-use of code are all fundamental to achieving a codebase that is open to refactoring.

3. Keep these services as independent from each other as possible by adopting loose coupling.

To achieve this, to the extent possible - write your service functions as if they were global functions. This way you're restricting yourself to just what is passed in as parameters, and reducing your dependency on any state that might be maintained in the class and the rest of the application.

4. Keep these services as independent from the structure of the data as possible by adopting loose coupling.

Another aspect of loose coupling is to not strictly rely on the structure of the data. This is quite difficult in C#, but from what I've read, easier in the next major C# language revision. With today's technologies, this means making extensive use of typed-datasets wherever possible as they offer a slightly more dynamic alternative to storing your data in fields in a plain old C# object.

5. Keep a healthy distinction between data and code.

Keep your data in your typed datasets, and your code in service functions. In any company and in any system, the data is the most important asset. Most of the services, if they're written as discreet units, can be replaced at a later date - but the data cannot. Having easily replaceable services is what makes the system agile, easier to build upon and more adaptable to changes in the business and application requirements.

6. Don't code like you own the data.

Always treat the data like it's a guest to your application. Wherever possible, without making any significant compromises to performance, reduce where you're caching the data. Disconnected and semi-permanent caching of data often reduces your ability to expand the application, because now any additional services that use that data must get it from your cache. If you must cache data, find a way to keep that cache live - maybe not in real-time but at least give the cache an expiry so it can periodically refresh.

7. Keep it simple!

Above all, keep your code simple. And if you're looking for a principle, this has been coined the KISS or "Keep It Simple, Stupid!" Principle.

Keeping code simple is often a matter of weighing up the cost of changing something later on, compared to putting up with unnecessary complexity now. If you really think about it, most of the time the opportunity to refactor code at a later date is a real possibility.

For example, don't create separate projects for each class, on the basis that you might expand that class at some point and it might be too big to share with another project, or that you might want to share that code with other applications one day so it deserves to be put into another project. Neither of those reasons justify creating a new project! It's simply a matter of keeping your code agile enough that it can be refactored into separate projects as and when it's needed.

Another couple of classic over-complications: why create an interface for your class just because it might need an interface one day? As long as the class isn't used outside of your project, the day that you need to expose it through an interface will be a simple matter of refactoring and rebuilding your class. The overhead of maintaining both the class and interface until then just isn't justified. The same goes for public properties. If it's an internal class, why not just declare a public field instead? When you do need to control access to the field, then you can easily add a property to encapsulate that field.

Conclusion

So I think the bottom line is that you don't need any more layers than a semi-formal distinction between your domain logic (services), your data (model) and the glue that keeps it all together (the view and controller). If the level of separation is just a matter of different functions, then that's fine and can be refactored to something more structured when the need arises.

And above all, KISS!

 

12월 29일

User-Names and Remoting

One of the nicest aspects of .net is its security model, especially code access security. CAS lets you control access to certain resources or parts of your code, and best of all it's completely extensible. But when you start fiddling with .Net Remoting, you soon realize that security doesn't integrate so well with remoting. And not just CAS: neither is the principal maintained between the client and server, so you lose all information about the user that is logged in who is making the call to your remote object.

One solution I've found to the latter is to make use of this thing called the CallContext. The CallContext is a class that available in the System.Runtime.Remoting.Messaging namespace, and it's basically a wrapper for a static hashtable. You can use it to get or set any data you wish from anywhere in your application.

It's particularly cool for passing stuff around without having to put it explicitly into parameters, and you're pretty much guaranteed it'll be there throughout your call path. You could just do this yourself with a static hashtable in your own class, but what makes it particularly cool is that (a) it's standardized, (b) it's thread safe and (c) it works well with remoting, which is why I'm bringing it up.

Any object you put into the CallContext that is serializable and implements ILogicalThreadAffinative will also be transmitted alongside any call that goes across remoting boundaries.

So my solution to losing the security principal across remoting boundaries involves a helper class for storing and retrieving the user name (or principal, user token or whatever identifier you like) from a slot in the CallContext, stored in a really simple class suitable for remoting. Something like:

[Serializable]

public class UserInformation : ILogicalThreadAffinative

{

       const string CallContextKey = "UserInfo";

       string userName;

 

       public UserInformation(string userName)

       {

              this.userName = userName;

       }

 

       public string UserName

       {

              get

              {

                     return userName;

              }

       }

 

       public static void SetUser(string userName)

       {

              CallContext.SetData(CallContextKey, new UserInformation(userName));

       }

 

       public static string GetUser()

       {

              return ((UserInformation) CallContext.GetData(CallContextKey)).UserName;

       }

}

I set the username on application startup:  eg.

            UserInformation.SetUser("John");

...and know for sure that this information will be available throughout any call path and even across machines, with a simple call of:

            UserInformation.GetUser();

This information can then be used to make decisions on access rights etc. Keep in mind it's not really secure at all -- you could write code to put in whatever user name you wish -- but it's better than nothing and certainly better than passing a user token around in every call.  Of course you can change this code to pass around anything you wish, a user ID token from the database or even a hash of the user's password.

A more transparent solution to passing the principal around between remote calls can be found at Mike Woodring's site: http://www.bearcanyon.com/dotnet/#remprincipal, and I also hear this stuff has been fixed in .Net v2.

12월 24일

Gray Services

When is a service not a service? When it's a web service!

Well, at least according to this guy:

http://staff.newtelligence.net/clemensv/PermaLink.aspx?guid=0339fe35-2328-44be-93e6-f004bd869a84

Basically claiming that, because web services are often not autonomous, they are not really services.

I really wonder, though, whether a truly autonomous service is even possible.

All services rely on one or more resources. A resource is basically an entity that is shared between several services. Even the most basic of a service, perhaps a service that calculates a time differential, would at least rely on the processor to run - so the processor is the resource. And, of course, the most common of resources is the database.

Services that rely on a single database are very common. Web services that rely on databases or a cache are very common. But to say that a web service that is dependent on a resource is not really a service is false, I think.

Services can be autonomous and dependent simultaneously -- nothing is ever black or white. For example, a service that validates an order may not require access to the order table because the order can be passed in as a parameter. That makes it autonomous, but only to a certain degree. In order to validate the order, perhaps it has to look up the customer in a customer database - and so it is then dependent on the database for that information.

Real world development is all about evaluating benefits and drawbacks and then compromising, and so I doubt we'll ever get to that point where life is perfect and every piece of business logic is 100% PEACE-ful.

Perhaps what we need is a scale, a metric by which we can measure how peaceful a service really is. Let's call it the peace index. On a scale of 1 to 10, I think the closest we'll ever get to a fully peaceful service is going to be a 9, and that's not a terribly bad thing at all. In fact, the further we get away from 1 the better the our software will be.

In reality, services are everywhere and always have been. In fact, even a piece of particularly badly written obfuscated assembly code hidden deep within a super complex mega deep multi-inheritance object hierarchy that takes 500 lines of code to instantiate with a peace index of 0.01 -- even that is still a service, it's just not a very good service. It's all just shades of gray.

12월 23일

The Lone Developer's Backup Strategy

These days few of us have to worry about backing up our work, because our companies typically have a pretty good system in place that backs files up transparently to us. However, if you do any private work or research at home, then backing up can often be forgotten completely.

Usually I back up on to my keyring memory stick. These days those things come standard with 128 or even 256Mb... and some friends of mine have 1Gb sticks.

The trick to backing up quickly and efficiently is in filtering the files that you copy. My memory stick is just a 64Mb one and assuming you also have limited space, you also probably wouldn't want to copy all binaries, program databases and support files onto your backup device either.

In fact, my requirements are pretty precise:

  1. I want to backup only the things that aren't generated during a build (eg. mostly just manually written source files)
  2. I want to keep all the history of the files.
  3. Each time I back up, I only want to update files that have changed, not the entire thing.

Like any sensible developer, I keep all my source in a source control system. In fact this is pretty much a requirement if you want to backup a history of each file.

I'm a great fan of Perforce <http://www.perforce.com/>(it's free for a single user which is perfect), but this should also work with CVS, SourceSafe or any other source control system. You just have to locate the folder containing the depot (or repository, or whatever it's called in your case).

So here's my simple one-line backup solution - I use xcopy:

xcopy

"
c:\program files\perforce\depot" "f:\backup\" /d /s /c /y

Just replace "c:\program files\perforce\depot" with the name of your depot (or CVS repository), and "f:\backup\" with the folder on your memory stick that you wish to contain your backup.

I use a few options in xcopy:

  • /D
- Copies only newer files (or new files)
  • /S
  • - Copies all subfolders
  • /C
  • - Continue copying even if errors occur
  • /Y
  • - Overwrite files without prompting.

    I put this into a batch file in my path (for me, called backup.bat), and put a shortcut on my desktop.

    So now whenever I want to back up my work, I insert the memory stick, double click the icon, wait a few seconds, pull it out and feel safe knowing my work is on my key chain wherever I go.

    12월 11일

    Shortcomings of the DataSet

    Now I'm done praising the DataSet and loosely coupled programming, to balance it out a bit I'm going to highlight some of my 'pet peeves' relating to my experiences using ADO.Net, and specifically the implementation of the DataSet (in no particular order):

    1. One-To-One vs. One-To-Many Relationships

    To be fair, DataSets do support the concept of a relationship being either one-to-one or one-to-many, but only indirectly through the ParentConstraint and ChildConstraints properties. However, while this is kind-of supported at the DataSet level, it is seemingly ignored by the typed-dataset generator that creates our type-safe DataSets. Instead it treats all relationships as one-to-many.

    This makes coding against the typed-dataset in C# a little ugly -- something that should be as simple as stock.quote.price becomes stock.quote[0].price, introducing magic numbers that really make your code look more cryptic than necessary.

    2. Fill method not suitable for remoting.

    The Data Adapter model for populating a dataset involves running a Fill method on the data adapter, and passing it a reference of the DataSet instance to populate. While this works fine if both the data adapter and dataset are local, when the adapter is accessed through remoting you no longer have the option of passing in the DataSet as a parameter, because the DataSet does not extend MarshalByRefObject and is always passed by value across remoting boundaries.

    This means that when we run the command, a new copy of the DataSet is passed to the adapter, and any changes it might make to that structure will be lost when the function returns.

    Given that DataSets were really designed for this service-oriented environment, it seems to me that it would have made sense to design the adapter pattern to work better in a distributed environment.

    The way around this is to wrap the Fill method in a new service that instead returns a new DataSet. At the receiving end, this DataSet can then be used to access the data, or in the case where a pre-existing DataSet is defined in a form and bound to a number of objects, this DataSet can then be merged into the main DataSet. Which leads me on to...

    3. Merge is only supported on DataSets, not on DataTables.

    ADO.Net is very much oriented around the concept of DataSets, holding multiple tables that are related to one another. However, things aren't always as complex as that and more-often-than-not you end up with a DataSet containing just one DataTable. Such a situation is common, and overly complicated thanks to the seemingly needless DataSet required to use the DataTable in many common environments.

    One such environment is merging changes or new records received from a service. This is only supported through the DataSet, a merge on a table alone is not supported.

    4. Typed DataTable doesn't support serialization.

    Another environment where there is a lack of alignment between the typed DataTable and typed DataSet is when you come to remoting or persistence. While the typed DataSet supports serialization so that it can be passed by value in remoting calls, the typed DataTable does not.

    However, I should add that support for serialization in the DataTable has been fixed in the next version of .Net (version 2.0). Also, it is possible to work-around this by editing the generated code and adding the required serialization constructor to the typed-datatables (although this is not recommended as it will be lost in the next generation).

    Of course, they don't provide serialization of DataRows, but that's a whole other can of worms...

    5. DataSets don't support realtime streaming updates.

    Maybe because it's designed to work in a disconnected environment, but given that so many systems work in an environment where data is constantly updating it seems surprising that DataSets don't have better support for realtime updates. The main problem is that there is no way to differentiate between data updated by the user (through in-place editing, for example) and data updated from the source, and even if there was a way to differentiate there certainly aren't any merge conflict resolution features built in to the DataSet.

    The workaround is basically to disable user-editing of the DataSet completely, or to implement your own realtime data store that exposes the required ADO.Net interfaces (IBindingList and ITypedList).

    6. Row-based updates, rather than field based updates.

    Another issue is to do with the granularity of the updates managed by the DataSet.

    In a highly concurrent environment, you may be in a situation where two or more users are updating different fields of the same row, that a field representing a small fraction of the size of the row is being updated multiple times a second, or that a user only has permission to update certain fields.

    In each of these cases you may run into problems because the DataSet does not record which fields have been updated in a particular row. As such, the adapter / command builder will try to commit the entire row even though one field has changed, causing problems such as permission exceptions and others as listed above.

    A possible workaround is to compare old values for new values and determine yourself which values should be sent as part of the update. However, the work involved in doing this is not trivial, and you may still have to handle merge conflict resolution for cases where the same field is being updated.

    7. Error reporting is rather limited.

    When the data comes back from a service call to populate the DataSet the typical pattern is to put the details of any failures into the DataSet's rows themselves, supposedly so you can display them automatically in a grid. This makes the assumption that your DataSet is only really used for binding to user-interface forms, which is a dangerous assumption.

    It's dangerous because it's not true, and in many cases you will not code to check for the presence of those error flags in the row and will continue processing under the impression that everything is OK.

    It's also bad because the error is reported as a simple string. No exception, no error structure, not even an error code to go on.

    The workaround is to expose your errors by throwing an exception, guaranteeing that the error will be noticed - if not by the calling code then by terminating the application with an unhandled exception error. It guarantees that processing will not continue with the impression everything is OK - because things certainly are not OK if errors have occurred.

    If you wish to retain the ability to have errors associated with rows, I suggest you create a custom exception that contains a copy of the DataSet containing the rows with errors flagged.

    8. Relationships are processed on the data in the cache only

    Relationships are a big step forward in making relational data more manageable in an application and from user-interface components. I can now provide a DataSet to a grid and, using the relationship data held within that DataSet, the grid will then display that data hierarchically, automatically processing the relationships to sub-tables when the rows are expanded.

    The problem here is the assumption that the DataSet already contains all the data it needs. In many cases, the data is being expanded into a large sub-table of related rows, and it's simply not manageable or scalable to pre-fetch all the related data when the DataSet is first populated.

    The most common workaround is to delay the population of the sub-table fetch until the data is required. However this is more difficult than you may think. The real problem here is that the DataSet raises no events and has no callbacks that allow you to intercept a request for the related data.

    Instead, people rely on grid events and intrinsic code knowledge in order to intercept interests in the relationship and delay the fetch till that time. A better solution would be to have the DataSet raise an event whenever the child rows are requested, populating the required rows on-demand.

    9. Child rows are exposed as row collections.

    The result of processing a relationship on a DataSet is a list of child rows, represented as a DataRow array. It would have made more sense to me if this were returned as a type of DataView or table, so I can then further filter the rows in which I'm interested, make changes to that list that I can later commit and so on...

    10. DataViews cannot join between multiple tables.

    If you're a UI component programmer, the DataView class is a wonderful thing: It provides most of the functionality you need to display data in a DataTable, including filtering and knowing when to update rows that have been edited by the user or outside source.

    However, while the filtering is great - offering SQL like statements that are processed in-memory, and keeping the results of that query up-to-date in realtime - if only lets you process the data in that single table.

    Now if it just let you join between more than one table, then that would save a considerable amount of coding, especially when it comes to processing hierarchical views and updates to data involved in complex joins.

    Conclusion

    The DataSet is a wonderful piece of architecture, and has provided a great bridge from the connected to disconnected data processing world. However there are shortcomings when using the DataSet in real world applications and I hope I've highlighted some of them here.

    I'm sure I'm missing some, and I'm open to the possibility that some of my statements may be misunderstood or misunderstandings on my behalf, so please comment if you feel so inclined.

    More to ADO.Net than meets the eye

    ADO.Net is really so radically different to traditional ADO that it's somewhat misleading to give the technology the same name.

    Traditional ADO is largely oriented around recordsets that let us navigate over data retrieved from a connected data source (typically ODBC or OLE/DB drivers) without having to know where the data orignated (at least, that's the theory).

    ADO.Net largely replaces the old recordset concept with a 'DataSet'. The concept of DataSets dramatically changes the way we program databases by separating the code that accesses the database from the data itself. In old ADO the focus was on separating the interface onto the database from the database itself, through interfaces that can have implementation 'plugged in', often relying on ODBC drivers to provide the adapter to the database engine. However, the data and the implementation are still tightly bound to one another, making access to the data in a disconnected environment largely impossible.

    DataSets achieve separation of data and database by providing what is really a disconnected cache of the data: a recordset onto an in-memory copy of the data.

    Although largely sold as a data access technology optimized for the Web, the disconnected nature of ADO.Net runs deeper than that, having its roots in the movement towards something known as service orientation, an architecture based on loosely coupled systems.

    For years a fundamental conflict existed between the requirements for enterprise applications as being distributed, autonomous components running over a potentially unreliable network, and the intrinsicly connected and not particularly forgiving technology commonly referred to as 'distributed objects', such as DCOM and CORBA. It wasn't really until the Internet gained sufficient popularity and web services standards were established that the industry started to catch on that having control of an object living on another machine really wasn't that useful in a highly dynamic and stateless environment such as the Web.

    A web service becomes something you can request once-off, without any previously established connections or state required, making it far more suitable for use over stateless protocols such as HTTP. .Net also provides us with the ability to access these web services through its built-in support for standards such as SOAP and single-call .Net remoting.

    However we need more than simply the ability to run services. Loosely coupled programming requires a whole new paradigm shift and a whole new set of tools. No longer can you rely on the server being there the whole time, and instead you shoot off service requests, get the result and disconnect. And as such traditional ADO's connected recordset model really doesn't fit in, because it intrinsicly requires the server to be available while you use the data.

    And so DataSets provide the missing link in this puzzle: They provide a way to store the result of a communication with a server in a disconnected environment so you can freely read, search, and even modify the data without requiring a persistent connection to the server. In fact, given the fact that disconnected programming is really the central focus of ADO.Net, I think it probably would have made more sense for them to call the technology DDO - Disconnected Data Objects - rather than Active Data Objects.

    DataSets are really just the tip of the iceberg in ADO.Net, and I hope to cover other aspects of this disconnected data toolset in future entries.

    For more a longer introduction to loosely coupled programming and service orientated architecture, read my beginner's article at http://www.serviceframework.com/services/default.aspx?article=1

    Welcome!

    Welcome to my work blog.

    I'm an independent software consultant specializing in enterprise development using .Net tools, and so you'll find that most of my posts are about either .Net or enterprise development. I hope you find something useful in here from time to time.