I develop au Universal App using MVVM-Light.
On a page, there is a list of comments coming from a WebService. If the current user is the author of a comment, I show a FlyoutMenu allowing him to "Edit" or "Delete" its comment. There is also a AppBarButton for adding a new comment:
My problem is that the comments are never refreshed after the first load of this page...
I use a "LoadComments()" method in the ViewModel that allows me to get the comments when I arrive on the page, but also after editing, deleted or added an item:
private async void LoadComments()
{
List<Comment> commentsWS = await WebServiceGetCommentList();
if (commentsWS != null)
Comments = new ObservableCollection<Commentaire>(commentsWS);
}
This method so calls another method "WebServiceGetCommentList()" that prepares the call to the WebService, in the same ViewModel:
private async Task<List<Comment>> WebServiceGetCommentList()
{
// Parameters
List<KeyValuePair<string, string>> parametres = new List<KeyValuePair<string, string>>();
parametres.Add(new KeyValuePair<string, string>("option", _currentUserAccount.option));
parametres.Add(new KeyValuePair<string, string>("id_article", Article.id_article.ToString()));
// Call WebService and deserialize
Exception custEx = null;
try
{
List<Comment> comments = await WebServices.GetCommentList(_currentUserAccount.url, parametres, "");
return comments;
}
// Exceptions
catch (Exception e)
{
...
}
return null;
}
I then go in the "GetComments()" method on the "WebServices" class:
public static async Task<List<Comment>> GetCommentList(String url, List<KeyValuePair<String, String>> parametres, String protocol)
{
// Call WebService and deserialize
var response = await JSONParser.getJSONFromUrl(url, parametres, "");
List<Comment> comments = new List<Comment>();
WsResponse wsResponse = ManageWsReponse(response, Constants.WebServiceTask.GetCommentList.Value);
try
{
WsResponseResult wsResponseResult = JsonConvert.DeserializeObject<WsResponseResult>(wsResponse.data.ToString());
comments = JsonConvert.DeserializeObject<List<Comment>>(wsResponseResult.result.ToString());
return comments;
}
catch (Exception e)
{
throw new DeserializeException("Deserialize exception", e, DateTime.Now, "Comment");
}
}
This method calls the "getJSONFromUrl()" method in the "JSONParser" class that launches the "client.GetAsync()":
public static async Task<string> getJSONFromUrl(String url, List<KeyValuePair<String, String>> parameters, String protocol)
{
var client = new HttpClient();
// Preparing URI
string sParameters = null;
int i = 1;
foreach (var param in parameters)
{
sParameters += param.Key + "=" + param.Value;
sParameters += i != parameters.Count ? "&" : string.Empty;
i++;
}
var uri = new Uri(url + "?" + sParameters);
// Calls the WebService
var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
// Code and results
var statusCode = response.StatusCode;
// EnsureSuccessStatusCode throws exception if not HTTP 200
response.EnsureSuccessStatusCode();
// responseText
var responseText = await response.Content.ReadAsStringAsync();
return responseText;
}
I can add, delete or edit a comment with success, but when I'm back to this method "LoadComments()", the changes are not taken into account, and I get the same list than at the first call...
I also placed breakpoints in the "GetJSONFromURL()" method and I don't see the added, deleted or edited comments in the response var.
In the same time, if I copy the URI in a brower, for calling the same WebService with the same parameters, the changes are taken into account.
=> I think so there is a caching on client.GetAsync(), but I don't see how to desactive it, or force it to refresh datas...
I tried this solution httpclient-caching that doesn't work for me.
I think so that there is cache managing cause when I
That's the platform caching. I haven't had any success with the Cache-Control headers, the most reliable way to address the issue is to make sure the request is different.
Add an additional parameter - a timestamp. As the request is different, the platform cannot used the cached response.
parametres.Add(new KeyValuePair<string, string>("mytmstmp", DateTime.Now.Ticks);
Or: Use an additional header that allows for a date. I've used:
"If-Modified-Since", DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)
Related
i am new to integration tests. I have a controller method which adds a user to the database, as shown below:
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
try
{
var command = new CreateUserCommand
{
Login = request.Login,
Password = request.Password,
FirstName = request.FirstName,
LastName = request.LastName,
MailAddress = request.MailAddress,
TokenOwnerInformation = User
};
await CommandBus.SendAsync(command);
return Ok();
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
As you have noticed my method returns no information about the user which has been added to the database - it informs about the results of handling a certain request using the status codes. I have written an integration test to check is it working properly:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "Aleksander",
LastName = "Kowalski",
MailAddress = "akowalski#onet.poczta.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
I am not sure is it enough to assert just a status code of response returned from the server. I am confused because, i don't know, shall i attach to assert section code, which would get all the users and check does it contain created user for example. I don't even have any id of such a user because my application finds a new id for the user while adding him/her to the database. I also have no idea how to test methods like that:
[HttpGet("{userId:int}")]
public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
{
try
{
var query = new GetUserQuery
{
UserId = userId,
TokenOwnerInformation = User
};
var user = await QueryBus
.SendAsync<GetUserQuery, UserDto>(query);
var result = user is null
? (IActionResult) NotFound(new
{
Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
})
: Ok(user);
return result;
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
I believe i should somehow create a user firstly in Arrange section, get it's id and then use it in Act section with the GetUserAsync method called with the request sent by HttpClient. Again the same problem - no information about user is returned, after creation (by the way - it is not returned, because of my CQRS design in whole application - commands return no information). Could you please explain me how to write such a tests properly? Have i missed anything? Thanks for any help.
This is how I do it:
var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);
The second line above is not necessary, just there for illustration.
Also, your response it's better when you return a 201 (Created) instead of the 200(OK) on Post verbs, like:
return Created($"api/users/{user.id}", user);
To test NotFound's:
var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);
The NotFoundObjectResult assumes you are returning something. If you are just responding with a 404 and no explanation, replace NotFoundObjectResult with a NotFoundResult.
And finally InternalServerErrors:
var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
You can use integrationFixture for that using this NuGet package. This is an AutoFixture alternative for integration tests.
The documented examples use Get calls but you can do other calls too. Logically, you should test for the status code (OkObjectResult means 200) value and the response (which could be an empty string, that is no problem at all).
Here is the documented example for a normal Get call.
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
In the example above, the controller is resolved using the DI described in your Startup class.
You can also do an actual REST call using using Refit. The application is self hosted inside your test.
using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var refitClient = fixture.GetRefitClient();
var response = await refitClient.GetNumberOfCharacters("Hoi");
await response.EnsureSuccessStatusCodeAsync();
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
}
}
I'm using a couple of Azure Functions with SharePoint webhook.
The first function is the one used to save messages from SharePoint webhook to a queue (Azure storage queue). This is the function content:
[FunctionName("QueueFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info($"Webhook was triggered!");
// Grab the validationToken URL parameter
string validationToken = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "validationtoken", true) == 0)
.Value;
// If a validation token is present, we need to respond within 5 seconds by
// returning the given validation token. This only happens when a new
// web hook is being added
if (validationToken != null)
{
log.Info($"Validation token {validationToken} received");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(validationToken);
return response;
}
log.Info($"SharePoint triggered our webhook...great :-)");
var content = await req.Content.ReadAsStringAsync();
log.Info($"Received following payload: {content}");
var notifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(content).Value;
log.Info($"Found {notifications.Count} notifications");
if (notifications.Count > 0)
{
// get the cloud storage account
string queueName = "MYQUEUE";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("AzureWebJobsStorage"));
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue = queueClient.GetQueueReference(queueName);
await queue.CreateIfNotExistsAsync();
// store each notification as a queue item
foreach (var notification in notifications)
{
string message = JsonConvert.SerializeObject(notification);
log.Info($"Adding to {queueName}: {message}");
await queue.AddMessageAsync(new CloudQueueMessage(message));
log.Info($"added.");
}
// if we get here we assume the request was well received
return new HttpResponseMessage(HttpStatusCode.OK);
}
The message in queue is correctly added.
Then I've another function triggered by queue. This is the code of the function:
[FunctionName("OCRFunction")]
public static void Run([QueueTrigger("MYQUEUE", Connection = "QueueConn")]string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
string siteUrl = "https://MYSHAREPOINT.sharepoint.com/sites/MYSITE";
log.Info($"Processing notifications...");
string json = myQueueItem;
var data = (JObject)JsonConvert.DeserializeObject(json);
string notificationResource = data["resource"].Value<string>();
ClientContext SPClientContext = LoginSharePoint(siteUrl);
log.Info($"Logged in SharePoint");
GetChanges(SPClientContext, notificationResource, log);
}
public static ClientContext LoginSharePoint(string BaseUrl)
{
// Login using UserOnly Credentials (User Name and User PW)
ClientContext cntReturn;
string myUserName = config["spUN"];
string myPassword = config["spPWD"];
SecureString securePassword = new SecureString();
foreach (char oneChar in myPassword) securePassword.AppendChar(oneChar);
SharePointOnlineCredentials myCredentials = new SharePointOnlineCredentials(myUserName, securePassword);
cntReturn = new ClientContext(BaseUrl);
cntReturn.Credentials = myCredentials;
return cntReturn;
}
static void GetChanges(ClientContext SPClientContext, string ListId, TraceWriter log)
{
Web spWeb = SPClientContext.Web;
List myList = spWeb.Lists.GetByTitle("MY LIST");
SPClientContext.Load(myList);
SPClientContext.ExecuteQuery();
ChangeQuery myChangeQuery = GetChangeQueryNew(ListId);
var allChanges = myList.GetChanges(myChangeQuery);
SPClientContext.Load(allChanges);
SPClientContext.ExecuteQuery();
log.Info($"---- Changes found : " + allChanges.Count());
foreach (Change oneChange in allChanges)
{
if (oneChange is ChangeItem)
{
int myItemId = (oneChange as ChangeItem).ItemId;
log.Info($"---- Changed ItemId : " + myItemId);
ListItem myItem = myList.GetItemById(myItemId);
Microsoft.SharePoint.Client.File myFile = myItem.File;
ClientResult<System.IO.Stream> myFileStream = myFile.OpenBinaryStream();
SPClientContext.Load(myFile);
SPClientContext.ExecuteQuery();
byte[] myFileBytes = ConvertStreamToByteArray(myFileStream);
[...] SOME CODE HERE [...]
myItem["OCRText"] = myText;
myItem.Update();
SPClientContext.ExecuteQuery();
log.Info($"---- Text Analyze OCR added to SharePoint Item");
}
}
}
public static ChangeQuery GetChangeQueryNew(string ListId)
{
ChangeToken lastChangeToken = new ChangeToken();
lastChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.AddMinutes(-1).ToUniversalTime().Ticks.ToString());
ChangeToken newChangeToken = new ChangeToken();
newChangeToken.StringValue = string.Format("1;3;{0};{1};-1", ListId, DateTime.Now.ToUniversalTime().Ticks.ToString());
ChangeQuery myChangeQuery = new ChangeQuery(false, false);
myChangeQuery.Item = true; // Get only Item changes
myChangeQuery.Add = true; // Get only the new Items
myChangeQuery.ChangeTokenStart = lastChangeToken;
myChangeQuery.ChangeTokenEnd = newChangeToken;
return myChangeQuery;
}
public static Byte[] ConvertStreamToByteArray(ClientResult<System.IO.Stream> myFileStream)
{
Byte[] bytReturn = null;
using (System.IO.MemoryStream myFileMemoryStream = new System.IO.MemoryStream())
{
if (myFileStream != null)
{
myFileStream.Value.CopyTo(myFileMemoryStream);
bytReturn = myFileMemoryStream.ToArray();
}
}
return bytReturn;
}
public static async Task<TextAnalyzeOCRResult> GetAzureTextAnalyzeOCR(byte[] myFileBytes)
{
TextAnalyzeOCRResult resultReturn = new TextAnalyzeOCRResult();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "XXXXXXXXXXXXXXXXXXXX");
string requestParameters = "language=unk&detectOrientation=true";
/* OCR API */
string uri = "https://MYOCRSERVICE.cognitiveservices.azure.com/vision/v3.0/ocr" + "?" + requestParameters;
string contentString = string.Empty;
HttpResponseMessage response;
using (ByteArrayContent content = new ByteArrayContent(myFileBytes))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response = await client.PostAsync(uri, content);
contentString = await response.Content.ReadAsStringAsync();
resultReturn = JsonConvert.DeserializeObject<TextAnalyzeOCRResult>(contentString);
return resultReturn;
}
}
Before current approach with two functions, I was using a single function where I managed the notifications and I executed some code to update a field in my SharePoint list. This method was having some problem when I was receiving many notifications from SharePoint so I decided to use queue as suggested in Microsoft documentation. This solution was working fine with a single notification received and my SharePoint list item were updated without problem.
To avoid problems with multiple notification, I decided to split functions, one registering notifications in a queue and the other executing some operations and updating a SharePoint field.
The first one function QueueFunction is working fine, the second one is triggering correctly but it is not getting changes from SharePoint list even if I just add one item.
I've tried to check GetChanges code to find why it is always returning no changes, but the code is the same of the one I used when I had only one function, so I can't understand why the behaviour is changed.
What's wrong with my approach? Is there something I could do to correct the second function?
According to the comments, just summarize the solution as below for other communities reference:
Use a function to save the message in a queue and then call an azure web job, the problem was caused by the the running time of the function may exceed 5 minutes.
By the way, the default timeout of azure function(with consumption plan) is 5 minutes, we can see all of the default timeout for different plan on this page (also shown as below screenshot).
If we want longer timeout, we can set the functionTimeout property in host.json of the function(but can not exceed the Maximum timeout). Or we can also use higher plan for the function app, such as Premium plan and App Service plan.
I have the following web server method, that returns data to our front-end applicaiton.
[FunctionName("SearchCustomerBySearchTerm")]
public static async Task<HttpResponseMessage> SearchCustomerBySearchTerm([HttpTrigger(AuthorizationLevel.Function, WebRequestMethods.Http.Get, Route = "Customer/SearchCustomerBySearchTerm/{searchTerm}/pageSize/{pageSize}")]HttpRequestMessage req, TraceWriter log, string searchTerm, int pageSize)
{
try
{
var continuationToken = req.Headers.TryGetValues("continuationToken", out IEnumerable<string> values) ? values.FirstOrDefault() : null;
PagedResponse<CustomerSearchResult> pagedResponse = await _customerComponent.FindCustomerBy(searchTerm, continuationToken, pageSize);
if (pagedResponse == null) return req.CreateResponse(HttpStatusCode.NoContent, $"Could not find any data related to {searchTerm}");
HttpResponseMessage responseMessage = req.CreateResponse(HttpStatusCode.OK, pagedResponse.Results);
responseMessage.Content.Headers.Add("continuationToken", pagedResponse.Continuation);
responseMessage.Content.Headers.Add("Access-Control-Expose-Headers", "*");
return responseMessage;
}
catch (Exception ex)
{
log.Error(ex.Message);
return req.CreateResponse(HttpStatusCode.InternalServerError, "Something went wrong. Could not search for customers");
}
}
I am allowing all headers to be exposed, by adding the Access-Control-Expose-Headers.
From my Angular application, I am doing the request as follow:
searchCustomersPaged(searchTerm: string, continuationToken: string): Observable<HttpResponse<CustomerSearchResult>> {
let customHeaders = new HttpHeaders().set("continuationToken", this.currentContinuationToken);
const url = "http://localhost:7071/api/Customer/SearchCustomerBySearchTerm/andrew/pageSize/10";
const parsedUrl = encodeURI(url);
return this.http.get<HttpResponse<CustomerSearchResult>>(parsedUrl, { headers: customHeaders });
}
As you can see above, I am expecting an HttpResponse<CustomerSearch> back.
Here is how I try and read my headers:
nextClikcedHandle(continuationToken: string): void {
this.customerService.searchCustomersPaged(this.customerService.searchTerm, this.currentContinuationToken)
.subscribe(resp => {
//add current continuation token, to previous now, as this will be used for 'previous' searching
this.previousContinuationTokens.push(this.currentContinuationToken);
//set next continuation token received by server
this.currentContinuationToken = resp.headers.get('continuationToken');
//return search results
this.customerService.searchResults.next(resp.body);
});
}
With the above code, the resp.headers and the resp.body is always undefined. Why is this happening?
If I look at the Network tab within Chrome, I can see my data is returned, as well as my header.
What am I doing wrong?
I found a useful article here:
By default the HttpClient returns the body of the response. You can
pass-in an object with an observe key set to a value of ‘response’ to
get the full response. This can be useful to inspect for certain
headers:
So I changed my code as follow, with the added observe key.
searchCustomersPaged(searchTerm: string, continuationToken: string): Observable<HttpResponse<CustomerSearchResult>> {
let customHeaders = new HttpHeaders().set("continuationToken", this.currentContinuationToken);
const url = "http://localhost:7071/api/Customer/SearchCustomerBySearchTerm/andrew/pageSize/10";
const parsedUrl = encodeURI(url);
return this.http.get<CustomerSearchResult>(parsedUrl, { headers: customHeaders, observe: 'response' });
}
After changing above method, I could query body and headers as per normal:
nextClikcedHandle(continuationToken: string): void {
this.customerService.searchCustomersPaged(this.customerService.searchTerm, this.currentContinuationToken)
.subscribe(resp => {
//add current continuation token, to previous now, as this will be used for 'previous' searching
this.previousContinuationTokens.push(this.currentContinuationToken);
//set next continuation token received by server
this.currentContinuationToken = resp.headers.get('continuationToken');
//return search results
this.customerService.searchResults.next(resp.body);
});
This is paypals sample code for the ipn. I have been on it for a few days now and am getting no where. I am not sure what i am meant to call. In a previous question i asked some said you dont call the Receive method. So this then lead me on to think how will Paypal know where to go. (i.e what method to use.)
public class IPNController : Controller
{
[HttpPost]
public HttpStatusCodeResult Receive()
{
//Store the IPN received from PayPal
LogRequest(Request);
//Fire and forget verification task
Task.Run(() => VerifyTask(Request));
//Reply back a 200 code
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
private void VerifyTask(HttpRequestBase ipnRequest)
{
var verificationResponse = string.Empty;
try
{
var verificationRequest = (HttpWebRequest)WebRequest.Create("https://www.sandbox.paypal.com/cgi-bin/webscr");
//Set values for the verification request
verificationRequest.Method = "POST";
verificationRequest.ContentType = "application/x-www-form-urlencoded";
var param = Request.BinaryRead(ipnRequest.ContentLength);
var strRequest = Encoding.ASCII.GetString(param);
//Add cmd=_notify-validate to the payload
strRequest = "cmd=_notify-validate&" + strRequest;
verificationRequest.ContentLength = strRequest.Length;
//Attach payload to the verification request
var streamOut = new StreamWriter(verificationRequest.GetRequestStream(), Encoding.ASCII);
streamOut.Write(strRequest);
streamOut.Close();
//Send the request to PayPal and get the response
var streamIn = new StreamReader(verificationRequest.GetResponse().GetResponseStream());
verificationResponse = streamIn.ReadToEnd();
streamIn.Close();
}
catch ( Exception exception)
{
//Capture exception for manual investigation
}
ProcessVerificationResponse(verificationResponse);
}
private void LogRequest(HttpRequestBase request)
{
// Persist the request values into a database or temporary data store
}
private void ProcessVerificationResponse(string verificationResponse)
{
if (verificationResponse.Equals("VERIFIED"))
{
// check that Payment_status=Completed
// check that Txn_id has not been previously processed
// check that Receiver_email is your Primary PayPal email
// check that Payment_amount/Payment_currency are correct
// process payment
}
else if (verificationResponse.Equals("INVALID"))
{
//Log for manual investigation
}
else
{
//Log error
}
}
}
There is no ActionResult so how am i meant to give a URL.
I thought of this but it seems to return invalid
public ActionResult IPN() {
Receive()
}
I have asked previous questions but still cant seem to get my head around this IPN.
For anyone that sees this, this was an inexperienced me and all i had to do was
return Ok() so my action result would look like the below:
public ActionResult IPN()
{
Receive();
return Ok();
}
Is there any way to count the number of calls to the post method in Web API?
For example: I want to disable user if he 3 times enters wrong username and password combination.
So after third call in a row, of the post method of Web API controller I want to do something (for example I will disable user somehow or something else).
How to count this calls to API controller method? Is there any already define property or method for this case?
UPDATE:
This is my Web API method:
[Route("login")]
public async Task<HttpResponseMessage> LoginUser(Login model)
{
using (AuthRepository repo = new AuthRepository())
{
Regex rgx = new Regex("[^a-zA-Z0-9 -]");
string deviceId = rgx.Replace(model.DeviceId, "");
var request = HttpContext.Current.Request;
var user = await repo.FindUserAsync(deviceId, model.PIN);
var tokenServiceUrl = request.Url.GetLeftPart(UriPartial.Authority) + request.ApplicationPath + "/Token";
if (user != null)
{
MatrixLogManager.Debug("User " + model.DeviceId + "successfully logged in on MatrixSTS.");
try
{
using (var client = new HttpClient())
{
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", deviceId),
new KeyValuePair<string, string>("password", model.PIN)
};
var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
var tokenServiceResponse = await client.PostAsync(tokenServiceUrl, requestParamsFormUrlEncoded);
var responseString = await tokenServiceResponse.Content.ReadAsStringAsync();
var responseCode = tokenServiceResponse.StatusCode;
var responseMsg = new HttpResponseMessage(responseCode)
{
Content = new StringContent(responseString, Encoding.UTF8, "application/json")
};
return responseMsg;
}
}
catch (Exception ex)
{
MatrixLogManager.Error("Error: ", ex);
throw ex;
}
}
else
{
//IF LOGIN FAILD I WOULD NEED TO COUNT SOMEHOW THAT ONE CALL WAS UNSUCCESSFUL, AFTER THIRD I WILL BLOCK USER, BUT ONLY IT HE MAKES SAME MISTAKE 3 TIMES IN A ROW.
//Adding simple int counter didn't worked for me.
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid username or password.");
}
}
}
You should save this stuff in the DB in a sub table called let's say UserInvalidFailureLogins:
The table will have a foreign key to the userId and will have a counter value.
When a user tries to login, you first check how many failures the users has and
if he exceeded the threshold then don't let him login until you reset the number of attempts.
If the user fails a login, you increment the counter by 1.
One simple way you could implement this is keeping a static Dictionary<TKey, TValue> which maps users to the amount of times they've attempted to log-in. Note this can bloat memory quite a bit depending on the number of users and how long you want to keep their counting status:
private static readonly Dictionary<string, int> loginAttemptsByDeviceId =
new Dictionary<string, int>();
And then add this to your else clause:
else
{
int loginAttempts;
if (loginAttemptsByDeviceId.TryGetValue(deviceId, out loginAttempts)
{
loginAttemptsByDeviceId[deviceId] = ++loginAttempts;
}
else
{
loginAttemptsByDeviceId.Add(deviceId, 1);
}
return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid username or password.");
}
And of course checking the value prior to logging the user in:
int currentUserAttempts;
if (loginAttemptsByDeviceId.TryGetValue(deviceId, out currentUserAttempts) &&
currentUserAttempts == MaxLoginThreshhold)
{
// Return some error to the user.
}
This assumes a DeviceID is a unique identifier per user. If it isn't, use a value which will uniquely will identify your users.
Note i would advice you for the long term, to keep this data persistent somewhere (if needed). Also note that this doesn't take into account concurrent requests which may be attempted. If that is an issue, consider using a ConcurrentDictionary<TKey, TValue> instead and locking in relevant places.