繁体   English   中英

如何在 Asp.Net Core MVC 的 ExceptionFilter 中返回带有 model 的视图

[英]How return View with model in the ExceptionFilter in Asp.Net Core MVC

我创建了一个 Asp.Net Core MVC 应用程序。 我想处理两种类型的错误。

我创建了两个异常: UserFriendlyExceptionUserFriendlyViewException

我已尝试根据以下规则创建我需要处理这两个异常的ExceptionFilter

如果调用了UserFriendlyViewException异常,那么我想返回带有原始 ViewName 和AddModelError的 ViewResult 并返回原始 Model。

如果调用了UserFriendlyException异常,那么我想重定向到Error视图。

这是我的ExceptionFilterAttribute

public class ControllerExceptionFilterAttribute : ExceptionFilterAttribute
{
    private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public ControllerExceptionFilterAttribute(ITempDataDictionaryFactory tempDataDictionaryFactory,
                IModelMetadataProvider modelMetadataProvider)
    {
        _tempDataDictionaryFactory = tempDataDictionaryFactory;
        _modelMetadataProvider = modelMetadataProvider;
    }
    public override void OnException(ExceptionContext context)
    {
        if (!(context.Exception is UserFriendlyException) && !(context.Exception is UserFriendlyViewException)) return;

        var tempData = _tempDataDictionaryFactory.GetTempData(context.HttpContext);
        //CreateNotification(NotificationHelpers.AlertType.Error, tempData, context.Exception.Message);
        if (!tempData.ContainsKey(NotificationHelpers.NotificationKey)) return;

        if (context.Exception is UserFriendlyViewException userFriendlyViewException)
        {
            context.ModelState.AddModelError(userFriendlyViewException.ErrorKey, userFriendlyViewException.Message);
        }

        if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
        {
            //How pass here Model from context??
            //If exists more views with same name but in another controller how pass correct ViewName?
            var result = new ViewResult
            {
                ViewName = context.Exception is UserFriendlyViewException ?
                controllerActionDescriptor.ActionName
                : "Error",
                TempData = tempData,
                ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState)
                            {
                                {"Notifications", tempData[NotificationHelpers.NotificationKey] },
                            }
            };

            context.ExceptionHandled = true;
            context.Result = result;
        }

        tempData.Remove(NotificationHelpers.NotificationKey);
    }
}

我有两个问题:

1.) 如何将原始ModelExceptionContext传递到ViewResult

2.) 如果存在更多具有相同名称但在另一个 Controller 中的视图,如何为UserFriendlyViewException返回正确的 ViewName?

如何将原始模型从 ExceptionContext 传递给 ViewResult?

您可以使用context.ModelState集合。

 foreach(var item in context.ModelState)
 {
    string parameter = item.Key;
    object rawValue = item.Value.RawValue;
    string attemptedValue = item.Value.AttemptedValue;

    System.Console.WriteLine($"Parameter: {parameter}, value: {attemptedValue}");
 }

请注意,集合将仅包含绑定参数。


如果存在更多同名但在另一个控制器中的视图,如何为 UserFriendlyViewException 返回正确的 ViewName?

框架将使用与控制器操作中相同的视图发现过程,因此您可以指定路径而不是视图名称:

可以提供视图文件路径而不是视图名称。 如果使用从应用根目录开始的绝对路径(可以选择以“/”或“~/”开头),则必须指定 .cshtml 扩展名:

 return View("Views/Home/About.cshtml");

您还可以使用相对路径在不带 .cshtml 扩展名的情况下指定不同目录中的视图。 在 HomeController 中,您可以使用相对路径返回 Manage 视图的 Index 视图:

 return View("../Manage/Index");

同样,您可以使用“./”前缀指示当前特定于控制器的目录:

 return View("./About");

通过 ViewData 传递东西

public class UIFriendlyExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is UIFriendlyException uex)
        {
            var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), context.ModelState);

            //here we go
            viewData.Add("Message", MY_CUSTOM_MESSAGE);

            var res = new ViewResult() {
                ViewName = "Error",
                ViewData = viewData
            };
            context.Result = res;
        }
        context.ExceptionHandled = true;
    }
}

然后在 Startup 中注册

services.AddControllersWithViews(options =>
{ 
    options.Filters.Add(typeof(UIFriendlyException.UIFriendlyExceptionFilter));
});

准备两个过滤器。 一个缓存绑定模型,另一个处理异常。

动作过滤器

public class BindModelCacheFilter : IActionFilter
{
    public static readonly string Key = "BindModelCacheFilterKey. This string can be anything.";

    public void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Items[BindModelCacheFilter.Key] = context.ActionArguments;
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

异常过滤器

public class ExceptionHandlingFilter : IExceptionFilter
{
    private readonly ILogger<ExceptionHandlingFilter> _logger;

    public ExceptionHandlingFilter(ILogger<ExceptionHandlingFilter> logger)
    {
        _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        // ...

            var requestPayload = "";
            try
            {
                var actionArguments = context.HttpContext.Items[BindModelCacheFilter.Key] as IDictionary<string, object?>;
                if (0 < actionArguments?.Count)
                {
                    object requestDto = actionArguments.First().Value!;
                    var options = new System.Text.Json.JsonSerializerOptions()
                    {
                        IncludeFields = true,
                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All)
                    };
                    requestPayload = System.Text.Json.JsonSerializer.Serialize(requestDto, options);
                }
            }
            catch
            {
            }
        // ...
    }
}

创建一个从 IExceptionFilter 和 IActionFilter 派生的 ExceptionFilter

这使您可以访问视图和 controller

[TypeFilter(typeof(ExceptionFilter))]
public class MyController : ControllerBase
{
    public IActionResult SomeAction([Bind("ID")] MyView vm) {}
}

public class ExceptionFilter : IExceptionFilter, IActionFilter
{
    private MyController? MyController { get; set; }
    private MyViewModel vm { get; set; }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        MyController = (MyController)context.Controller;
        //
        if (context.ActionArguments.TryGetValue("vm", out object vmObj))            
           vm = vmObj as MyView;
        else
           vm = null;

    }

    public void OnException(ExceptionContext context)
    {
        // Do something using MyController and MyView
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM