The Elephant and the Silverlight

Thoughts and analysis of Silverlight for business applications

Page List

    The Problem with the DomainDataSource

    WARNING: This post is not meant to be documentation on the DomainDataSource. Just a stream of consciousness mini-rant on DomainDataSource posted here because the 140 characters that Twitter limits me to was too few.

    Jeff Handley has a poll going on the DomainDataSource. Please check it out and give an answer.

    Since the question is on the table, I decided to put together my own thoughts on the subject. I have not been shy to state that I simply do not use the DomainDataSource. Personally, I find it clunky and not nearly as refined as the rest of RIA Services. It also does not work well with my ViewModel. I think what Jeff is trying to figure out though is why that is. I can say it is clunky, we can all say it doesn’t feel right, but can we put our finger on what the problem is?

    I think the fundamental problem with the DomainDataSource is that it is not extensible at all. It is a completely sealed black box and there is very little way for us to extend it. This isn’t nearly as refined and elegant as the rest of RIA Services, such as the DomainContext which uses an EntityContainer to store all of the EntityLists and the DomainClient to talk to the server. If we want we want RIA Services to talk to the server using a port with everything under ROT13 encryption we can do that, we just have to create our own DomainClient that does that and tell the DomainContext to use it. We can’t do anything like that with the DomainDataSource. It just sits there in the XAML talking directly to our DomainContext and cutting our code completely out of the loop.

    What makes the DDS really annoying though is that the really useful paging code is locked inside it and its internal lackey the EntityCollectionView. I still live in hope that this will be fixed and that we will get a back end object that can generate the paging queries.

    So, what is the DomainDataSource doing exactly (this is not meant to be a full list)?

    1. Declarative access to DomainContext status (IsBusy, IsLoading, IsSubmittingChanges, etc.)
    2. Paging display attributes (PageSize)
    3. AutoLoad (which is data access logic)
    4. Paging load attributes (LoadSize) (data access logic)
    5. Setting the query name
    6. Setting the query attributes
    7. Submit changes

    I would have to say that from a MVVM perspective, 3, 4, 5, 6, and 7 are all problematic to me. The UI shouldn’t be worrying about the name of the query, how many records are being loaded outside of the PageSize, and I don’t think the UI should know what of its bound values are being used to filter queries. Letting the UI call directly to the DomainContext to SubmitChanges is also a big problem for me.  I do like the idea of a UI object that can allow binding of DomainContext statuses such as IsLoading and setting page size is a definite UI job.


    Permalink | Comments (14) | Post RSSRSS comment feed

    Comments

    Jeff Handley United States

    Wednesday, September 02, 2009 4:12 PM

    Jeff Handley

    Thanks for posting the feedback!

    Community Blogs

    Thursday, September 03, 2009 10:00 PM

    trackback

    Trackback from Community Blogs

    Silverlight Cream for September 03, 2009 - 2 -- #683

    Ward Bell United States

    Friday, September 04, 2009 1:17 PM

    Ward Bell

    Good to see you expanding on this subject ... and I completely agree inspite of the fact that we are adding our own version of DDS to our DevForce product ... perpetuating the exact same problems. Such is the power of market pressure; people really want this thing and, if it's just a sugary treat (not crank), you have to offer it.

    The RIAS "sealed box" aspect drove us to creating our own sealed box. We would much prefer to have tied in to the DDS but, despite many appeals, the SL team have "postponed" an interface-based approach to an indefinite future. They understand the issue perfectly well; I appreciate that resource/timing constraints are behind the delay ... because I know what that feels like and I know the terrible tradeoffs that become necessary when you simply have to ship.

    I know and agree with the design argument that says the DDS is unsound because it mixes non-UI concerns with virtually untestable UI. We can talk about "what is supposed to be" forever and we will win only the already converted. So let me change tactics and describe how the DDS (and our ObjectDataSource equivalent) harm your application.

    First and foremost, persistence operations inevitably fail. You will lose your connection. The database will go down. You will try to save something that the database disallows. There will be concurrency conflicts. The declaration of DomainContext (or our equivalent) gives you no opportunity to manage exceptions and events. Your app is toast if anything goes wrong.

    I usually need to pre- and post-process entities that I am quering or saving; there is no way to tie into that.

    Partial remedy: maybe you can define a subclass that somehow configures the DomainContext with all the handlers you need; you declare this subclass instead of the DomainContext itself. I don't know if this is actually feasible with DomainContext as it is with our EntityManager equivalent ... but it shouldn't be a big stretch to extend the DC accordingly.

    Note that, as you pursue this path, you are doing more coding ... becoming more aware of the nuances of persistence ... to the point that you might ask yourself whether "you might as well just do the right thing in the first place."

    Second, when you declare the query you are naming a property of the DomainContext that returns an EntityQuery<TEntity>. This is a generated property of your DC. If what you need isn't available you'll either be adding a new method to your DomainService or looking at subclassing the DC again. This dilemma will present itself repeatedly ... I guarantee it. Will you simply "live" with an almost good enough query that gives the wrong results at unexpected times? Or will your need to declare the query in the XAML drive you to change your DC or even your DomainService. You're either giving errant results or writing code and ... as I said ... once you start down that path, "you might as well do the right thing from the start."

    Third, putting the load size in the UI will adversely affect performance. I know it is there to improve performance. But the very fact that it is a performance dial means that you have lost control over your application's performance characteristics because a key variable is baked into your XAML, out of the view of your code. You say, "but I can modify it in the code behind" ... and indeed you can ... and you're back to programming again so "you might as well do the right thing from the start."

    Fourth, there is no easy way to debug into this thing. If you like the DDS, I have to assume that you don't do unit testing. Fine. You use the debugger a lot. So do I. When you have to figure out what is going on you will find precious few places to break. You can break in your DomainContext ... and that's about it. Want to know why it decided to query? Want to know what is happening to the EntityCollectionView? Want to add something to the EntityCollectionView? The answers are not obvious to me. Not only is a black box hard to extend ("ho hum" you say) ... it is hard to inspect and step through.

    By now you've followed the path of my critique. Time and again you are driven to writing the code you were hoping to avoid by declaring your intentions in XAML. What did this save you?

    Try living without it. You're a programmer, right? You will find that actually wiring the UI control that displays your data (whether to a helper in the ViewModel or the code-behind as you prefer) is very easy. The DDS isn't doing much for you in this regard. The number of lines of code are about what you'd use to achieve the same effect in the XAML. It's just code instead of XAML. No fancy-pants patterns needed either.

    What do you miss? I missed two things: (1) paging and (2) grouping.

    Pet peeve: There used to be declarative grouping in the DataGrid back in SL 3 beta but it was removed for the release (grrr); now it must be done programmatically ... unless you let the DDS do it. That's right: declarative grouping was removed from the DataGrid XAML API where everyone could use it and moved into the DDS where you have to buy-in to the whole RIAS apparatus. I'm sure there was a reason.

    I wish the DDS were interface-based. More than that, I just wish we stopped creating "kitchen sink" controls that try to do everything. Why not dial it back to a pager control that could work with any data providing object that satisfied the pager's API needs; That would have been helpful. RIAS could also provide an implementation of that interface that worked great with the DC if that is your preference. Others of us could have written our own implementation and put it where we thought it belonged.

    I don't want to be a sour-puss. I'm not just attacking RIA Services either. Everything I say here applies to our own, similar control, about which I have the same misgivings. As the product manager I'm trying to ameliorate what are inevitable and fundamental flaws ... as is Jeff Handley I'm sure.

    Thanks for letting me vent on your blog, Colin. I trust I am safe in assuming that you were inviting us to make this particular post a forum for the topic you raised; else, just erase me. Tong

    Jeff Handley United States

    Friday, September 04, 2009 2:25 PM

    Jeff Handley

    I'm loving the discussion on this topic.  This is the kind of thing I was hoping to spark with my poll.  I think that Ward understates what's involved in replicating the work of the DomainDataSource though.  It really does a lot more than paging, grouping, and allowing you to show your data on the screen.  Here are some important characteristics:

    Understanding of Related Entities of the Same Type
    If you load Employees and their Managers, who also happen to be Employees, they all get loaded into the same EntityList on your DomainContext, just like they would from your O/RM.  But you need to separate which employees were loaded from your query, and which ones came along for the ride.  For instance, if your query was "Employees with a title of Developer, and include their managers" you don't want to see "Dev Manager" titled employees in the list.  DomainDataSource gets the data that it surfaces from the e.LoadedEntities property on the LoadOperation result.

    Control Parameter Binding
    While it's not ideal that we have to use ControlParameter right now (instead of just a binding), the DomainDataSource does still let you bind QueryParameters and FilterDescriptors to controls in the UI.  This is controversial I know, but consider the value in the following:

    1) Ability to automatically reload whenever a parameter value changes
    2) Ability to delay that automatic reload to whatever amount of time makes sense for the view

    Interface Goo
    In order to fully light up all of the features of the DataGrid, DataForm, and DataPager, there are a handful of interfaces that must be implemented:

    - ICollectionView (which includes IEnumerable and INotifyCollectionChanged)
    - IEditableCollectionView
    - IPagedCollectionView
    - INotifyPropertyChanged

    I've done the work of implementing these interfaces on my data without DomainDataSource, and it's a PITA.  See: jeffhandley.com/.../asyncvalidation.aspx for more info.  There's no way you would ever want to burden every single ViewModel with this implementation goo.  While I'd like to see some helper classes (and QuickSilverlight is working toward that), we don't have them now.  You'll be amazed at how much code you have to write to properly implement these interfaces.  Expect it to be well over 500 lines of mechanical code.

    What Does This Mean?
    I understand the DomainDataSource control isn't for everyone.  I'm hearing good feedback about extensibility and hook points.  That's the kind of stuff I'd like to hear more of.  I know there's a connotation that the DomainDataSource is Data Access in the UI, but with the layers of separation that RIA Services offers, I don't know that it's entirely true.  I would love to nail down a list of feature that need to be added to the DDS so that it could be used in a way that doesn't make some folks cringe at the thought of using it.

    Thanks all,
    Jeff Handley

    ColinBlair

    Friday, September 04, 2009 2:46 PM

    ColinBlair

    1) I currently use a PagedCollectionView for the first example. I prefer to be actively filtering based on the data currently in the EntityList, not what came back from a query operation. Also, some of the data I need filtered this way in my system comes from cached data in my isolated storage so it may literally have been months since that data was actually queried from the server. I also don't use the LoadOperation.Entities for anything for the same reason, I always deal with the EntityLists directly.

    2) Yes, there is value in loading data as key values are modified on screen, but that is what my ViewModel is for.

    3) I will be thinking some more on the hook points thing.

    ColinBlair

    Friday, September 04, 2009 4:25 PM

    ColinBlair

    I've done the work of implementing these interfaces on my data without DomainDataSource, and it's a PITA.  See: jeffhandley.com/.../asyncvalidation.aspx for more info.  There's no way you would ever want to burden every single ViewModel with this implementation goo.  While I'd like to see some helper classes (and QuickSilverlight is working toward that), we don't have them now.  You'll be amazed at how much code you have to write to properly implement these interfaces.  Expect it to be well over 500 lines of mechanical code.

    Why would anyone want to implement any of those interfaces on a ViewModel? This brings up a point that I don't think should be missed. The DomainDataSource and the ViewModel are nowhere close to the same thing. Here is some mockup of what the DomainDataSource would look like it was similar to the ViewMode:

    <riacontrolsLaughingomainDataSource x:Name="Source">
        <riaControlsLaughingomainDataSource.DomainContext>
            <domain:AdventureWorksDomainContext />
        </riaControlsLaughingomainDataSource.DomainContext>
        <riaControlsLaughingomainDataSource.Queries>
            <riaControls:Query x:Name="Employees" PageSize="15" LoadSize="30" QueryName="GetEmployeesByGender" AutoLoad="True">
                <riaControls:Query.Parameters>
                    <riaData:ControlParameter ParameterName="gender"
                         ControlName="gender"
                        PropertyName="SelectedItem.Content"
                        RefreshEventname="SelectionChanged" />
                 <riaControls:Query.SortDescriptors />
                 <riaControls:Query.GroupDescriptors />
                 <riaControls:Query.FilterDescriptors />
            </riaControls:Query>
    </riacontrolsLaughingomainDataSource>

    Now, that design you could look at and call it a declarative version of a ViewModel. In that design it would make any sense for the DomainDataSource to implement the interfaces Jeff mentioned (that is the job of what I call the Query in that mockup); the same is true for a ViewModel. A ViewModel is usually the DataContext of the entire View, that means it may be exposing many different collections. I just want to be able to expose a collection that exposes IPagedCollectionView and then executes calls the ViewModel. Something like:

    myPagedView.GetDataHandler = MyEntityGetPagedDataHandler;

    private void MyEntityGetPagedDataHandler(PagedView<MyEntity> pagedView)
    {
        this.Context.Load(pagedView.ProcessQuery(this.Context.GetMyEntity, this.MyEntityLoadSize));
    }

    Ward Bell United States

    Friday, September 04, 2009 4:40 PM

    Ward Bell

    Jeff - This _is_ a valuable discussion … especially if it helps future developers appreciate the significance of their decision to use or avoid the DDS. Colin and I have made our choices. We are now striving to make clear that these are not simply preferences ("Loved Planet of the Apes; hated Aping the Planets"); they are grounded in the consequences.

    You did a nice job of elaborating on how the DDS might spare the developer from difficult and error-prone coding.

    I do not for a second doubt the enormous amount of work that goes into the DDS. I certainly omitted features that many will find valuable. And many will want them all in one tool called the DDS.

    One of my points is that this tool is a Swiss Army Knife. While the Swiss Army Knife has its appeal -- it looks cool in the parking lot --, it suffers in the field when you need the features to work.

    Colin nailed what would have been my answer to your best point ... that implementing all the collection interfaces is a bear. It seems to me that you actually made the case for stand-alone collections that do the full job, are extensible, and are not locked away in the DDS.

    But then that already exists, right? As Colin observes, it takes very little effort to fill a PagedCollectionView. I can manage it much more closely. If it doesn't meet our needs, let's build THAT.

    Let's look at your example.

    First, I keep forgetting that the DomainContext cache is difficult to inspect and has a tendency, that I regard as regrettable, to conflate what you just queried now with what was already retrieved. In my world, I'm used to querying  my cache for developer employees without forcing a trip to the database. It's just a filter on the cache.

    Second, as Colin observes, the displayed list of developer employees usually needs to be actively maintained. It isn't just composed of the developers I retrieved on my last trip to the database. I might be adding new developers that I haven't saved yet. I might have developers that I pulled in from iso-store. I might be preparing to delete one and make a manager of another (that Handley guy has management material written all over him!). These changes haven't been submitted yet ... I'm in the middle of a decision process that isn't ready to be committed. That's not do-able with the DDS.

    Maybe I've put some dials on the UI that let me refine my view of employee developers. Suppose I am looking for some with certain skills, years of experience, locations, whatever.

    I shouldn't have to go to the database every time I swing a filter into place or push it aside; I've got my basic list of developer employees and my user should be able to explore that list nimbly, without a server trip each time.  All of this is possible when you, the developer, manage the list.

    I don't think this is an advanced scenario. It's the bread and butter of quotidian applications. In fact, every customer application I can think of provides an interface for "playing" with a collection of data once they have been retrieved.

    It should be easy to bind values in controls (e.g., TextBox) to filter collection criteria ... and to have the displayed collection update when the criteria change. You have a nice, declarative way of arranging for this binding in the DDS.

    But, honestly, binding that TextBox to a ViewModel property is trivial and having the setter trigger a refetch with filter criteria added ... or removed .. is a one-liner. Does it take a tad more knowledge? Yes it does. I have to be able to express the filter condition and add it to the query and resubmit the query ... and you're doing that for me. This is not hard and it takes no more work to THINK THROUGH what I want than it would take to do the same thing declaratively in the DDS. I will concede that it takes a smattering of code ... that the DDS handles for me.

    What to make of this? My objection is that your helpfulness is all in the DDS ... in the UI. Suppose you gave me something that made expression of the adjust-filter-and-submit-query a one-liner? Than I could put that in my ViewModel property ... where I think it belongs. Of course this means breaking up the Swiss Army knife.

    I hasten to add that this example assumes we want to make another trip to the server when the criteria change. Does that make sense? It does if we have thousands of employee developers. It makes no sense if we have a hundred or so. Guess which is more often the case? Yet the DDS favors the server-heavy approach every time ... and fosters that mentality. It actively encourages designs that are injurious to application behavior.

    I know it is paternalistic to say so ... but tools like the DDS (and our ObjectDataSource) are insidious in the way they promote poor designs and obscure decisions the developer ought to be making consciously.

    "Ought" has the whiff of rhetoric. My hair goes up every time I read it. But sometimes there is no better way to say it. Your doctor ought to think before prescribing medicine; that’s what we pay her to do. We give doctors tools to make the diagnosis faster, less tedious, less error prone ... but she still has to think. And we don't want tools that mask the disease.

    So it is for developers. Give us tools that help us make good decisions faster and consistently. We don't need automated tools that make as many bad decisions as good decisions.

    To be clear, I still marvel at the effort and skill that goes into the DDS. I'm struggling to do as well with our own ObjectDataSource. Sometimes that brain power should be put to more productive use.

    Ward Bell United States

    Friday, September 04, 2009 4:44 PM

    Ward Bell

    Looks like Colin said in code what took me twenty paragraphs.

    topsy.com

    Friday, September 04, 2009 5:11 PM

    pingback

    Pingback from topsy.com

    Twitter Trackbacks for
            
            The Elephant and the Silverlight | The Problem with the DomainDataSource
            [riaservicesblog.com]
            on Topsy.com

    Jeff Handley United States

    Friday, September 04, 2009 5:39 PM

    Jeff Handley

    Ward,
    You do make a very good point that DomainDataSource is very server-query centric.  That is its purpose.  And the PagedCollectionView does a very good job of the client-centric work.  And you and Colin are right -- there isn't anything yet works well with both server queries and client-side data.

    Colin,
    Interesting code.  I guess I'm not sold that moving the properties down into a Query object alone would make that much of a difference.  Care to elaborate on that?

    Thanks,
    Jeff Handley

    ColinBlair United States

    Friday, September 04, 2009 6:17 PM

    ColinBlair

    Sorry, I should have expanded that more. I wasn't just moving the properties down into a query object, I was trying to show how the DomainDataSource could be refactored to have multiple sources of data in it to illustrate some of the fundamental differences between the DDS and a ViewModel. Here is an expanded version with multiple queries.

    <riacontrols: DomainDataSource x:Name="Source">
        <riaControls DomainDataSource.DomainContext>
            <domain:AdventureWorksDomainContext />
        </riaControls: DomainDataSource.DomainContext>
        <riaControls: DomainDataSource.Queries>
            <riaControls:Query x:Name="Employees" PageSize="15" LoadSize="30" QueryName="GetEmployeesByType" AutoLoad="True">
                <riaControls:Query.Parameters>
                    <riaData:ControlParameter ParameterName="employeeType"
                         ControlName="employeeType"
                        PropertyName="SelectedItem.Content"
                        RefreshEventname="SelectedEmployeeTypeChanged" />
                 <riaControls:Query.SortDescriptors />
                 <riaControls:Query.GroupDescriptors />
                 <riaControls:Query.FilterDescriptors />
            </riaControls:Query>
            <riaControls:Query x:Name="EmployeeTypes" QueryName="GetEmployeeTypes" AutoLoad="True">
                <riaControls:Query.Parameters/>
                 <riaControls:Query.SortDescriptors />
                 <riaControls:Query.GroupDescriptors />
                 <riaControls:Query.FilterDescriptors />
            </riaControls:Query>
            <riaControls:Query x:Name="Customers" PageSize="15" LoadSize="30" QueryName="GetCustomers" AutoLoad="True">
                <riaControls:Query.Parameters/>
                 <riaControls:Query.SortDescriptors />
                 <riaControls:Query.GroupDescriptors />
                 <riaControls:Query.FilterDescriptors />
            </riaControls:Query>
    </riacontrols: DomainDataSource>

    Kasimier Buchcik Germany

    Sunday, January 24, 2010 8:30 PM

    Kasimier Buchcik

    Hi,
    this response is probably way out of date, but I feel the urge to put my Euro into the mind-machine:

    @Jeff Handley: "Interface Goo", "There's no way you would ever want to burden every single ViewModel with this implementation goo"

    The ViewModel is not to be burdened with such code. The ViewModel aggregates/composes the goo encapsulated in thingies with are similar to the DDS (I call mine EntityPresenter(s)), but designed to work within ViewModels rather than XAML.

    @Jeff Handley: "Understanding of Related Entities of the Same Type"

    My EntityPresenter is generic and it handles only related entities of the same type.

    @Jeff Handley: "Control Parameter Binding"

    I can declare bindable properties in my EntityPresenter(s) which are marked with attributes in order to identify those as being filter-/sort-/order-by-/whatever-properties. The EntityPresenter takes care of applying those properties when loading. I can define the data to be reloaded when those properties change.
    I can define load delays in my EntityPresenter(s).

    This replies to Jeff Handley are intended to express that I, Jeff and all of us can implement components and features which do the same
    and more as the DDS does for use in ViewModels. It's no magic after all. And no, it does not clutter the ViewModel, because it is also
    all implemented inside *one* (or several composable) reusable class(es).

    Maybe it's too late for me (time-of-day-wise) to fully understand what you bunch of guys are arguing about.
    My issue is that the DDS should have never been directed for use by XAML in the first place.
    I'll list the thoughts and issues I have on my list:

    1) Although I don't use the DDS in my apps, I think I see its potential. Surely there are many scenarios where it is/will be nice to have.
        I once implemented similar declarative data providers (expressed in XML), because my target platform was the Compact Framework and
        everything needed to be expressed in XML; also the logic; so there was also an XML programming language interpreter involved.
        You know, MS could further and try go the same way and implement all logic in XML (like the new WF stuff) - but I can tell you that a
        programming language in XML is really not that human readable; and not that verifiable without proper tools. I understand MS' decision
        to steer to the land of Model Driven Development (if this is partly the reason for all the XAML based stuff), but please try to bear in mind that,
        before there's any working real world MDD designer suite, which generates the XAML of our DDSs and validates it: currently the XAML based
        DDS looks to me like painting fragile code without being able to adjust/tweak it to work in my scenarios.

    2) I don't understand and fear the order of development of the DDS:
        Why is a complex data provider directed (and implemented) in a monolithic manner for use in XAML first?
        Why are the components of the DDS not implemented to be used conveniently via code first?
        Those components are:
        - The (Paged)EntityCollectionView
        - The pager (I have a separate EntityPager class which encapsulates the pager logic and states for reuse in multiple components).
           In the DDS the pager logic and state is inside the PagedEntityCollectionView.
        - Sort/order/filter mechanism - why the control parameters & Co. in the first place?
          Why can I not use properties in order to define sort/order/filter information?
        - The collection which sits on top of the EntitySet (this one is internal and coupled to the workings of the DDS).
          I have a collection which sits on top of the EntitySet and can be reused by other components; i.e. a collection which
          contains a specific subset of Entities in the EntitySet. So I can use this collection for funny and interesting other stuff as well.

        I think, with directing the usage of the DDS to XAML only, MS is loosing opportunities to incorporate ideas from the community.
        Currently MS is only able to react on bugs and feature requests which touch only the uppermost surface of the DDS, because there's
        no public inner surface (the multiple components it is composed of) to code and experiment against.
        So my current fear is that MS is coding through the previews and betas and finally comes with a public API; and this API
        will most likely be a PITA wrt inventions and code-reuse - no input on the inner workings, could mean poor inner working results.

        From my point of view, the use in XAML is a higher level use, thus I would normally be desperate to make things work
        in lower levels before trying to implement higher lever usage in XAML.

    Hmm, two points are a bit lame, but I'm bloody tired now, so I bid farewell.

    Regards,
    Kasimier

    Kasimier Buchcik Germany

    Sunday, January 24, 2010 9:08 PM

    Kasimier Buchcik

    Hi,

    Supplement:
    The reason I find this all so interesting and am fearful of the course MS' development and am trying to
    inject my point of view is because:
    I cannot easily implement all the stuff I want *safely* because I cannot reuse any of the code of MS; i.e. when implementing e.g. ICollectionView,
    I cannot look at the code of MS in order to avoid the lessons learned.
    I'm currently trying to find unit tests for ICollectionView and IEditableCollectionView because the documentation for those interfaces are poor.
    So it *is* really of interest to me that MS succeeds in a nice implementation of all this stuff. Otherwise the burden would be on me
    to provide not only the functionality I want, but also the *tests*. And the current ways of development on top of MS stuff does not
    make testing a fun ride.

    Regards,
    Kasimier

    305.tgrconversions.com

    Thursday, May 20, 2010 12:54 PM

    pingback

    Pingback from 305.tgrconversions.com

    Typhoon System Cooling Fan Cl P0114, Typhoon Mirage

    Add comment


    (Will show your Gravatar icon)

      Country flag

    Click to change captcha
    biuquote
    • Comment
    • Preview
    Loading