📜 ⬆️ ⬇️

IIS + .NET + Json. We write the Application Server

In this article, I tried to talk about my vision of what an application server is, interacting with various clients, and how to implement it in .NET. In order to avoid overloading the article, I tried not to go into the details of the implementation, but I think you will be interested in the basic ideas.

And so, what is an "Application Server"?

I will try to formulate in my own words.
- “Application Server” is one of the terms related to the architecture of distributed N-tier systems, where it occupies a central place and is located between clients making requests to process and receive data and store this data.
“An application server is software based on a specific architecture and technology that runs on the server side. Specificity consists in serving a multitude of clients both in their number and in the diversity of their types.
- The application server performs the necessary processing and transformation of data in certain formats, both received from clients and sent to them.
- The application server provides an application-level API, whose methods can be used to implement a particular client application.

The background of this project was as follows.
Developing somehow another application based on the new-fashioned Microsoft Windows Communication Faundation (hereinafter referred to as WCF), besides a thick .NET client under Windows, there was a desire to make a web interface to this service. ExtJs was chosen for mastering. Having loaded some of the material on the topic “how to connect an Ajax client to a WCF service,” the first sketches were made. The model has earned, but already then some complaints began to arise in ways to achieve seemingly elementary things. At first glance, there were too many “unnecessary” things to do for this, which in practice were simply necessary.
')
A little more history.
There was experience and several successfully implemented projects at WCF. In one smart book from O'Reilly there was a recommendation not to do many methods in the service interface (not more than a dozen + -). Proceeding from this and from the real need to have a lot of different methods for the service, an evolutionary decision was made to make one main executable method plus several additional ones as needed. Because at that time, it was only the .NET client of the service that was used, this main method accepted the name of the method being called and the parameters serialized by BinaryFormatter, plus another AssemblyQualifiedName in case the methods were in external assemblies.
But this solution was completely incompatible with Web-clients and had to go back to the “flat” methods implemented in the service interface.
There were problems when working with threads, despite the support for MTOM. I did not manage to force WCF to pass threads through a proxy server normally.
Another nuisance in WCF + Ajax is Json serialization. Well, it seemed so much easier, so no, and a lot of its own nuances were formed here.
In addition, the practice all the time showed that customers who have implemented solutions based on WCF, very often have problems installing .NET 3.5 SP1 on the server. That the framework itself is not installed, the .svc is not registered, which is most often, then something else.
Little by little, WCF ceased to please.

And in my head, meanwhile, the thought did not give rest, how do the services of well-known social networks, web projects, various App Store work on the Internet. After all, not pkhp one. It is clear that this is not "Windows", but that's not the point. The bottom line is that these services provide data to different types of clients. One service - a lot of different customers, it's “cool” and “arch-important” nowadays, but how to do this on .NET?

It was decided to try using .asmx WebServices from .NET 2.0, moreover, extensions appeared in .NET 3.5 that allow them to interact with the Ajax world. I will say right away that something happened, something didn’t, but still there was a feeling that all these extensions for XML web services to them were “pulled by their ears” and they are not their own.

I do not want to say that WCF and WebService technologies are not at all suitable for the implementation of Web 2.0, but I repeat - what seemed to be done intuitively, sometimes caused such “misunderstandings” that I wanted to quit. Somehow it was difficult to implement simple things, and some things could not be done at all.
One could cite here a list of unpleasant moments that arose during the development process, but to be honest - now I don’t even want to remember, after everything has become elegant and simple.

So there must be something in .NET suitable for achieving goals? Our salvation is System.Net and System.Web.
And so, choose IHttpHandler.

Take a look at HttpContext with its HttpRequest and HttpResponse. They are almost completely implemented low-level details of the Http protocol from Webengine and laid out to us on a platter. We can only draw a “blue border”.

/// <summary> /// Enables processing of HTTP Web requests by a custom HttpHandler. /// </summary> /// <param name="httpContext"> Http .</param> void IHttpHandler.ProcessRequest(HttpContext httpContext) 

We received a request from some customer.
And then what should we do with it? The answer is everything your heart desires. Yes Yes exactly.
Once again, we formulate what we want:
1. One application server - many types of clients (.NET, Java, Web + Ajax, Silverlight, iPhone, Android, etc.);
2. Call the required application server application method from any client;
3. Support “GET” and “POST” requests, i.e. passing parameters via Url or request body;
4. Support for compressing parameters and executing methods sent back to clients;
5. Work with flows in both directions without any effort;
6. Speed, ease, asynchrony, transparency and clarity of the decision.

When implementing the 1st item, I came to the conclusion that there are data types that exist in almost all languages ​​and platforms that are compatible with each other and are suitable for transferring them across the border of the medium (I also call it - discovered America). I emphasize the word "compatible", because This is a fundamental point:
characters, strings (including encoding), simple data types (various types of numbers, boolean values), arrays of bytes and streams. A date usually has a specific string representation. Here it is possible to refer arrays, lists and dictionaries, as containers of the above data types.
The natural choice was Json, as the exchange format. As an option, the Json.NET library from Newtonsoft was chosen.

And so, the client should be able to call a specific application method (RPC) on the server and get an answer that it can understand from the server. The called method is specified by a string with the name. The method needs parameters. He must recognize and process them correctly. For greater flexibility, the methods may belong not only to the server itself, but also to any assembly that is accessible to the server, albeit with some security restrictions.
REST style is popular, but not quite flexible. This is a "template" style. Step left and right, and universality is lost. Remains an indispensable QueryString.

The server receives a request from the client:
/service.ashx? method = GetImage (“DSCN2099.JPG”)

The names of the key parameters are highlighted in bold.
It is clear that the method belongs to the service itself, since there is no other indication where to look for it; it takes the name of the requested picture as a parameter. Where the picture is located is known only to you as the developer of the service. To choose: a directory in the file system; resource from any assembly; DB; runtime drawing; external resource, etc. This is applied logic.

The full format of the query string:
 /service.ashx?
 session = xxxxxxxxxxxxxxxxx & - session ID
 class = Full Assembly Type Name & - “FullTypeName, AssemblyName”
 method = Method Name (Parameters) & - (case-sensitive method name)
 format = Json / DotNetBinary & –– data format (expandable)
 zip = on / off - whether method input parameters are compressed

If any of the parameters is missing, then its default value is used. The only thing that must be specified is the name of the method being called.

What to return to the client? Depends on the client application itself. If the picture is a thumbnail, you can return an array of bytes. If this is a big picture, then it is better to return the stream.

And so, the application server receiving the request, processes it (the parsit), creates the execution context of the specified method and calls the method for execution. The method implements the applied logic and at the end returns return result; And where does this result go next? Not in the air. And the result is returned from the method back to the core of the application server as a universal object. Next, the type of the return value is analyzed. If it is typeof (void), then nothing else is done; if it is Stream, then it is redirected to the output stream of HttpResponse. Otherwise, the result is given to the Json serializer for conversion to a string, and then this string is written via HttpResponse-> Write ().

Thus, having access to the Http context of the request, we were free to choose the implementation of the core infrastructure of the application server.

SOAP .asmx WebService infrastructure is implemented in approximately the same way. Only there is heavy but omnivorous XML, and here Json is easy and fast. .aspx and WCF are also handlers.

So what about our customers, for whom it all started?

Everything is simple with .NET.
Here you have the choice of both the native binary format and Json, and HttpWebResponse-> GetResponseStream () is the stream that was returned by our application server's “GetImage ()” method, the only difference is in the type of this stream, here it will be a network stream - one of the inner classes of the .NET Framework. Yes, it does not matter to us. It is important that we can read it and make for example Image.FromStream () or simply save to a file. If the application server returned an array of binary data (byte []), then this is essentially the same thing, since There is no other way than GetResponseStream (). Only we have to convert it back to this very array. What to do with this thread is decided by the developer of the client application, based on the application API that the application server provides to it.

The developer writes an assembly in which there can be both client and server methods, plus general data structures. Then this assembly is simply put into the server-side project, and the client’s request indicates the name of this assembly. Those. it turns out that you can add additional business logic without recompiling the service itself. Designed, assembled, put and, voila - the new functionality immediately becomes available.

Ajax
The presented ideology of the application server fits very well with the implementation of clients based on Ajax frameworks. After all, Json is even more familiar to them than XML.
 Ext.Ajax.request({ method: 'GET', url: '/service.ashx?method=GetImageInfo(“DSCN2099.JPG”)', success: function(response, options) { var result = Ext.decode(response.responseText); }, failure: function(response, options) { ... } }); 

Server Method:
 public ImageInfo GetImageInfo(Json json) { string fileName = json.AsString; string filePath = Path.Combine(IMAGES_DIR, fileName); return new ImageInfo(filePath); } 

The ImageInfo structure returned by the “GetImageInfo ()” method will be converted by the serializer to Json into something like the following line:
 {
    "Name": "DSCN2099.JPG",
    "Height": 1536,
    "Width": 2048,
    "PixelFormat": 137224,
    "RawFormat": "Jpeg",
    "HorizontalResolution": 300.0,
    "VerticalResolution": 300.0,
    "ThumbImage": "/ 9j / 4AAQSkZJRgABAQ ...",
    "FileInfo": {
       "FileSize": 1849625,
       "CreationTime": "\ / Date (1246514398257 + 0400) \ /",
       "FileAttributes": 32
    }
 }

And the application server will send it to the client, where it will be decoded into a javascript object.
The date can be obtained by calling:
 var date = Date.parseDate(result.FileInfo.CreationTime, 'M$').format('dmY h:i'), 

miniature:
 var image = { xtype: 'box', autoEl: { tag: 'div', children: [{ tag: 'img', src: String.format('data:image/jpg;base64,{0}', result.ThumbImage) }] }, listeners: { render: function(comp) { comp.getEl().on({ dblclick: function() { var url = '/service.ashx?method=GetImage(result.Name)'; window.open(url, 'imageWindow', 'menubar=no, location=no, resizable=yes, scrollbars=yes, status=no, width=640, height=480'); }, scope: comp }); } } }; 

Our base64 ThumbImage was successfully transformed into a card of the appropriate size, and double-clicking on it will open a new browser window and display a full-fledged image.

For example, another such trick. A string from the style sheet. The icons are in the resource assembly:
 .loading { background-image: url(/service.ashx?method=LoadIcon%28%22loading.gif%22%29) !important; } 

Well, it seems so far so good. We already have two clients.

Try Silverlight?
Differences from the “thick” .NET client are the complete absence of synchronous methods from HttpWebRequest and HttpWebResponse. To work with Json, there is an implementation of Json.NET for Silverlight. The rest is almost the same as for desktop .NET applications.
To simplify interaction with the application server, a special class Request has been developed, which forms the query string and executes it. It encapsulates the work with HttpWebRequest and HttpWebResponse:
 RequestParams requestParams = new RequestParams(); requestParams.Method.Name = "GetImage(fileName)"; requestParams.Method.Params.Add("fileName", "DSCN2099"); Request request = new Request("http://localhost/AppService"); request.Execute(requestParams, Action<RequestCompletedEventArgs> onRequestCompleted); 

Those. Having prepared the request parameters, we simply call the server method and process the result in the callback method. Easier does not happen!

Next in line is Java.
The main class is HttpURLConnection, which has a getInputStream () method. Called - find two differences from .NET (not counting the name). The idea is the same - the helper classes RequestParams and Request are created. The RequestParams class is the name of the method being called and its parameters, and Request encapsulates the logic of working with HttpURLConnection. For Json serialization, a library from Google is used - Gson, which can also be used for development for Android. All that comes in Json format from an application server, implemented on an antagonistic to Java platform, is “digested” without any problems. The only thing that does not understand Java by default is the MS date format. But Gson is an expandable library, and the problem is solved simply:
 public class MSDateJsonSerializer implements JsonSerializer<Date> { public JsonElement serialize(Date date, Type typeOfT, JsonSerializationContext context) { return new JsonPrimitive("/Date(" + date.getTime() + ")/"); } } public class MSDateJsonDeserializer implements JsonDeserializer<Date> { public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { String jsonDateToMilliseconds = "\\/(Date\\((.*?)(\\+.*)?\\))\\/"; Pattern pattern = Pattern.compile(jsonDateToMilliseconds); Matcher matcher = pattern.matcher(json.getAsJsonPrimitive().getAsString()); String result = matcher.replaceAll("$2"); return new Date(new Long(result)); } } 

Streams from .NET are also compatible with Java (they are also streams in Africa):
 ImageIcon icon = new ImageIcon(“http://localhost/AppService/service.ashx? method=GetImage('DSCN2099.JPG')”); 

So, we already have 4 clients, and taking into account that Android applications are written in Java, then all 5.

I think it is now becoming clear that there are practically no restrictions on the type of clients, and all platforms that can work with Http and Json will be able to interact with our application server.

Source: https://habr.com/ru/post/123797/


All Articles