Web Services 在我们的ASP.NETMVC5Web应用程序中,以标准的方式编写我们的API端点,以覆盖所有业务场景

8iwquhpp  于 2022-11-15  发布在  .NET
关注(0)|答案(3)|浏览(128)

我在ASP.NETMVC5中有这个Action方法:

namespace LDAPMVCProject.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult UsersInfo(string username, string password)
        {
            DomainContext result = new DomainContext();

            try
            {
                // create LDAP connection object  
                DirectoryEntry myLdapConnection = createDirectoryEntry();
                string ADServerName = System.Web.Configuration.WebConfigurationManager.AppSettings["ADServerName"];
                string ADusername = System.Web.Configuration.WebConfigurationManager.AppSettings["ADUserName"];
                string ADpassword = System.Web.Configuration.WebConfigurationManager.AppSettings["ADPassword"];

                using (var context = new DirectoryEntry("LDAP://mydomain.com:389/DC=mydomain,DC=com", ADusername, ADpassword))
                using (var search = new DirectorySearcher(context))
                {
                    // validate username & password
                    using (var context2 = new PrincipalContext(ContextType.Domain, "mydomain.com", ADusername, ADpassword))
                    {
                        bool isvalid = context2.ValidateCredentials(username, password);
                        
                        if !(isvalid)
                            return **** // authentication error
                    }

                    // create search object which operates on LDAP connection object  
                    // and set search object to only find the user specified  

                    //    DirectorySearcher search = new DirectorySearcher(myLdapConnection);
                    //  search.PropertiesToLoad.Add("telephoneNumber");
                    search.Filter = "(&(objectClass=user)(sAMAccountName=test.test))";

                    // create results objects from search object  

                    // user exists, cycle through LDAP fields (cn, telephonenumber etc.)  
                    SearchResult r = search.FindOne();

                    ResultPropertyCollection fields = r.Properties;

                    foreach (String ldapField in fields.PropertyNames)
                    {
                        if (ldapField.ToLower() == "telephonenumber")
                        {
                            foreach (Object myCollection in fields[ldapField])
                            {
                                result.Telephone = myCollection.ToString();
                            }
                        }
                        else if (ldapField.ToLower() == "department")
                        {
                            foreach (Object myCollection in fields[ldapField])
                            {
                                result.Department = myCollection.ToString();
                            }
                        }
                       // }
                    }
                    
                    if (result.Telephone == null)
                        return ***** //Telephone is empty

                    if (result.Department)
                        return **** // department is empty

                    string output = JsonConvert.SerializeObject(result);

                    return Content(output, "application/json");//success
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception caught:\n\n" + e.ToString());
            }

            return View(result);
        }
    }
}

action方法充当Web应用程序的API端点,其中API接受用户名和密码,并执行以下操作:
1.根据Active Directory验证用户名/密码
1.如果有效,检查电话号码是否为空〉〉如果为空,则返回错误
1.如果有效;检查部门是否为空〉〉如果为空,则返回错误
1.如果有效且找到信息;返回用户的部门和电话
现在我有点搞不清楚我需要如何返回前3点的JSON?我应该总是返回带有状态消息的http 200吗(状态:“成功”或状态:“failed”)?或者如果用户名/密码验证失败,则应返回http 401,而不必返回任何JSON内容?
有人能帮我吗?
我需要以一种标准的方式编写动作方法,以便第三方应用程序使用。
第二个问题:如果代码引发了异常,我需要返回什么呢?
谢谢

6ioyuze2

6ioyuze21#

这是一个API错误处理和日志记录设计,以下类型的方法非常有效,可以分离关注点并保持主逻辑清晰:

设计错误响应

这对客户端应该很有用,例如,如果他们需要显示一个错误或基于特定原因执行一些操作。4xx错误可能会有这样的负载,沿着一个HTTP状态:

{
  "code": "authentication_failed",
  "message": "Invalid credentials were provided"
}

500错误通常会根据UI在这种情况下显示的内容以及您在日志中查找错误的方式而被赋予不同的有效负载:

{
  "code": "authentication_error",
  "message": "A problem was encountered during a backend authentication operation",
  "area": "LDAP",
  "id": 12745,
  "utcTime": "2022-07-24T10:27:33.468Z"
}

设计API测井

在第一种情况下,服务器日志可能包含如下字段:

{
  "id": "7af62b06-8c04-41b0-c428-de332436d52a",
  "utcTime": "2022-07-24T10:27:33.468Z",
  "apiName": "MyApi",
  "operationName": "getUserInfo",
  "hostName": "server101",
  "method": "POST",
  "path": "/userInfo",
  "errorData": {
    "statusCode": 401,
    "clientError": {
      "code": "authentication_failed",
      "message": "Invalid credentials were provided",
      "context": "The account is locked out"
    }
  }
}

在第二种情况下,服务器日志可能包含如下字段:

{
  "id": "7af62b06-8c04-41b0-c428-de332436d52a",
  "utcTime": "2022-07-24T10:27:33.468Z",
  "apiName": "MyApi",
  "operationName": "getUserInfo",
  "hostName": "server101",
  "method": "POST",
  "path": "/userInfo",
  "errorData": {
    "statusCode": 500,
    "clientError": {
      "code": "authentication_error",
      "message": "A problem was encountered during a backend authentication operation",
      "area": "LDAP",
      "id": 12745,
      "utcTime": "2022-07-24T10:27:33.468Z"
    },
    "serviceError": {
      "details": "Host not found: error MS78245",
      "stack": [
        "Error: An unexpected exception occurred in the API",
        "at DirectorySearcher: 871 ... "
      ]
  }
}

代码

也许可以使用类似的代码来表示你想要的错误和日志行为。ClientErrorServiceError类支持上面的响应和日志。当抛出错误时,这应该可以让你添加有用的上下文信息:

public class HomeController : Controller
{
    public ActionResult UsersInfo(string username, string password)
    {
        DomainContext result = new DomainContext();

        try
        {
            DirectoryEntry myLdapConnection = createDirectoryEntry();
            string ADServerName = System.Web.Configuration.WebConfigurationManager.AppSettings["ADServerName"];
            string ADusername = System.Web.Configuration.WebConfigurationManager.AppSettings["ADUserName"];
            string ADpassword = System.Web.Configuration.WebConfigurationManager.AppSettings["ADPassword"];

            using (var context = new DirectoryEntry("LDAP://mydomain.com:389/DC=mydomain,DC=com", ADusername, ADpassword))
            using (var search = new DirectorySearcher(context))
            {
                using (var context2 = new PrincipalContext(ContextType.Domain, "mydomain.com", ADusername, ADpassword))
                {
                    bool isvalid = context2.ValidateCredentials(username, password);
                    if !(isvalid)
                        throw new ClientError(401, "authentication_failed", "Invalid credentials were provided", "optional context goes here");
                }

                DirectorySearcher search = new DirectorySearcher(myLdapConnection);
                search.Filter = "(&(objectClass=user)(sAMAccountName=test.test))";

                SearchResult r = search.FindOne();
                ResultPropertyCollection fields = r.Properties;
                foreach (String ldapField in fields.PropertyNames)
                {
                    if (ldapField.ToLower() == "telephonenumber")
                    {
                        foreach (Object myCollection in fields[ldapField])
                        {
                            result.Telephone = myCollection.ToString();
                        }
                    }
                    else if (ldapField.ToLower() == "department")
                    {
                        foreach (Object myCollection in fields[ldapField])
                        {
                            result.Department = myCollection.ToString();
                        }
                    }
                }
                
                if (result.Telephone == null)
                    throw new ClientError(400, "invalid_user_data", "User data is invalid", "Telephone is missing");

                if (result.Department)
                    throw new ClientError(400, "invalid_user_data", "User data is invalid", "Department is missing");

                string output = JsonConvert.SerializeObject(result);
                return Content(output, "application/json");
            }
        }
        catch (Exception e)
        {
            throw new ServiceError("authentication_error", "A problem was encountered during a backend authentication operation", "LDAP", e);
        }

        return View(result);
    }
}

中间件

通常的模式是使用小的中间件类来处理处理异常、返回错误响应和编写错误日志:

此处所写的逻辑类型将取决于您的偏好,但可能类似于以下内容:

public class ErrorFilterAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        var logEntry = new ErrorLogEntry();
        var jsonResponse = ""
        var statusCode = 500;

        if (filterContext.Exception is ClientError)
        {
            var clientError = filterContext.Exception as ClientError;
            logEntry.AddClientErrorDetails(clientError);
            statusCode = clientError.StatusCode;
            jsonResponse = clientError.toResponseFormat();
        }
        if (filterContext.Exception is ServiceError)
        {
            var serviceError = filterContext.Exception as ServiceError;
            logEntry.AddServiceErrorDetails(serviceError);
            statusCode = serviceError.StatusCode;
            jsonResponse = serviceError.toResponseFormat();
        }
        logEntry.Write();

        filterContext.Result = new JsonResult(jsonResponse);
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = statusCode;
        filterContext.ExceptionHandled = true;
    }
}
szqfcxe2

szqfcxe22#

有很多方法可以实现这一点,最终您希望让您的端点以任何使用您的端点的人所期望的方式运行。
我偶然发现这是一种处理端点请求中细微错误的有趣方法。尽管这是用于Graph API的,但您可以根据自己的需要使用这个概念。DR将具有标准化的json响应,如:

{
  "error": {
    "message": "Message describing the error", 
    "type": "OAuthException", 
    "code": 190,
    "error_subcode": 460,
    "error_user_title": "A title",
    "error_user_msg": "A message",
    "fbtrace_id": "EJplcsCHuLu"
  }
}
ymzxtsji

ymzxtsji3#

HTTP状态代码是非常灵活的,可能会混淆什么时候使用什么。我的建议:
1.标识Http状态系列(X 00)

  • 100 s:信息代码:服务器确认由浏览器发起的请求,并且确认该请求正在被处理(100-199)。
  • 200 s:成功代码:请求被接收、理解、处理和期望的信息被中继到浏览器(200-299)。
  • 300 s:重定向代码:不同的目的地已经替代了所请求的资源;可能需要浏览器的进一步动作(300-399)。
  • 400 s:客户端错误代码:未访问网站或页面;页面不可用或请求存在技术问题(400-499)。
  • 500 s:服务器错误代码

1.搜索您的响应的特定Http状态代码(2XX)以下是200系列的一些示例:

  • 201:已创建。请求已完成;已创建新资源。POST请求后的典型响应。
  • 202:已接受。浏览器请求已接受,仍在处理中。可能成功,也可能不成功。

对于您的示例,我将返回:

  • 403:禁止-如果用户凭据错误。
  • 200:OK -如果一切都运行良好(返回所有信息)。

另一个选项有点棘手,当用户通过身份验证但没有有效数据时。您可以返回:

  • 204:没有内容-因为用户已被验证但没有数据
  • 500:内部服务器错误-因为服务器无法返回请求的对象
  • 404:页面没有找到-(不是我的个人选择,但它是一个选项)

这也取决于你的客户和你的喜好。
快乐的coddind:)

相关问题