Работа с сессиями и несколькими рабочими процессами в Asp.Net MVC 5 - PullRequest
0 голосов
/ 01 марта 2019

У меня есть веб-приложение 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 Ответ

0 голосов
/ 02 марта 2019

Я нашел временное решение для передачи 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; }
}

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

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

...