Работа с сессиями и несколькими рабочими процессами в Asp.Net MVC 5

У меня есть веб-приложение Asp.Net MVC 5, в котором есть механизм обработки ошибок для регистрации ошибок в БД и отображение errorId для пользователя, который поднял эту ошибку в отдельной форме, которая называется ErrorPage. Когда в веб-приложении возникает ошибка, я сохраняю этот errorId в сеансе и считываю его из сеанса в ErrorPage, чтобы показать этот errorId пользователю, столкнувшемуся с этой ошибкой, чтобы иметь возможность выполнять операции резервного копирования. Веб-сервер этого веб-приложения в настоящее время обрабатывает запросы только с одним рабочим процессом, поэтому все сгенерированные сеансы являются действительными и доступны во всем веб-приложении.

Я собираюсь увеличить количество рабочих процессов с 1 до 4 для этого веб-приложения, но у меня есть некоторые проблемы с моим веб-приложением. Кроме того, в IIS я установил режим состояния сеанса в режим " В процессе ", поскольку в веб-приложении во многих случаях я использовал сеанс и не могу установить его в режим SQL Server, так как это приведет к увеличению производительности.

Проблема в том, что запрос отправляется в рабочий процесс A (например), и для этого запроса в рабочем процессе A создается сессия, и предположим, что этот запрос обнаруживает ошибку в веб-приложении, я перенаправлю пользователя на ErrorPage, и это возможно, этот новый запрос (перенаправление пользователя на действие ErrorPage в ErrorController) переходит в другой рабочий процесс B (например). Но в рабочем процессе B я не могу получить доступ к тому сеансу, который сгенерирован для первого запроса, потому что сеансы определены на уровне рабочего процесса, и они действительны только в этом рабочем процессе.

Поэтому после долгих поисков я решил сохранить информацию о сеансе в БД, а не в Ram, и загрузить ее из БД, когда мне понадобится эта информация. Но я понятия не имею о сохранении этой информации в БД с каким ID ключа?

Вообразите этот сценарий, чтобы легче выяснить мою реальную проблему:

Давайте:

WorkerProcessId1 = W1;
WorkerProcessId2 = W2;
SessionId1 = S1;
SessionId2 = S2;
RequestId1 = R1;
RequestId2 = R2;

и сценарий:

R1 comes to web server
==> web server passes R1 to W1
==> W1 generates S1 for R1
==> R1 faces an error
==> for the user who sends R1 (it is possible the user has not logged in yet so I don't know the userId), I will save the error in DB using the combination of S1 and userId in a specific pattern as a unique identifier in Error table in DB
==> the user will redirect to ErrorPage with another request R2
==> web server passes R2 to W2
==> W2 generates S2 for R2
==> after the redirect is done, in the ErrorPage I need the errorId of that error which I save it to DB, for showing it to the user for backup operations
==> I don't know which error belongs to this user and which error should be load from DB????

Если это невозможно сделать, есть ли способ иметь общий идентификатор для всех рабочих процессов веб-сервера?

Редактировать:

В этом редакторе я объясню, где и как я использовал сессию в моем механизме ErrorHandling. В конце строки назначения есть закомментированная фраза, где написано: "Здесь я использую сессию":

namespace MyDomain.UI.Infrastructure.Attributes
{
    public class CustomHandleErrorAttribute : HandleErrorAttribute
    {
        public CustomHandleErrorAttribute()
        {

        }

        public override void OnException(ExceptionContext filterContext)
        {
            if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            {
                return;
            }

            if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
            {
                return;
            }

            var errorid = 0;
            try
            {
                errorid = SaveErrorToDatabase(filterContext);
            }
            catch (Exception e)
            {
                //Console.WriteLine(e);
                //throw;
            }

            // if the request is AJAX return JSON else view.
            if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
            {
                filterContext.Result = new JsonResult
                {
                    JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                    Data = new
                    {
                        error = true,
                        message = "Error Message....",
                        errorid,
                    }
                };
            }
            else
            {
                var controllerName = (string)filterContext.RouteData.Values["controller"];
                var actionName = (string)filterContext.RouteData.Values["action"];
                var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);

                filterContext.Controller.TempData.Clear();
                filterContext.Controller.TempData.Add("ErrorCode", errorid);//Here I am using session

                filterContext.Result = new ViewResult
                {
                    ViewName = View,
                    MasterName = Master,
                    ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                    TempData = filterContext.Controller.TempData
                };
            }

            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = 500;

            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }

        private int SaveErrorToDatabase(ExceptionContext exception)
        {
            MyDomainDBContext dbContext = new MyDomainDBContext();

            var browserType = exception.HttpContext.Request.Browser.Capabilities["type"];

            var error = new Error
            {
                ErrorURL = exception.HttpContext.Request.Url.ToString(),
                ExceptionType = exception.Exception.GetType().Name,
                IsGlobalError = false,
                Message = exception.Exception.Message,
                StackTrace = exception.Exception.StackTrace,
                ThrownTime = DateTime.Now,
                UserIP = IPAddress.Parse(exception.HttpContext.Request.UserHostAddress).ToString(),
                BrowserName = browserType.ToString() + "," +
                GetUserPlatform(exception.HttpContext.Request)
            };

            AddRequestDetails(exception.Exception, exception.HttpContext.Request, error);

            if (exception.Exception.InnerException != null)
            {
                error.Message += "\n Inner Excpetion : \n " + exception.Exception.InnerException.Message;

                if (exception.Exception.InnerException.InnerException != null)
                {
                    error.Message += "\n \t Inner Excpetion : \n " + exception.Exception.InnerException.InnerException.Message;
                }
            }

            if (exception.HttpContext.User.Identity.IsAuthenticated)
            {
                error.UserID = exception.HttpContext.User.Identity.GetUserId<int>();
            }

            dbContext.Errors.Add(error);
            dbContext.SaveChanges();

            return error.ErrorID;
        }

        private void AddRequestDetails(Exception exception, HttpRequestBase request, Error err)
        {
            if (exception.GetType().Name == "HttpAntiForgeryException" && exception.Message == "The anti-forgery cookie token and form field token do not match.")
            {
                if (request.Form != null)
                {
                    if (request.Cookies["__RequestVerificationToken"] != null)
                    {

                        err.RequestDetails = "Form : " + request.Form["__RequestVerificationToken"] +
                                             " \n Cookie : " + request.Cookies["__RequestVerificationToken"].Value;

                    }
                    else
                    {
                        err.RequestDetails = "Does not have cookie for forgery";
                    }
                }
            }
        }

        private String GetUserPlatform(HttpRequestBase request)
        {
            var ua = request.UserAgent;

            if (ua.Contains("Android"))
                return $"Android";

            if (ua.Contains("iPad"))
                return $"iPad OS";

            if (ua.Contains("iPhone"))
                return $"iPhone OS";

            if (ua.Contains("Linux") && ua.Contains("KFAPWI"))
                return "Kindle Fire";

            if (ua.Contains("RIM Tablet") || (ua.Contains("BB") && ua.Contains("Mobile")))
                return "Black Berry";

            if (ua.Contains("Windows Phone"))
                return $"Windows Phone";

            if (ua.Contains("Mac OS"))
                return "Mac OS";

            if (ua.Contains("Windows NT 5.1") || ua.Contains("Windows NT 5.2"))
                return "Windows XP";

            if (ua.Contains("Windows NT 6.0"))
                return "Windows Vista";

            if (ua.Contains("Windows NT 6.1"))
                return "Windows 7";

            if (ua.Contains("Windows NT 6.2"))
                return "Windows 8";

            if (ua.Contains("Windows NT 6.3"))
                return "Windows 8.1";

            if (ua.Contains("Windows NT 10"))
                return "Windows 10";

            //fallback to basic platform:
            return request.Browser.Platform + (ua.Contains("Mobile") ? " Mobile " : "");
        }
    }

    public class IgnoreErrorPropertiesResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty property = base.CreateProperty(member, memberSerialization);

            if (new[]{
                "InputStream",
            "Filter",
            "Length",
            "Position",
            "ReadTimeout",
            "WriteTimeout",
            "LastActivityDate",
            "LastUpdatedDate",
            "Session"
            }.Contains(property.PropertyName))
            {
                property.Ignored = true;
            }
            return property;
        }
    }
}

Как вы можете видеть, я заполнил TempData который будет сохранен в сеансе для передачи errorId по ключу ErrorCode в ErrorPage для показа пользователю.

1 ответ

Я нашел временное решение для передачи errorId в ErrorPage, создав новый класс, унаследованный от HandleErrorInfo со следующей структурой, и используя этот errorId в ErrorPage:

public class HandleErrorInfoExtension : HandleErrorInfo
{
    public HandleErrorInfoExtension(Exception exception, string controllerName, string actionName, int errorId) : base(exception, controllerName, actionName)
    {
        ErrorId = errorId;
    }

    public int ErrorId { get; set; }
}

Но я не принимаю свой собственный ответ, потому что все же я ищу реальное решение для решения основной проблемы этого вопроса, которая заключается в возможности обмена данными (или структурой данных) между всеми рабочими процессами приложения. Вы должны знать, что я использовал сессию в некоторых других местах своего приложения, что некоторые из этих мест жизненно важны (например, модуль оплаты), поэтому я не нахожу основного решения для устранения использования сессии (кроме использования хранения данных БД из-за накладных расходов) пока нет. Поэтому я прошу сообщество разработчиков Stackru.com помочь мне решить эту проблему.

Спасибо всем вашим дорогим коллегам.

Другие вопросы по тегам