I have this Action method in ASP.NET MVC 5:
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:nn" + e.ToString());
}
return View(result);
}
}
}
The action method acts as an API endpoint for our web application, where the API accepts username & password, and does the following:
-
Validate the username/password against Active Directory
-
If valid; check if the telephone number is empty >> if so return an error
-
If valid; check if department is empty >> if so return an error
-
If valid and info found; return the department & telephone for the user
Now I am a bit confused on how I need to return the JSON for the first 3 points? Should I always return http 200 with a status message (Status : "success" OR Status: "failed")? or if the username/password validation failed then i should return http 401 without having to return any JSON content?
Can anyone help me with this?
I need to write the action method in a standard way that can be consumed by 3rd party application.
Second question: what do I need to return in case the code raised an exception?
Thanks
3
Answers
There are a lot of ways to go about this and ultimately you want to have your endpoint behave in a way that whoever is consuming your endpoint expects.
I stumbled across this as an interesting way to handle nuanced errors in a request to your endpoint. Even though this is used for Graph API, you could use the concept for your needs. https://developers.facebook.com/docs/graph-api/guides/error-handling. The TL;DR is to have a standardized json response like:
The HTTP statuse codes are very flexable and can be confused to tell when to use what.
My advice:
initiated by the browser and that it is being processed (100–199).
expected info relayed to browser (200–299).
for the requested resource; further action by the browser may be
required (300–399).
unavailable or there was a technical problem with the request
(400–499).
after POST requests.
succeed.
For your example I would return:
The other option is a little tricky, when the user is authenticate but have no valid data.
you can return:
object
It also depends on your client and you preferences.
Happy coddind 🙂
This is an API error handling and logging design, and the following type of approach works well, to separate the concerns and keep your main logic clean:
DESIGN ERROR RESPONSES
These should be useful to clients, eg if they need to display an error or do something based on a specific cause. A 4xx error might have this payload, along with an HTTP status:
A 500 error is often given a different payload based on what a UI will display in this case, and how you look the error up in logs:
DESIGN API LOGS
In the first case the server logs might have fields such as these:
In the second case the server logs might have fields such as these:
CODE
Perhaps aim to use code similar to this, to represent your desired error and logging behaviour. The
ClientError
andServiceError
classes enable the above responses and logs. When errors are thrown this should enable you to add useful contextual info:MIDDLEWARE
The usual pattern is then to use small middleware classes to deal with processing exceptions, returning error responses and writing error logs:
The type of logic written here will depend a little on your preferences, but might look similar to this: