I'm making a Web API for handle my Android app's HTTP requests and rethinkDB connection. But something wrong in IIS i think. It produces null values for my User identity when i send POST request to the API. My db is running on localhost.
My POST method is (there is no DB syntax errors.) =
public IHttpActionResult PostNewUserModel(StudentViewModel studentViewModel)
{
if (!ModelState.IsValid)
return BadRequest("Invalid data.");
var conn = connection();
var newStudentViewModel = new StudentViewModel
{
Id = studentViewModel.Id,
FirstName = studentViewModel.FirstName,
LastName = studentViewModel.LastName
};
R.Db(MYDBNAME).Table(MYTABLENAME).Insert(newStudentViewModel).Run(conn);
return Ok();
}
I POST'ed this entity =
{
"Id": 6,
"FirstName": "Nihat Can Doğamaz",
"LastName": "CANITIN"
}
But i see this entity in RethinkDB table like this =
{
"FirstName": null ,
"Id": 0 ,
"LastName": null
}
How to solve it ?
I solve the error. DB cannot recognize returned JSON format. It must be identified in POSTMAN. You must select JSON(application/json) Body format to write your JSON objects into your db.
Related
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'm using OData 8 in .NET Core 5 with the [EnableQuery] attribute. Problem is when I want to return Problem() or BadRequest("Some message"), the API always returns some default OData BadRequest and never the message I wanted (only when [EnableQuery] attribute is there).
Example:
[HttpGet]
[EnableQuery(EnsureStableOrdering = false )]
public IActionResult GetList(ODataQueryOptions<List> queryOptions)
{
if (queryOptions.Filter == null)
{
return BadRequest( "Filter is required for this endpoind!" );
}
try
{
queryOptions.Filter.Validator = new OdataCustomFilterValidator();
this.BaseValidateQueryOptions(queryOptions);
}
catch (ODataException ex)
{
return this.BadRequest(ex.Message);
}
IQueryable<List> list = this._service.GetList();
return this.Ok(list);
}
So in the above example, if the code gets to the first IF, i do not recieve this message but ALWAYS the same Odata error:
{
"error": {
"code": "",
"message": "The query specified in the URI is not valid. The requested resource is not a collection. Query options $filter, $orderby, $count, $skip, and $top can be applied only on collections.",
"details": [],
Hi you ran into this bug which was fixed in OData/WebApi https://github.com/OData/WebApi/issues/2511 but it seems like it has not yet been migrated to AspNetCoreOData, A pull request exists which when merged and published should allow you to continue with your use case which is valid.
I've gotten stuck on this for quite a while now, with no luck advancing it on my own.
I am trying to connect from an Azure App Service to a EF CodeFirst managed database, using an MSI token.
When I deployed the App Service using ARM I produced an Output that ensured that it created a Service Principal:
{
"principalId":"98f2c1f2-0a86-4ff1-92db-d43ec0edxxxx","
tenantId":"e6d2d4cc-b762-486e-8894-4f5f440dxxxx",
"type":"SystemAssigned"
}
In Kudu the environment variables show that it is being installed:
MSI_ENDPOINT = http://127.0.0.1:41239/MSI/token/
MSI_SECRET = 7C1B16Fxxxxxxxxxxxxx
I have provided in the Azure Portal a connection string as follows:
Data Source=nzmoebase0000bt.database.windows.net;Initial Catalog=nzmoebase0001bt;Connect Timeout=300;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=300;
I've added the principal to the database as an Owner.
Note: I cannot do the same for the master db.
The token is added to the DbContext as follows:
The token is being added using:
static async Task AttachAccessTokenToDbConnection(IDbConnection dbConnection)
{
SqlConnection sqlConnection = dbConnection as SqlConnection;
if (sqlConnection == null)
{
return;
}
string msiEndpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
if (string.IsNullOrEmpty(msiEndpoint))
{
return;
}
var msiSecret = Environment.GetEnvironmentVariable("MSI_SECRET");
if (string.IsNullOrEmpty(msiSecret))
{
return;
}
// To get around:
// "Cannot set the AccessToken property if 'UserID', 'UID', 'Password', or 'PWD' has been specified in connection string."
var terms = new[] {"UserID","Password","PWD=","UID=" };
string connectionString = dbConnection.ConnectionString;
foreach (var term in terms)
{
if (connectionString.Contains(term, StringComparison.InvariantCultureIgnoreCase))
{
return;
}
}
string accessToken = await AppCoreDbContextMSITokenFactory.GetAzureSqlResourceTokenAsync();
sqlConnection.AccessToken = accessToken;
}
With tracing on, the token is:
.eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI....
Which decoded using jwt.io gave:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "FSimuFrFNoC0sJXGmv13nNZceDc",
"kid": "FSimuFrFNoC0sJXGmv13nNZceDc"
}.{
"aud": "https://database.windows.net/",
"iss": "https://sts.windows.net/e6d2d4cc-b762-486e-8894-4f5f440dxxxx/",
"iat": 1522783025,
"nbf": 1522783025,
"exp": 1522786925,
"aio": "Y2NgYPjNdyJd9zrzpLavJSEzNIuPAAA=",
"appid": "d1057cea-461b-4946-89a9-d76439c2xxxx",
"appidacr": "2",
"e_exp": 262800,
"idp": "https://sts.windows.net/e6d2d4cc-b762-486e-8894-4f5f440dxxxx/",
"oid": "98f2c1f2-0a86-4ff1-92db-d43ec0edxxxx",
"sub": "98f2c1f2-0a86-4ff1-92db-d43ec0edxxxx",
"tid": "e6d2d4cc-b762-486e-8894-4f5f440dxxxx",
"uti": "59bqKWiSL0Gf0bTCI0AAAA",
"ver": "1.0"
}.[Signature]
I Added Persist Security Info = True as per several recommendations on the net, but that did nothing detectable.
Data Source=nzmoebase0000bt.database.windows.net;Initial Catalog=nzmoebase0001bt;MultipleActiveResultSets=False;Persist Security Info = True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
The error I am getting is:
[InvalidOperationException: This operation requires a connection to the 'master' database. Unable to create a connection to the 'master' database because the original database connection has been opened and credentials have been removed from the connection string. Supply an unopened connection.]
Has anyone gotten a connection to a database, using CodeFirst, with Migrations, and MSI? At this point, after several weeks of being really stuck, I'm starting to wonder if it is possible.
Thanks for any help -- even if just proof that it can work, for starters.
Unfortunately, to my knowledge, no. A major stumbling block to a project that had to fall back to unsecure Username/password loaded connection strings.
You can set the access token on the sql connection like that:
Install the Microsoft.Azure.Services.AppAuthentication nuget package
set up your context class like that:
public class MyDatabaseContext : DbContext
{
public MyDatabaseContext(DbContextOptions<MyDatabaseContext> options)
: base(options)
{
// Get the db connection
var connection = (SqlConnection)Database.GetDbConnection();
// Add the access token
connection.AccessToken = new AzureServiceTokenProvider()
.GetAccessTokenAsync("https://database.windows.net/")
.ConfigureAwait(false).GetAwaiter().GetResult();
}
public DbSet<MyTable> MyTable { get; set; }
}
I'm developing a web service in which I have resources of type A owning resources of type B.
I want to develop an API for getting stats about an A, but only considering a subset of its Bs.
So I'd have a route taking an A's ID, a collection of IDs of Bs, and returns the stats. I'd use it like the following:
POST /api/StatsAboutA/{aId}
JSON payload:
[1, 4, 12]
And it'd return something like that:
[
{"key": ..., "value": ...},
...
]
Here is my controller:
class StatsAboutAController : ApiController
{
// ...
// POST api/StatsAboutA/{aId}
[HttpPost]
public Stats Post(long aId, IEnumerable<long> bIds)
{
A a = _aRepository.SelectById(aId);
if (a == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return _aRepository.CollectStats(a, bIds);
}
// ...
}
I can't manage to configure my router so that my controller matches my route.
Here is the error message I get:
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost:51398/api/StatsAboutA/1'.",
"messageDetail": "No action was found on the controller 'StatsAboutA' that matches the request."
}
Change the method signature to
public Stats Post(long aId, [FromBody]IEnumerable<long> bIds)
Because it is trying to get the bIds from the URI and that route is not defined.
At the moment I am working with the Facebook API and ASP.Net (MVC4).
Is it possible to post a certain message on my Facebook Wall and receive that message in my web application?
I already tried the next code, but it doesn't work:
Maybe somebody has a solution? (Also I don't want the posts of the news feed, but of my profile wall)
JsonObject fbJsonFeedObj = (JsonObject)fbApp.Get(" https://graph.facebook.com/me/home?fields=from,message,name,picture&access_token=" + token);
string str = fbApp.Get("/me/feed").ToString();
JObject obj = JObject.Parse(str);
JToken jUser = obj["data"];
int numb = jUser.Count();
//Get latest post
int id_post = 0;
string stringMsg = obj["data"][id_post]["likes"].ToString();
Update:
I just want a method that gets the latest posts of my personal FB wall. How to accomplish this?
Update 2:
Today I tried:
JsonObject fbJsonFeedObj = (JsonObject)fbApp.Get("
https://graph.facebook.com/" + FacebookModel.TestFacebookID +
"/feed?access_token=" + token);
With this code I can get the latest post, but how do I split it in a readable string? So I only get the UserId who send the post and the real (string) message?
Update 3:
At the moment I am using this code to get the wall posts:
var wall = (JsonObject)fbApp.Get("/me/feed");
But Visual Studio shows this error:
An active access token must be used to query information about the
current user.
I am using the next method to receive the accesstoken:
public static string GetAccessToken()
{
var fb = new FacebookClient();
dynamic result = fb.Get("oauth/access_token", new
{
client_id = FACEBOOK_APP_ID,
client_secret = FACEBOOK_SECRET,
grant_type = "client_credentials"
});
return result.access_token;
}
How to solve this problmen?
The initial problem is that you were using /home and not /feed.
/home - The user's news feed.
/feed - The user's wall.
https://developers.facebook.com/docs/reference/api/user/
You can split it the same way you did with the previous (/home) object
//Get latest post
int id_post = 0;
string stringPostId = obj["data"][id_post]["id"].ToString(); //Id of the post
string stringPostSender = obj["data"][id_post]["from"]["name"].ToString(); //Name of sender
string stringPostSenderId = obj["data"][id_post]["from"]["id"].ToString(); //ID of sender
Each object looks like this so you should be able to figure out how to drill down
{
"id": "5_11111111",
"to": {
"data": [
{
"name": "User A",
"id": "4"
}
]
},
"from": {
"name": "User B",
"id": "5"
},
"message": "This is a test message",
"created_time": "2013-10-04T23:01:42+0000"
}
Your last update is a common mistake many first time developers make.
An application access token should not be used for these calls. You must have a login flow to grant a user access token via OAuth by starting with
https://www.facebook.com/dialog/oauth?
client_id={app-id}
&redirect_uri={redirect-uri}
Please read in detail https://developers.facebook.com/docs/facebook-login/login-flow-for-web-no-jssdk/ for an explanation of the process.