re-motion supports passing the IDs of domain objects in URLs, so that you can send around pages like these as links and have the recipient see exactly what you see:
Sending around pages associated with a particular object can be very practical, and many content management systems support this. For example, if I share my PhoneBook application with other people, I can send my fried Josh Szygyäivsük (Polish mother, Finnish father) a link to his
EditPersonForm and ask him to correct the spelling of his last-name if I got it wrong and to supplement his address, something like:
Note that in this e-mail, the URL for the
EditPersonForm sports an URL-parameter with some object ID, presumably the one for Josh's
Person domain object instance. If Josh clicks on the URL, the server will send a form with his
Person object (i.e. with the ID/GUID c5efd202c-e559-4645-a13e-c254b9c79da5). This is one of the reasons why web-applications have become popular: single pages can be referenced from e-mail, MS Word-documents, wikis and other web-applications like bug-management systems – and across the entire internet, for that matter.
Having a unique URL for each form and domain object instance is not supported by default in re-motion; the edit forms for all instances of a particular domain object class look alike. It is a natural consequence of using the economical
Server.Transfer, but it flies into the face of idea that an URL provides an unambiguous reference to one particular page in the (inter)network. It is sometimes desirable to have the URL contain enough information to reconstruct the form the user sees, but this feature comes with some overhead.
Such an URL uniquely identifying a particular form (including all its input parameters) is called a "perma-URL". (Like a "permalink" to a blog-post or a link to a certain Google-query. You've probably sent around URLs like this occasionally.) This feature comes at the expense of performance, because
Server.Transfer can't be used for such invocations, and
Response.Redirect requires an extra round-trip (see FIXME for a backgrounder). Using perma-URLs for uniquely identifying forms for particular domain objects requires some modifications to the generated defaults, however.
Introducing call options, perma-URLs
Perma-URLs get their name from the fact that they keep their particular meaning forever (permanently), but in this light, the term is misleading. Some perma-URLs keep their meaning forever, but it requires extra work.
You can tell re-call to use perma-URLs when invoking the desired page/function. Remember (from the re-call section in the PhoneBook tutorial) that there are two overloaded
Call methods for a
WxePage. In the example for calling
EditLocationForm upon New location..., we have used the simpler
Call, the one without the args argument (two arguments). For invoking the
EditLocationForm with options for a perma-URL, we must use the more elaborate three-argument
Call. Here is an example. We create a call argument of type
WxePermaUrlCallArguments and pass it to the
Call method to signal that we insist on a perma-URL for the call.
[ The new code is not protected by an
if (!IsReturningPostback) because it has no side-effects. What it does it can do twice without doing any harm. The only extra cost is that the invocation drains some (negligible) performance, but that's cheap for not messing up our code with an extra if. ]
For the called form, this code gives you a long URL with a
WxeFunctionToken parameter for identifying the call stack built up to the point where the page in question is invoked. The problem with such WXE function tokens is that they are short-lived and will fall prey to the garbage collector eventually. WXE function tokens can't help much if you want to have your perma-URLs for longer – years, for example.
Let's talk about the resulting URL first. If you run this code and click on New location..., you will note that the URL for invoking the
EditLocationForm has become much longer:
Now being invoked with a perma-url
... and here it is
The information transported by this parameter is also present if no perma-URL (i.e. a
Server.Transfer) is used, but than it is invisible to the user.
The type of perma-URL we've programmed here sort of works, albeit not for very long. It only works for as long as the transaction and function is alive on the server. If you send the URL to a friend in the email, he or she will see what you see for as long as you have not committed the form and the referred function stack still exists on the server. Not very bright.
Why isn't "the domain object" included in the URL?
Generating and parsing meaningful URL-parameters for forms called with the form's
Call method includes the arguments for the
Call-method, under one condition: only values for arguments that can easily be serialized into the URL get a corresponding URL-parameter. Observe that this clearly rules out the
Location object passed in
obj. After all, it is hard to represent the property values of all its field into a URL.
You can see how URL-parameter generation works with a simple experiment. If you add an integer argument to the
EditLocationForm function/page, you will see it show up as a URL-parameter, because an integer is easy to serialize – just write the number as a string. It's also easy to parse (with
Int32.Parse(), for example). This only works with perma-URL representation turned on for a call of the
EditLocationForm. Try this:
1.) add a new Int32-parameter named foo to
EditLocationForm, so that the re-call header looks like this:
[ We make the parameter optional with the
required="false" attribute to avoid a mess in other spots of the web application. ]
2.) Add some integer to the EditLocationForm invocation in the event-handler for New location...:
3.) For completeness, add some integer to the
SearchResultLocationForm.aspx.cs in the event-handler for
Your project should compile and link (but again: you have to build it twice to get rid of the errors). If you stimulate your PhoneBook web app into displaying the
EditLocationForm, you will see the
foo-parameter in the URL. You can either click on New Location... (
Edit (SearchResultLocationForm) (see screenshots).
Either way, your URL will display the utterly redundant (but instructive)
Domain objects in the perma-URL
Let's return to our original proposition of sending around URLs that encode a pointer to one particular domain object, (a particular location, for example). It is not feasible to represent the domain object's entire set of values in an URL, but we can easily encode an object ID. Remember from the PhoneBook-tutorial (re-store part) that a complete
ObjectID consists of
- class information ("Location", for example)
- some ID (at this time, only GUIDs are supported)
- information on how the ID should be interpreted ("System.Guid")
The static method
ObjectID.Parse can parse such a string representation in a format like
Location|9388...E7|Guid. In contrast to the dummy foo-int from the previous exercise, having an object ID in the perma-URL actually makes sense: it enables users to link to a particular object.
So if we want to give each domain object in
EditLocationForm|s its own unique URL, we do the following:
- modify the signature of the Call method in such a fashion that the domain object's ID is passed rather than the .NET object itself
- adapt the invocations to fit the new signature
- for compatibility reasons, we should keep the current obj variable for holding the actual
[ Before working on this exercise, you should remove the instructive Int32 foo parameter from the EditLocationForm and restore the invocations to their original form. ]
Modify the re-call header for the object ID
The new header in
EditLocationForm.aspx.cs shall look like this:
We keep the
obj as a local variable, so that the rest of the web-application remains compatible. However, me must take care that obj keeps its meaning by setting it in the edit form's
Page_Load method. This method is also the spot where a new object is created if the
objid parameter is null. Before our changes,
Page_Load looked like this:
Take care that the new parameter is actually used
In the new and improved web application we want to set the obj page-local variable like this:
obj = Location.GetObject(objid); (With objid being the new ObjectID page-parameter.)
Consequently, the new and improved Page_Load shall look like this:
Change the EditLocationForm's Call invocation
At this point, your application will not build correctly, because the existing call to the
Call method has become incompatible with the new state of affairs. The
EditPersonControl.aspx.cs (in the event handler for New location...) is uncritical and does not need to be changed. After all, just a
null is passed as
objid, so no modification is required. This invocation is also uninteresting, because New location... creates a new object instead of dealing with an existing one. To see how the improved URLs work, the
Edit handler in
SearchResultLocation.aspx.cs is much more instructive. The existing
Call looks like this:
However, we don't need the entire
(Location)e.BusinessObject, just its ID:
Consequently, the new
LocationList_ListItemCommandClick handler shall look like this:
Try it out
As explained before, nothing interesting happens URL-wise when you create a new
Location object with New location..., but clicking on the new object will give you a long URL. Here is what the author got from this demonstration: