gifts2017

Прокси-сервер для веб-клиента 1С: Предприятие 8.2

Опубликовал Сергей Карташев (Elisy) в раздел Администрирование - Системное

Прокси-сервер для веб-клиента 1С:Предприятие 8.2 демонстрирует возможности подключения, управления содержимым, мониторинга и отладки html- и javascript-кодов, возвращаемых сервером 1С. Работу прокси-сервера можно наглядно посмотреть в Интернете по адресу: http://proxy.1csoftware.com

Прокси-сервер для веб-клиента 1С: Предприятие 8.2 демонстрирует возможности подключения, управления содержимым, мониторинга и отладки html- и javascript-кодов, возвращаемых сервером 1С. Работу прокси-сервера можно наглядно посмотреть в Интернете по адресу: http://proxy.1csoftware.com

Введение


Одной из главных функциональных особенностей 1С: Предприятие 8.2 стала возможность получения доступа к данным 1С через Интернет. Но компания 1С делает это в своей традиционной манере, скрывая детали генерации веб-содержимого для браузеров и не обращая внимания на некоторые общепринятые стандарты. Программисту нужно относиться к процессу выдачи веб-содержимого, как к черному ящику, в котором по неизвестным законам происходит преобразование метаданных и данных в html-, json- и jscript-ответы от сервера. Прокси-сервер поможет вмешаться в процесс отображения данных и глубже разобраться с генерацией контента. Он будет находится между браузером и сервером 1С: Предприятие, перехватывать и перенаправлять запросы.


Структура и место прокси сервера между браузером и веб-сервером

Статья ссылается на технологии: Asp.Net MVC 3, .Net framework 4, IIS 7/7.5. Настоятельно рекомендуется запускать решение под IIS, а не в Visual Studio Development Server.
В качестве средства разработки была выбрана технология Asp.Net MVC 3 не случайно. Гибкость и наглядность предоставляемых средств позволяет быстро выполнить разработку и сэкономить на поддержке в будущем. Эту же задачу можно было бы решить на более низком уровне, например, через многопоточные HttpListener, но такое решение сопровождалось бы упомянутыми издержками. Правда, не исключено, что встретившись с нерешаемыми трудностями в будущем, придется переписать прокси-сервер на более низкоуровневых объектах. В случае с 1С: Предприятие такие трудности гарантированно есть всегда, и далеко не факт, что они были все выявлены и устранены. Речь о них пойдет ниже.

Пример опубликован в Интернете, и его можно посмотреть здесь: http://proxy.1csoftware.com

Проект Asp.Net MVC 3


Любой проект Asp.Net MVC начинается с проектирования структуры URL в методе RegisterRoutes, вызываемом в Application_Start из Gloval.asax. Для 1С: Предприятия URL строится так:

///?


Среди параметров одним из самых частых является sysver. Язык присутствует везде, кроме общего запроса к приложению. Соответственно этой структуре будет код, регистрирующий правила ProxyLanguage и Proxy:


        routes.MapRoute(
            "ProxyLanguage",                                              // Route name
            "{application}/{lang}/{*pathInfo}",                      // URL with parameters
            new { lang = System.Globalization.CultureInfo.CurrentUICulture.Name, controller = "Proxy", action = "Transfer", pathInfo = UrlParameter.Optional },
            new { lang = @"\w{2}_\w{2}|\w{2}" }
        );

        routes.MapRoute(
            "Proxy",                                              // Route name
            "{application}/{*pathInfo}",                      // URL with parameters
            new { controller = "Proxy", action = "Transfer", pathInfo = UrlParameter.Optional }
        );
        
        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );

Последняя команда была изначально в проекте и позволит открыть главную страницу с описанием примера, обратившись по адресу без пути. Для этого выделен отдельный контроллер Home, действие Index и вид Index с html-разметкой.
Исходя из кода, запросы для 1С будут перенаправлены на контроллер Proxy с действием Transfer. Контроллер лучше взять сразу асинхронный, наследовав от AsyncController, чтобы увеличить производительность. В этом случае действие Transfer будет состоять из двух методов:

public void TransferAsync(string pathInfo, string sysver)
public ActionResult TransferCompleted(HttpWebResponse response)


Так как приложение Application и язык Language предопределены, их целесообразно вынести в строковые свойства для доступа из любой части класса:       

public string Language { get; set; }
public string Application { get; set; }

И инициализировать в перегруженном методе Initialize так:       

protected override void Initialize(System.Web.Routing.RequestContext requestContext)
        {
            base.Initialize(requestContext);

            if (requestContext.RouteData.Values.ContainsKey("lang"))
                Language = requestContext.RouteData.Values["lang"].ToString();

            if (requestContext.RouteData.Values.ContainsKey("application"))
                Application = requestContext.RouteData.Values["application"].ToString();
            else
                RedirectToAction("Index", "Home");
        }

Метод TransferAsync принимает запрос от клиента, инициализирует объект HttpWebRequest, передавая в него информацию из свойства Request контроллера о методе (GET или POST), заголовках браузера, куки, содержимом POST-запроса. Метод приведен полностью:

        public void TransferAsync(string pathInfo, string sysver)
        {
            AsyncManager.OutstandingOperations.Increment();

            ViewBag.SysVer = sysver;
            ViewBag.PathInfo = pathInfo;

            HttpWebRequest remoteRequest = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://demo-ma.1c.ru/" + Application + (string.IsNullOrEmpty(Language)? "" : "/" + Language) + "/" + pathInfo + Request.Url.Query));
            remoteRequest.Method = Request.HttpMethod;
            remoteRequest.CookieContainer = new CookieContainer();
            if (Request.UrlReferrer != null)
                remoteRequest.Referer = Request.UrlReferrer.ToString();
            remoteRequest.UserAgent = Request.UserAgent;

            for (int i = 0; i < Request.Cookies.Count; i++)
            {
                HttpCookie cookie = Request.Cookies.Get(i);

                Cookie newCookie = new Cookie();

                newCookie.Domain = remoteRequest.RequestUri.Host;

                newCookie.Expires  = cookie.Expires;
                newCookie.Name     = cookie.Name;
                newCookie.Path     = cookie.Path;
                newCookie.Secure   = cookie.Secure;
                newCookie.Value    = cookie.Value;

                remoteRequest.CookieContainer.Add(newCookie);
            }

            foreach(string key in Request.Headers)
            {
                if (key == "Connection")
                {
                    try
                    {
                        remoteRequest.Connection = Request.Headers.Get(key);
                    }
                    catch (Exception)
                    { }
                    continue;
                }
                if (key == "Accept")
                {
                    remoteRequest.Accept = Request.Headers.Get(key);
                    continue;
                }
                if (key == "Host")
                    continue;
                if (key == "User-Agent")
                    continue;
                if (key == "Referer")
                    continue;
                if (key == "Content-Length")
                    continue;
                if (key == "Content-Type")
                {
                    remoteRequest.ContentType = Request.Headers.Get(key);
                    continue;
                }
                remoteRequest.Headers.Add(key, Request.Headers.Get(key));
            }

            if (remoteRequest.Method == "POST")
            {
                using (var inputStream = remoteRequest.GetRequestStream())
                {
                    MemoryStream memoryStream = new MemoryStream();

                    byte[] buffer = new byte[255];
                    int bytesRead;
                    double totalBytesRead = 0;
                    Request.InputStream.Position = 0;
                    while ((bytesRead = Request.InputStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        totalBytesRead += bytesRead;
                        memoryStream.Write(buffer, 0, bytesRead);
                    }

                    inputStream.Write(memoryStream.ToArray(), 0, (int)memoryStream.Length);
                    memoryStream.Close();
                }
            }

            remoteRequest.BeginGetResponse(result =>
                {
                    try
                    {
                        WebResponse response = remoteRequest.EndGetResponse(result);
                        AsyncManager.Parameters["response"] = (HttpWebResponse)response;
                    }
                    catch (WebException e)
                    {
                        AsyncManager.Parameters["response"] = (HttpWebResponse)e.Response;
                    }
                    AsyncManager.OutstandingOperations.Decrement();
                },
                null
            );
        }


Код метода TransformCompleted небольшой по размерам и представлен далее. В этом методе целесообразно отдельно получить поток ответа GetResponseStream() и сохранить его содержимое в переменную ViewBag.ResponseContent для повторного использования, так как несколько раз к этому потоку обратиться не получится.
Ответ от сервера может быть любой, необходимо определить свой ActionResult-наследованный класс ContentActionResult, и возвратить его. Он может содержать рисунки, html, json, jscript, текст и другие форматы.       

public ActionResult TransferCompleted(HttpWebResponse response)
        {
            using (var responseStream = response.GetResponseStream())
            {
                MemoryStream memoryStream = new MemoryStream();

                byte[] buffer = new byte[255];
                int bytesRead;
                double totalBytesRead = 0;
                while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    totalBytesRead += bytesRead;
                    memoryStream.Write(buffer, 0, bytesRead);
                }
                ViewBag.ResponseContent = memoryStream.ToArray();
            }

            return new ContentActionResult() { RemoteResponse = response, FilePath = filePath, ResponseContent = ViewBag.ResponseContent };
        }

Класс ContentActionResult преобразует ответ от оригинального сервера 1С и возвратит клиенту куки, заголовки и тело ответа, а также код статуса.

    public class ContentActionResult : ActionResult
    {
        public HttpWebResponse RemoteResponse { get; set; }
        public string FilePath { get; set; }
        public byte[] ResponseContent { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            var response = context.HttpContext.Response;
            response.ContentType = RemoteResponse.ContentType;
            response.Charset = RemoteResponse.CharacterSet;
            response.StatusCode = (int)RemoteResponse.StatusCode;

            for (int i = 0; i < RemoteResponse.Cookies.Count; i++)
            {
                Cookie cookie = RemoteResponse.Cookies[i];

                HttpCookie newCookie = new HttpCookie(cookie.Name);

                newCookie.Domain = context.HttpContext.Request.Url.Host;

                if (string.IsNullOrEmpty(newCookie.Domain))
                    newCookie.Domain = context.HttpContext.Request.Url.Host;

                newCookie.Expires = cookie.Expires;
                newCookie.Name = cookie.Name;
                newCookie.Path = cookie.Path;
                newCookie.Secure = cookie.Secure;
                newCookie.Value = cookie.Value;

                response.SetCookie(newCookie);
            }

            
            foreach (string key in RemoteResponse.Headers.AllKeys)
            {
                response.AddHeader(key, RemoteResponse.Headers.Get(key));
            }
            response.BinaryWrite(ResponseContent);
        }
    }

Проблемы реализации



При разработке прокси-сервера было насколько проблем. Все они были связаны с невнимательностью компании 1С к стандартам веб-разработки. Если рассматривать пример статьи как unit-тест, то разработчикам компании 1С следует обратить внимание и зарегистрировать 2 проблемы:

Двоеточие в пути к ресурсу


Сервер от 1С допускает двоеточие в пути к ресурсу. Ответ от него может быть примерно следующим:
http://demo-ma.1c.ru/demoen/en_US/e1cib/pictureCollection/picture/0:dfa91944-c44c-403e-93b5-93d998359611?confver=01bdd81e-8d68-421d-a0e3-a381ab938613&t=false&w=48&h=48

Двоеточие является зарезервированным символом, и по стандарту rfc 3986 не допускается его использование в пути. Эта сложность приводит к невозможности принять запрос через Visual Studio Development Server и необходимости использовать IIS. Для IIS требуется дополнительная настройка в web.config:

<httpRuntime requestValidationMode="2.0" requestPathInvalidCharacters=",*,&,\,?" /> 


Настройка позволяет исключить двоеточие из недействительных символов пути.
Кто знает, может странное поведение тонкого клиента на IIS 7.x версии, отмеченное в сообществе 1С-разработчиков связано тоже с данной проблемой.

Неверный формат JSON



Некоторые ответы от 1С-сервера возвращают JSON-содержимое в виде:
{"root":{"cacheID":undefined, ...
Проблема возникает со значением undefined, которое по общепринятым стандартам должно быть заключено в кавычки. Значение может быть только строкой в двойных кавычках, числом, булевым значением: true или false, массивом в квадратных скобках или значением null.
Такое несоответствие приводит к ошибке: «Invalid JSON primitive: undefined», когда Asp.Net MVC пытается автоматически привести JSON к параметрам действия Transfer. Решается проблема исключением формата JSON из списка фабрик преобразований значений в Global.asax.   

void Application_Start(object sender, EventArgs e)
    {
        //Workaround error Invalid JSON primitive: undefined. when Post data contains {"root":{"cacheID":undefined, ...
        ValueProviderFactories.Factories.Remove(
                    ValueProviderFactories.Factories.OfType().First());

Это некрасивый шаг, лишающий решение некоторой гибкости и расширяемости, но более изящного подобрать не удалось.

Управление веб-страницами



Пример нового html-содержимого в окне инициализации

Прокси-сервер позволяет не только исследовать возвращаемые файлы сервером 1С, но и вмешаться в их генерацию. На рисунке видно простой пример, когда при инициализации показывается баннер в правом верхнем углу. Достигается это в методе TransferCompleted через отдельную сборку AgilityPack так:           

//Add new content
            if (ViewBag.PathInfo != null)
            {
                if (ViewBag.PathInfo == "mainform.html")
                {
                    HtmlDocument html = new HtmlDocument();
                    html.OptionFixNestedTags = true;
                    html.LoadHtml(Encoding.UTF8.GetString(ViewBag.ResponseContent, 0, ViewBag.ResponseContent.Length));

                    var res = html.DocumentNode.SelectSingleNode("//div[@id='preloader']");
                    HtmlNode node = html.CreateElement("img");
                    node.Attributes.Add("id", "1csoftware-powered");
                    node.Attributes.Add("style", "position:absolute;top:10px;right:10px;");
                    node.Attributes.Add("src", VirtualPathUtility.ToAbsolute("~/i/1csoftware.png"));
                    res.ChildNodes.Add(node);

                    ViewBag.ResponseContent = Encoding.UTF8.GetBytes(html.DocumentNode.OuterHtml);
                }
            }


 


За создание страницы загрузки отвечает файл mainform.html. Если в его div-раздел с именем preloader вставить какое-то содержимое, то содержимое появится в браузере при загрузке.
В более сложном варианте можно, например, исследовать работу форм и вмешаться в их логику, добавив свои элементы управления или обработчики, подключить jQuery. Можно поменять таблицу стилей и придать элементам свои цвета. Можно даже исправить самим ошибки Компании 1С, зная ее «оперативность» по борьбе с багами.

 

Заключение



Представленный в статье прокси-сервер находится между веб-браузером и сервером 1С: Предприятие 8.2. Перехватывает все запросы от браузера и передает их серверу. Таким образом, позволяет изучать передаваемые файлы и влиять на передаваемую информацию.
В качестве платформ разработки взяты .Net framework 4 и Asp.Net MVC 3. Решение построено через асинхронный контроллер для увеличения производительности. Кроме перенаправления запросов в прокси-сервер заложена логика обходных путей для 2х проблем: двоеточие в пути к ресурсу и некорректный формат JSON.
Решение обладает достаточной гибкостью и позволяет вмешаться в генерацию исходного кода html-, js- и других файлов.
В решении мало внимания уделялось логике работы 1С и взаимосвязи возвращаемых ответов от 1С-сервера. Это тема отдельной обширной статьи. Нереализованной и неисследованной осталась возможность работы по защищенному протоколу https. Работа тонкого клиента, соединенного через прокси-сервер также не исследовалась, хотя теоретически возможна.
Используя статью, можно написать прокси-сервер не только для узкой области 1С: Предприятие, но и для других своих решений. Случай с 1С: Предприятие более сложный, и кроме обычной трансформации запросов и откликов необходимо искать некоторые обходные пути, чтобы решение заработало.
Пример доступен в Интернете по адресу: http://proxy.1csoftware.com

Скачать файлы

Наименование Файл Версия Размер Кол. Скачив.
Исходный код проекта Asp.Net MVC 3
.zip 506,92Kb
20.08.12
22
.zip 506,92Kb 22 Скачать

См. также

PowerTools от 1 000
Подписаться Добавить вознаграждение

Комментарии

1. Dmitry Dmitry (Dimasik2007) 20.08.12 21:00
На почту вам откатал, продублирую и тут (сначала на хабре статью увидел). Пример не работает под хромом:
Server Error in '/' Application.

Object reference not set to an instance of an object.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:


Line 173: node.Attributes.Add("style", "position:absolute;top:10px;right:10px;");
Line 174: node.Attributes.Add("src", VirtualPathUtility.ToAbsolute("~/i/1csoftware.png"));
Line 175: res.ChildNodes.Add(node);
Line 176:
Line 177: ViewBag.ResponseContent = Encoding.UTF8.GetBytes(html.DocumentNode.OuterHtml);
2. Misha ⁠ (Magister) 21.08.12 00:11
3. Сергей Карташев (Elisy) 21.08.12 07:17
(1) Dimasik2007,

Из сообщения не понятно, ошибка у вас возникает при запуске по ссылке http://proxy.1csoftware.com или через запуск исходных кодов?
4. Dmitry Dmitry (Dimasik2007) 21.08.12 10:55
Вылет на вашем сайте, по ссылке http://proxy.1csoftware.com/demo_ma/ru_RU/
причем демо от 1с открывается нормально.
5. Dmitry Dmitry (Dimasik2007) 21.08.12 10:56
И кстати, с яндекса вам письмо на суппорт не оптравляется, пишет типа ошибка релея и т.п.
6. Сергей Карташев (Elisy) 21.08.12 11:14
(4) Dimasik2007,
На хабре тоже жалуются на ошибки при открытии. Не могу пока предположить, в чем проблема.
Вот скриншоты для ФФ и ИЕ нормального открытия с моего компьютера:
fotki.yandex.ru/users/ebishkek/album/185477/?p=0
7. Сергей Карташев (Elisy) 21.08.12 11:22
(5) Dimasik2007,
К сожалению E-mail опубликован был устаревший. Рабочий адрес:
support(а)1csoftware.com
8. Stamper (Stamper) 22.08.12 11:09
9. Александр МАН (1977) 22.08.12 12:09
Принажатии сылки http://proxy.1csoftware.com/trade/ru_RU/
Выкидывает ошибки в хроме?
Server Error in '/' Application.
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:

Line 173: node.Attributes.Add("style", "position:absolute;top:10px;right:10px;");
Line 174: node.Attributes.Add("src", VirtualPathUtility.ToAbsolute("~/i/1csoftware.png"));
Line 175: res.ChildNodes.Add(node);
Line 176:
Line 177: ViewBag.ResponseContent = Encoding.UTF8.GetBytes(html.DocumentNode.OuterHtml);


Source File: c:\inetpub\vhosts\1csoftware.com\httpdocs\proxy\App_Code\ProxyController.cs Line: 175
10. Сергей Карташев (Elisy) 22.08.12 12:44
(9) 1977,
к сожалению не могу воспроизвести проблему. Специально установил Хром, открывается страница без ошибок:
11. Dmitry Dmitry (Dimasik2007) 22.08.12 12:54
(10 Если что - версия 22.0.1229.12 dev-m, на стабильных 22 тож не работает.
12. Александр МАН (1977) 22.08.12 12:55
Мне не приснилось
Прикрепленные файлы:
13. Сергей Карташев (Elisy) 22.08.12 13:04
(11)(12)
На других браузерах ФФ или ИЕ такая же проблема?
14. Александр МАН (1977) 22.08.12 13:17
15. Сергей Карташев (Elisy) 22.08.12 13:31
(14)
Получается дело не в конкретном браузере, а, может, в настройке сети.
Код, в котором происходит ошибка пытается в файле mainform.html найти div с id="preloaded".
Вы можете вручную загрузить файл http://proxy.1csoftware.com/trade/ru_RU/mainform.html
и в исходном коде посмотреть есть ли элемент <div id="preloader" ?
Может быть софт какой-нибудь Интернет-объявления обрезает и загрузочный рисунок 1С обрезал?

Вот исходный код:
//Add new content
            if (ViewBag.PathInfo != null)
            {
                if (ViewBag.PathInfo == "mainform.html")
                {
                    HtmlDocument html = new HtmlDocument();
                    html.OptionFixNestedTags = true;
                    html.LoadHtml(Encoding.UTF8.GetString(ViewBag.ResponseContent, 0, ViewBag.ResponseContent.Length));

                    var res = html.DocumentNode.SelectSingleNode("//div[@id='preloader']");
                    HtmlNode node = html.CreateElement("img");
                    node.Attributes.Add("id", "1csoftware-powered");
                    node.Attributes.Add("style", "position:absolute;top:10px;right:10px;");
                    node.Attributes.Add("src", VirtualPathUtility.ToAbsolute("~/i/1csoftware.png"));
                    res.ChildNodes.Add(node);

                    ViewBag.ResponseContent = Encoding.UTF8.GetBytes(html.DocumentNode.OuterHtml);
                }
            }
...Показать Скрыть
16. Andrey Semenov (aprol) 10.10.12 16:42
добавил в вебКонфиг
<system.web>
<httpRuntime requestPathInvalidCharacters="<,>,*,&,\,?" />
</system.web>
Но ошибка осталась. Не подскажете с чем еще может быть связана ошибка:

ошибка при работе с ресурсом /e1cib/logForm?cmd=call

?
17. Andrey Semenov (aprol) 10.10.12 18:01
хм, помог казалось бы самый бесполезный совет: "обновить публикацию под администратором"
18. Сергей Карташев (Elisy) 11.10.12 11:30
(16) aprol,
Добавлять нужно в раздел <system.web>
Привожу образец web.config для рабочего сайта
<?xml version="1.0"?>
<configuration>
  <configSections>
  </configSections>
  <appSettings>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
  </appSettings>
  <system.web>

    <globalization uiCulture="ru-RU" culture="ru-RU" />

    <!-- 
            Set compilation debug="true" to insert debugging 
            symbols into the compiled page. Because this 
            affects performance, set this value to true only 
            during development.
    -->
    <compilation debug="true">
      <assemblies>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
      </assemblies>
    </compilation>
    <!--
            The <authentication> section enables configuration 
            of the security authentication mode used by 
            ASP.NET to identify an incoming user. 
    -->
    <authentication mode="Forms">
      <forms loginUrl="~/account/logon" timeout="2880"/>
    </authentication>
    <!--<customErrors mode="RemoteOnly" defaultRedirect="GenericErrorPage.htm">
      -->
    <!--<error statusCode="403" redirect="NoAccess.htm" />
      <error statusCode="404" redirect="FileNotFound.htm" />-->
    <!--
    </customErrors>-->
    <customErrors mode="Off"/>

    <!--Enable % character-->
    <!--<httpRuntime requestPathInvalidCharacters="<,>,*,:,&,\,?" />-->
    <httpRuntime requestValidationMode="2.0" requestPathInvalidCharacters="<,>,*,&,\,?" />

    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages"/>
      </namespaces>
    </pages>
  </system.web>
  <system.webServer>
    <defaultDocument />
    <httpErrors errorMode="Detailed"/>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true">
    </modules>

  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
...Показать Скрыть
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа