First, Iโll talk a little bit about what I'm trying to accomplish.
I am creating a custom HTTP module whose purpose is to intercept messages for several (15+) different ArcGIS REST web services. Intercepted requests and / or responses will be deprived of any limited information based on the current user.
For example, a call that returns multiple layers may contain specific layers.
Unmodified answer:
"layers" : [ { "id" : 0, "name" : "Facilities", "parentLayerId" : -1, "defaultVisibility" : true, "subLayerIds" : [1, 2, 3] }, { "id" : 1, "name" : "Hazardous Sites", "parentLayerId" : 0, "defaultVisibility" : true, "subLayerIds" : null }, ]
Modified Answer:
"layers" : [ { "id" : 0, "name" : "Facilities", "parentLayerId" : -1, "defaultVisibility" : true, "subLayerIds" : [1, 2, 3] } ]
There are many services available, all uniquely identified by URL. Each service returns very different information and therefore it needs to be filtered differently. In addition, each service can return data in various formats (HTML, JSON, etc.).
Thus, I will need to create many different filters to apply to HttpRequest.Filters and / or HttpResponse.Filters filters.
Example:
// Request for layers and the format is JSON IPolicy policy = GetPolicy(userContext); Filter filter = new LayerJsonResponseFilter(Response.Filter, policy); Response.Filter = filter;
Request and response filters are implemented by inheriting from Stream (or another class that inherits from Stream, such as a MemoryStream). I want to be able to easily create new filters without overriding the stream for each filter.
A potential solution is described here: http://www.west-wind.com/weblog/posts/72596.aspx
However, I want to simplify the solution without losing the flexibility of defining many different transformations without overriding the flow. I think I can do this:
- Inherit from MemoryStream to reduce re-implementation of methods.
- Always use full content, not fragmented content.
- Replace events with an abstract method (e.g. Filter ())
I examined two potential solutions.
Solution 1. Create some filters inheriting from ResponseFilter
In this scenario, each filter contains logic to perform filtering. There would be another 15 filters created by inheriting from the common ResponseFilter base class as follows:
This will be used as follows.
// Example var policy = GetPolicy(); var filter = new MapServiceJsonResponseFilter(response.Filter, policy); response.Filter = filter;
The advantage of this option is that the number of classes is minimized. However, it becomes difficult to reuse any filter logic elsewhere in the application if it becomes necessary. In addition, unit filter testing will require flow mixing, another drawback.
Solution 2: creating multiple filters, embedding in a common response filter
In this case, one response filter is created. The actual logic or filter algorithm is entered into the filter. All filters inherit from the abstract base class FilterBase.
// Represents an HttpResponse Filter. Renamed to avoid confusion with // the filter algorithm. public class ResponseFilterStream : MemoryStream { public ResponseFilterStream(Stream stream, FilterBase filter) { } // Overridden to cache content. public override void Write(byte[] buffer, int offset, int count) { } // Overridden to perform the filter/transformation before the content is written. public override void Flush() { // Get stream content as a string string content = _filter.Filter(content); // Write new content to stream } } // All filter algorithms inherit from FilterBase and must implement // the filter method. public abstract class FilterBase { protected TransformBase(Policy policy) { } // Overridden to perform the filter/transformation. public abstract string Filter(string content); }
This will be used as follows.
// Example var policy = GetPolicy(); var filter = new MapServiceJsonResponseFilter(policy); ResponseFilter responseFilter = new ResponseFilter(response.Filter, filter); response.Filter = filter;
The advantage of this solution is that the filtering logic is completely independent of any classes that implement the stream. If necessary, the logic can be more easily reused. Unit testing is a bit simpler since I donโt need to mock the stream.
However, there are more classes (exactly 1), and the use is a little more complicated, although not so scary.
Note. I might want to rename FilterBase to avoid confusion with ResponseFilter. Possibly TransformBase.
I have several goals that I want to meet with any solution.
- The solution must be highly verifiable. Unit testing will be used to validate filters. It is imperative that testing be as simple as possible.
- The solution should easily support the creation of multiple filters (15+).
- The solution should be readable (i.e. easy to maintain).
I think solution 2 is the best solution for this scenario. I can fully test the filtering logic regardless of Stream with minimal additional complexity. Any solution will support # 2 and # 3, so testing gets the edge.
What other considerations could there be? Are there any better alternatives?