Call an Angular Route from ASP.NET WEB API - c#

I have configured my application to send a confirmation Email to the user after registration. After the registration is completed the user will see a page that is saying you need to confirm you Email:
<div *ngIf="!emailConfirmed">
<p> Please activate your account by clicking the relevant link in your Email </p>
</div>
<div *ngIf="emailConfirmed">
<p>
Your Email is now confirmed, please click the below button to Log-In.
</p>
<a class="btn btn-md btn-success btn-color" [routerLink]="['/login']">
Sign In
</a>
</div>
And emailConfirmed is just a simple variable that I have defined in the emailConfirmed's typescript relevant file:
export class EmailConfirmed {
public emailConfirmed: boolean;
}
After the user clicks on the link in his/her Email, his/her account will be verified and then the application will be redirected to the ConfirmEmail page again using the below code:
[HttpGet]
[Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IActionResult> ConfirmEmail(string userId = "", string code = "")
{
//....
IdentityResult result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return Redirect("http://localhost:5000/emailconfirmed");
}
}
Now the question is: I don't know how can I set the emailConfirmed variable of EmailConfirmed component to true from WEB API and in the return Redirect line, in order the user see the second message this time? Also I doubt that I have chosen the best way to redirect the application to an Angular route using the return Redirect("http://localhost:5000/emailconfirmed"); line.

#ManojChoudhari is right. You can't route like this!
First it should be a "HttpPost". Return a response and then redirect on the clientside using router.
Here is a little example. This does not take into account the separation of concerns!
Serverside
Models
public class UserRequest {
public string UserId { get; set; }
public string Code { get; set; }
}
public class EMailConfirmationResponse {
public boolean EmailConfirmed { get; set; }
}
Controller
...
[HttpPost]
[Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IHttpActionResult> ConfirmEmail(UserRequest user)
{
var result = await UserManager.ConfirmEmailAsync(user.UserId, user.Code)
if (result.Succeeded)
{
return Ok(new EMailConfirmationResponse { EmailConfirmed = true });
}
else
{
return BadRequest("An error occurred confirming the given email-address.");
}
}
...
Clientside
import { Component } from "#angular/core";
import { Router } from "#angular/router";
import { HttpClient } from "#angular/common/http";
#Component({
selector: "your",
templateUrl: "./your.component.html"
})
export class YourAngularComponent {
constructor(
private _router: Router,
private _http: Http
) {
...
// put this into your method
const httpOptions = { headers: new HttpHeaders({'Content-Type': 'application/json', 'Authorization': 'my-auth-token'}) };
this.http
.post("webapiurl", { userId: "TheUserId", code: "TheUserCode" }, httpOptions)
.subscribe((response) => {
const emailConfirmationResponse = response.json();
if(emailConfirmationResponse.emailConfirmed) {
this._router.navigate(["emailconfirmed"]);
}
}, (err: HttpErrorResponse) => {
// do some error handling here:
console.log(err.error);
console.log(err.name);
console.log(err.message);
console.log(err.status);
}
);
...

One thing you need to understand is - the Angular routes are only available on client side.
You will not be able to redirect user to angular template from server side.
The option you have probably is to return some flag from web API. This flag should be unique and then angular should redirect user to other page.
Your API Code should be:
[HttpGet]
[Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IActionResult> ConfirmEmail(string userId = "", string code = "")
{
//....
IdentityResult result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return Ok();
}
else
{
return BadRequest("An error occurred confirming the given email-address.");
}
}
In your client side typescript you can add below code:
//// result = call web api and take result
//// if result is 200 OK then
this.router.navigate(['/your-path'])
Hope this helps.

I guess directly from webapi you can't redirect. use Api response and then in angular redirect to another page or domain.

Related

Attempting to post object from frontend to backend. Serialize object in frontend and deserialize object in backend. React C#

const [payload, setPayload] = useState({
Email: null,
Password: null
});
const handleSubmit = async (e) => {
e.preventDefault();
var json = JSON.stringify(payload);
try {
const { data } = await axios.post(
"https://localhost:5001/api/User/Login",
{
json
}
);
console.log(data);
ctxDispatch({ type: "USER_LOGIN", payload: data });
localStorage.setItem("userInfo", JSON.stringify(data));
toast.success("Login Successful");
navigate("/");
} catch (err) {
toast.error(getError(err));
}
};
[AllowAnonymous]
[HttpPost("Login")]
public async Task<ActionResult<UserDTO>> Login([FromBody] LoginDTO loginDTO)
{
//LoginDTO loginDTO = Newtonsoft.Json.JsonConvert.DeserializeObject<LoginDTO>(input);
var pwd = Encrypt.ConvertToEncrypt(loginDTO.Password);
User currentUser = await _context.user.FirstOrDefaultAsync(user =>
user.Email.ToLower() == loginDTO.Email.ToLower()
&&`enter code here`
user.Password == pwd);
}
public class LoginDTO
{
public string Email { get; set; }
public string Password { get; set; }
}
When I post this string to my backend it attempts to parse my string but fails at the first char {. When i send this string wrapped in quotes through swagger it passes with no issues. When i send this string through postman with no quotes wrapped around the string I get the same exact error as i get when posting through my frontend.
fix the action input parameters, you don't need to deserialize it , it will be deserialized by MVC
public async Task< ....([FromBody] LoginDTO loginDto)
and since you are sending a json string, add content type to axios
axios.post('..your url', json, {
headers: {
// Overwrite Axios's automatically set Content-Type
'Content-Type': 'application/json'
}
});

How to change the default "[field value] already taken" error message in ASP.Net MVC 5?

In ASP.Net MVC 5 View we have an automatic error message popped-up when a control/input in the View tries to create column value in the database (or a field value in a class) which is supposed to be unique but we violate it - such as when creating new user account's email with an already existing email as shown below.
Now, I am supposed to change the error message above into a local language in my project:
... is already taken <-> ... [is already taken in local language]
How to change the "[field value] already taken" (automatic) error message?
According to this post:
Issue with username validation message in asp.net identity
We could just try to search for "Name {0} is already taken" in the whole solution, but when I did that, I couldn't find it.
Was that a valid solution in earlier MVC but outdated for MVC 5?
(Note: unfortunately, the post does not indicate which ASP.Net MVC version the OP is working with)
I have implemented this via using Remote in System.ComponentModel.DataAnnotations. Please refer following code :
In ViewModel (Properties) :
[Display(Name = "Email")]
[Remote("IsValidUserName", "Remote", AdditionalFields = "Email", HttpMethod = "Post")]
public string Email { get; set; }
Here "Remote" is a Controller Name and "IsValidUserName" our Method. Like below:
public class RemoteController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult IsValidUserName()
{
string email= Request.Form["Email"];
string message = "Email '"+ email+ "' is already taken";
if (!string.IsNullOrEmpty(email))
{
using (DbContext db = new DbContext())
{
if (!db.tb_Users.Any(x => x.email== email))
{
message = string.Empty;
}
}
}
if (message == string.Empty)
return Json(true, JsonRequestBehavior.AllowGet);
else
return Json(message, JsonRequestBehavior.AllowGet);
}
}
and on View page you have to use this like below:
#Html.TextBoxFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
Output will be :
Points to remember:
You have to use following JavaScripts for running this:
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.15.1/jquery.validate.min.jss"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"></script>
Hope it helps you.
Thanks.
A quick and a bit hacky solution to this would be:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = UserRepo.CreateUser(model.Email, model.Password);
if (!result.Succeeded)
{
AddErrors(result);
}
}
}
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
string errorMessage = error;
if (error.EndsWith("is already taken."))
errorMessage = "Your error message";
ModelState.AddModelError("", errorMessage);
}
}
As someone answered. I am correcting something.
In your Account controller, find the following class and changes like this.
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
string errorMessage = error;
if (error.EndsWith("is already taken."))
{
errorMessage = "This email id is alreday registered !";
ModelState.AddModelError("", errorMessage);
break;
}
ModelState.AddModelError("", error);
}
}
In your model class try do it with data annotiations by adding following line before your property:
[Required(ErrorMessage = "is already taken in local language")]
You can install .NET Framework with your language pack if it is possible. Then you will have all error messages in local language.

ASP.NET MVC Multiple Actions for one job

So I am just starting out learning MVC and was wondering if I should use multiple actions in my controller or just one to achieve a simple registration page.
Should I do something like this (Multiple actions):
HTML (RegisterForm)
<form action="CheckRegistration" method="post">
Username:
<input type="text" name="username"/>
<input type="submit" value="Login" />
</form>
Controller
public ActionResult RegisterForm()
{
return View();
}
public ActionResult CheckRegistration()
{
bool success = true;
// Create User Object and populate it with form data
User currentUser = new User();
currentUser.Username = Request.Form["username"].Trim().ToString();
// Validate Registration
// code
// Add user to database
// code
if (success)
{
return View("Login");
}else
{
return View("RegistrationSuccess");
}
}
or this (Single action):
HTML (Register)
<form action="Register" method="post">
Username:
<input type="text" name="username"/>
<input type="submit" value="Login" />
</form>
Controller
public ActionResult Register()
{
bool success = true;
String otherData = ""
// Create User Object and populate it with form data
User currentUser = new User();
currentUser.Username = Request.Form["username"].Trim().ToString();
// Validate Registration
// code
// Add user to database
// code
if (success)
{
return View("Login");
}else
{
return View("Register", otherData);
}
}
With the first way I thought of, it has multiple actions and separates it into multiple steps.
The second way uses one actions so when Register view is called the first time, it won't add a user to the database since validation fails and will just return the View().
Which way is better from a professional standpoint (better) or are these both bad ways and there is a better way.
You should have a simple view and do login in the post.
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
You can use same name method in MVC.
1) Declare First method As [HttpGet] so it will return View and
2) Declare Second Method As [HttpPost]
[HttpGet]
public ActionResult RegisterForm()
{
return View();
}
[HttpPost]
public ActionResult RegisterForm()
{
bool success = true;
String otherData = ""
// Create User Object and populate it with form data
User currentUser = new User();
currentUser.Username = Request.Form["username"].Trim().ToString();
// Validate Registration
// code
// Add user to database
// code
if (success)
{
return RedirectToAction("RegisterSuccess");
}else
{
return View("RegisterForm");
}
}

How to return to previous URL after login? (ajax)

So I have a MVC5 web application (it came with ASP.Identity framework) and I have this custom Authorize attribute.
public class FrontEndAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
dynamic user = (ClaimsPrincipal)httpContext.User;
if (user.Identity.IsAuthenticated) {
return true;
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
if (context.HttpContext.Request.IsAjaxRequest()) {
dynamic urlHelper = new UrlHelper(context.RequestContext);
context.HttpContext.Response.TrySkipIisCustomErrors = true;
context.HttpContext.Response.StatusCode = 403;
context.Result = new JsonResult {
Data = new {
Error = "NotAuthorized",
LogOnUrl = urlHelper.Action("LogIn", "Account")
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
} else {
base.HandleUnauthorizedRequest(context);
}
}
}
And I have some problem with ajax method. For example if I have Articles page warheat1990.com/articles/batman-arkham-knight-review with a button to submit comment.
[HttpPost]
[FrontEndAuthorize]
public ActionResult SubmitComment(string comment)
{
//code here
return Content("success");
}
And I have a script to call the method when user click the Submit Comment button.
$.ajax({
url: '/Articles/SubmitComment/'
type: 'POST'
//rest of code here
})
When user click the Submit Comment button without LogIn first, the user will be redirected to LogIn page, however on LogIn success, user is taken back to Home page aka warheat1990.com instead of warheat1990.com/articles/batman-arkham-knight-review.
This is cause by SubmitComment method is called from ajax. So my question is what should I do to be able to redirect user to previous page on ajax call?
The basic idea is that your HandleUnauthorizedRequest method needs to provide a "return to" parameter to the page/controller handling the login. Instead of sending your user to just the login page:
LogOnUrl = urlHelper.Action("LogIn", "Account")
You would provide an additional returnUrl query param:
// for your ajax
LogOnUrl = urlHelper.Action("LogIn", "Account", new { returnUrl = context.HttpContext.Request.RawUrl })
// for your non-ajax:
context.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{ "controller", "Account" },
{ "action", "Login" },
{ "returnUrl", context.HttpContext.Request.RawUrl }
});
The user would land on a page like:
warheat1990.com/login?returnUrl=warheat1990.com%2Farticles%2Fbatman-arkham-knight-review
If login succeeds, then you would parse the request for the query param returnUrl value (if it exists), and redirect the user to that location.
You can get the caller url like below
if (context.HttpContext.Request.IsAjaxRequest())
{
UrlHelper urlHelper = new UrlHelper(context.RequestContext);
context.HttpContext.Response.TrySkipIisCustomErrors = true;
context.HttpContext.Response.StatusCode = 403;
/* Get return url */
var returnUrl = context.RequestContext.HttpContext.Request.Url.PathAndQuery;
context.Result = new JsonResult
{
Data = new
{
Error = "NotAuthorized",
LogOnUrl = urlHelper.Action("LogIn", "Account", new {returnUrl})
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(context);
}
and in account controller and index action redirect to that url
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
/* Together with some other code */
return RedirectToLocal(returnUrl);
}
you can get previous request by Request.UrlReferrer or Request.ServerVariables["http_referer"].
Then once you are done with login process you can redirect to this url

web api routing and http post [duplicate]

This question already has an answer here:
WebAPI - Attribute Routing POST not working with WebAPI Cors?
(1 answer)
Closed 8 years ago.
I'm building an API using WEB API 2.
I have the following API controller:
[RoutePrefix("api/account")]
public class AccountController : ApiController
{
[Route("login")]
[HttpPost]
public IHttpActionResult AuthenticateUser(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
return BadRequest("You must submit username and password");
}
if (!Membership.ValidateUser(username, password))
{
return BadRequest("Incorrect username or password");
}
FormsAuthentication.SetAuthCookie(username, true);
return Ok();
}
}
And jquery function:
<script>
$(document).ready(function () {
$("#login-form").submit(function (e) {
e.preventDefault();
var username = $('#username').val();
var password = $('#password').val();
$.ajax({
type: 'POST',
url: '/api/account/Login/',
data: { username: username, password: password },
success: function() {
location.reload();
}
});
});
});
</script>
When I submit the login-form, I get the following error in Google Chrome's console:
POST http://localhost:60898/api/account/Login/ 404 (Not Found)
How can I create a route that accepts HTTP POST?
Thanks!
I'm sorry, I didn't see this post: WebAPI - Attribute Routing POST not working with WebAPI Cors?
I've updated my API controller like this:
[RoutePrefix("api/account")]
public class AccountController : ApiController
{
public class LoginInfo
{
public string username { get; set; }
public string password { get; set; }
}
[Route("login")]
[HttpPost]
public IHttpActionResult AuthenticateUser(LoginInfo loginInfo)
{
if (string.IsNullOrEmpty(loginInfo.username) || string.IsNullOrEmpty(loginInfo.password))
{
return BadRequest("You must submit username and password");
}
if (!Membership.ValidateUser(loginInfo.username, loginInfo.password))
{
return BadRequest("Incorrect username or password");
}
FormsAuthentication.SetAuthCookie(loginInfo.username, true);
return Ok();
}
}
And everything works fine now.

Categories

Resources