ASMX Web Services Development Techniques

Published on March 27, 2015

ASMX Web Services Development Techniques

    In this article, I will talk about various techniques for developing SOAP web services using ASMX technology, as well as about this technology in general. In addition to SOAP, an implementation of AJAX will also be considered. The article will be useful both to those who are already familiar with it, and to those who are just about to create their first web service.



    Content



    ASMX and WCF Historical Background
    Introduction
    1. Simple construction
    2. Recommended Design
    3. Proxy class using wsdl.exe
    4. Server class for this wsdl
    5. ajax
    6. Request metadata
    7. File Access
    8. web.config
    9. Multiple asmx files
    10. Webpage Replacement
    11. Extension Replacement
    12. Hiding wsdl
    13. Exceptions
    14. soap: Header
    15. Caching
    16. Soapextension
    17. X64 Debugging in Visual Studio
    18. Deploy (publication)
    19. IIS Application Pools
    20. Development tools


    History reference


    From the very beginning, Microsoft was one of the main developers of the SOAP standard. In 2002, as part of the very first version of ASP.NET 1.0, she introduced ASMX (Active Server Method Extended) technology, which allowed developers in the newest Visual Studio 2002 to easily create and consume SOAP web services. I note that this technology officially on MSDN has the name "XML Web Services". In those years, SOAP was only taking the first serious steps in the world of web development. The W3C consortium approved SOAP 1.1 in 2000, SOAP 1.2 in 2003 (updated in 2007). Therefore, it was very important to make the technology easy to learn and apply for the new standard. And this goal was achieved - to work with web services, the developer did not even have to know XML, SOAP and WSDL.

    In subsequent years, ASMX technology has become very widespread and recognized. Also, from the very beginning Microsoft supplied the Web Services Enhancements (WSE) addon to it, which allowed implementing various WS- * security specifications such as WS-Security, WS-Policy, WS-ReliableMessaging. The latest version - WSE 3.0 was released in 2005. And in 2007, as part of .NET 3.0, Windows Communication Foundation (WCF) technology was introduced, which became the official replacement for ASMX. Despite the fact that ASMX technology has not been developed for a long time, it continues to be widely used and is supported by the latest versions of the .NET Framework.

    ASMX and WCF


    It is interesting to compare how many web services of both types Google sees: 314,000 ASMX and 6,280 WCF
    Why is ASMX technology still so popular? Everything is very simple: it is easy to use and perfectly solves the problem in most cases. The advantage of WCF is manifested, for example, in those cases when you need high speed transport, duplex, streaming, compliance with modern security standards, REST. By the way, if you need only REST, then instead of WCF it is worth using ASP.NET Web API technology.

    We list specifically the advantages of each technology:
    Pros of ASMX:
    • Ease of development
    • Easy to learn
    • There is no "hell" of configuration

    WCF Pros:
    • Very diverse and flexible means of transport
    • Actual and evolving technology
    • Various hosting options
    • Ability to implement a wide variety of WS- * standards

    So, WCF is a “Swiss knife” in the field of data transport, and ASMX is a “solid screwdriver”. And best of all, of course, be able to use both tools. Since WCF development techniques on the Internet are described more fully and relevantly, I decided that I need to write an article about ASMX, which is useful to those who have to support old web services, and those who continue to use this technology to create new ones.



    Introduction


    The article describes 20 different practical methods that can be applied when developing web services using this technology. The scenario for the examples will be as follows. There is a regularly updated database of financial statements. It is necessary to develop a universal mechanism by which various customers will always have relevant data on these reports. Solution: write a SOAP web service with two methods:

    • The first method takes a period in time and returns the identifiers of all reports that appeared in this period
    • The second method takes a report identifier and returns the report data itself

    Consumers of the web service regularly send requests to the first method, indicating the period from the moment of their last request, and if there are identifiers in the response, they request data through the second method.

    The examples are shown based on the code from the Recommended Design, and to test them, just call the GetReportInfo web method as shown in the Proxy Class example.

    1. The simplest design


    Let's start with a description of the simplest web service design. Attention, the example is purely theoretical! Although he is a worker, never do this in practice. This is just a demonstration of the simplicity of ASMX technology itself.

    In Visual Studio, create a new project called “ASP.NET Empty Web Application” or “ASP.NET Web Service Application” called FinReportWebService . Add two files to it: FinReport.asmx and FinReportService.cs , and add FinReport.asmx as a Text File, not a Web Service, so that it is a single file.

    FinReport.asmx
    <% @  Class = "FinReportWebService.FinReportService"  %>

    FinReportService.cs
    using  System;
    using  System.Web.Services;
     
    namespace  FinReportWebService {
     
        public  class  FinReportService  {
            [ WebMethod ]
            public  int [] GetReportIdArray ( DateTime  dateBegin,  DateTime  dateEnd) {
                int [] array =  new  int [] {357, 358, 360, 361};
                return  array;
            }
     
            [ WebMethod ]
            public  FinReport  GetReport ( int  reportID) {
                FinReport  finReport =  new FinReport () {
                    ReportID = reportID, 
                    Date =  new  DateTime (2015, 03, 15), 
                    Info =  "Some info"
                };
     
                return  finReport;
            }
        }
     
        public  class  FinReport  {
            public  int  ReportID {  get ; set ; }
            public  DateTime  Date {  get ; set ; }
            public  string  Info {  get ; set ; }
        }
    }

    Press F5 to start the web server and open FinReport.asmx in the browser , you should see



    Done. Now we will analyze in order. A web service is represented by one ordinary class with one mandatory feature - some of its methods are marked with a special attribute [WebMethod] . Such class methods become web service web methods with an appropriate call signature. This class must have a default constructor. With each new request, IIS instantiates it with the default constructor and calls the appropriate method.

    The second required part of the minimal construction is a file with the extension asmx, inside which you must specify this class.

    It is interesting to compare this manually created asmx file with the one that Visual Studio will create. Suppose we want to make another web service that returns a currency exchange rate. Add the ExchangeRate.asmx file of the Web Service type through the Add New Item menu . By pressing F7 once or twice, you can see the following:




    <% @  WebService  Language = "C #"  CodeBehind = "ExchangeRate.asmx.cs" Class = "FinReportWebService.ExchangeRate"    %>

    The Language = "C #" operator is rudimentary, and is only needed if you will be writing the source code directly inside the asmx file. Such code will compile dynamically. But I believe that in general, dynamic compilation of a web service is not a good practice, and in particular, I do not recommend using a special App_Code folder. And the CodeBehind = "ExchangeRate.asmx.cs" statement simply links the two files at the Visual Studio level.

    2. Recommended design


    In this example, the same web service is implemented in a more correct way. Although this is more correct code, it also serves as a demonstration only. For example, such important standard things as authorization, exception handling, and logging are missing here. Also, this example will be the basis on which other techniques of this article will be demonstrated. In the FinReportService.cs file, replace the contents with the following source code:

    FinReportService.cs
    using  System;
    using  System.Web.Services;
    using  System.Xml.Serialization;
     
    namespace  FinReportWebService {
     
        [ WebServiceBinding (ConformsTo =  WsiProfiles .BasicProfile1_1)]
        [ WebService (Description =  " Fin .  Reports " , Namespace = XmlNS)]
        public  class  FinReportService  : WebService {
            public  const  string  XmlNS = "http :  //asmx.habrahabr  . / " ;
     
            [ WebMethod (Description = "Getting a list of report IDs by period" )]
            public  GetReportIdArrayResult  GetReportIdArray ( GetReportIdArrayArg  arg) {
                return  new  GetReportIdArrayResult () {
                    ReportIdArray =  new  int [] {357, 358, 360, 361}
                };
            }
     
            [ WebMethod (Description =  " Retrieving the report by  ID" )] public GetReportResult  GetReport ( GetReportArg  arg) { return new GetReportResult () {  
             
                  
                    Report =  new  FinReport {
                        ReportID = arg.ReportID,
                        Date =  new  DateTime (2015, 03, 15),
                        Info = getReportInfo (arg.ReportID)
                    }
                };
            }
     
            private  string  getReportInfo ( int  reportID) {
                return  "ReportID ="  + reportID;
            }
        }
     
     
    // [Serializable]
    // [XmlType (Namespace = FinReportService.XmlNS)]
        public  class  FinReport  {
            public  int  ReportID { get ; set ; }
            public  DateTime  Date {  get ; set ; }
            public  string  Info {  get ; set ; }
        }
     
        public  class  GetReportIdArrayArg  {
            public  DateTime  DateBegin {  get ; set ; }
            public  DateTime  DateEnd {  get ; set ; }
        }
     
        public  class  GetReportIdArrayResult  {
            public  int [] ReportIdArray {  get; set ; }
        }
     
        public  class  GetReportArg  {
            public  int  ReportID {  get ; set ; }
        }
     
        public  class  GetReportResult  {
            public  FinReport  Report {  get ; set ; }
        }
    }

    Let's analyze the changes.
    The attribute [WebServiceBinding (ConformsTo = WsiProfiles.BasicProfile1_1)] means that the web service is checked for compliance with the WSI Basic Profile 1.1 specification. For example, it forbids overloading the name of an operation, or using the [SoapRpcMethod] attribute. Such violations will lead to a web service error "The service" FinReportWebService.FinReportService "does not meet the Simple SOAP Binding Profile Version 1.0 specification." If this attribute is absent, violations will only lead to the warning “This web service does not meet the requirements of WS-I Basic Profile v1.1.”. In general, it is recommended to add this attribute, which provides greater interoperability.

    Attribute [WebService (Description = “Fin. Reports”, Namespace = XmlNS)]it has only three properties:
    Namespace - default CML namespace - must be specified
    Description - description of the web service displayed in the browser
    Name - name of the web service (the class name is taken by default)

    Inheritance from the WebService class gives access to HttpContext, HttpSessionState and some others that in some cases may be useful.

    In the attribute [WebMethod (Description = "Obtaining a report by ID")], as a rule, only Description is specified, which describes the web method in the browser, other properties are rarely used.

    Incoming parameters and return values ​​I personally recommend encapsulating in special classes. For example, I name them, adding the suffixes -Arg and -Result to the name of the method, which means the argument and the result. In this example, for simplicity, they are all in the same file FinReportService.cs, but in real projects, I place each of them in a separate file in a special folder like FinReportServiceTypes. It is also convenient to inherit them from common classes.

    In theory, the attributes [Serializable] and [XmlType (Namespace = FinReportService.XmlNS)] must be specified for all native classes in web methods. However, in this case it is not necessary. Indeed, if only XML serialization is performed, then the [Serializable] attribute is not needed, and XML namespace is taken from the [WebService] attribute by default. I note that unlike WCF, ASMX uses the usual XmlSerializer, which allows you to widely control serialization using standard attributes such as [XmlType], [XmlElement], [XmlIgnore], etc.

    3. Proxy class using wsdl.exe


    The wsdl.exe utility is the appropriate technique for consuming SOAP web services for asmx. Using a wsdl file or link, it generates a proxy class - a special class that simplifies access to this web service as much as possible. Of course, it doesn’t matter what technology the web service is implemented on, it can be anything - ASMX, WCF, JAX-WS or NuSOAP. By the way, WCF has a similar utility called SvcUtil.exe.

    The utility is located in the C: \ Program Files (x86) \ Microsoft SDKs \ Windows folder, moreover, it is presented there in different versions, depending on the .net version, bit depth, windows version and visual studio.



    Examples of using
    wsdl http://192.168.1.101:8080/SomeDir/SomeService?wsdl
    wsdl HabraService.wsdl


    Let's make a client for FinReportWebService. In the current or new solution, create a new Windows Forms project FinReportWebServiceClient . Add the ProxyClass folder in it, copy the wsdl.exe utility into it and create the GenProxyClass.bat batch file in it:
    wsdl /n:FinReportWebServiceClient.ProxyClass http: // localhost: 3500 / FinReport.asmx? wsdl
    pause

    Using the argument /n:FinReportWebServiceClient.ProxyClass we specify the namespace for the class. Running it, you should get the file FinReportService.cs. Through Solution Explorer - Show All Files, include all three files in solution.



    Add a button on the form, and the following three methods are in the source code of the form:
    public  static  FinReportService  GetFinReportService () {
        var  service =  new  FinReportService ();
        service.Url =  "http: // localhost: 3500 / FinReport.asmx" ;
        service.Timeout = 100 * 1000;
        return  service;
    }
     
     
    private  void  webMethodTest_GetReportIdArray () {
        var  service = GetFinReportService ();
        var  arg =  new  GetReportIdArrayArg ();
        arg.DateBegin =  new  DateTime (2015, 03, 01);
        arg.DateEnd =  new  DateTime (2015, 03, 02);
     
        var  result = service.GetReportIdArray (arg);
        MessageBox .Show ( "result.ReportIdArray.Length ="  + result.ReportIdArray.Length);
    }
     
     
    private  void  webMethodTest_GetReport () {
        var  service = GetFinReportService ();
        var  arg =  new  GetReportArg ();
        arg.ReportID = 45;
     
        var  result = service.GetReport (arg);
        MessageBox .Show (result.Report.Info);
    }

    The most important properties of the proxy class are Url and Timeout, and the timeout is indicated in milliseconds and 100 seconds is its default value. Now using them you can test the operation of the web service. A demonstration of the work of further techniques will be shown by calling the GetReport method and filling in the result.Report.Info field.

    If you create a proxy class using a wsdl file that refers to external xsd schemes, all these schemes must be listed in the command:
    wsdl / n: MyNamespace HabraService.wsdl Data.xsd Common.xsd Schema.xsd

    However, in addition to manually creating a proxy class, Visual Studio allows you to create it automatically. The “Add Service Reference” item allows you to create a proxy class using WCF technology, and there is also an “Add Web Reference” button in Advanced that creates it using ASMX technology.

    4. Server class for this wsdl


    As you know, wsdl description of a web service in ASMX technology is automatically generated. However, sometimes the inverse problem arises: to develop a corresponding web service for a given wsdl file. It is solved using the same wsdl.exe utility. It can create the necessary skeleton from classes and you just have to implement the program logic of web methods.

    For example, take the wsdl of our web service. Save it from the browser as a file FinReport.wsdl or copy from here:
    FinReport.wsdl
    <?xml version="1.0" encoding="utf-8"?>
    <wsdl:definitions xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://asmx.habrahabr.ru/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://asmx.habrahabr.ru/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
      <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Фин. отчеты</wsdl:documentation>
      <wsdl:types>
        <s:schema elementFormDefault="qualified" targetNamespace="http://asmx.habrahabr.ru/">
          <s:element name="GetReportIdArray">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="arg" type="tns:GetReportIdArrayArg" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="GetReportIdArrayArg">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="DateBegin" type="s:dateTime" />
              <s:element minOccurs="1" maxOccurs="1" name="DateEnd" type="s:dateTime" />
            </s:sequence>
          </s:complexType>
          <s:element name="GetReportIdArrayResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="GetReportIdArrayResult" type="tns:GetReportIdArrayResult" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="GetReportIdArrayResult">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="ReportIdArray" type="tns:ArrayOfInt" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="ArrayOfInt">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="unbounded" name="int" type="s:int" />
            </s:sequence>
          </s:complexType>
          <s:element name="GetReport">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="arg" type="tns:GetReportArg" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="GetReportArg">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="ReportID" type="s:int" />
            </s:sequence>
          </s:complexType>
          <s:element name="GetReportResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="GetReportResult" type="tns:GetReportResult" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:complexType name="GetReportResult">
            <s:sequence>
              <s:element minOccurs="0" maxOccurs="1" name="Report" type="tns:FinReport" />
            </s:sequence>
          </s:complexType>
          <s:complexType name="FinReport">
            <s:sequence>
              <s:element minOccurs="1" maxOccurs="1" name="ReportID" type="s:int" />
              <s:element minOccurs="1" maxOccurs="1" name="Date" type="s:dateTime" />
              <s:element minOccurs="0" maxOccurs="1" name="Info" type="s:string" />
            </s:sequence>
          </s:complexType>
        </s:schema>
      </wsdl:types>
      <wsdl:message name="GetReportIdArraySoapIn">
        <wsdl:part name="parameters" element="tns:GetReportIdArray" />
      </wsdl:message>
      <wsdl:message name="GetReportIdArraySoapOut">
        <wsdl:part name="parameters" element="tns:GetReportIdArrayResponse" />
      </wsdl:message>
      <wsdl:message name="GetReportSoapIn">
        <wsdl:part name="parameters" element="tns:GetReport" />
      </wsdl:message>
      <wsdl:message name="GetReportSoapOut">
        <wsdl:part name="parameters" element="tns:GetReportResponse" />
      </wsdl:message>
      <wsdl:portType name="FinReportServiceSoap">
        <wsdl:operation name="GetReportIdArray">
          <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Получение списка ID отчетов по периоду</wsdl:documentation>
          <wsdl:input message="tns:GetReportIdArraySoapIn" />
          <wsdl:output message="tns:GetReportIdArraySoapOut" />
        </wsdl:operation>
        <wsdl:operation name="GetReport">
          <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Получение отчета по ID</wsdl:documentation>
          <wsdl:input message="tns:GetReportSoapIn" />
          <wsdl:output message="tns:GetReportSoapOut" />
        </wsdl:operation>
      </wsdl:portType>
      <wsdl:binding name="FinReportServiceSoap" type="tns:FinReportServiceSoap">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="GetReportIdArray">
          <soap:operation soapAction="http://asmx.habrahabr.ru/GetReportIdArray" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="GetReport">
          <soap:operation soapAction="http://asmx.habrahabr.ru/GetReport" style="document" />
          <wsdl:input>
            <soap:body use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
      </wsdl:binding>
      <wsdl:binding name="FinReportServiceSoap12" type="tns:FinReportServiceSoap">
        <soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name="GetReportIdArray">
          <soap12:operation soapAction="http://asmx.habrahabr.ru/GetReportIdArray" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="GetReport">
          <soap12:operation soapAction="http://asmx.habrahabr.ru/GetReport" style="document" />
          <wsdl:input>
            <soap12:body use="literal" />
          </wsdl:input>
          <wsdl:output>
            <soap12:body use="literal" />
          </wsdl:output>
        </wsdl:operation>
      </wsdl:binding>
      <wsdl:service name="FinReportService">
        <wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">Фин. отчеты</wsdl:documentation>
        <wsdl:port name="FinReportServiceSoap" binding="tns:FinReportServiceSoap">
          <soap:address location="http://localhost:3500/FinReport.asmx" />
        </wsdl:port>
        <wsdl:port name="FinReportServiceSoap12" binding="tns:FinReportServiceSoap12">
          <soap12:address location="http://localhost:3500/FinReport.asmx" />
        </wsdl:port>
      </wsdl:service>
    </wsdl:definitions>

    Create a new empty web project in solution with the name FinReportWebServiceByWsdl . Add the ServerClass folder to it, into which copy the files FinReport.wsdl and wsdl.exe. Create a GenServerClass.bat batch file in it:
    wsdl / server /n:FinReportWebServiceByWsdl.ServerClass FinReport.wsdl
    pause

    Running it, you should get the file FinReportService.cs. Include all four files in solution.



    So, as you can see, the only difference from generating a proxy class is the server attribute. This creates an abstract class inherited from WebService with abstractly described web methods. You can inherit from it, but you still have to copy all the attributes, so I propose to do it as follows. Copy the class definition to a new file and namespace, remove the word abstract, and write an implementation of the methods. After formatting the code, I got the following file
    using  System;
    using  System.Web.Services;
    using  System.Web.Services.Description;
    using  System.Web.Services.Protocols;
    using  FinReportWebServiceByWsdl.ServerClass;
     
    namespace  FinReportWebServiceByWsdl {
     
            [ WebService (Namespace = "http://asmx.habrahabr.ru/" )]
            [ WebServiceBinding (Name = "FinReportServiceSoap" , Namespace = "http://asmx.habrahabr.ru/" )]
            public  class  FinReportService  :  WebService  {
                
                [ WebMethod ]
                [ SoapDocumentMethod ( "http://asmx.habrahabr.ru/GetReportIdArray" , 
                    RequestNamespace =  "http://asmx.habrahabr.ru/" ,
                    ResponseNamespace =  "http://asmx.habrahabr.ru/" , 
                    Use =  SoapBindingUse .Literal, 
                    ParameterStyle =  SoapParameterStyle .Wrapped)]
     
                public  GetReportIdArrayResult  GetReportIdArray ( GetReportIdArrayArg  arg) {
                    return  new  GetReportIdArrayResult ();
                }
                
     
                [ WebMethod ]
                [ SoapDocumentMethod ( "http://asmx.habrahabr.ru/GetReport" , 
                    RequestNamespace =  "http://asmx.habrahabr.ru/" , 
                    ResponseNamespace =  "http://asmx.habrahabr.ru/" , 
                    Use =  SoapBindingUse .Literal, 
                    ParameterStyle =  SoapParameterStyle .Wrapped)]
                
                public  GetReportResult  GetReport ( GetReportArg  arg) {
                    return  new  GetReportResult () {
                        Report =  new  FinReport  {
                            ReportID = arg.ReportID,
                            Date =  new  DateTime (2015, 03, 15),
                            Info =  "ByWSDL"
                        }
                    };
                }
            }
    }

    In this code, the utility explicitly described using the attributes those parameters of the web service that were implicitly determined by default. It remains only to add the file FinReportByWsdl.asmx , which will point to this new class:
    <% @  Class = "FinReportWebServiceByWsdl.FinReportService"  %>


    5. ajax


    ASMX web service can receive and return data in JSON format, which allows implementing ajax technique. For the example to work, your web project must have the following three files:

    FinReport.asmx - the same as in the first examples, only 1 line
    <% @  Class = "FinReportWebService.FinReportService"  %>

    FinReportService.cs - the code changes to the next
    using  System;
    using  System.Text;
    using  System.Web.Script.Serialization;
    using  System.Web.Script.Services;
    using  System.Web.Services;
    using  Newtonsoft.Json;
     
    namespace  FinReportWebService {
        [ ScriptService ]
        [ WebServiceBinding (ConformsTo =  WsiProfiles .BasicProfile1_1)]
        [ WebService (Description =  " Fin .  Reports " , Namespace =  "http://asmx.habrahabr.ru/" )]
        public  class  FinReportService/ :  WebService {
     
            [ ScriptMethod (ResponseFormat =  ResponseFormat .Json)]
            [ WebMethod ]
            public  GetReportResult  Method_1_POST_Objects ( GetReportArg  arg) {
                return  getFinReportResult (arg.ReportID,  "Method_1_POST_Objects" );
            }
     
     
            [ ScriptMethod (ResponseFormat =  ResponseFormat .Json, UseHttpGet =  true )]
            [ WebMethod ]
            public  string  Method_2_GET ( int  id) {
                var result = getFinReportResult (id,  "Method_2_GET" );
                string  text =   JsonConvert .SerializeObject (result);
                return  text;
            }
     
     
            [ ScriptMethod (ResponseFormat =  ResponseFormat .Json)]
            [ WebMethod ]
            public  string  Method_3_POST ( int  id) {
                var  result = getFinReportResult (id,  "Method_3_POST" );
                JavaScriptSerializer  js =  new  JavaScriptSerializer ();
                return  js.Serialize (result);
            }
     
     
            [ScriptMethod (ResponseFormat =  ResponseFormat .Json)]
            [ WebMethod ]
            public  string  Method_4_POST_ComplexArg ( string  json) {
                var  arg =  JsonConvert .DeserializeObject < GetReportArg > (json);
                var  result = getFinReportResult (arg.ReportID, arg.Token +  "  Received ." );
                return  JsonConvert .SerializeObject (result);
            }
     
     
            [ ScriptMethod (ResponseFormat =  ResponseFormat .Json)]
            [ WebMethod ]
            public  DateTime  Method_5_TransformDate ( DateTime  dateTime) {
                return  dateTime.AddYears (-3) .AddDays (-5) .AddHours (-2) .AddMinutes (6);
            }
     
     
            [ ScriptMethod (ResponseFormat =  ResponseFormat .Json)]
            [ WebMethod ]
            public  void  Method_6_POST_NonStandard ( int  id) {
                var  result = getFinReportResult (id,  "Method_6_POST_NonStandard,  My text " ); string  text =  JsonConvert .SerializeObject (result); byte [] data =  
                
                Encoding .UTF8.GetBytes (text);
     
                Context.Response.Clear ();
                Context.Response.ContentType =  "application / json; charset = utf-8" ;
                Context.Response.AddHeader ( "content-length" , data.Length.ToString ());
                Context.Response.BinaryWrite (data);
                Context.Response.Flush ();
            }
     
     
            private  GetReportResult  getFinReportResult ( int  id,  string  info) {
                return  new  GetReportResult () {
                    Report =  new  FinReport () {
                        ReportID = id,
                        Info = info,
                        Date =  new  DateTime (2015, 03, 15),
                    }
                };
            }
        }
     
     
        public  class  FinReport  {
            public  DateTime  Date {  get ; set ; }
            public  string  Info {  get ; set ; }
            public  int  ReportID {  get ; set ; }
        }
     
        public  class  GetReportArg  {
            public  int  ReportID { get ; set ; }
            public  string  Token {  get ; set ; }
        }
     
        public  class  GetReportResult  {
            public  FinReport  Report {  get ; set ; }
        }
    }

    Page.htm - the actual web page
    <! doctype  html >
    < html >
    < head >
     
    < meta  charset = utf-8>
    < script  src = "// ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></ script >
                    
    < script >
        $ (document) .ready ( function  () {
            $ ( "# btn1" ) .click ( function  () {
                var  arg = {arg: {ReportID: 1}};
     
                $ .ajax ({
                    type:  " POST " ,
                    contentType: "application / json; charset = utf-8" ,
                    url:  "/FinReport.asmx/Method_1_POST_Objects" ,
                    data: JSON.stringify (arg),
                    dataType:  "json" ,
                    success:  function  (data, status) {
                        $ ( " # div1 " ) .html (data.d.Report.Info);
                    },
                    error:  function  (request, status, error) {alert ( "Error" ); }
                });
            });
     
            $ ( "# btn2" ) .click ( function  () {
                $.
                    type:  "GET" ,
                    contentType:  "application / json; charset = utf-8" ,
                    url:  "/FinReport.asmx/Method_2_GET" ,
                    data: {id: 2},
                    dataType:  "json" ,
                    success:  function  (data , status) {
                        $ ( "# div2" ) .html (JSON.parse (data.d) .Report.Info);
                    },
                    error:  function  (request, status, error) {alert ( "Error" ); }
                });
            });
     
    ) .click ( function  () {
                $ .ajax ({
                    type:  "POST" ,
                    contentType:  "application / json; charset = utf-8" ,
                    url:  "/FinReport.asmx/Method_3_POST" ,
                    data:  '{"id ": 3} ' ,
                    dataType:  " json " ,
                    success:  function  (data, status) {
                        $ ( " # div3 " ) .html (JSON.parse (data.d) .Report.Info);
                    },
                    error:  function  (request, status,error) {alert ( "Error"); }
                });
            });
     
     
            $ ( "# btn4" ) .click ( function  () {
                var  arg = {ReportID: 4, Token:  " Method  4. Token "  }; var  argObj = {json: JSON.stringify (arg)};             $ .ajax ( {                 type:  "POST" ,                 contentType:  "application / json; charset = utf-8" ,                 url:  "/FinReport.asmx/Method_4_POST_ComplexArg" ,                 data: JSON.stringify (argObj),                 dataType:  "json" , 
                
     






                    success:  function  (data, status) {
                        $ ( "# div4" ) .html (JSON.parse (data.d) .Report.Info);
                    },
                    error:  function  (request, status, error) {alert ( "Error" ); }
                });
            });
     
            $ ( "# btn5" ) .click ( function  () {
                var  now =  new  Date ();
                var  arg = {dateTime: now};
     
                $ .ajax ({
                    type:  "POST" ,
                    contentType:  "application / json; charset = utf-8 ",
                    url:  "/FinReport.asmx/Method_5_TransformDate" ,
                    data: JSON.stringify (arg),
                    dataType:  "json" ,
                    success:  function  (data, status) {
                        var  date =  new  Date (parseInt (data.d.replace ( "/ Date (" ,  "" ) .replace ( ") /" ,  "" ), 10));
                        $ ( "# div5" ) .html (date.toString ());
                    },
                    error:  function  (request, status, error) {alert ( "Error" ); }
                });
            });
     
            $ ( "# btn6" ) .click ( function  () {
                $ .ajax ({
                    type:  "POST" ,
                    contentType:  "application / json; charset = utf-8" ,
                    url:  "/FinReport.asmx/Method_6_POST_NonStandard" ,
                    data:  '{"id": 6}' ,
                    dataType:  "json" ,
                    success:  function  (data, status) {
                        $ ( "# div6" ) .html (data.Report.Info);

                    error:  function  (request, status, error) {alert ( "Error" ); }
                });
            });
     
     
        });
    </ script >
                    
    </ head >
    < body >
        
        < div  id = "div1"> Div 1 </ div >
        < div  id = "div2"> Div 2 </ div >
        < div  id = "div3"> Div 3 < / div >
        < div  id = "div4">>
        < Div  id = "div5"> Div 5 </ div >
        < div  id = "div6"> Div 6 </ div >
        < br />
        < button  id = "btn1"> Method 1 </ button >
        < br / >
        < button  id = "btn2"> Method 2 </ button >
        < br />
        < button  id = "btn3"> Method 3 </ button >
        < br/>
        < Button  id = "btn4"> Method 4 </ button >
        < br />
        < button  id = "btn5"> Method 5 </ button >
        < br />
        < button  id = "btn6"> Method 6 </ button >
     
    </ body >
    </ html >

    The example also uses the Json.NET library aka Newtonsoft.Json.

    In order for the web service to work with JSON, you need to apply 2 new attributes:
    [ScriptService] - without properties, and [ScriptMethod], in which the ResponseFormat property is responsible for the response format - JSON or XML, and UseHttpGet - for the request type - GET or POST .

    This web service has 6 methods that demonstrate different ways to implement ajax.

    Method 1 . As in example 2, it receives and returns objects of the GetReportArg and GetReportResult classes. In a formatted form, the request and response are as follows:
    {
       " arg ":  {
          "ReportID":  1
       }
    }

    {
       " d ":  {
          "__type":  "FinReportWebService.GetReportResult" ,
          "Report": {
             "ReportID":  1 ,
             "Date":  "/ Date (1426356000000) /" ,
             "Info":  "Method_1_POST_Objects" }    } }}
          


    If everything is clear with the argument, then you need to comment on the answer. For security reasons, the web service wraps all JSON responses in the “d” node. We can also see the class name "__type": "FinReportWebService.GetReportResult". But the date format "/ Date (1426356000000) /" is a problem. What to do with this format is described in method 5, in addition, a similar format is easy to avoid, as shown below.

    Method 2 . A fundamentally different way of implementation. It uses the GET request type, takes a number, returns a json string, not the object itself, and uses a third-party serializer. In the script, the argument is set as data: {id: 2}, which complements the request URL to the form http: // localhost: 3500 / FinReport.asmx / Method_2_GET? Id = 2, but you can immediately specify this final URL.

    The method returns a response of the form
    {
       " d":  "{\" Report \ ": {\" ReportID \ ": 2, \" Date \ ": \" 2015-03-15T00: 00: 00 \ ", \" Info \ ": \" Method_2_GET \ "}}"
    }
    As you can see, you need to parse JSON.parse (data.d) to get the original object of the form:
    {
       " Report": {
          "ReportID":  2 ,
          "Date":  "2015-03-15T00: 00: 00" ,
          "Info":  "Method_2_GET" } }
       

    Please note that the date due to the Json.NET library is presented here as standard. Also note the mandatory contentType header: “application / json; charset = utf-8 ", despite the fact that it is GET. This is done to protect against CSRF. For this reason, an attempt to open the request URL in a browser will throw an exception. In general, using GET is not recommended.

    Method 3. Similar to the previous method, but using the POST request type and, for a change, the native serializer. The request looks like this:
    {
       " id ":  3
    }
    The answer differs in an inconvenient date format:
    {
       " d":  "{\" Report \ ": {\" ReportID \ ": 3, \" Date \ ": \" \\ / Date (1426356000000) \\ / \ ", \" Info \ ": \ "Method_3_POST \"}} "
    }

    Method 4. A complicated version of the previous one, passing a complex argument var arg = {ReportID: 4, Token:  " Method  4 token  }; in serialized form:
    {
       "json":  "{\" ReportID \ ": 4, \" Token \ ": \" Method  4 token \ "}" } 

    The answer is similar.

    Method 5. Now we will describe the work with the date when using the native serializer using the example of a method that accepts and returns a date. In method 1, the web service returned the date as "Date": "/ Date (1426356000000) /". The number in parentheses is the number of milliseconds that have elapsed since midnight January 1, 1970 UTC (UNIX epoch). At the same time, we recall that the Date type has the corresponding constructor new Date (milliseconds), that is, it is enough to select this number and use it in the date constructor:
    var  date =  new  Date (parseInt (data.d.replace ( "/ Date (" ,  "" ) .replace ( ") /" ,  "" ), 10));

    At the same time, the web service itself correctly understands the normal date format in the argument:
    {
       " dateTime":  "2015-03-25T05: 49: 13.604Z"
    }

    Method 6. This is a non-standard way of generating an answer, and in some cases it may be the only way to get the desired result. As you can see, the code itself sets the response headers and determines the content at the binary level.
    {
       " Report": {
          "Date":  "2015-03-15T00: 00: 00" ,
          "Info":  "Method_6_GET_NonStandard,  My text " , "ReportID":  6    } } 
          


    Note that there is no root node “d”. You can also use GET. Moreover, in this way, you can return something other than “application / json;”.

    maxJsonLength

    To return heavy responses, you must increase the value of maxJsonLength in web.config:
    <? xml version = " 1.0 " ?> < configuration >     < system.web >         < compilation debug = " true " targetFramework = " 4.0 "  />     </ system.web >   < system.web.extensions >     < scripting >       < webServices >         < jsonSerialization maxJsonLength = " 

      

      

     



     
          </ webServices >
        </ scripting >
      </ system.web.extensions >
     
    </ configuration >


    6. Request metadata


    In addition to the request data itself, it often makes sense to log its metadata, such as the outgoing IP address and the requested URL. This allows you to determine who, when and through which network made requests. In addition to these two main parameters, you can save any headings. And even try to get a DNS name, although this is not always possible and takes time. Change the code of the getReportInfo () method to the following
    private  string  getReportInfo () {
        var  request =  this .Context.Request;
    // var request = HttpContext.Current.Request;
     
        StringBuilder  sb =  new  StringBuilder ();
        sb.Append ( "IP =" ) .AppendLine (request.UserHostAddress);
        sb.Append ( "URL =" ) .AppendLine (request.Url.OriginalString);
        sb.Append ( "Header 'Connection' =" ) .AppendLine (request.Headers [ "Connection" ]);
     
        DateTime  dnsDate =  DateTime .Now;
        TimeSpan  dnsSpan;
     
        try  {
    // throw new Exception (" Comment me out ."); var  entry = Dns.GetHostEntry (request.UserHostAddress);         dnsSpan =  DateTime .Now.Subtract (dnsDate);         sb.Append ( "HostName =" ) .AppendLine (entry.HostName);     }  catch  ( Exception  ex) {         dnsSpan =  DateTime .Now.Subtract (dnsDate);         sb.AppendLine (ex.Message);     }     sb.Append ( "dnsSpan =" ) .AppendLine (dnsSpan.ToString ()); return  sb.ToString (); } 
            


     




     

        



    7. Access to files


    Sometimes there is a need to access files along a path that is relative to a given web service location. For example, change the getReportInfo method to the following code:
    private  string  getReportInfo ( int  reportID) {
        string  dirRoot =  HttpContext .Current.Server.MapPath ( "~" );
     
        StringBuilder  sb =  new  StringBuilder ();
        sb.AppendLine ( "dirRoot ="  + dirRoot);
      
        string  fileCars =  HttpContext .Current.Server.MapPath ( "~ / bin / MyFiles / Cars.txt" );
        sb.AppendLine ( "fileCars ="  + fileCars);
     
        try  {
            sb.AppendLine ( "Line 1 ="  +  File .ReadAllLines (fileCars) [0]);
            File.AppendAllText (fileCars,  Environment .NewLine +  DateTime .Now +  "ReportID ="  + reportID);
     
        }  catch  ( Exception  ex) {
            sb.AppendLine (ex.Message);
        }
     
        string  dirFiles =  Path .Combine (( new  DirectoryInfo (dirRoot)). Parent.FullName,  "FinReportWebService_Files" );
        sb.AppendLine ( "dirFiles ="  + dirFiles);
     
        try {
            Directory .CreateDirectory (dirFiles);
            string  newFile =  Path .Combine (dirFiles,  Guid.NewGuid () +  ".txt" );
            File .WriteAllText (newFile,  "ReportID ="  + reportID);
            sb.AppendLine (newFile);
     
        }  catch  ( Exception  ex) {
            sb.AppendLine (ex.Message);
        }
     
     
        return  sb.ToString ();
    }
    In the project, create the MyFiles folder and in it the text file Cars.txt with the Build Action parameters: None / Copy always and any first line:



    Thus, when compiling, the MyFiles folder will be automatically created in the bin folder with the specified file, which we will refer to . This code also demonstrates access to the parent folder FinReportWebService_Files, which is created automatically.

    So, the getReportInfo method returns text that contains the following information:

    Location of the web service itself.
    Path to the attached Cars.txt file.
    First line of this file.
    Text of a possible error of its reading or modification.
    Path to the higher folder FinReportWebService_Files
    Path to the file successfully created in it
    The text of a possible error creating a folder or file

    After publishing a web service in IIS, where its account is allowed to read files only, we will receive the following text with errors:
    dirRoot = C: \ CommonFolder \ publish_FinReport
    fileCars = C: \ CommonFolder \ publish_FinReport \ bin \ MyFiles \ Cars.txt
    Line 1 = Audi
    Access to the path 'C: \ CommonFolder \ publish_FinReport \ bin \ MyFiles \ Cars.txt' is denied .
    dirFiles = C: \ CommonFolder \ FinReportWebService_Files
    Access to the path 'C: \ CommonFolder \ FinReportWebService_Files' is denied.
    In this case, you must give the account permission to create files. How to do this is described in Reception 19. IIS Application Pools.

    8. web.config


    The web.config file is the main mechanism for configuring any ASP.NET application. Using it, you can make a large number of general ASP.NET settings, as well as some asmx-specific web services. In this example, only a general technique for working with your custom settings will be considered. Everything described is applicable for any type of ASP.NET application.

    web.config

    Change the contents of web.config to the following:
    <? xml version = " 1.0 " ?> < configuration >   < appSettings >     < add key = " ReportType " value = " 8 " />     < add key = " ReportSubject " value = " Medium business " />   </ appSettings >   < system. web >     < 

     

      
       

     

     " true " targetFramework = " 4.0 " /> </ system.web > </ configuration > 
      
     

    Here we see two custom settings and the standard form of compilation.

    It is recommended to access the values ​​of custom settings through a special class that will return their typed values ​​and implement other low-level logic. Here is an example of such a class, add it to the project:
    using  System;
    using  System.Collections.Generic;
    using  System.Configuration;
     
    namespace  FinReportWebService {
        internal  static  class  WebConfig  {
            
            public  static  int  ReportType {  get  {  return  getStructureValue < int > ( "ReportType" ); }}
            public  static  string  ReportSubject {  get  {  return  getTextValue ( "ReportSubject" ); }}
            public  static  string  DbLogin {  get {  return  getTextValue ( "DbLogin" ,  true ); }}
            public  static  string  DbPass {  get  {  return  getTextValue ( "DbPass" ,  true ); }}
     
            // =============================================== ============
     
            private  static  string  getTextValue ( string  name,  bool  getDefaultOnNotFound =  false ) {
                string  value =  ConfigurationManager .AppSettings [name];
     
                if  (value ==  null  &&! getDefaultOnNotFound) {
                    throw statement  new  KeyNotFoundException ( " The file  web.config  not found setup  '"  + name +  "'" );             } return  value;         } private static  T getStructureValue <T> ( string  name,  bool  getDefaultOnNotFound =  false )  where  T:  struct  { string  textValue = getTextValue (name, getDefaultOnNotFound); if  (textValue ==  null ) { return default (T);   

     
                

     
     
             
                
     
                
                     
                }
     
                try  {
                    T value = (T)  Convert .ChangeType (textValue,  typeof  (T));
                    return  value;
     
                }  catch  ( Exception  ex) {
                    string  message =  "In the web.config file, the setting '{0}' with the value '{1}' could not be resolved as '{2}'" ;
                    message =  string .Format (message, name, textValue,  typeof  (T) .Name);
                    throw  new  InvalidCastException (message, ex);
                }
            }
     
        }
    }

    Finally, in the FinReportService.cs file, replace the getReportInfo method with this code:
    private  static  string  getReportInfo ( int  reportID) {
        StringBuilder  sb =  new  StringBuilder ();
        try  {
            sb.Append ( "ReportType =" ) .AppendLine ( WebConfig .ReportType.ToString ());
            sb.Append ( "ReportSubject =" ) .AppendLine ( WebConfig .ReportSubject);
            sb.Append ( "DbLogin =" ) .AppendLine ( WebConfig .DbLogin);
            sb.Append ( "DbPass =" ) .AppendLine ( WebConfig .DbPass);
     
        }  catch  (Exception  ex) {
            sb.AppendLine (ex.Message);
        }
                
        return  sb.ToString ();
    }
    Now you can test reading the configuration.

    web_alpha.config

    Going further, the ability to place part of the settings in an additional file is very useful. For example, when the test and combat environments are distinguished by the connection string to the database. It also allows you to store the secret part of the settings separately. In this case, repeated settings are overridden by this file and new ones can also be declared in it.

    Add the web_alpha.config file to the project :
    <? xml version = " 1.0 " ?> < appSettings >   < add key = " ReportSubject " value = " Small business " />   < add key = " DbLogin " value = " reader " />   < add key = " DbPass " value = " uYE4_wn7xc5Sp " /> 

       
      
      
    >
    And change the web.config itself to:
    <? xml version = " 1.0 " ?> < configuration >   < appSettings file  = " web_alpha.config " >     < add key = " ReportType " value = " 8 " />     < add key = " ReportSubject " value = " Medium business " />   </ appSettings > 

     
     
      
       

     
    >
        < compilation debug = " true " targetFramework = " 4.0 " />   </ system.web > </ configuration >  

     


    web_beta.config

    A third-party file can also be located in the parent folder. Create a new web_beta.config file in the parent folder :
    <? xml version = " 1.0 " ?> < appSettings >   < add key = " ReportSubject " value = " Big business " />   < add key = " DbLogin " value = " admin " />   < add key = " DbPass " value = " guXu4awewr $ w " /> 

       
      
      
    >
    Change web.config accordingly to:
    <? xml version = " 1.0 " ?> < configuration >   < appSettings file  = " .. /web_beta.config " >     < add key = " ReportType " value = " 8 " />     < add key = " ReportSubject " value = " Medium Business " />   </ appSettings > 

     
     
      
       

     
    system.web >
        < compilation debug = " true " targetFramework = " 4.0 " />   </ system.web > </ configuration >  

     


    web_gamma.config

    There is an alternative way to place the settings in an additional file. Change web.config to:
    <? xml version = " 1.0 " ?> < configuration >   < appSettings configSource  = " web_gamma.config " />   < system.web >     < compilation debug = " true " targetFramework = " 4.0 " />   </ system.web > </ configuration > 

     
     
     

      

     

    And create the web_gamma.config file :
    <? xml version = " 1.0 " ?> < appSettings >   < add key = " ReportType " value = " 9 " />   < add key = " ReportSubject " value = " Non-commercial " />   < add key = " DbLogin " value = " writer " />   < 

      
      
      
     = " DbPass " value = " hQ5zGPPSrkqqfsb " /> </ appSettings > 

    This approach is less flexible, as it has a number of serious differences from the previous one:
    • All values ​​of the settings section are defined only in the specified third-party file. No redefinition of web.config.
    • The web_gamma.config file must exist, in the case of web_alpha.config it is not necessary.
    • Its modification will lead to a restart of the pool, in the case of web_alpha.config it will not.
    • Applicable to other sections, not only to <appSettings>


    web.Debug.config

    Now we describe another useful technique for working with web.config, which appeared in Visual Studio 2010. We are talking about the files web.Debug.config and web.Release.config . These files transform web.config when publishing , depending on the current type of build. Change the contents of these files to the following:
    <? xml version = " 1.0 " ?> < configuration xmlns : xdt = " http : // schemas . microsoft . com / XML - Document - Transform " > < appSettings >     < add key = " ReportSubject " value = " Debug " xdt: Transform = " Replace " xdt: Locator 
     
     
      
         = " Match (key) " />
      </ appSettings >
     
    </ configuration >
     

    <? xml version = " 1.0 " ?> < configuration xmlns: xdt = " http://schemas.microsoft.com/XML-Document-Transform " >   < system.web >     < compilation xdt: Transform = " RemoveAttributes (debug) "  / > </ system.web > </ configuration > 
     
     

     
      
      


    The conversion syntax is described on MSDN .

    But even without knowing the syntax, you can understand that in the case of Debug , the ReportSubject setting value is replaced , and in the case of Release , the debug = “true” attribute is deleted . By the way, do not forget to remove the debug = "true" attribute in production or set it to false, this improves performance and security.

    In addition, you can create your own web.Habr.config transformation via the Build -> Configuration Manager menu, and the Add Config Transoforms context menu of the web.config file.

    MaxRequestLength

    From the general settings for ASP.NET, I want to single out a limit on the maximum size of an incoming request. It is equal to the smaller of the two parameters. Moreover, maxRequestLength is indicated in kilobytes, and maxAllowedContentLength in bytes. Here is an example to set a limit of 100 megabytes, as well as 30 minutes of query execution.
    <? xml version = " 1.0 " ?> < configuration >   < appSettings  >     < add key = " ReportType " value = " 8 " />      < add key = " ReportSubject " value = " Medium business " />   </ appSettings >   < system. web >     <! 

     

      
       

     
      

      30  minutes  ( valid only with  compilation.debug = false) ->     < httpRuntime maxRequestLength = " 102400 " executionTimeout = " 1800 "  />     < compilation targetFramework = " 4.0 " >     </ compilation >   </ system.web >   < system. webServer >     < security >       < requestFiltering >        <! -  
      
     
     


     



    100  mb ->
            < requestLimits maxAllowedContentLength = " 104857600 "  />       </ requestFiltering >     </ security >   </ system.webServer > </ configuration > 



     



    Their default values ​​are 4096 KB and 30,000,000 bytes, i.e. 4 MB and 28.61 MB

    Web.config hierarchy

    In fact, this web.config is at the very bottom of the hierarchy of config files.

    The top level configuration is the machine.config file . Its settings apply globally to all web applications only if they are not overridden by lower-level config files. For .net 4 pools, it is located in the % windir% \ Microsoft.NET \ Framework \ v4.0.30319 \ Config or % windir% \ Microsoft.NET \ Framework64 \ v4.0.30319 \ Config folders , depending on the bit depth.

    The next level is also global - this is the web.config file located in the same place.

    The third level is the website level, by default it is the \ inetpub \ wwwroot folder , and initially the web.config file is not even there.

    Fourth is the web application tier, the standard tier described here.

    There is also a fifth level - when web.config settings are applied to subfolders of a web application, but this is true for other types of web applications.

    You can read more on MSDN

    9. Multiple asmx files


    In one web application there can be not one, but several asmx files. Obviously, this way you can develop several different web services that have some common source code. However, this technique can be used for one web service, the example below shows why.

    In the file FinReportService.cs, change the code to the following:
    using  System;
    using  System.Configuration;
    using  System.Web.Services;
     
    namespace  FinReportWebService {
     
        [ WebServiceBinding (ConformsTo =  WsiProfiles .BasicProfile1_1)]
        [ WebService (Description =  " Fin .  Reports  v.2" , Namespace = XmlNS)]
        public  class  FinReportService_v2  :  FinReportService  {
            public  FinReportService_v2 ():  base (2) {
            }
        }
     
     
     
        [ WebServiceBinding (ConformsTo = WsiProfiles .BasicProfile1_1)]
        [ WebService (Description =  "CHECK:  Fin .  Reports " , Namespace =  FinReportService .XmlNS)]
        public  class  FinReportService_Check {
            private  FinReportService  _service;
     
            public  FinReportService_Check () {
                _service =  new  FinReportService ();
            }
     
            [ WebMethod (Description =  " Enter a report ID ." )] Public GetReportResult  
              GetReport ( int  reportID) {
                GetReportArg  arg =  new  GetReportArg ();
                arg.ReportID = reportID;
                return  _service.GetReport (arg);
            }
     
            [ WebMethod (Description =  " Period :  yanvar  2015  goda ." )]
            Public  GetReportIdArrayResult  GetReportIdArray () {
                GetReportIdArrayArg  arg =  new  GetReportIdArrayArg ();
                arg.DateBegin =  new  DateTime (2015, 01, 01);
                arg.DateEnd = new  DateTime (2015, 02, 01);
                return  _service.GetReportIdArray (arg);
            }
        }
     
     
     
        [ WebServiceBinding (ConformsTo =  WsiProfiles .BasicProfile1_1)]
        [ WebService (Description =  " Fin .  Reports " , Namespace = XmlNS)]
        public  class  FinReportService  :  WebService  {
            public  const  string  XmlNS =  "http://asmx.habrahabr.ru/ " ;
            private  int  _version;
     
            public  FinReportService () {
                _version = 1;
            }
     
            public  FinReportService ( int  version) {
                _version = version;
            }
     
            [ WebMethod (Description =  " Get a list of report  IDs  by period " )] public GetReportIdArrayResult  GetReportIdArray ( GetReportIdArrayArg  arg) { return new GetReportIdArrayResult () {                 ReportIdArray =  new int [] {357, 358, 360, 361}             };         }   
             
                  
     


     
            [ WebMethod (Description =  " Get report by  ID" )] public GetReportResult  GetReport ( GetReportArg  arg) { return new GetReportResult () {                 Report =  new FinReport  {                     ReportID = arg.ReportID,                     Date =  new DateTime (2015, 03, 15),                     Info =  "Phone:"  +  ConfigurationManager .AppSettings [ "phone" ] +  "Version:"  + _version  
             
                  
     

     

                    }
                };
            }
        }
     
     
        public  class  FinReport  {
            public  int  ReportID {  get ; set ; }
            public  DateTime  Date {  get ; set ; }
            public  string  Info {  get ; set ; }
        }
     
        public  class  GetReportIdArrayArg  {
            public  DateTime  DateBegin {  get ; set ; }
            public  DateTime  DateEnd {  get; set ; }
        }
     
        public  class  GetReportIdArrayResult  {
            public  int [] ReportIdArray {  get ; set ; }
        }
     
        public  class  GetReportArg  {
            public  int  ReportID {  get ; set ; }
        }
     
        public  class  GetReportResult  {
            public  FinReport  Report {  get ; set ; }
        }
     
    }
    Also, the project should have the following 3 asmx files:
    FinReport.asmx - no change:
    <% @  Class = "FinReportWebService.FinReportService"  %>
    FinReport_v2.asmx - new:
    <% @  Class = "FinReportWebService.FinReportService_v2"  %>
    FinReport_CHECK.asmx - new:
    <% @  Class = "FinReportWebService.FinReportService_Check"  %>
    And web.config change to:
    <? xml version = " 1.0 " ?> < configuration >   < system.web >       < compilation debug = " true " targetFramework = " 4.0 "  />   </ system.web >   < appSettings >     < add key = " phone " value = " nokia " />   </ appSettings > 

     

      

     

      

      
     
     path = " FinReport.asmx " >
        < appSettings >
          < add key = " phone " value = " samsung " />     </ appSettings >   </ location >   < location path = " FinReport_v2.asmx " >     < appSettings >       < add key = " phone " value = "htc "  


     
     
     

      />
        </ appSettings >
      </ location >   < location path = " FinReport_CHECK.asmx " >     < appSettings >       < add key = " phone " value = " apple " />     </ appSettings >     < system.web >       < webServices >         < protocols >           < clear />          < add
     
      
     

      

     




     name  = " Documentation " />
              < add name  = " HttpPostLocalhost " />         </ protocols >       </ webServices >     </ system.web >   </ location > </ configuration > 




     

    As you can see, two new asmx files refer to two new classes. First, I will explain the purpose of the FinReportService_v2 class. Its only functional difference from the base class is that its default constructor initializes the int _version field with a value of 2, not 1. Thus, the web service has a clone that has exactly the same contract, but there are differences in request processing. For example, this clone may be intended for testing or indeed present a new version.

    The FinReportService_Check class has a completely different purpose. As you know, if you open a web service in a browser with localhost, then for web methods with primitive argument types, you can query and see the answer directly in the browser itself. This allows you, the administrator and everyone who has access to the server, to easily verify that it is working correctly.




    Now I will comment on the config file. Using the design, you can override any settings for a specific file. In this case, different asmx files will have different custom settings for “phone”.

    Using the <protocols> section, we first clear all the ways of interacting with FinReport_CHECK.asmx, and then add a view and call from localhost. This makes remote access to it impossible.

    10. Webpage Replacement


    By default, the asmx web page of the web service that you see in the browser is created by the DefaultWsdlHelpGenerator.aspx web form, which for x64 is in the% windir% \ Microsoft.NET \ Framework64 \ v4.0.30319 \ Config folder, I recommend that you read .

    However, using web.config it is easy to specify your own aspx file. Add the file FinReportPage.aspx to the project:
    <! doctype  html >
    < html >
        < head >
            < meta  charset = utf-8>
            < title > Welcome </ / title >
        </ head >
        < body >
            < p > Welcome! < / p >
        </ body >
    </ html >
    And specify in web.config
    <? xml version = " 1.0 " ?> < configuration >     < system.web >       < webServices >         < wsdlHelpGenerator href = " FinReportPage.aspx "  />       </ webServices >       < compilation debug = " true " targetFramework = " 4.0 "  />     </ system.web > < 


     

     

     
      
     



    11. Replacing an extension


    Using web.config, you can easily change the asmx extension to any other. Add a file with the extension habr to the project, for example, FinReportClone.habr , with the same contents as FinReport.asmx. And change the config to the following:
    <? xml version = " 1.0 " ?> < configuration >   < system.web >     < compilation debug = " true " targetFramework = " 4.0 "  >       < buildProviders >         < remove extension = " .habr " />         < add extension = " .habr " type = " 


      

     
      System.Web.Compilation.WebServiceBuildProvider "  />
          </ buildProviders >
        </ compilation >
      </ system.web >
     
      < system.webServer >
        < handlers >
          < add name = " HabraHandler " verb = " * " path = " * .habr " type = " System.Web.Services.Protocols.WebServiceHandlerFactory "  />     </ handlers >   </    

    system.webServer > </ configuration >
      

    I note that when starting from Visual Studio, FinReportClone.habr will not work, for this you need to publish to IIS. By the way, using this technique, you can replace the ASMX web service with the WCF web service while maintaining the original URL.

    12. Hide wsdl


    By default, a wsdl description of any SOAP web service is available by adding? Wsdl to its URL. This means that anyone who knows and sees this address can easily call its web methods. And if it does not have an authorization mechanism, it can be very unsafe. But even if there is such a mechanism, it’s generally undesirable to show the contract of your web service.

    1 way. Add the following setting to web.config:
      < system.web >
        < webServices >
          < protocols >
            < remove name  = " Documentation " /> </ protocols >     </ webServices >   </ system . web > 
          


    This is the standard way to hide information about a web service. Essentially, it simply prohibits GET requests. Attempting to open an address in a browser will throw an exception, while POST requests for web methods work as usual. The non-critical minus of this method is that the browser says an error.



    2 way. GET requests can be intercepted using a custom HTTP handler. Add the following class to the project:
    using  System.Web;
    namespace  FinReportWebService {
        public  class  FinReportGetHandler  :  IHttpHandler  {
            public  void  ProcessRequest ( HttpContext  context) {
                string  response =
    @ "<! doctype html>
    <html>
    <head>
        <meta charset = utf-8>
    </head>
    <body>
        <p > {0} </p>
    </body>
    </html> " ;
     
                bool  wsdlRequested = (context.Request.QueryString.ToString (). ToLower () ==  "wsdl" );
     
                
    .Format (response,  " Refer to the administrator for  the wsdl." );             }  else  {                 response =  string .Format (response,  " Web -based financial reporting service ." );             }             context.Response.ContentType =  "text / html; charset = utf-8" ;             context.Response.Write (response); // string filePage = HttpContext.Current.Server.MapPath ("~ / FinReportPage.htm"); // context.Response.WriteFile (filePage);         } public   

      

     


     



     
             bool  IsReusable {
                get  {  return  false ; }
            }
        }
    }
    And change web.config:
    <? xml version = " 1.0 " ?> < configuration >   < system.web >     < compilation debug = " true " targetFramework = " 4.0 "  >     </ compilation >   </ system.web >   < system.webServer >     < handlers >       < add name = " FinReportGetHandler " 

      

      


     


      GET " path = " FinReport.asmx " type = " FinReportWebService.FinReportGetHandler "  /> </ handlers >   </ system.webServer > </ configuration >  
        

      


    As in the previous trick, the handler will work only in IIS. By the way, it is better not to hardcode a web page, but to read from a file.

    13. Exceptions


    Exception handling in a web service is always very important. Building a successful mechanism for identifying and returning errors will save a lot of time and nerves for developers on both sides. The following are two different ways to inform a web service client that something went wrong.

    <soap: Fault>

    The <soap: Fault> element is the standard SOAP method for returning an error. Consists of four subelements:
    • <faultcode> - error code
    • <faultstring> - human readable error description
    • <faultactor> - source of error
    • <detail> - an arbitrary XML structure for detailed data

    <faultcode> and <faultstring> are required, the other two are optional.
    To demonstrate it, change the getReportInfo method to the following code:
            private  string  getReportInfo ( int  reportID) {
                throwException_1 ();
    // throwException_2 ();
    // throwException_3 ();
                return  "ReportID ="  + reportID;
            }
     
     
            private  void  throwException_1 () {
                int  x = 0;
                x = 1 / x;
            }
     
     
            Private  void  throwException_2 () {
                throw  new  SoapException ( " Invalid  ReportID" ,  SoapException .ClientFaultCode, Context.Request.Url.AbsoluteUri);    
            }
     
     
            private  void  throwException_3 () {
                XmlDocument  xmlDoc =  new  XmlDocument ();
                XmlNode  rootNode = xmlDoc.CreateNode ( XmlNodeType .Element,  SoapException .DetailElementName.Name,  SoapException .DetailElementName.Namespace);
     
                XmlNode  descNode = xmlDoc.CreateNode ( XmlNodeType .Element,  "Description" , XmlNS);
     
                XmlNode  descTypeNode = xmlDoc.CreateNode ( XmlNodeType .Element,  "Type" , XmlNS);
                descTypeNode.InnerText =  "DbConnection" ;
                descNode.AppendChild (descTypeNode);
     
                XmlNode  descMessageNode = xmlDoc.CreateNode ( XmlNodeType .Element,  "Message" , XmlNS);
                descMessageNode.InnerText =  "The host was not found ." ;             descNode.AppendChild (descMessageNode); XmlNode  habraNode = xmlDoc.CreateNode ( XmlNodeType .Element,  "HabraInfo" , XmlNS); XmlAttribute  habraNodeAttribute = xmlDoc.CreateAttribute ( "User" );             habraNodeAttribute.Value =  "capslocky" ;  

     
                
                

                habraNode.Attributes.Append (habraNodeAttribute);
     
                rootNode.AppendChild (descNode);
                rootNode.AppendChild (habraNode);
     
                XmlQualifiedName  faultCode =  new  XmlQualifiedName ( "TempError" , XmlNS);
                throw  new  SoapException ( " Temporary error " , faultCode, Context.Request.Url.AbsoluteUri, rootNode);         } 

    Call the GetReport web method with throwException_1 (), in which an unhandled division by zero error is generated. ASP.NET DevServer (or IIS) in this case will return the http code “500 Internal Server Error” instead of “200 OK” and the following content, which is formatted for convenience:
     
    <? xml version = " 1.0 " encoding = " UTF-8 " ?> < soap: Envelope xmlns: soap = " http://schemas.xmlsoap.org/soap/envelope/ " xmlns: xsd = " http: // www. w3.org/2001/XMLSchema " xmlns: xsi = " http://www.w3.org/2001/XMLSchema-instance " >   < soap: Body >     < soap: Fault >       < faultcode > soap: Server <  
       


    >
          < faultstring > Server was unable to process request. --- & gt;  Attempted to divide by zero. < / faultstring >
          < detail  />
        </ soap: Fault >
      </ soap: Body >
    </ soap: Envelope >
    Unhandled exceptions always give soap: Server. The display of the exception stack depends on the customErrors setting:
     
    <? xml version = " 1.0 " ?> < configuration >   < system.web >     < compilation debug = " true " targetFramework = " 4.0 "  />     < customErrors mode = " On " />   </ system.web > </ configuration > 

      

      
     

      

    The mode attribute has only three possible values:
    • On - Never show stack
    • Off - Always show
    • RemoteOnly - Only show for localhost requests

    By manually throwing a SoapException, you can define the <soap: Fault> structure yourself. Call the throwException_2 method. This code will result in the following response content
    <? xml version = " 1.0 " encoding = " UTF -8 " ?> < soap: Envelope xmlns: soap = " http://schemas.xmlsoap.org/soap/envelope/ " xmlns: xsd = " http: // www. w3.org/2001/XMLSchema " xmlns: xsi = " http://www.w3.org/2001/XMLSchema-instance " >   < soap: Body >     < soap: Fault >       < faultcode > soap:  
       


    faultcode >
          < faultstring > Invalid ReportID </ faultstring >
          < faultactor > http: //win2012/custom_folder/FinReport.asmx </ faultactor >
          < detail  />
        </ soap: Fault >
      </ soap: Body >
    </ soap: Envelope >
    Here we changed the error code, wrote our explanation text, and also indicated in which the request URL is usually indicated.
    In general, there are four standard fault codes that are represented by static fields in the SoapException class:
    • Server - a problem in the web service itself
    • Client - the client sent an invalid request
    • MustUnderstand - not remoteness to process soap: Header, obligatory for processing
    • VersionMismatch - Incorrect SOAP Version

    The throwException_3 () method demonstrates the formation of its own <detail> and error code:
    <? xml version = " 1.0 " encoding = " UTF-8 " ?> < soap: Envelope xmlns: soap = " http://schemas.xmlsoap.org/soap/envelope/ " xmlns: xsd = " http: // www. w3.org/2001/XMLSchema " xmlns: xsi = " http://www.w3.org/2001/XMLSchema-instance " >   < soap: Body >     < soap: Fault >       < faultcode xmlns: q0 = "  
       


     http://asmx.habrahabr.ru/ " > q0: TempError </ faultcode >
          < faultstring > Temporary error </ faultstring >
          < faultactor > http: //win2012/custom_folder/FinReport.asmx </ faultactor >
          < detail >
            < Description xmlns = " http://asmx.habrahabr.ru/ " >           < Type > DbConnection </ Type >           <Message > 

    Host not found. < / Message >
            </ Description >
            < HabraInfo xmlns = " http://asmx.habrahabr.ru/ " User = " capslocky "  />       </ detail >     </ soap: Fault >   </ soap: Body > </ soap : Envelope >  




    However, it is recommended that you do not allow the possibility of unhandled exceptions in web methods at all. That is, the method should always have a global try-catch, which will catch any unhandled or manually thrown exceptions, and return them to the client in a predefined format, which can be represented as <soap: Fault>, as well as in the manner described below.

    enum

    The idea of ​​this method is to use enumerations to inform the client about the successful or unsuccessful processing of his request. It is important that all enumeration values ​​are reflected in wsdl, therefore they are automatically present in the client proxy class.

    Add the file FinReport_GetReport.cs to the project:
    using  System;
     
    namespace  FinReportWebService {
     
        public  class  WebServiceError  {
            public  ErrorType  Type {  get ; set ; }
            public  string  Message {  get ; set ; }
        }
     
     
        public  enum  ErrorType  {
            Error,
            FoundBasicData,
            FoundFullData
        }
     
     
        public  enum  ErrorType  {
            Undefined,
            DbConnection,
            InvalidArgument,
            Forbidden
        }
     
     
        public  class  WebServiceErrorException  :  Exception  {
            public  WebServiceError  Error {  get ; set ; }
        }
     
     
        public  class  FinReport_GetReport  {
            private  GetReportArg  _arg;
            private  GetReportResult  _result;
     
            public  GetReportResult  GetReport ( GetReportArg  arg) {
                _arg = arg;
                initializeResultWithError ();
     
                try  {
                    checkArg ();
                    fillResult ();
     
                }  catch  ( WebServiceErrorException  ex) {
                    setResultType ( ResultType .Error);
                    _result.Error = ex.Error;
     
                }  catch  ( Exception  ex) {
                    setResultType ( ResultType .Error);
                    _result.Error.Type =  ErrorType .Undefined;
                    _result.Error.Message = ex.GetType () +  ":"  + ex.Message;
                }
     
                if  (_result.ResultType ==  ResultType .Error) {
                    _result.Report =  null;
     
                }  else  {
                    _result.Error =  null ;
                }
     
                return  _result;
            }
     
            
            private  void  setResultType ( ResultType  type) {
                _result.ResultType = type;
            }
     
     
            private  void  initializeResultWithError () {
                _result =  new  GetReportResult ();
                setResultType ( ResultType .Error);
     
                _result.Error =  new  WebServiceError ();
                _result.Error.Type =  ErrorType.Undefined;
                _result.Error.Message =  "No result specified" ;
            }
     
     
            The private  void  checkArg () {
                the if  (_arg.ReportID <= 0) {
                    throw statement  new  WebServiceErrorException () {
                        the Error =  new  WebServiceError () {
                            the Type =  ErrorType .InvalidArgument,
                            the Message =  " Invalid ID report :"  + _arg.ReportID                     }                 } ;             }         }  




     
     
            private  void  fillResult () {
                _result.Report =  new  FinReport ();
                _result.Report.ReportID = _arg.ReportID;
                _result.Report.Date =  new  DateTime (2015, 03, 15);
     
    Throw new WebServiceErrorException // () {
    // new WebServiceError Error = () {
    // Type = ErrorType.DbConnection,
    // Message = " Host ripped compound " //} //}; // throw new InvalidOperationException (" Oops ");             _result.Report.Info =   


     

     
    "Some info" ;
                setResultType ( ResultType .FoundFullData);
            }
     
        }
    }
    GetReport web method fix to
            [ WebMethod (Description =  "Get report by ID" )]
            public  GetReportResult  GetReport ( GetReportArg  arg) {
                return  new  FinReport_GetReport () .GetReport (arg);
            }
    And extend the definition of the GetReportResult class
        public  class  GetReportResult  {
            public  ResultType  ResultType {  get ; set ; }
            public  WebServiceError  Error {  get ; set ; }
            public  FinReport  Report {  get ; set ; }
        }
    So, using the ResultType enumeration, we report that an error occurred or that the web method worked successfully with some type of result. In case of an error, we indicate the type of error and some text through a special structure. The example also demonstrates a technique for globally catching any exceptions. Thus, the client always receives “200 OK” and the GetReportResult structure in response.

    Sample answers:
    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Body>
        <GetReportResponse xmlns="http://asmx.habrahabr.ru/">
          <GetReportResult>
            <ResultType>FoundFullData</ResultType>
            <Report>
              <ReportID>45</ReportID>
              <Date>2015-03-15T00:00:00</Date>
              <Info>Some info</Info>
            </Report>
          </GetReportResult>
        </GetReportResponse>
      </soap:Body>
    </soap:Envelope>
     
     
    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Body>
        <GetReportResponse xmlns="http://asmx.habrahabr.ru/">
          <GetReportResult>
            <ResultType>Error</ResultType>
            <Error>
              <Type>DbConnection</Type>
              <Message>Хост разорвал соединение</Message>
            </Error>
          </GetReportResult>
        </GetReportResponse>
      </soap:Body>
    </soap:Envelope>
     
     
    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Body>
        <GetReportResponse xmlns="http://asmx.habrahabr.ru/">
          <GetReportResult>
            <ResultType>Error</ResultType>
            <Error>
              <Type>Undefined</Type>
              <Message>System.InvalidOperationException: Упс</Message>
            </Error>
          </GetReportResult>
        </GetReportResponse>
      </soap:Body>
    </soap:Envelope>
     
     
    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Body>
        <GetReportResponse xmlns="http://asmx.habrahabr.ru/">
          <GetReportResult>
            <ResultType>Error</ResultType>
            <Error>
              <Type>InvalidArgument</Type>
              <Message>Некорректный идентификатор отчета: -245</Message>
            </Error>
          </GetReportResult>
        </GetReportResponse>
      </soap:Body>
    </soap:Envelope>
    I want to note that if the http request has an incorrect structure and it cannot be successfully deserialized into the argument of the web method, the web method is not called, and soap: Fault is returned to the client with a description of the problem.

    14. soap: Header


    Headers are an optional part of the SOAP envelope and carry various auxiliary data in relation to its body. Using them, you can solve the issues of authentication, grouping and organizing envelopes, routing, unification of metadata for various web services, and so on.

    Change the web service code to the following:
    using System;
    using System.Text;
    using System.Web.Services;
    using System.Web.Services.Protocols;
     
    namespace FinReportWebService{
     
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [WebService(Description = "Фин. отчеты", Namespace = XmlNS)]
        public class FinReportService : WebService{
            public const string XmlNS = "http://asmx.habrahabr.ru/";
     
            public HabraSoapHeader HabraHeader { get; set; }
            public ResultTimeSoapHeader ResultTimeHeader { get; set; }
            public SoapUnknownHeader[] UnknownHeaders { get; set; }
     
            
            [SoapHeader("HabraHeader")] //закомментируйте эту строку
            [SoapHeader("ResultTimeHeader", Direction = SoapHeaderDirection.Out)]
            [SoapHeader("UnknownHeaders")]
            [WebMethod(Description = "Получение отчета по ID")]
            public GetReportResult GetReport(GetReportArg arg) {
    //            throw new SoapHeaderException("Ошибка", SoapException.ClientFaultCode);
                return new GetReportResult() {
                    Report = new FinReport {
                        ReportID = arg.ReportID,
                        Date = new DateTime(2015, 03, 15),
                        Info = getReportInfo(arg.ReportID)
                    }
                };
            }
     
     
            private string getReportInfo(int reportID) {
                StringBuilder sb = new StringBuilder();
                sb.Append("ReportID = ").Append(reportID).AppendLine();
     
                if (HabraHeader != null) {
                    sb.Append("Login = ").Append(HabraHeader.Login).AppendLine();
                    sb.Append("Password = ").Append(HabraHeader.Password).AppendLine();    
                }
     
                foreach (var header in UnknownHeaders) {
                    sb.Append("Обнаружен SoapHeader = ").Append(header.Element.Name).AppendLine();
                    sb.Append("MustUnderstand = ").Append(header.MustUnderstand).AppendLine();
     
    //                if (header.Element.Name == "HabraSoapHeader") {
    //                    sb.AppendLine("HabraSoapHeader распознан");
    //                    sb.Append("Login = ").Append(header.Element["Login"].InnerText).AppendLine();
    //                    sb.Append("Password = ").Append(header.Element["Password"].InnerText).AppendLine();
    //                    header.DidUnderstand = true;
    //                }
                }
     
                ResultTimeHeader = new ResultTimeSoapHeader();
                ResultTimeHeader.ResultTime = DateTime.Now;
                return sb.ToString();
            }
        }
        
     
        public class HabraSoapHeader : SoapHeader {
            public string Login { get; set; }
            public string Password { get; set; }
        }
     
     
        public class ResultTimeSoapHeader : SoapHeader {
            public DateTime ResultTime { get; set; }
        }
     
     
        public class FinReport {
            public int ReportID { get; set; }
            public DateTime Date { get; set; }
            public string Info { get; set; }
        }
     
     
        public class GetReportArg {
            public int ReportID { get; set; }
        }
     
     
        public class GetReportResult {
            public  FinReport  Report {  get ; set ; }
        }
    }
    Then restart the web server and regenerate the proxy class in the client project, since the declared headers will be reflected in wsdl. Make the call method as follows:
            private void webMethodTest_GetReport() {
                var service = GetFinReportService();
                var arg = new GetReportArg();
                arg.ReportID = 45;
     
                service.HabraSoapHeaderValue = new HabraSoapHeader();
                service.HabraSoapHeaderValue.Login = "neo";
                service.HabraSoapHeaderValue.Password = "3Ku2kcQfNLOW";
                service.HabraSoapHeaderValue.MustUnderstand = true;
     
                var result = service.GetReport(arg);
                var resultTimeSoapHeader = service.ResultTimeSoapHeaderValue;
     
                if  (resultTimeSoapHeader! =  null ) {
                    MessageBox .Show ( "ResultTimeSoapHeader ="  + resultTimeSoapHeader.ResultTime);
                }
     
                MessageBox .Show (result.Report.Info);
            }

    So, to add the soap header to the asmx web service, you need to do three things:
    • Declare SoapHeader inherited header class
    • Declare a property for this class
    • Apply the [SoapHeader] attribute to the web method that points to this property.

    In this example, the web service declared two headers - HabraSoapHeader and ResultTimeSoapHeader, with the first expected in the request and the second returned in the response.

    Envelopes themselves will look like this:
    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Header>
        <HabraSoapHeader xmlns="http://asmx.habrahabr.ru/" soap:mustUnderstand="1">
          <Login>neo</Login>
          <Password>3Ku2kcQfNLOW</Password>
        </HabraSoapHeader>
      </soap:Header>
      <soap:Body>
        <GetReport xmlns="http://asmx.habrahabr.ru/">
          <arg>
            <ReportID>45</ReportID>
          </arg>
        </GetReport>
      </soap:Body>
    </soap:Envelope>

    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Header>
        <ResultTimeSoapHeader xmlns="http://asmx.habrahabr.ru/">
          <ResultTime>2015-03-24T11:37:00.6135717+06:00</ResultTime>
        </ResultTimeSoapHeader>
      </soap:Header>
      <soap:Body>
        <GetReportResponse xmlns="http://asmx.habrahabr.ru/">
          <GetReportResult>
            <Report>
              <ReportID>45</ReportID>
              <Date>2015-03-15T00:00:00</Date>
              <Info>
                ReportID = 45
                Login = neo
                Password = 3Ku2kcQfNLOW
              </Info>
            </Report>
          </GetReportResult>
        </GetReportResponse>
      </soap:Body>
    </soap:Envelope>
     
    Pay attention to the soap attribute: mustUnderstand = "1", which means that the web service must understand this header. The SoapHeader class has a property public bool DidUnderstand {set; get; }, which determines whether the title was understood. For known headers, it is initially true, while for unknown headers, false. And in the case where the header had mustUnderstand = "1", and DidUnderstand turned out to be false, the web server will return soap: Fault. Comment out the attribute [SoapHeader ("HabraHeader")], then the same request will result in:
    <?xml version="1.0" encoding="UTF-8"?>
    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <soap:Body>
        <soap:Fault>
          <faultcode>soap:MustUnderstand</faultcode>
          <faultstring>Заголовок SOAP HabraSoapHeader не распознан.</faultstring>
        </soap:Fault>
      </soap:Body>
    </soap:Envelope>
    Now this header falls into the public array SoapUnknownHeader [] UnknownHeaders {get; set; }, and to process it successfully, uncomment the code inside foreach.

    You can also work with headers using the SoapExtension extensions. This allows you to separate header logic from web method logic. They can process incoming headers themselves or add them to the response. At the same time, they can either exchange data with the web service class (or proxy class) through the interface, or be completely independent of them.

    Also note that for header exceptions, there is a special SoapHeaderException class inherited from SoapException. Interestingly, when it is thrown on the server, the client will also receive exactly this type of exception, and not SoapException, although they are both passed as soap: Fault. Try to understand how it turns out.

    15. Caching


    There are two standard caching methods for asmx web services. The first is to set the CacheDuration property in the attribute of the web method, and the second to use HttpContext.Cache . Both methods are demonstrated in the following code:
    [WebMethod(Description = "Получение отчета по ID", CacheDuration = 5)]
    public GetReportResult GetReport(GetReportArg arg){
        return new GetReportResult(){
            Report = new FinReport{
                ReportID = arg.ReportID,
                Date = DateTime.Now,
                Info = getReportInfo(arg.ReportID)
            }
        };
    }
     
     
    private string getReportInfo(int reportID){
        string key = "getReportInfo_" + reportID;
        string cachedInfo = Context.Cache.Get(key) as string;
     
        if (cachedInfo != null){
            return cachedInfo;
        }
     
        string info = DateTime.Now + " reportID = " + reportID;
        this.Context.Cache.Add(key, info, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero, CacheItemPriority.High, null);
        return info;
    }
    The attribute caching method keeps in memory for a specified number of seconds all incoming and outgoing soap messages. And if the incoming message is completely identical with the one in memory, the web method is not executed, but the cached result is returned.

    The second method is more flexible and functional, in it we ourselves determine the objects and caching keys and its other features.

    In this example, if the client sends a request once a second, then the Date field will change every 5 seconds, and the Info field once every 10 seconds.

    Let me remind you that caching should be used deliberately, as it leads to large memory consumption and the risk of returning irrelevant data.

    16. SoapExtension


    SoapExtension is a powerful technique that allows you to manually modify requests and responses, both from the side of the web service, and from the client. Unfortunately, a full description of it with various examples draws to a separate article, so I will describe it only in general and give links to materials.

    Features SoapExtension:
    • Reading request or response content (viewing soap envelopes as a MemoryStream)
    • Modification of request or response content
    • Reading and processing headers (soap header)
    • 1 way to bind: to a web method (web service or client) through a custom attribute
    • 2 way to bind: to a web service (or client) using web.config (app.config) without recompilation!
    • Interaction with the web service class itself or the proxy class
    • Create chains from various SoapExtension

    What can be done with SoapExtension
    • full low-level logging
    • authentication
    • imposing and checking EDS
    • encryption
    • compression
    • soap header processing
    • any content conversion

    References


    17. Debugging x64 in Visual Studio


    Debugging web applications in Visual Studio is complicated by the lack of 64-bit mode in the embedded web server. If you specify Platform target: x64, then starting the application will result in the error “Failed to load the file or assembly“ FinReportWebService ”or one of their dependencies. An attempt was made to download a program that has the wrong format. "

    There are three different ways to solve this problem.

    1 way. In the web project settings, select “Use Local IIS Web server”, that is, use the local IIS



    2 method. Publish the web application to the local IIS and connect to its Debug -> Attach to Process process .

    3 way.In the absence of IIS, you can physically replace the standard ASP.NET Development Server with an alternative CassiniDev web server . Download CassiniDev sources from here: https://cassinidev.codeplex.com/SourceControl/latest#ReadMe.htm After

    unpacking, open and compile \ trunk \ src \ CassiniDev.sln, you may need to update the references from trunk \ src \ packages. Now in the solution properties, change the project platform to x64 and recompile:



    The standard web server is located in the folder C: \ Program Files (x86) \ Common Files \ microsoft shared \ DevServer . Having made a copy of it, replace it from the \ trunk \ src \ CassiniDev \ bin \ x64 \ Debug folder



    18. Deploy (publication)


    Using the Publish command and the File System method, Visual Studio creates in the specified folder the set of files that must be provided for IIS



    wwwroot

    In the simplest case, this web service folder is located in a special folder C: \ inetpub \ wwwroot , which makes it easy to convert it to a web application. Let me remind you that IIS can be called with the inetmgr command .





    HTTP Error 404.3 - Not Found

    If an error occurs HTTP Error 404.3 - Not Found, then you must add the following IIS components



    And run the command
    % windir% \ Microsoft.NET \ Framework64 \ v4.0.30319 \ aspnet_regiis.exe –ir

    Custom folder

    The web service can also be located in a custom folder.



    But in this case errors may occur due to the lack of permission for IIS to read it:
    HTTP Error 500.19 - Internal Server Error.
    The requested page cannot be accessed because the related configuration data for the page is invalid.
    or
    Access is denied.
    Error message 401.3: You do not have permission to view this directory or page using the credentials you supplied
    How to fix them is described in the next trick.

    19. IIS Application Pools


    Web applications are recommended to keep each in its specially created pool. This allows them to be isolated and individually customized. Create a new FinReportPool pool and switch the web service to it.

    Permission settings

    The pool account with the ApplicationPoolIdentity identity has a system name of the form:
    IIS AppPool \ <pool name>

    For example, IIS AppPool \ FinReportPool , or IIS AppPool \ DefaultAppPool

    And in the case of a custom folder in its security properties you will need to add an account with read, execute and view the contents



    As well as switch the anonymous user identity



    I also note that instead of the account you can specify the group <Computer Name> \ IIS_IUSRS

    There are a lot of interesting settings in the additional pool settings.



    In particular, x86 / x64 bit conflict error
    Failed to load the file or assembly "FinReportWebService" or one of their dependencies. An attempt was made to download a program that has the wrong format.
    Could not load file or assembly 'FinReportWebService' or one of its dependencies. An attempt was made to load a program with an incorrect format.
    may occur due to an invalid Enable 32-Bit Applications parameter .

    And using the Identity parameter, you can change the account under which the web application runs.

    Appcmd.exe

    Each pool when running spawns a separate process w3wp.exe. You can see the correspondence between them using the Task Manager or the following batch file:
    AppcmdList.bat
    % systemroot% \ System32 \ inetsrv \ appcmd list wp
    % systemroot% \ System32 \ inetsrv \ appcmd list sites
    % systemroot% \ System32 \ inetsrv \ appcmd list app
    % systemroot% \ System32 \ inetsrv \ appcmd list appPools
    pause



    If you do not see the process, then to start it, just open the web application page in the browser. More information about the AppCmd.exe utility can be found here .

    20. Development Tools


    If you are just starting to work with web services, then you will need additional tools that will greatly facilitate the development process.
    • Fiddler . Universal HTTP debugger. Free and very functional. It is described on Habré here . Must have.
    • SoapUI. Мощный инструмент анализа и тестирования веб-сервисов. Существует в различных редакциях, есть бесплатная.
    • Oxygen XML Editor. Очень удобный инструмент работы с XML. Умеет работать с WSDL и SOAP.
    • www.wsdl-analyzer.com. Анализ и сравнение WSDL.
    • www.freeformatter.com. Множество различных инструментов для форматирования, валидации и преобразования.