IIS + .NET + Json. Writing your Application Server
In this article, I tried to talk about my vision of what an application server interacts with various clients, and how to implement it on .NET. In order to avoid overloading the article, I tried not to go into the implementation details, but, I think, the main thoughts will be of interest to you.
So what exactly is an “Application Server”?
I will try to formulate it 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 that fulfill requests for processing and receiving data and store this data.
- An application server is software based on special architecture and technologies that work on the server side. The specificity lies in serving many customers both in terms of their number and 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, the methods of which can be used to implement a particular client application.
The background of this project was this.
Developing somehow the next application based on the newfangled Microsoft Windows Communication Faundation technology (hereinafter simply referred to as WCF), in addition to the thick .NET client for Windows, there was a desire to create a web interface for this service. ExtJs was chosen for mastering. Googling a little material on the topic “How to connect an Ajax client to the WCF service”, the first drafts were made. The layout worked, but even then some discontent began to arise in the ways of achieving seemingly elementary things. Too many “unnecessary things” at first glance had to be done for this, which in practice were simply necessary.
A little more history.
There was work experience and several successfully implemented projects at WCF. In one smart book from O'Reilly, there was a recommendation not to make many methods in the service interface (no more than a dozen + -). Based on this and the real need to have many different methods for the service, it was a decision to make one main executable method plus several additional ones as needed. Because at that time it was only about .NET clients of the service, then this main method accepted the name of the called method and the parameters serialized by the BinaryFormatter, plus AssemblyQualifiedName, in case the methods were in external assemblies.
But such a 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 streams, despite the support for MTOM. I still could not get WCF to normally pass streams through the proxy server.
Another nuisance in the WCF + Ajax bundle is Json serialization. Well, it seemed so much easier, no, and here a lot of its nuances formed.
In addition, practice has always shown that clients who have implemented WCF-based solutions often have problems installing .NET 3.5 SP1 on their servers. Either the framework itself is not installed, then .svc is not registered, which is most often, then something else.
Little by little, quietly, WCF ceased to like.
And in the meantime, the thought was still haunting how the services of well-known social networks, web projects, various App Store work on the Internet. After all, not a single php. It’s clear that this is not Windows, but that’s not the point. The bottom line is that these services provide data to various types of customers. One service - many different clients, this is “cool” and “arch-important” in our time, but how to do this on .NET?
It was decided to try using .asmx WebService from .NET 2.0, in addition to .NET 3.5 there were extensions that allow them to interact with the Ajax world. I’ll say right away that something worked out, something didn’t, but still there was a feeling that all these extensions-additions for XML web services were “far-fetched” and not native to them.
I do not want to say that WCF and WebService technologies are not at all suitable for implementing Web 2.0, but I repeat - what seemed to be done intuitively simply caused such “misunderstandings” at times that I wanted to quit. The implementation of simple things seemed somehow complicated, and some things were completely impossible to do.
One could give 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 became elegant and simple.
So there must be something in .NET suitable for achieving your goals? Our salvation is System.Net and System.Web.
And so, select IHttpHandler.
Take a look at the HttpContext with its HttpRequest and HttpResponse. They almost completely implement the low-level details of the Http protocol from Webengine and put it on a silver platter. We can only draw a "blue border".
We received a request from some client.
And then what shall we do with it? The answer is everything your heart desires. Yes Yes exactly.
Only once again we state what we want:
1. One application server - many types of clients (.NET, Java, Web + Ajax, Silverlight, iPhone, Android, etc.);
2. Call the required application method of the application server from any client;
3. Support for “GET” and “POST” requests, ie passing parameters via Url or request body;
4. Support for compressing parameters and results of methods sent back to clients;
5. Work with flows in both directions without any effort;
6. Speed, ease, asynchrony, transparency and clarity of the solution.
When implementing the first paragraph, I came to the conclusion that there are data types that exist in almost all languages and platforms, compatible with each other and suitable for transferring them across the border of the environment (also called to me - discovered America). I emphasize the word "compatible" because this is a fundamental point:
characters, strings (taking into account the encoding), simple data types (various kinds of numbers, Boolean values), byte arrays and streams. A date usually has a specific string representation. This also includes arrays, lists and dictionaries, as containers of the above data types.
The natural choice was Json, as an exchange format. Alternatively, the Json.NET library from Newtonsoft was chosen.
And so, the client must be able to call the server a specific application method (RPC) and get a clear answer from the server. The called method is given by a string with a name. The method needs parameters. He must correctly recognize and process them. For greater flexibility, methods can belong not only to the server itself, but also to any assembly that is available to the server, however, taking into account some security restrictions.
The REST style is popular, but not entirely flexible. This is a "template" style. Step left and right, and versatility is lost. An indispensable QueryString remains.
The server receives a request from the client like:
/service.ashx? method = GetImage (“DSCN2099.JPG”)
Bold key parameter names.
It is clear that the method belongs to the service itself, because there is no other indication of where to look for it; it takes the name of the requested image as a parameter. Where the picture is located is known only to you as the developer of the service. To choose: directory in the file system; Resource from any assembly DB runtime drawing; external resource, etc. This is applied logic.
Full query string format:
If any of the parameters is missing, then its default value is used. The only thing that must be indicated is the name of the called method.
What to return to the customer? Depends on the client application itself. If the picture is a thumbnail, then an array of bytes can be returned. If this is a large picture, it is better to return the stream.
And so, the application server receives the request, processes it (parses), creates the execution context of the specified method, and calls the method for execution. The method implements applied logic and finally returns return result; And where does this result go further? 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 HttpResponse output stream. Otherwise, the result is returned to Json to the serializer for conversion to a string, and then this string is written through HttpResponse-> Write ().
Thus, having access to the Http request context, we were free to choose the implementation of the application server infrastructure core.
The SOAP .asmx WebService infrastructure is implemented in approximately the same way. Only there is heavy but omnivorous XML, and here is light and fast Json. .aspx and WCF are also handlers.
So what about our customers, for whose sake it all started?
With .NET, everything is simple.
Here you have a choice of the native binary format and Json, and HttpWebResponse-> GetResponseStream () is the same stream that our application method “GetImage ()” of the application server returned, the only difference is the type of this stream, here it will be a network stream - one of the internal classes of the .NET Framework. Yes, it doesn’t matter to us. It is important that we can read it and make it for example Image.FromStream () or just save it to a file. If the application server returned an array of binary data (byte []), then this is essentially the same, because There is no other way than GetResponseStream (). Only we have to convert it back to this same array. What to do with this stream is decided by the developer of the client application, based on the application API that the application server provides him with.
The developer writes an assembly in which there can be both client and server methods, plus general data structures. Then this assembly is simply placed in the server-side project, and the name of this assembly is indicated in the client’s request. 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 in the implementation of clients based on Ajax frameworks. After all, Json is even more dear to them than XML.
Server Method:
The ImageInfo structure returned by the GetImageInfo () method will be converted by the Json serializer to something like the following line:
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:
and thumbnail:
Our base64 ThumbImage successfully converted to a picture of the appropriate size, and double-clicking on it will open a new browser window and display a full-fledged picture.
For example, such a trick. A line from the stylesheet. Icons are in the resource assembly:
Well, it seems so far so good. We already have two clients.
Let's try Silverlight?
Differences from the "thick" .NET client are the complete absence of synchronous mettos in 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 Request class has been developed that forms a query string and executes it. It encapsulates work with HttpWebRequest and HttpWebResponse:
Those. Having prepared the request parameters, we simply call the server method and process the result in the callback method. It can't be easier!
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 - helper classes RequestParams and Request are created. The RequestParams class is the name of the called method and its parameters, and Request encapsulates the logic for working with HttpURLConnection. For Json serialization, a library from Google is used - Gson, which can also be used for Android development. Everything that comes in Json format from an application server, implemented on an antagonistic platform with respect to Java, is "digested" without problems. The only thing that Java does not understand by default is the date format from MS. But Gson is an extensible library, and the problem is solved simply:
Streams from .NET are also compatible with Java (they are streams in Africa as well):
So, we already have 4 clients, and taking into account the fact that Android applications are written in Java, that's all 5.
I think it’s now clear that there are practically no restrictions on the type of clients, and all platforms that can work with Http and Json can Interact with our application server.
So what exactly is an “Application Server”?
I will try to formulate it 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 that fulfill requests for processing and receiving data and store this data.
- An application server is software based on special architecture and technologies that work on the server side. The specificity lies in serving many customers both in terms of their number and 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, the methods of which can be used to implement a particular client application.
The background of this project was this.
Developing somehow the next application based on the newfangled Microsoft Windows Communication Faundation technology (hereinafter simply referred to as WCF), in addition to the thick .NET client for Windows, there was a desire to create a web interface for this service. ExtJs was chosen for mastering. Googling a little material on the topic “How to connect an Ajax client to the WCF service”, the first drafts were made. The layout worked, but even then some discontent began to arise in the ways of achieving seemingly elementary things. Too many “unnecessary things” at first glance had to be done for this, which in practice were simply necessary.
A little more history.
There was work experience and several successfully implemented projects at WCF. In one smart book from O'Reilly, there was a recommendation not to make many methods in the service interface (no more than a dozen + -). Based on this and the real need to have many different methods for the service, it was a decision to make one main executable method plus several additional ones as needed. Because at that time it was only about .NET clients of the service, then this main method accepted the name of the called method and the parameters serialized by the BinaryFormatter, plus AssemblyQualifiedName, in case the methods were in external assemblies.
But such a 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 streams, despite the support for MTOM. I still could not get WCF to normally pass streams through the proxy server.
Another nuisance in the WCF + Ajax bundle is Json serialization. Well, it seemed so much easier, no, and here a lot of its nuances formed.
In addition, practice has always shown that clients who have implemented WCF-based solutions often have problems installing .NET 3.5 SP1 on their servers. Either the framework itself is not installed, then .svc is not registered, which is most often, then something else.
Little by little, quietly, WCF ceased to like.
And in the meantime, the thought was still haunting how the services of well-known social networks, web projects, various App Store work on the Internet. After all, not a single php. It’s clear that this is not Windows, but that’s not the point. The bottom line is that these services provide data to various types of customers. One service - many different clients, this is “cool” and “arch-important” in our time, but how to do this on .NET?
It was decided to try using .asmx WebService from .NET 2.0, in addition to .NET 3.5 there were extensions that allow them to interact with the Ajax world. I’ll say right away that something worked out, something didn’t, but still there was a feeling that all these extensions-additions for XML web services were “far-fetched” and not native to them.
I do not want to say that WCF and WebService technologies are not at all suitable for implementing Web 2.0, but I repeat - what seemed to be done intuitively simply caused such “misunderstandings” at times that I wanted to quit. The implementation of simple things seemed somehow complicated, and some things were completely impossible to do.
One could give 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 became elegant and simple.
So there must be something in .NET suitable for achieving your goals? Our salvation is System.Net and System.Web.
And so, select IHttpHandler.
Take a look at the HttpContext with its HttpRequest and HttpResponse. They almost completely implement the low-level details of the Http protocol from Webengine and put it on a silver platter. We can only draw a "blue border".
///
/// Enables processing of HTTP Web requests by a custom HttpHandler.
///
/// Контекст Http запроса.
void IHttpHandler.ProcessRequest(HttpContext httpContext)
We received a request from some client.
And then what shall we do with it? The answer is everything your heart desires. Yes Yes exactly.
Only once again we state what we want:
1. One application server - many types of clients (.NET, Java, Web + Ajax, Silverlight, iPhone, Android, etc.);
2. Call the required application method of the application server from any client;
3. Support for “GET” and “POST” requests, ie passing parameters via Url or request body;
4. Support for compressing parameters and results of methods sent back to clients;
5. Work with flows in both directions without any effort;
6. Speed, ease, asynchrony, transparency and clarity of the solution.
When implementing the first paragraph, I came to the conclusion that there are data types that exist in almost all languages and platforms, compatible with each other and suitable for transferring them across the border of the environment (also called to me - discovered America). I emphasize the word "compatible" because this is a fundamental point:
characters, strings (taking into account the encoding), simple data types (various kinds of numbers, Boolean values), byte arrays and streams. A date usually has a specific string representation. This also includes arrays, lists and dictionaries, as containers of the above data types.
The natural choice was Json, as an exchange format. Alternatively, the Json.NET library from Newtonsoft was chosen.
And so, the client must be able to call the server a specific application method (RPC) and get a clear answer from the server. The called method is given by a string with a name. The method needs parameters. He must correctly recognize and process them. For greater flexibility, methods can belong not only to the server itself, but also to any assembly that is available to the server, however, taking into account some security restrictions.
The REST style is popular, but not entirely flexible. This is a "template" style. Step left and right, and versatility is lost. An indispensable QueryString remains.
The server receives a request from the client like:
/service.ashx? method = GetImage (“DSCN2099.JPG”)
Bold key parameter names.
It is clear that the method belongs to the service itself, because there is no other indication of where to look for it; it takes the name of the requested image as a parameter. Where the picture is located is known only to you as the developer of the service. To choose: directory in the file system; Resource from any assembly DB runtime drawing; external resource, etc. This is applied logic.
Full query string format:
/service.ashx? session = xxxxxxxxxxxxxxxxx & - Session ID class = Full AssemblyTypeName & - “FullTypeName, AssemblyName” method = MethodName (Parameters) & - (case-sensitive method name) format = Json / DotNetBinary & –– data format (extensible) zip = on / off - whether the input parameters of the method are compressed
If any of the parameters is missing, then its default value is used. The only thing that must be indicated is the name of the called method.
What to return to the customer? Depends on the client application itself. If the picture is a thumbnail, then an array of bytes can be returned. If this is a large picture, it is better to return the stream.
And so, the application server receives the request, processes it (parses), creates the execution context of the specified method, and calls the method for execution. The method implements applied logic and finally returns return result; And where does this result go further? 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 HttpResponse output stream. Otherwise, the result is returned to Json to the serializer for conversion to a string, and then this string is written through HttpResponse-> Write ().
Thus, having access to the Http request context, we were free to choose the implementation of the application server infrastructure core.
The SOAP .asmx WebService infrastructure is implemented in approximately the same way. Only there is heavy but omnivorous XML, and here is light and fast Json. .aspx and WCF are also handlers.
So what about our customers, for whose sake it all started?
With .NET, everything is simple.
Here you have a choice of the native binary format and Json, and HttpWebResponse-> GetResponseStream () is the same stream that our application method “GetImage ()” of the application server returned, the only difference is the type of this stream, here it will be a network stream - one of the internal classes of the .NET Framework. Yes, it doesn’t matter to us. It is important that we can read it and make it for example Image.FromStream () or just save it to a file. If the application server returned an array of binary data (byte []), then this is essentially the same, because There is no other way than GetResponseStream (). Only we have to convert it back to this same array. What to do with this stream is decided by the developer of the client application, based on the application API that the application server provides him with.
The developer writes an assembly in which there can be both client and server methods, plus general data structures. Then this assembly is simply placed in the server-side project, and the name of this assembly is indicated in the client’s request. 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 in the implementation of clients based on Ajax frameworks. After all, Json is even more dear 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 Json serializer to 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('d.m.Y h:i'),
and thumbnail:
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 successfully converted to a picture of the appropriate size, and double-clicking on it will open a new browser window and display a full-fledged picture.
For example, such a trick. A line from the stylesheet. 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.
Let's try Silverlight?
Differences from the "thick" .NET client are the complete absence of synchronous mettos in 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 Request class has been developed that forms a query string and executes it. It encapsulates 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 onRequestCompleted);
Those. Having prepared the request parameters, we simply call the server method and process the result in the callback method. It can't be easier!
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 - helper classes RequestParams and Request are created. The RequestParams class is the name of the called method and its parameters, and Request encapsulates the logic for working with HttpURLConnection. For Json serialization, a library from Google is used - Gson, which can also be used for Android development. Everything that comes in Json format from an application server, implemented on an antagonistic platform with respect to Java, is "digested" without problems. The only thing that Java does not understand by default is the date format from MS. But Gson is an extensible library, and the problem is solved simply:
public class MSDateJsonSerializer implements JsonSerializer {
public JsonElement serialize(Date date, Type typeOfT, JsonSerializationContext context) {
return new JsonPrimitive("/Date(" + date.getTime() + ")/");
}
}
public class MSDateJsonDeserializer implements JsonDeserializer {
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 streams in Africa as well):
ImageIcon icon = new ImageIcon(“http://localhost/AppService/service.ashx? method=GetImage('DSCN2099.JPG')”);
So, we already have 4 clients, and taking into account the fact that Android applications are written in Java, that's all 5.
I think it’s now clear that there are practically no restrictions on the type of clients, and all platforms that can work with Http and Json can Interact with our application server.