Creating multiple (15+) HTTP Response filters, Inheritance vs. Composition w/ Injection

泄露秘密 提交于 2019-12-01 09:19:36

问题


First a little back story on what I am trying to accomplish.

I am in the process of creating a custom HTTP Module whose purposes is to intercept messages to multiple (15+) different ArcGIS REST web services. The intercepted requests and/or responses will be stripped of any restricted information based on the current user.

For instance, a call that returns multiple layers might have certain layers stripped out.

Unmodified Response:

"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 Response:

"layers" : [
    {
      "id" : 0, 
      "name" : "Facilities", 
      "parentLayerId" : -1, 
      "defaultVisibility" : true, 
      "subLayerIds" : [1, 2, 3]
    }
]

There are numerous services available, all uniquely identified via a URL. Each service returns very different information and so needs to be filtered different. Additionally, each service may return the data in a variety of formats (HTML, JSON, etc).

As such, I will need to create a multitude of different filters to apply to HttpRequest.Filters and/or HttpResponse.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 MemoryStream). I want to be able to easily create new filters without reimplementing Stream for each filter.

A potential solution is described in here: http://www.west-wind.com/weblog/posts/72596.aspx

However, I want to simplify the solution without losing the flexibility of specifying many different transformations without reimplementing the stream. I think that I can accomplish this by:

  1. Inherit from MemoryStream so as to reduce the reimplementation of methods.
  2. Always operate on full content, rather than chunked content.
  3. Replace the events with an abstract method (e.g., Filter())

I have considered two potential solutions.

Solution 1: Create Multiple Filters Inheriting from ResponseFilter

In this scenario each filter contains the logic for performing the filtration. There would be 15+ filters created all inheriting from a common ResponseFilter abstract base class like so:

// All filters will inherit from ResponseFilter
public abstract class ResponseFilter : MemoryStream
{
    public ResponseFilter(Stream stream, Policy policy) { }

    // Must be overridden in a derived class with specific Filter logic.
    public abstract string Filter(string content);

    // 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(content);

         // Write new content to stream
    }
}

This would be used in the following way.

// Example
var policy = GetPolicy();
var filter = new MapServiceJsonResponseFilter(response.Filter, policy);
response.Filter = filter;

The advantage to this option is that the number of classes is kept to a minimum. However, it becomes difficult to reuse any filter logic anywhere else in the application should it become necessary. Additionally, unit testing the filters would require mocking the Stream, another disadvantage.

Solution 2: Create Multiple Filters, Inject into a Common ResponseFilter

In this scenario, a single response filter is created. The actual filter logic or algorithm is injected into the filter. All filters inherit from an 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 would be used in the following way.

// Example
var policy = GetPolicy();
var filter = new MapServiceJsonResponseFilter(policy);
ResponseFilter responseFilter = new ResponseFilter(response.Filter, filter);
response.Filter = filter;

The advantage to this solution is that the filtration logic is completely independent of any classes that implement stream. The logic can be more easily reused if necessary. Unit testing is a little simpler as well as I do not need to mock the stream.

However, there are more classes (exactly 1) and the usage is a little more complex, though not terribly so.

Note: I'll probably want to rename FilterBase as to avoid confusion with ResponseFilter. Perhaps TransformBase.


I have several goals that I want to meet with either solution.

  1. The solution must be highly testable. Unit testing will be used to check for correctness of the filters. It is imperative that testing is as simple as possible.
  2. The solution must easily support the creation of multiple filters (15+).
  3. The solution should be readable (i.e., easy to maintain).

I think that solution 2 is the best solution for this given scenario. I can test the filtration logic completely independently of Stream with minimal additional complexity. Either solution will support #2 and #3, so testing gets the edge.

What other considerations might there be? Are there better alternatives?


回答1:


Solution 2 is obviously preferable. However, it seems that the major crux of the problem lies in the construction of the Filters themselves. Hopefully there is a lot of reusable composition within the Filter implementations. Can a new filter be "configured" from composite parts?



来源:https://stackoverflow.com/questions/4433386/creating-multiple-15-http-response-filters-inheritance-vs-composition-w-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!