I am new to RxJava and still exploring its wonders.
I am transforming the Response object into a LoginSession object using map() operator but there can be an IOException thrown during transformation. Following is the example code.
Observable
public Observable<LoginSession> login(#NonNull String username, #NonNull String password) {
return Observable.create(new Observable.OnSubscribe<Response>() {
#Override
public void call(Subscriber<? super Response> subscriber) {
loginInternal(username, password, subscriber);
}
}).map(response -> {
try {
LoginSession session = Utils.methodThatCanReturnAnIoException(response.body().byteStream(), LoginSession.class);
return session;
} catch(IOException e){
return null; // what should I do here ????
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());
}
Subscriber
login("username", "password").subscribe( loginSession -> {
//i hate checking for null here. there should be a better way...
if(loginSession != null){
Log.d("MY_SESSION", "Login session successful.");
} else {
Log.d("MY_SESSION", "Login Session is null unfortunately.");
}
});
What is the best way to handle this scenario? except checking for null in subscribe() which I am sure not the best way of doing it.
The pattern you are after is to use flatMap:
public Observable<LoginSession> login(#NonNull String username, #NonNull String password) {
return Observable.create(new Observable.OnSubscribe<Response>() {
#Override
public void call(Subscriber<? super Response> subscriber) {
loginInternal(username, password, subscriber);
}
}).flatMap(response -> {
try {
LoginSession session = Utils.methodThatCanReturnAnIoException(response.body().byteStream(), LoginSession.class);
return Observable.just(session);
} catch(IOException e){
return Observable.error(e);
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread());
}
Then use the onError method in the subscriber.
Related
I am learning Blazor, and I have a WebAssembly client application.
I created a WebAPI at the server which does some additional validation over and above the standard data annotation validations. For example, as it attempts to write a record to the database it checks that no other record exists with the same email address. Certain types of validation can't reliably happen at the client, particularly where race conditions could produce a bad result.
The API controller returns a ValidationProblem result to the client, and Postman shows the body of the result as:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f06d4ffe-4aa836b5b3f4c9ae.",
"errors": {
"Email": [
"The email address already exists."
]
}
}
Note that the validation error is in the "errors" array in the JSON.
Back in the Blazor Client application, I have the typical HandleValidSubmit function that posts the data to the API and receives a response, as shown here:
private async void HandleValidSubmit()
{
var response = await Http.PostAsJsonAsync<TestModel>("api/Test", testModel);
if (response.StatusCode != System.Net.HttpStatusCode.Created)
{
// How to handle server-side validation errors?
}
}
My question is, how to best process server-side validation errors? The user experience ought to be the same as any other validation error, with the field highlighted, the validation message shown, and the summary at the top of the page.
I ended up solving this by creating a ServerValidator component. I'll post the code here in case it is helpful for others seeking a solution to the same problem.
This code assumes you are calling a Web API endpoint that returns a ValidationProblem result if there are issues.
public class ServerValidator : ComponentBase
{
[CascadingParameter]
EditContext CurrentEditContext { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
if (this.CurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(ServerValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(ServerValidator)} " +
$"inside an EditForm.");
}
}
public async void Validate(HttpResponseMessage response, object model)
{
var messages = new ValidationMessageStore(this.CurrentEditContext);
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var body = await response.Content.ReadAsStringAsync();
var validationProblemDetails = JsonSerializer.Deserialize<ValidationProblemDetails>(body);
if (validationProblemDetails.Errors != null)
{
messages.Clear();
foreach (var error in validationProblemDetails.Errors)
{
var fieldIdentifier = new FieldIdentifier(model, error.Key);
messages.Add(fieldIdentifier, error.Value);
}
}
}
CurrentEditContext.NotifyValidationStateChanged();
}
// This is to hold the response details when the controller returns a ValidationProblem result.
private class ValidationProblemDetails
{
[JsonPropertyName("status")]
public int? Status { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("errors")]
public IDictionary<string, string[]> Errors { get; set; }
}
}
To use this new component, you will need to add the component within your EditForm:
<EditForm Model="agency" OnValidSubmit="HandleValidSubmit">
<ServerValidator #ref="serverValidator" />
<ValidationSummary />
... put all your form fields here ...
</EditForm>
Lastly, you can kick off the validation in your #code section:
#code {
private TestModel testModel = new TestModel();
private ServerValidator serverValidator;
private async void HandleValidSubmit()
{
var response = await Http.PostAsJsonAsync<TestModel>("api/TestModels", testModel);
if (response.StatusCode != System.Net.HttpStatusCode.Created)
{
serverValidator.Validate(response, testModel);
}
else
{
Navigation.NavigateTo(response.Headers.Location.ToString());
}
}
}
In theory, this ought to allow you to bypass client validation entirely and rely on your Web API to do it. In practice, I found that Blazor performs client validation when there are annotations on your model, even if you don't include a <DataAnnotationsValidator /> in your form. However, it will still catch any validation issues at the server and return them to you.
how to best process server-side validation errors? The user experience ought to be the same as any other validation error, with the field highlighted, the validation message shown, and the summary at the top of the page.
I don't know what comes in your response, so I made a generic version of a component that do what you need.
Get the CascadingParameter of the EditContext
[CascadingParameter]
public EditContext EditContext { get; set; }
Have a ValidationMessageStore to hold the errors and a function that will display the errors
private ValidationMessageStore _messageStore;
private EventHandler<ValidationRequestedEventArgs> OnValidationRequested => (s, e) =>
{
_messageStore.Clear();
};
private EventHandler<FieldChangedEventArgs> OnFieldChanged => (s, e) =>
{
_messageStore.Clear(e.FieldIdentifier);
};
protected override void OnInitialized()
{
base.OnInitialized();
if (EditContext != null)
{
_messageStore = new ValidationMessageStore(EditContext);
EditContext.OnFieldChanged += OnFieldChanged;
EditContext.OnValidationRequested += OnValidationRequested;
}
}
public override void Dispose()
{
base.Dispose();
if (EditContext != null)
{
EditContext.OnFieldChanged -= OnFieldChanged;
EditContext.OnValidationRequested -= OnValidationRequested;
}
}
private void AddFieldError(ERROR_CLASS_YOU_ARE_USING validatorError)
{
_messageStore.Add(EditContext.Field(validatorError.FIELD_NAME), validatorError.ERROR_MESSAGE);
}
Call the function of the component using it's ref
private async void HandleValidSubmit()
{
var response = await Http.PostAsJsonAsync<TestModel>("api/Test", testModel);
if (response.StatusCode != System.Net.HttpStatusCode.Created)
{
// How to handle server-side validation errors?
// You could also have a foreach or a function that receives an List for multiple fields error display
MyHandleErrorComponent.AddFieldError(response.ERROR_PROPERTY);
}
}
https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-validation has an example of how to handle server-side validation errors:
private async Task HandleValidSubmit(EditContext editContext)
{
customValidator.ClearErrors();
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
.ReadFromJsonAsync<Dictionary<string, List<string>>>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Count() > 0)
{
customValidator.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code: {response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
Use two phase validation.
Hook up an event for when the email is entered which calls an "IsEmailUnique" method on your api. This offers your user real time validation information. Perhaps disable the "Save" button until the email has been validated on the server.
You can then handle the Bad Request as you would any other server-side errors.
I created a form where the user can update his data account. In this form the user is also able to change the account password, before doing so, I ask him the current password, this is the field:
<div class="form-group">
<label>Current Password</label>
<input class="form-control" id="oldPassword"
asp-for="#Model.ExistingPassword" type="password" />
<div class="invalid-feedback"></div>
</div>
as you can see the oldPassword input bound the property ExistingPassword which is part of the ViewModel of that View and have the following declaration:
[Required, MinLength(6), MaxLength(50), DataType(DataType.Password)]
public string ExistingPassword { get; set; }
when the form is submitted I call the following ajax function:
$.post(url, user, function (response) {
//Some stuff
}).done(function (response) {
alert("Updated executed");
}).fail(function (jqXHR, textStatus, errorThrown) {
alert("Error happened!");
});
the parameter of the function are taken by the form, in particular:
url: $(this).attr('action');
user: $(this).serialize();
the action of the form will call the following controller: User\UpdateUser.
Inside the UpdateUser method I execute the following check:
public async Task<UserProfileViewModel> UpdateUserAsync(UserProfileViewModel updatedUser)
{
if (!await _userManager.CheckPasswordAsync(originalUser, updatedUser.ExistingPassword))
throw new Exception("Invalid password");
essentially, the condition check if the current password is correct, if not, then an exception will raised.
Now, my question with this is: how can I know which type of exception the method has generated?
I need to know which type of exception the method UpdateUser has generated because there are different exceptions in the method.
Suppose the Invalid Password exceptions is raised, I need to display a message inside invalid-feedback div, next to oldPassword, so the user know why the update has failed.
Thanks in advance for any help.
Normally, I recommend not using an exception except in actual exception circumstances, but given the way you've designed this, you have a few options.
I'd suggest creating a custom "UpdateUserException" that you can throw that will include additional information, which can be provided by an enum or just string.
public class UpdateUserException : Exception {
public UpdateUserError ErrorCondition;
public UpdateUserException(UpdateUserError error, string message)
{
ErrorCondition = error;
Message = message;
}
}
then you would throw it
throw new UpdateUserException(UpdateUserError.BadPassword, "Invalid Password");
then you would catch it
try {}
catch (UpdateUserException e)
{
if (e.ErrorCondition == UpdateUserException.BadPassword)
{
// handle your exception.
}
}
Have a look at the UserManager ChangePassword Method.
You can bind the UserManager to use DependencyInjection like this (in Startup.cs)
public async void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
app.UseMvc();
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
UserManager<User> userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
}
}
And then in your Controller s Constructor
private readonly UserManager<User> _userManager;
public AccountController(UserManager<User> userManager)
{
_userManager = userManager;
}
And finally your endpoint:
[HttpPost("ChangePassword")]
public async Task<IActionResult> ChangePassword([FromBody]ChangePasswordRequest changePasswordParams)
{
if (changePasswordParams == null)
return BadRequest($"{nameof(changePasswordParams)} must not be null!");
if (string.IsNullOrWhiteSpace(changePasswordParams.OldPassword) || string.IsNullOrWhiteSpace(changePasswordParams.NewPassword))
return BadRequest("old and new passwords have to be provided, but they both are empty.");
var userId = User.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
var user = await _userManager.FindByIdAsync(userId);
var result = await _userManager.ChangePasswordAsync(user, changePasswordParams.OldPassword, changePasswordParams.NewPassword);
if (result.Succeeded)
return NoContent();
return BadRequest(result.Errors);
}
after that you can handle the errors in a switch statement.
Using Exceptions for handled errors are not recommended since they generally ends up with Internal Server error and actually It is beyond of its purpose.
The best approach would be to send BadRequest as It is stated by #maerlin.
However, If you insist to use Exceptions in your application or your applciation is architected to work in this way. I suggest you to inherit new CustomApplcationException class from ApplicationException and then inherit UpdateUserException and vs. from CustomApplicationException class. After that, I Suggest you to handle your exceptions in ErrorHandlingMiddleware and return HandledExceptions at least with BadRequest (400) status code.
The Example Code would be
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILoggerManager _logger;
public ExceptionMiddleware(RequestDelegate next, ILoggerManager logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (CustomApplicationException cae)
{
await HandleCustomExceptionAsync(httpContext, cae);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong: {ex}");
await HandleExceptionAsync(httpContext, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error from the custom middleware."
}.ToString());
}
private static Task HandleCustomExceptionAsync(HttpContext context, Exception exception)
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
}
}
then you need to regiter middleware in your Startup.cs
app.UseMiddleware<ExceptionMiddleware>();
please see https://code-maze.com/global-error-handling-aspnetcore/ and http://www.talkingdotnet.com/global-exception-handling-in-aspnet-core-webapi/ for further details.
Hi I wrote the following code:
private bool GetIsCompleted()
{
return Email.SendMessageAsync().IsCompletedSuccessfully;
}
[HttpPost]
public ViewResult CheckOut(Order order)
{
if (Cart.Lines.Count() == 0)
{
ModelState.AddModelError("","Your Cart is empty!");
}
if (ModelState.IsValid)
{
order.CartLines = Cart.Lines;
order.DateTime = DateTime.Now;
order.TotalPrice = Cart.ComputeTotalValue();
if (Repository.SaveOrder(order))
{
if (User.Identity.Name != null)
{
Email.SetMessageBody(order.OrderID);
if (GetIsCompleted())
{
Cart.Clear();
return View("Completed");
}
}
}
ViewBag.Error = "An error Occured while sending you an email with the order details.";
return View(new Order());
}
else
{
ViewBag.Error = "An error Occured while trying to save your order. Please try again!";
return View(new Order());
}
}
public async Task SendMessageAsync()
{
this.Message = new MailMessage(this.MailFrom.ToString(), this.MailTo.ToString(), this.GetSubject(), this.GetMessageBody());
//Message.Dispose();
try
{
await this.Client.SendMailAsync(this.Message);
}
catch (Exception ex)
{
Logger.LogInformation("The Email couldn't send to the recipient");
}
}
I get
An error Occured while sending you an email with the order details.
in the View. I want GetIsCompleted() to return true to proceed the code. It is developed under .net core. I do not understand why IsCompletedSuccessfully() does not return true; Any suggestion?
The current flow of your code is this:
Start sending the e-mail.
Check if it is completed successfully, decide that it hasn't and return failure.
The e-mail completes sending.
You're awaiting the actual SendMailAsync(..) method, and that's great, but nothing awaits SendMessageAsync(...) so it immediately returns the incomplete task to the caller. Because there isn't enough time between starting to send the e-mail and checking if the task completed, the status will be false.
You need to use async all the way up. Change your method definition to be async:
public async Task<ViewResult> CheckOut(Order order)
Replace this code:
if (GetIsCompleted())
{
Cart.Clear();
return View("Completed");
}
with this:
try
{
await Email.SendMessageAsync();
Cart.Clear();
return View("Completed");
}
catch (Exception e)
{
// handle exception
}
It's worth noting that you'll only ever get an exception if the call to new MailMessage(...) fails because your try/catch block in SendMessageAsync is swallowing all other exceptions.
I'm writing my first Web API 2. I'm performing authorization using a custom HttpMessageHandler. However, my controller needs to know the username specified in the credentials.
Researching this, it appears that ApiController does not have a Controller.HttpContext property. And I see there are potential issues accessing HttpContext.Current. So while I am actually able to store the username in HttpContext.Current.Items in my HttpMessageHandler and then access that information from my controller, I'm not sure that will always be reliable.
I also saw recommendations to use the RequestContext.Principal property; however, I could not find the current request's username anywhere in this data.
How can my controller reliably get the username for the current request?
NOTE: I refer to the username but in this case the actual user is another piece of software calling the API. The "username" reflects the software that is making the call.
#Win: Well, that part is what I am developing. But currently basic
authentication seems appropriate, where the username identifies the
software contacting us and the password is a special key
Here is the sample code for BasicAuthenticationMessageHandler which uses message handler to support HTTP Basic Authentication.
You can read more at Page 121 of ASP.NET Web API 2: Building a REST Service from Start to Finish.
IBasicSecurityService
public interface IBasicSecurityService
{
bool SetPrincipal(string username, string password);
}
BasicSecurityService
public class BasicSecurityService : IBasicSecurityService
{
public bool SetPrincipal(string username, string password)
{
// Get user from database
var user = GetUser(username);
IPrincipal principal = null;
if (user == null || (principal = GetPrincipal(user)) == null)
{
// System could not validate user
return false;
}
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
return true;
}
public virtual IPrincipal GetPrincipal(User user)
{
var identity = new GenericIdentity(user.Username, Constants.SchemeTypes.Basic);
identity.AddClaim(new Claim(ClaimTypes.GivenName, user.Firstname));
identity.AddClaim(new Claim(ClaimTypes.Surname, user.Lastname));
// Get authroized roles and add them as Role Claim.
identity.AddClaim(new Claim(ClaimTypes.Role, "Manager"));
return new ClaimsPrincipal(identity);
}
}
BasicAuthenticationMessageHandler
public class BasicAuthenticationMessageHandler : DelegatingHandler
{
public const char AuthorizationHeaderSeparator = ':';
private const int UsernameIndex = 0;
private const int PasswordIndex = 1;
private const int ExpectedCredentialCount = 2;
private readonly IBasicSecurityService _basicSecurityService;
public BasicAuthenticationMessageHandler(IBasicSecurityService basicSecurityService)
{
_basicSecurityService = basicSecurityService;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// Already authenticated; passing on to next handler...
return await base.SendAsync(request, cancellationToken);
}
if (!CanHandleAuthentication(request))
{
// Not a basic auth request; passing on to next handler...
return await base.SendAsync(request, cancellationToken);
}
bool isAuthenticated;
try
{
isAuthenticated = Authenticate(request);
}
catch (Exception e)
{
// Failure in auth processing
return CreateUnauthorizedResponse();
}
if (isAuthenticated)
{
var response = await base.SendAsync(request, cancellationToken);
return response;
}
return CreateUnauthorizedResponse();
}
public bool CanHandleAuthentication(HttpRequestMessage request)
{
return (request.Headers != null
&& request.Headers.Authorization != null
&& request.Headers.Authorization.Scheme.ToLowerInvariant() == Constants.SchemeTypes.Basic);
}
public bool Authenticate(HttpRequestMessage request)
{
// Attempting to authenticate...
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
return false;
}
var credentialParts = GetCredentialParts(authHeader);
if (credentialParts.Length != ExpectedCredentialCount)
{
return false;
}
return _basicSecurityService.SetPrincipal(credentialParts[UsernameIndex], credentialParts[PasswordIndex]);
}
public string[] GetCredentialParts(AuthenticationHeaderValue authHeader)
{
var encodedCredentials = authHeader.Parameter;
var credentialBytes = Convert.FromBase64String(encodedCredentials);
var credentials = Encoding.ASCII.GetString(credentialBytes);
var credentialParts = credentials.Split(AuthorizationHeaderSeparator);
return credentialParts;
}
public HttpResponseMessage CreateUnauthorizedResponse()
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Constants.SchemeTypes.Basic));
return response;
}
}
I was trying to write an interceptor using spring AOP.The interceptor will find if a request URL is a bookmark,if so will redirect to the authentication page.
Code Snippet:
public Object invoke(MethodInvocation invocation) throws Throwable {
logger.entering(this.getClass().getSimpleName(), "invoke", invocation);
Object result = null;
try {
// Logic to exclude the beans as per the list in the configuration.
boolean excluded = false;
for (String excludebean : excludedBeans) {
if (excludebean != null && excludebean.equalsIgnoreCase(invocation.getThis().getClass().getSimpleName())) {
excluded = true;
break;
}
}
// If the Target Method is "toString", then set EXCLUDE to TRUE and process the request
if(excluded == false && invocation.getMethod().getName().equalsIgnoreCase("toString"))
{
excluded = true;
}
// if user session object is available, then process the request or
// else forward to the configured view.
if (excluded || getSessionHolder().getUserVO() != null) {
result = invocation.proceed();
}
else {
logger.logp(Level.INFO, this.getClass().getSimpleName(),
"invoke(MethodInvocation)", "User Object is "+ getSessionHolder().getUserVO()
+ ". So redirecting user to home page");
result = new ModelAndView("redirect:/security/authenticate.do");
}
}
catch (Throwable ex) {
throw ex;
}
logger.exiting(this.getClass().getSimpleName(), "invoke");
return result;
}
When I debug the control comes inside the else block as expected but after I return the result,control goes to the handle method for the bookmarked URl ratehr than the handler for the redirect view.
Pls help me on this..Thanks in advance.
Why Do you need AOP for the interceptor. You can redirect easily using Regular interceptor.
public class RedirectInterceptor extends HandlerInterceptorAdapter{
private String redirectMapping;
public void setRedirectMapping(String redirectMapping) {
this.redirectMapping = **maintenanceMapping**;
}
//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
if (somethingHappened){
response.sendRedirect(redirectMapping);
return false;
} else
return true;
}
}