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:\n\n" + 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
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:
{
"code": "authentication_failed",
"message": "Invalid credentials were provided"
}
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:
{
"code": "authentication_error",
"message": "A problem was encountered during a backend authentication operation",
"area": "LDAP",
"id": 12745,
"utcTime": "2022-07-24T10:27:33.468Z"
}
DESIGN API LOGS
In the first case the server logs might have fields such as these:
{
"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"
}
}
}
In the second case the server logs might have fields such as these:
{
"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 ... "
]
}
}
CODE
Perhaps aim to use code similar to this, to represent your desired error and logging behaviour. The ClientError and ServiceError classes enable the above responses and logs. When errors are thrown this should enable you to add useful contextual info:
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);
}
}
MIDDLEWARE
The usual pattern is then to use small middleware classes to deal with processing exceptions, returning error responses and writing error logs:
logging filter
exception filter
The type of logic written here will depend a little on your preferences, but might look similar to this:
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;
}
}
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:
{
"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"
}
}
The HTTP statuse codes are very flexable and can be confused to tell when to use what.
My advice:
Identify the Http status family (X00)
100s: Informational codes: the server acknowledges the request
initiated by the browser and that it is being processed (100–199).
200s: Success codes: request received, understood, processed and
expected info relayed to browser (200–299).
300s: Redirection codes: a different destination has been substituted
for the requested resource; further action by the browser may be
required (300–399).
400s: Client error codes: website or page not reached; page
unavailable or there was a technical problem with the request
(400–499).
500s: Server error codes
Search for the specific Http status code for your response (2XX) here some exemples for the 200 family:
201: Created. Request fulfilled; new resource created. Typical response
after POST requests.
202: Accepted. Browser request accepted, still in process. May or may not
succeed.
For your example I would return:
403: Forbidden - if the user credentials are wrong.
200: Ok - if everythig works well (all the info returned).
The other option is a little tricky, when the user is authenticate but have no valid data.
you can return:
204: No content - because the user is auth but has no data
500: internal server error - because the server cant return the requested
object
404: Not found - (not my personal chois but it is an option)
It also depends on your client and you preferences.
Happy coddind :)
I have this :
[HttpDelete]
public HttpResponseMessage DeleteClient(int idCliente)
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
how to return a message text next to status?
You could do something like this:
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("The Message")
};
If you want to return JSON (using Newtonsoft.Json library), you could do:
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(
JsonConvert.SerializeObject(new { message = "The Message" }),
Encoding.UTF8, "application/json")
};
Why do you have HttpResponseException in the subject? If you really need to return an error status code with a message while throwing an exception, HttpResponseException has a constructor that takes an HttpResponseMessage instance.
But in .NET Core that exception only in the Microsoft.AspNetCore.Mvc.WebApiCompatShim backwards compatibility package. The recommended way is just to return the HttpResponseMessage with an error status code directly.
I am trying to get a basic push notification sent to my Action.
I am getting an access token as such
private static async Task<string> GetAccessTokenFromJsonKeyAsync(string jsonKeyFilePath, params string[] scopes)
{
using (var stream = new FileStream(jsonKeyFilePath, FileMode.Open, FileAccess.Read))
{
return await GoogleCredential
.FromStream(stream) // Loads key file
.CreateScoped(scopes) // Gathers scopes requested
.UnderlyingCredential // Gets the credentials
.GetAccessTokenForRequestAsync(); // Gets the Access Token
}
}
which returns me an access token.
I am then sending the following notification message
{
"customPushMessage": {
"userNotification":{
"title":"Notification Title"
},
"target":{
"userId":"ID_FROM_UPDATES_USER_ID",
"intent":"Notification Intent",
"locale":"en-US"
}
}
}
using the following code
try
{
var accessToken = await GetAccessTokenFromJsonKeyAsync("key.json", "https://www.googleapis.com/auth/actions.fulfillment.conversation");
var serialized = JsonConvert.SerializeObject(proactiveMessage);
var payload = "{\"customPushMessage\": " + serialized + "}";
// Wrap our JSON inside a StringContent which then can be used by the HttpClient class
var httpContent = new StringContent(payload, Encoding.UTF8, "application/json");
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
var httpResponseMessage = await _httpClient.PostAsync("https://actions.googleapis.com/v2/conversations:send", httpContent);
Debug.WriteLine(httpResponseMessage.IsSuccessStatusCode ? "Successfully sent notification message." : $"Failed to send notification message with {httpResponseMessage.StatusCode}.");
return httpResponseMessage;
}
catch (Exception ex)
{
Debug.WriteLine($"Alexa API Service: Failed to send notification message with exception: {ex.Message}");
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
The response code I get is a 403 Forbidden.
I am not sure if the Access Token code is incorrect, the notification structure is incorrect, or if I am missing something else.
The token type needs to be "Bearer" with a capitol B. So that line should be
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
I was not requesting permission properly.This provided the missing piece of the puzzle for me.
Needed the
"updatePermission": {
"intent": "notification.simple.text"
}
I have been stuck all day on a stupid problem with registering a user to my application.
Here is my code once the 'Register' button is clicked:
public ICommand RegisterCommand
{
get
{
return new Command(async() =>
{
var isSuccess = await _apiServices.RegisterAsync(Email, Password, ConfirmPassword);
if (isSuccess){
Message = "Registered Successfully";
}
else
{
Message = "Retry later";
}
});
}
}
Api services Register Async method:
public async Task<bool> RegisterAsync(string email, string password, string confirmPassword)
{
try
{
System.Diagnostics.Debug.WriteLine("Email: "+email);
var client = new HttpClient();
var model = new RegisterBindingModel
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword
};
var json = JsonConvert.SerializeObject(model);
HttpContent content = new StringContent(json);
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:63724/api/Account/Register", content);
if (response.IsSuccessStatusCode)
{
return true;
}
return false;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Error: "+e);
throw;
}
}
}
The Error that I get is:
System.Net.Http.HttpRequestException: An error occurred while sending the request ---> System.Net.WebException: Error: ConnectFailure (Connection refused) ---> System.Net.Sockets.SocketException: Connection refused
at System.Net.Sockets.Socket.Connect (System.Net.EndPoint remoteEP) [0x000b6] in <6c708cf596db438ebfc6b7e012659eee>:0
at System.Net.WebConnection.Connect (System.Net.HttpWebRequest request) [0x0016d] in <6c708cf596db438ebfc6b7e012659eee>:0
--- End of inner exception stack trace ---
To me this is very frustrating as I can register a use using Postman with the exact same localhost address. I am following Houssem Dellai's Xamarin.Forms mvc web api tutorials which can be found here
I had an issue with httpclient during the development of my app. I believe there was an issue with the cross-platform implementation of the httpclient class. iOS didn't know how to handle it.
Instead I implemented a very simple httpclient library called flurl: http://tmenier.github.io/Flurl/
First, you will need to install flurl in all project directories (iOS, Android, and the PCL) then the implementation is very simple.
using Flurl;
using Flurl.Http;
public async Task<User> CreateUserAsync(RegisterUserModel userModel)
{
string url = "your/backend/here";
//resp is a user object received and automatically converted into a c# object through the use of .ReceiveJson<typeofobject>();
var resp = await (url).PostJsonAsync(userModel)
.ReceiveJson<User>();
if (resp.LoginSession != null)
{
//Raise my registered event to let other classes know to proceed
OnUserRegistered(resp);
}
return resp;
}
As you can see it makes httpclient implementation very simple. Hopefully this helps.
I am trying to build a client side of making requests. So i do not know how i pass parameters on GetAsync method. The same problem on PostAsJsonAsync method.
Here is my code:
public static async Task<List<Users>> GetUsers(HttpClient client, Users users, string accessToken)
{
try
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.GetAsync("/api/2.0/users/?id=5&name=name");
response.EnsureSuccessStatusCode();
List<Users> listUsers= await response.Content.ReadAsAsync<List<Users>>();
Console.WriteLine("Returned list.");
return listUsers;
}
catch (HttpRequestException e)
{
Console.WriteLine("{0}", e.Message);
Console.ReadLine();
throw;
}
}
When i make this request from postman i get the results that i want. The Users class has more variables than the 2 that i request.
GetAsync without parameters works fine. When i run the project i get the error "JsonReaderException: Input string '100.0' is not a valid integer"
Is there another option to pass arguments on url?
I changed the int type of that integer property to float and the problem solved.