我正在玩一个webapi2项目。 使用控制器类->
调用处理业务逻辑的服务类->
使用处理数据库调用的存储库。 为了提高可读性,我决定在服务类中使用nullcheck(即:
var object = _repository.GetById(5) ?? throw new CustomException(CustomException.Object1NotFound_Exception_Message);
)。
通过这种方式,我的控制器逻辑保持清晰可读,避免在控制器方法[get / post / put / delete]中进行这些检查。
这样,我可以尝试/捕获我的控制器逻辑,然后捕获(customexception ex) 并调用扩展方法ex.converttostatuscoderesult。 (如下所示)。
public class CustomException : Exception
{
public const string Object1NotFound_Exception_Message = "Object not found using ID.";
public const string Object2NotFound_Exception_Message = "Object2 not found using ID.";
public const string UserNotAllowedX_Exception_Message = "Current user not allowed to do X.";
public const string UserNotAllowedY_Exception_Message = "Current user not allowed to do Y.";
<~even more strings containing ExceptionMessages>
public int ExceptionStatusCodeDefinition { get; set; }
public CustomException(string message) : base(message)
{
switch (message)
{
case Object1NotFound_Exception_Message:
case Object2NotFound_Exception_Message:
ExceptionStatusCodeDefinition = 404;
break;
case UserNotAllowedX_Exception_Message:
case UserNotAllowedY_Exception_Message:
case UserNotAllowedZ_Exception_Message:
ExceptionStatusCodeDefinition = 403;
break;
default:
ExceptionStatusCodeDefinition = 400;
break;
}
}
}
public static class CustomExceptionExtention
{
public static IActionResult ConvertToStatusCodeResult(this CustomException exception)
{
return new Microsoft.AspNetCore.MvcStatusCodeResult(exception.ExceptionStatusCodeDefinition);
}
}
但是,此方法要求我事先设置异常消息。 这不可避免地意味着我的异常消息列表太长了。
我尝试重构此方法,以推断类型的名称并具有一条异常消息NotFound_Exception_Message。并在运行时附加类型名称。
起初,我尝试对Type进行切换,由于编译器的原因(我理解它的方式,如果继承起着作用,编译器无法告诉我需要哪种类型名),该开关不起作用
试图绕过这个,我进了这堂课:
public class TypeCase
{
public static TypeCase GetType(Type type)
{
return new TypeCase(type);
}
public string TypeName { get; set; }
public TypeCase(object type)
{
TypeName = type.GetType().Name;
}
}
只要对象具有值,此方法就可以正常工作,因为如果该对象引用为null,则无法反映该对象的实例。
我一直在为这个问题而烦恼。 我希望有人可以阐明这个问题,或者向我解释为什么这是一个不好的解决方案。 因为我开始认为这种方法是确定的代码气味。
(我知道这种方法不会在IActionResult中返回异常消息。这也是一个问题,但超出了此问题的范围。)
非常感谢您在此问题上的帮助。
答案 0 :(得分:1)
直接的答案是不,您不能做自己想做的事情。如果由于函数返回null而引发异常,则无法检查将要返回的对象的类型。
您所知道的就是GetById
返回的声明的类型。换句话说,如果该函数声明为
Foo GetById(int id)
那么您知道它返回的是一个Foo
。如果返回结果,则可以检查它的类型是否为Foo
或从Foo
继承的其他类型。但是,如果您没有得到结果,您将只能知道它是Foo
。但是由于您要的是Foo
,所以这是唯一重要的类型。
换句话说,不需要推断该方法返回的类型。它声明返回的类型。您知道类型是什么,因为您正在调用方法来获取该类型的对象。如果您还不知道类型是什么,就没有理由调用该方法。
既然您知道类型,并且唯一使下一条异常消息与下一条不同的详细信息就是类型,那么下一步就是弄清楚如何在异常消息中传达该类型。
说实话,这是我们经常忽略的事情。您可能对此没事:
var object = _repository.GetById(5) ?? throw new CustomException("Foo not found using ID.");
真的,有多严重?即使该消息只是“找不到Foo”,堆栈跟踪也会向您显示该方法,然后您可以从中确定使用ID检索它的方法。
使用常量很好,但是当值具有重要含义时,它就变得更加重要。如果您的下一个例外有错别字-“未使用ID标记错误”-会很杂乱,但不会破坏任何内容。如果消息要长得多并且重复出现,我还可以看到使用一个常量。
到目前为止,这是我的第一个建议。如果您确实想确保异常消息是恒定的,并且只在一个地方声明,并且无论如何都在创建自定义异常,则可以执行以下操作(尽管我真的不是。)
// Really, don't do this.
public class ItemNotFoundByIdException<T> : Exception
{
public ItemNotFoundByIdException()
:base($"{typeof(T).Name} not found by ID.") { }
}
然后,如果您尝试通过ID获取Foo
,则可以执行以下操作:
var foo = _repository.GetById(5) ?? throw new ItemNotFoundByIdException<Foo>();
但这会导致异常的复杂层次结构。除非您或其他人要捕获此特定的异常类型并以与其他异常类型不同的方式处理它,否则这将是额外的复杂性,并且没有任何好处。
我知道我们会如何沉迷于这种事情,但这不是您的应用程序的重要组成部分。这不值得。我只是将这些简短的异常消息硬编码在需要的地方。
答案 1 :(得分:0)
您可以尝试使用泛型,并创建一个辅助函数来为您进行检查。
public static T GetWithNullCheck<T>(Func<T> fetchFunc)
{
T t = fetchFunc();
if (t != null) return t;
var typeOfT = typeof(T);
var typeName = typeOfT.Name;
throw new CustomException($"{typeName} not found.");
// short version
// return fetchFunc() ?? throw new CustomException($"{typeof(T).Name} not found.");
}
您可以像这样使用它
var object = GetWithNullCheck(() => _repository.GetById(5));