我有一个 Asp.Net MVC 5 Web 应用程序,它有一个错误处理机制,用于在 DB 中记录错误,并以名为 ErrorPage 的单独形式向引发该错误的用户显示 errorId。当 Web 应用程序中发生错误时,我将该 errorId 存储在会话中,并从 ErrorPage 中的会话中读取它,以向遇到此错误的用户显示该 errorId,以便能够进行备份操作。此 Web 应用程序的 Web 服务器当前仅使用一个工作进程处理请求,因此所有生成的会话在整个 Web 应用程序中都是有效且可访问的。
我打算将此 Web 应用程序的工作进程数从 1 个增加到 4 个,但我的 Web 应用程序有一些问题。同样在 IIS 中,我将会话状态模式设置为进程中模式,因为在 Web 应用程序中我在很多情况下都使用了会话,并且我无法将其设置为SQL Server模式,因为它会增加性能开销。
问题是请求进入工作进程 A(例如),并且将在工作进程 A 中为此请求生成一个会话,假设此请求在 Web 应用程序中遇到错误,我会将用户重定向到 ErrorPage,它是这个新请求(将用户重定向到 ErrorController 中的 ErrorPage 操作)可能会进入另一个工作进程 B(例如)。但是在工作进程 B 中,我无法访问为第一个请求生成的会话,因为该会话是在工作进程级别定义的,并且它们仅在该工作进程中有效。
因此,经过大量搜索后,我决定将会话信息保存在 DB 而不是 Ram 中,并在需要该信息时从 DB 加载它。但我不知道用哪个密钥 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????
如果无法做到这一点,有没有办法在 Web 服务器的所有工作进程中共享标识符?
编辑:
在本次编辑中,我将解释我在 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
的内容将存储在会话中,用于通过ErrorCode键将 errorId 传递给 ErrorPage 以显示给用户。