This post is as much about making a mental note as it is about anything new. As part of the project that is creating an RSS feed from a VSS database, I wanted to to perfrom some caching of the feed. If you haven't worked with the ActiveX component that provides the API to VSS, trust me when I say that it's slow. And because the retrieval of the history is recursive, the slowness can be magnified by an order of magnitude. So given the lack of volitility of the data, caching seemed in order.
Now there is a fair bit of information about how to add items to the ASP.NET Cache object. The Insert method is used, followed by a number of parameters. And because there are different combinations of criteria that can be used to identify when the cached item expires, the Insert method has a number of different overloads. The one that I'm interested in is
Cache.Insert(string, object, CacheDependency, DateTime, TimeSpan)
This particular overload is used to specify a cache dependency and either an absolute or a sliding expiration time. Ignore the cache dependency, as it is not germaine to the discussion. The issue arises when trying to specify either an absolute expiration time or the sliding time span. Since these choices are mutually exclusive, my problem was how to correctly provide a null value for the unimportant parameter. In my case, it was how to define a 'null' TimeSpan, as I wanted to use an absolute expiration. It took a little bit of searching before I found the answer (which, by the way, is the reason for this post...future documentation).
To define an absolute expiration, use TimeSpan.Zero as the last value. For example:
Cache.Insert(”key”, value, null, DateTime.Now.AddMinutes(15), TimeSpan.Zero)
To define a sliding expiration, use DateTime.Now as the fourth parameter. For example:
Cache.Insert(”key”, value, null, DateTime.Now, new TimeSpan(0, 15, 0))
My instinct (although I have nothing to back it up) is that under the covers the Insert method uses both parmaeter values to determine when the expiration should take place. But regardless, I'm left wondering why this particular overload exists. Why is there not one overload for absolute expiration and another for sliding? The signatures would be different, so that's not the reason. It wouldn't conflict with one of the other signatures. I'm left scratching my head. Hopefully someone out there in the blogsphere who's reading this post will have an answer.
I've spent the last couple of days working on a component that builds an RSS feed using Visual Source Safe data. The purpose for the feed is two-fold. First, the feed will be used by a nightly build to determine if anything has been updated since the previous build. Second, the feed can be subscribed to by others on the development team, allowing them to keep up to date on the progress of the project. Over the next week or so, I'll be blogging more about the problems/issues/resolutions that I've run into.
The first element of the technique that I'm using has to do with identifying the VSS project that is to be used as the source for the feed. Because it fits nicely with the VSS hierarchy (and because I thought it would be cool), we decided to include the VSS path in the URL. For example, a URL of http://localhost/VSSRSS/top/second/third/rss.aspx would retrieve the history information for the project $/top/second/third. To accomplish this, the RewritePath method associated with the HttpContext object is used.
So much for a description. My reason for the post is not to describe the technique so much as to identify an error that I received and provide a solution that is not immediately obvious from the message. While going through the development process, my first thought was to include the host information in the rewritten path. Including the http:. Why? Beats me. It was just the first thing that I did. But when I did, a page with the following error appeared:
Invalid file name for monitoring: 'c:\inetpub\wwwroot\VSSRSS\http:'. File names for monitoring must have absolute paths, and no wildcards
This interesting error is actually caused by the inclusion of the protocol (the http) in the rewritten path. The actual value of the path parameter should be relative to the current directory. Which also implies that the rewritten path cannot be outside of the current virtual directory.
Hopefully this little tidbit will help some else down the road.
While there are a number of quite useful articles about how to access and increment PerformanceCounters through the .NET Framework (the PerformanceCounter class description on MSDN and Creating Custom Performance Counters, also on MSDN to name two), the actual deployment of a web service (or any ASP.NET application, as it turns out) is not so thoroughly covered.
The biggest problem surrounding the move into production of an ASP.NET application that updates performance counters is permissions. By default, in order to increment a performance counter, the user needs to have Administrator or Power User rights. You could change the processModel value in machine.config to System, but that leaves a security hole wide enough to drive an 18-wheeler through. Which is another way of saying “Don't do this!!!!!”.
For completeness, the event log entry that appear as a result of the lack of permissions is as follows:
Event ID: 1000
Access to performance data was denied to ASPNET as attempted from C:\WINNT\Microsoft.NET\Framework\v1.1.4322\aspnet_wp.exe
Also, on the actual call to increment the PerformanceCounter, the following exception is thrown:
System.ComponentModel.Win32Exception: Access is denied
with the stack trace pointing to the GetData method in the PerformanceMonitor class.
As it turns out, the permission set that is required is much smaller than running as “System”. In the registry key HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib, set the Access Control List so that the ASPNET user has Full Control. Voila, the problem goes away.
If this solution is familiar to experienced XSL users, forgive me. I'm a casual XSL user, so the problem was difficult to identify and I didn't find much in the way of Google entries covering the answer. So that (in my small, secluded little world) makes it blog-worthy.
The situation I found myself in was needing to convert the WSDL output from an ASP.NET page to a specific XML format. The reason for the format is irrelevant. Suffice it to say, that I needed to create a list of the valid SOAP operations for a particular web service. I tool the WSDL that was generated by ASP.NET and started the trial and error process of identifying the correct XSL stylesheet to use. This is where I ran into problems.
I do understand the basics of XPath enough to try simple queries. My starting point was to list out the portTypes for the WSDL. To do this, my initial XPath query was //definitions/portType. This didn't actually return any nodes. I thought this strange, so I dropped down to the more straightforward //definitions. This too returned nothing. Running out of drop-back room, I went with the wildcard //*. Fortunately for what remains of my hair, this worked. So the question of why the other queries didn't work remained.
After more painful attempts (and a weekend to allow my brain some reset), I finally came across the solution. XPath queries don't work with the empty namespace. They require that the namespace be explicit within the query. But the WSDL generated by ASP.NET does not assign a namespace to the WSDL elements, choosing to use the empty namespace. So the XSL file needed to be modified to define a namespace for the URI associated with the WSDL elements. When done, the xsl:stylesheet element looked like the following:
The key here is the xmlns:wsdl attribute. This defines a namespace called wsdl and associates it with the listed URI. This URI needs to match exactly the URI associated with the empty namespace in the WSDL file. And I do mean exactly. Character for character. Byte for byte.
Once this has been added to the stylesheet tag, the XPath queries can be modified to be //wsdl:definitions/wsdl:portType and the results will be as expected.
This might seem a little on the simplistic side, but given the difficulty that I had finding this information, I'm posting it in the hope that it helps others.
I have created an HTTP Module whose job it is to raise EIF (Enterprise Instrumentation Framework) events on the receipt and response to SOAP requests. The key here is the SOAP requests. I don't want to pay any attention to the non-SOAP requests that come through. So I needed to find a way to separate the two classes of messages using the information that was available.
The answer is to use the HTTP_SOAPACTION header that is included with SOAP requests (but not with the normal GETs and POSTs that a web site sees). For example, the following code simply skips processing any non-SOAP requests.
private void Application_BeginRequest(Object source, EventArgs e)
HttpApplication application = (HttpApplication)source;
// Don't do anything for non-SOAP requests
if (application.Context.Request.ServerVariables["HTTP_SOAPACTION"] == null)
Naturally, I have tied this procedure into the BeginRequest event for the HttpApplication object in the Init method for the HttpModule.
If someone has a better idea (or a reason why/when this won't work), I'm open to suggestions. But for what I'm trying to accomplish it did the trick.
I had the distinct pleasure of trying to incorporate a non-COM compliant DLL into a web service yesterday. Along with the issues associated with marshalling parameters (and which I'll mention in a separate blog entry), I also had to get the web service to find the DLL that needed to be loaded. I would have thought that simply placing it into the bin directory under the virtual root would be sufficient. Apparently not. Even after doing so, I still got a could not load DLL message
The correct answer is that the DLL needs to be someplace in the directories listed in PATH. And by default, the bin directory is not in this list.
For those who are unfamiliar with the process, the PATH is build from a combination of sources. First, the System Path as defined in the System Environment Variables in the System administration applet is included. Then the User path as defined in the same place is appended. Finally the directories added to the PATH through Autoexec.bat are included.
So if you plan on using a DLL in a web service, make sure that either the DLL is installed someplace along your PATH or your PATH is modified to include the web service's bin directory.
Yet one more interesting Web Service problem reared its head today. The situation is as follows:
The result of a call to a Web Service method is supposed to be a class. The class contains the elements that, basically, make up a record in a database table. Because of the way that we structure data access, our data access class returns a DataSet with a matching xsd file. Once the xsd file had been created, the xsd.exe tool was used to create the class definition. An instance of the class was then hydrated using the XML format of the DataSet and returned through the web service.
(just for the curious amongst you, our reason for not returning the DataSet itself is that the client is an older version of WebSphere and they don't have easy support for processing returning DataSets.)
Key point coming: the minOccurs value on at least some of the data elements in the xsd was set to 0. As a result, for some of the data types (specifically integers, int16 and bytes), additional properties were created. For example, if there were a property called Status, a public boolean property called StatusSpecified would also be created.
No problem to this point. A web method is created with a return value of the class that was generated by xsd. The method is called and *bam* (picture Emeril at this point), an error gets generated. The error is the following:
Server was unable to process request. File or assembly name q2k5rmkd.dll, or one of its dependencies, was not found.
The experienced among you will recognize this as a error that occurs when the ASP.NET user doesn't have access to the temporary directory. But that was not the case here. It is also an error that gets returned when one of the classes used in a web service message has a ReadOnly property. But a review of the returned class shows that this is not the case either.
As it turns out, the problem is that the Specified properties (such as StatusSpecified) conflicts with an automatically generated property called, coincidentally, StatusSpecified. This automatic property is generated when the result set contains a minOccurs clause. It is used with numeric values to identify those properties that have not been assigned. It is necessary because numeric variables don't have a 'null' value. But the conflict between the autogen'ed properties and the properties that are created by xsd causes the error message seen above. The easiest solution is to change the name of the properties included in the xsd.exe generated class to have a name that does not have 'Specified' as the suffix.
I just had the joy of spending a day trying to get my .NET Web Service to interact with a WebSphere client. Regardless of the changes made, the following error was received on the Java side.
Unable to retrieve PropertyDescriptor for property Xxx
All of the property names matched up. All of the properties in the Java class had getters and setters. The ultimate solution? Change the case of the property names. The problem only occurred if the property name started with a capital letter. If changed to a camel case format, the problem goes away. Apparently, this is a problem with the standard BeanSerializer in older versions of WebSphere (version 5.0 and earlier). According to the notes that I found, it has been corrected at 5.1 and in a patch to 5.0. Can't vouch for this, as we just swallowed hard and changed the case of our exposed properties. I hate case sensitivity.