User ID in ActionLink - asp.net-mvc

I'm developing an app, and after user have signed in i want to redirect them to a page where they can update their profiles, for this I want to use the ID of the user who is logged in to retrieve their details.
Since I'm trying to use their ID like that, i don't know if the best method is still to show the ID in the URL or actionLink, my Edit ActionResult is:
// GET: ProfileFormViewModel/Edit/5
public ActionResult Edit(int id)
{
//My code to load the user details
return View(profileFormViewModel);
}
Could this mean that if a user changes the URL they would get the other user's details?
And since i'm trying to retrieve the user ID from the logged in user, do I even need to receive a int parameter in the ActionResult Method, could i just leave it like:
// GET: ProfileFormViewModel/Edit/5
public ActionResult Edit()
{
//get logged in user
return View(profileFormViewModel);
}
public ActionResult Edit(ProfileFormViewModel profileFormViewModel)
{
try
{
if (ModelState.IsValid)
{
//Go save it
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
My route config is as follows, so if i don't send an int ID in the ActionResult link it will give me an error
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

Could this mean that if a user changes the URL they would get the
other user's details?
The answer is yes. If you don't want this to happen, you can do this:
Check the input id with current logged in user id.
If they match, show him his Profile and let him make any changes as he wishes.
If they don't match, it means he's trying to view another profile. You can return an error code, or just redirect him to another page.
However, from my viewpoint, when a user try to view another user's profile by changing the id in the URL, just let him see it. All he can do is reading, editing is not allowed.
do I even need to receive an int parameter in the ActionResult Method?
Yes, you should. But to be clear, as I said: "just let him see other profile", you should separate this into 2 methods:
public ActionResult ViewProfile(int id)
{
// This method allows user to view his or another user's profile
}
public ActionResult Edit()
{
// This method is called when user clicks Edit in his profile.
// It returns a View for editing (data in some controls like textboxes, dropdownlist,...)
}

Related

2 Actions For Same Route in Asp.Net MVC 3

I have route defined as
routes.MapRoute(
"Company", // Route name
"Company/{companyname}", // URL with parameters
new { controller = "Company", action = "CompanyDetail", companyname = UrlParameter.Optional } // Parameter defaults
);
Now the problem is that i have made this route if now i made any request to company controller and pass a parameter it goes to CompanyDetail method , but in one condition i dont want to send to this method i want to send the control to another action CallCompany . How to solve this and note i also need to run both type of request .
you can set it in your controller method:
public ActionResult CompanyDetail(string companyname)
{
if (condition)
{
return RedirectToAction("ActionName", new { companyname = companyname});
}
return View();
}
As I understood your question, you want to realise the following behavior:
There is as set of company names (for example, "test") and they correspond with URL
yourhost/Company/test
They should be routed to CallCompany.
The other URL (such as yourhost/Company/another_company) should be routed to CompanyDetail.
I think, that the best way is to do redirect in CompanyDetail method
public ActionResult CallCompany(string companyname)
{
return View();
}
public ActionResult CompanyDetail(string companyname)
{
IEnumerable<string> myCompanies = GetSpecialCompany();
if (myCompanies.Contains(companyname))
{
return RedirectToAction("CallCompany", new { companyname = companyname });
}
return View();
}
private IEnumerable<string> GetSpecialCompany()
{
throw new NotImplementedException();
}
you should probabaly look into mvc route constraints. that would enable you to forward request on the simillar url to different action depending uopn different parameters which you can programatically set.
for example
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"},
new {productId = #"\d+" }
);
this would only go to controller:Product and action Details in product id is an int
in your case you will have to define the pattern in regex for which request should go to one route and place the second route next to this
so automatically every request which dosent fit the constraint for this route will be handeled by the next one.

presenting two different login screens based on roles

I was trying to figure out whether it would be possible to present two different login screens based on the authorization role. The requirement is simple. I have two roles say "admin" and "public". There are "Authorize" attributes marked all over my applications action methods for these two roles.
Now the requirements of my application specifies different login screens for "admin" and "public". The "admin" login screen is secured by an additional security code which is not required for "public" login screen. What I was looking for is some way to know who is trying to log in based on the Action method invoked. If the action method invoked is decorated by Authorize[Roles="admin"] then I would present the admin login screen, whereas if action method invoked is applied Authorize[Roles="public"] then I need to present the public login screen.
If the Login Screen is directly invoked then by default the public login screen would be presented.
It may sound a little weird but this is the scenario I am trying to figure out the solution for.
You could write a custom authorize attribute which will redirect to the proper logon action:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var roles = Roles.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "account",
action = "adminlogon",
returnUrl = filterContext.HttpContext.Request.RawUrl
}));
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "account",
action = "logon",
returnUrl = filterContext.HttpContext.Request.RawUrl
}));
}
}
}
and then decorate your controllers/actions with it:
[MyAuthorize(Roles = "admin")]
public ActionResult Foo()
{
return View();
}
[MyAuthorize(Roles = "normaluser")]
public ActionResult Bar()
{
return View();
}
Now if a non authenticated user tries to hit the Foo action he will be redirected to the /account/adminlogon action and if he tries to hit the Bar action he would be redirected to the /account/logon action. In both cases the current url will be passed as returnUrl parameter so that upon successful login the user could be brought to the page that he was initially trying to browse.

MVC (MVC3) checking is a Model exists or has values

I have created a working DropDownListFor which gets the data from a selectlist which is Model.IssueSocialSec and then setting the value coming from the database is Model.SocialDBValue
However, when I click a Edit link which with query a repository passing the Model back to the page, that works, but if I do a needed redirect route to the page and nothing is there to bind the Model, then the page fails. I'm going to try having it pass back an empty Model, but I figured I would post this as I always like to hear feedback on "best practices" and lessons learned.
#Html.DropDownListFor(m => m.SelectedSocial, new SelectList(Model.IssueSocialSec, "Value", "Text", Model.SocialDBValue), "")
It sounds like you just need to wrap the DropDownListFor in a <form> with a url pointing to an action that will allow you to edit. The form can use a GET request if it's an idempotent operation and you could use JavaScript to submit the form when the value of the <select> is changed, falling back to rendering a button for submission for when JavaScript is disabled.
Generally, I structure MVC controllers and actions as so
public class ProfilesController : Controller
{
public IProfileRepository Profiles { get; private set; }
public ProfilesController(IProfilesRepository profiles)
{
Profiles = profiles;
}
[HttpGet]
public ActionResult Index()
{
var profiles = Profiles.All();
return View(new ProfilesModel { Profiles = profiles });
}
[HttpGet]
public ActionResult Edit(int id)
{
var profile = Profiles.GetById(id);
return View(new ProfileModel { Profile = profile });
}
[HttpPost]
public ActionResult Edit(ProfileModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var profile = Profiles.GetById(id);
// update the profile
Mapper.Map(model, profile);
if (Profiles.Update(profile))
{
TempData["message"] = "Profile updated successfully";
}
return RedirectToAction("Edit");
}
}
Index will render all the profiles. Against each profile, an <a> will be rendered with a URL pointing to Edit and the URL will include the id for the profile to edit. Edit view will post a form to Edit and the profile will be updated with changes from the model.
I recommend looking at something like NerdDinner or MVC Music store to get an idea of how they structure their code.
I ended up fixing it like this:
ChildInfoModel childviewmodel = new ChildInfoModel();
return View(childviewmodel);
before I was trying to just do:
return View()

ASP.NET MVC, email address as parameter breaking routes

I have the following actionresult:
public ActionResult Confirmation(string emailAddress)
When I try to access it:
http://localhost:8080/Signup/Confirmation?emailAddress=test%40test.com
I get this:
The view 'test#test.com' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Signup/test#test.com.cshtml
~/Views/Signup/test#test.com.vbhtml
What gives why isn't it looking for the correct view? If I go to "/SignUp/" it correctly shows me the index, along with the other ActionResults working correctly. Why does an address break it?
You shouldn't be passing that info in the URL anyway.
If this is kind of a "Confirmation" page from a signup, you could pass another identifier, e.g the UserId that has just been created, then fetch it from the repo.
E.g:
[HttpPost]
public ActionResult Signup(SignupViewModel model)
{
//.. code to save.. etc
return RedirectToAction("Confirmation", new { id = newUser.UserId });
}
[HttpGet]
public ActionResult Confirmation(int id)
{
var user = repo.FindById(id);
// map to model, etc...
return View(model);
}
So your URL would be (without a specialized route)
http://localhost:8080/Signup/Confirmation?id=123213
Putting user's email addresses in the URL is asking for them to be spammed.
Have you tried registering the route in the global.asax.cs?
Something like:
routes.Add("confirmation",
new Route("Signup/Confirmation/{email}",
new RouteValueDictionary(new { controller = "Signup", action = "Confirmation", email = UrlParameter.Optional }),
new MvcRouteHandler())
);

ASP.NET MVC - Navigation Approach

I am new to ASP.MVC. My background is in ASP.NET Web Forms, I think this is what is causing my confusion. I understand that the "M" basically represents the data source, the "V" represents the resource I'm requesting and the "C" dictates what gets shown to an end-user. But then I get confused.
For instance, I'm just trying to create a Login screen. I envision a user visiting "http://www.myapp.com/Account/Login" and they will be presented with a traditional Login screen. To accomplish this, I have added the following in the RegisterRoutes method in my Global.asax file:
routes.MapRoute(
"Login",
"{controller}/{action}",
new { controller = "Account", action = "Login", id = "" }
);
The Login action executes, but this is where I get confused. You see, the first time the login screen loads, I would expect to just show a username/password field. Then on post, I would expect the form to be validated and processed. In an attempt to do this, I have created the following method:
public ActionResult Login()
{
bool isFormValid = ValidateForm();
if (isFormValid)
LoginUser();
else
ShowErrors();
return View();
}
My confusion rests with the Login action. Initially, there is no data. But the next time, I want to validate the data. How do I determine if the Action is a postback or not?
Thank you!
The easiest way to handle this is with two actions: one for get, one for post. Use the AcceptVerbs attribute to control which gets invoked depending on the method. BTW, the default routes should work just fine for this case since when the controller and action is supplied it gets directed as you would expect. I thought that this scenario was also covered in the project template -- did you set up a project using the template or an empty one?
[AcceptVerbs( HttpVerbs.Get )]
public ActionResult Login()
{
}
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Login( string userName, string password )
{
}
You need two different methods, for Post and Get.
[AcceptVerbs (HttpVerbs.Get]
public ActionResult Login ()
{
return View ();
}
[AcceptVerbs (HttpVerbs.Post]
public ActionResult Login (FormCollection form)
{
if (AuthenticationSuccess ())
return RedirectToAction ("Account");
else
return View ();
}
For Post version you can use the model binding mechanism:
public ActionResult Login (LoginModel loginModel)
Or
public ActionResult Login (string LoginName, string Password)

Resources