Asp.net core attribute route issue - asp.net

I have this code:
[Route("Users")]
public class UserRegistrationController : Controller
{
[HttpGet("details/{userId}")]
public async Task<IActionResult> UserDetails(Guid userId)
{
// .....
}
[HttpPost("Save")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveUser(UserVM userVM)
{
// ......
return RedirectToAction("details", "Users", new { userId = userVM.UserId });
}
}
If I save the user redirectToAction generate userId as query string, this create an issue.
I mean instead of
https://localhost:5001/Users/Details/5de304c7-4c69-4819-c879-08d90306b555
redirect to action creates the URL as
https://localhost:5001/Users/Details?userId=5de304c7-4c69-4819-c879-08d90306b555
which causes a 404 error.
How do I solve this issue? I want to pass userId in route as below
https://localhost:5001/Users/Details/5de304c7-4c69-4819-c879-08d90306b555
Thanks in advance.

The issue was, the action method UserDetails need to add route [Route("details")] This will solve the issue.
[Route("Users")]
public class UserRegistrationController : Controller
{
[HttpGet("details/{userId}")]
[Route("details")]
public async Task<IActionResult> UserDetails(Guid userId)
{
// .....
}
[HttpPost("Save")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveUser(UserVM userVM)
{
// ......
return RedirectToAction("details", "Users", new { userId = userVM.UserId });
}
}

Related

SendGrid Asp.net controller

I have two controllers
[HttpPost]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.InternalServerError)]
public async Task<ActionResult> SendEmail([FromForm]string message) {
await _emailSender.SendEmailAsync("mymail#somewhere.com", "Sending from contact", message.ToString());
return Ok();
}
And
[HttpPost]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.InternalServerError)]
[Route("subscribe")]
public async Task<ActionResult> Subscribe( string recipient, [FromForm] string message)
{
var emailTemplate = "";
await _emailSender.SendEmailAsync(recipient, "Thank you for subscribing", message.ToString());
return Ok();
}
Now the first one works absolutely fine when I send an api cal using axios like this:
const response = await axios.post(process.env.REACT_APP_API_URL + '/SendGrid', `message=${emailContent}`)
Where emailContent is a whole html page template with variables inside it. And it works great!
When I try and use the second controller my api call is almost the same I just add the email of the user but for some reason it does not work.
const response = await axios.post(process.env.REACT_APP_API_URL + '/SendGrid/subscribe', `recipient=${values.subscribeEmail}&message=${SubscribedEmailTemplate()}` )
No idea why. What I need is a SendGrid controller that accepts an email and html template as variables and send them respectively.
Thanks for the help!
So I figured it out. I cannot read more than one variable from body. So what I did was create a class and send the request as a json.
public class EmailData
{
public string Recipient { get; set; }
public string Message { get; set; }
}
[HttpPost]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.InternalServerError)]
[Route("subscribe")]
public async Task<ActionResult> Subscribe(EmailData emailData)
{
if (emailData.Recipient == null )
{
return BadRequest();
}
var emailTemplate = "";
await _emailSender.SendEmailAsync(emailData.Recipient.ToString(), "Thank you for subscribing", emailData.Message.ToString());
return Ok();
}

ASP.NET Unable to resolve service for type, while attempting to activate controller

While I understand there were other questions on this very topic I was having a difficult time understanding the answers and was hoping someone could walk me through how DbContext's work as I feel I might have created a second context when I shouldn't have.
So, as I'm teaching myself more about .NET Core I'm working on turning an old school project into a .NET project which is a simple Dentist office web app where users can sign up for appointments, view their appointments, etc. I was following along with this tutorial to add additional user attributes instead of just username & e-mail as I was trying to grab the current user when creating an appointment.
Before I added this custom attributes using the default IdentityUI I had my project working where a user could register and login, create a basic appointment with their 'username' pick a date and time and once created would display their appointments in a basic table format. My next step was to add the custom user attributes so it would display based on their real-name and not their username which is defaulted to their email.
Following the tutorial I'm not sure if I misunderstood but I created a new Context and IdentityUser which all worked but it has broken my 'Appointments' page giving me the:
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'WelchDentistry.Controllers.AppointmentsController'.** error.
Here is my ConfigureServices method as I believe it's an issue in regards to registering the 2 different Contexts.
public void ConfigureServices(IServiceCollection services)
{
/*
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
*/
/*
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
*/
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc();
}
Here is the original context
namespace WelchDentistry.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<WelchDentistry.Models.Appointment> Appointment { get; set; }
}
}
Here is my controller for my appointments
namespace WelchDentistry.Controllers
{
public class AppointmentsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<IdentityUser> _userManager;
public AppointmentsController(ApplicationDbContext context, UserManager<IdentityUser> userManager)
{
_context = context;
_userManager = userManager;
}
// GET: Appointments
public async Task<IActionResult> Index()
{
var user = await _userManager.GetUserAsync(HttpContext.User);
return View(await _context.Appointment.ToListAsync());
}
// GET: Appointments/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var appointment = await _context.Appointment
.FirstOrDefaultAsync(m => m.ID == id);
if (appointment == null)
{
return NotFound();
}
return View(appointment);
}
// GET: Appointments/Create
public IActionResult Create()
{
return View();
}
// POST: Appointments/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID, CustomerName, AppointmentTime,CustomerDoctor")] Appointment appointment)
{
if (ModelState.IsValid)
{
_context.Add(appointment);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(appointment);
}
// GET: Appointments/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var appointment = await _context.Appointment.FindAsync(id);
if (appointment == null)
{
return NotFound();
}
return View(appointment);
}
// POST: Appointments/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,CustomerName,AppointmentTime,CustomerDoctor")] Appointment appointment)
{
if (id != appointment.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(appointment);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!AppointmentExists(appointment.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(appointment);
}
// GET: Appointments/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var appointment = await _context.Appointment
.FirstOrDefaultAsync(m => m.ID == id);
if (appointment == null)
{
return NotFound();
}
return View(appointment);
}
// POST: Appointments/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var appointment = await _context.Appointment.FindAsync(id);
_context.Appointment.Remove(appointment);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool AppointmentExists(int id)
{
return _context.Appointment.Any(e => e.ID == id);
}
}
}
If more code is needed please ask or you can view on my Github
I appreciate all the help and bare with my as I'm still lost on most of this but slowly learning.
First of all remove your IdentityHostingStartup file in your Identity Area.
Then change your databasecontext to this ( You should introduce your User class ) :
public class ApplicationDbContext : IdentityDbContext<WelchDentistryUser, IdentityRole, string>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<Models.Appointment> Appointment { get; set; }
}
And add this codes in your startup file .
services.AddIdentity<WelchDentistryUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
And finally you should use your custom User class in controller.
public class AppointmentsController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<WelchDentistryUser> _userManager;
public AppointmentsController(ApplicationDbContext context, UserManager<WelchDentistryUser> userManager)
{
_context = context;
_userManager = userManager;
}
}

ASP Net Core Auth - RedirectToAction back to Target Action

In an ASP.NET Core Rest API project, I have set up custom authentication, and I can annotate Controller Actions with the [Authorize] attribute, which redirects unauthorized requests back to my AuthController:
[Route("api/[controller]")]
[ApiController]
public class ResponseController : ControllerBase
{
[Authorize]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return JsonConvert.SerializeObject(Repository.GetResponse(id), Formatting.Indented);
}
}
[Route("api/[controller]")]
[ApiController]
public class MetaController : ControllerBase
{
[Authorize]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return JsonConvert.SerializeObject(Repository.GetMeta(id), Formatting.Indented);
}
}
[Route("api/[controller]")]
[ApiController]
public class AuthController : Controller
{
UserManager _userManager;
public AuthController(UserManager userManager)
{
_userManager = userManager;
}
[HttpGet]
[HttpPost]
public ActionResult<string> LogIn()
{
try
{
//authenticate
var username = Request.Headers["username"];
var password = Request.Headers["pass"];
if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
throw new Exception("Authentication Exception: Missing Username or Password");
Task.Run(async () => {
await _userManager.SignInAsync(this.HttpContext, username, password);
}).GetAwaiter().GetResult();
return RedirectToAction("Search", "Home", null);
//^^^ How to send back to intended action?^^^
}
catch (Exception ex)
{
return AuthError();
}
}
}
This works, except how do I use the RedirectToAction method to return back to the intended controller (MetaController or ResponseController, in this case)? (the one with the method marked [Authorize], which put us into this authentication controller to begin with)
You need to use returnUrl parameter, like so:
[HttpGet]
[HttpPost]
public async Task<IActionResult> LogIn(string returnUrl = null)
{
try
{
//authenticate
var username = Request.Headers["username"];
var password = Request.Headers["pass"];
if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
throw new Exception("Authentication Exception: Missing Username or Password");
await _userManager.SignInAsync(this.HttpContext, username, password);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Search", "Home", null);
}
catch (Exception ex)
{
return BadRequest(new {error = "Authentication Failed"});
}
}
I also fix async/await for controller's action. You need use async Task<ActionResult> insted of your ActionResult<string>
Instead of redirection, create your own attribute inheriting from the AuthorizeAttribute and override the OnAuthorization method. That way you don't have to worry about redirection.

error in attribute routing MVC 5

I'm trying to route action with multiple optional parameters but it is not working. I'm sharing my code please guide me.
[HandleError]
[RouteArea("Admin", AreaPrefix = "sp-admin")]
[RoutePrefix("abc-system")]
[Route("{action}")]
public class AbcController : Controller
{
[Route("list/{id:int?}/{PersonID?}/{ref?}")]
public async Task<ActionResult> Index(int? id, int? PersonID, string #ref)
{
return view();
}
}
This will not work like this
http://anylocallink.com/sp-admin/abc-system/list/2/details
but work like this
http://anylocallink.com/sp-admin/abc-system/list/2/3/details
i want it to work if link has any of the optional parameter.
Please guide me
This won't work because the route won't know where to put the int parameter. You could do something like this though
[Route("list/{type}/{id}/{ref?}")]
public async Task<ActionResult> Index(string type, int id, string #ref)
{
if(type == "Person"){ ... }
else { ... }
return View();
}
Then you can do this for a route
list/Person/1/Details
list/ID/2/Details
You can specify 'alpha' as the constraint for the #ref action parameter and have two actions like following:
[Route("list/{id:int?}/{ref:alpha?}")]
public async Task<ActionResult> Index(int? id, string #ref)
{
return await Index(id, null, #ref);
}
[Route("list/{id:int?}/{personId:int?}/{ref:alpha?}")]
public async Task<ActionResult> Index(int? id, int? personId, string #ref)
{
return View();
}
This does work for both the scenarios. I prefer this because I do not have to
modify my routes again and again.

await not completing in extension method but working in controller

I am using async and await in a controller.
The following code works fine
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CompetitionViewModel viewModel)
{
if (ModelState.IsValid)
{
User user = null;
using (var facebookClient = new FacebookClient(viewModel.AccessToken))
{
var facebookUser = await facebookClient.Me();
user = entityStorage.GetUser(facebookUser);
FormsAuthentication.SetAuthCookie(user.FacebookId, true);
}
However if I try and execute the same code in an extension method then the await never completes.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CompetitionViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = entityStorage.GetCurrentUser(viewModel.AccessToken).Result;
and
public static class Helpers
{
public async static Task<User> GetCurrentUser(this IEntityStorage entityStorage, string accessToken)
{
User user = null;
using (var facebookClient = new FacebookClient(accessToken))
{
var facebookUser = await facebookClient.Me(); //STUCK HERE!!
user = entityStorage.GetUser(facebookUser);
FormsAuthentication.SetAuthCookie(user.FacebookId, true);
}
return user;
}
I am using MVC4 and have <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> set in my web.config as per other threads suggestions.
Can anyone tell me why this is and how I can get it to work?
I have a blog post that covers this in detail.
In short, you are causing a deadlock by calling Result. Instead, make your Create method async and use await to get the user.:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(CompetitionViewModel viewModel)
{
if (ModelState.IsValid)
{
var user = await entityStorage.GetCurrentUser(viewModel.AccessToken);

Resources