ASP.NET MVC a better way to post model ID? - asp.net

I have a ViewModel I am binding to a view:
ProductViewModel model = Mapper.Map<Product, ProductViewModel>(product);
return View(model);
The view (and viewmodel) is used to edit a Product so ProductViewModel has an ID property that corresponds to the ID in the database.
And to Post the ID back to the Controller I am doing this in my form on the view:
#Html.HiddenFor(x => x.Id)
Even though this works - I was wondering whether there was a better way to Post the ID back to the Controller? Route Values maybe? or is this a fairly standard pattern/approach?

If I have a GET action that includes the id in my route: /Products/Edit/1 then I usually keep it as a route value:
[HttpPost]
public ActionResult Edit(int id, EditProductViewModel model)
This is purely a preference of mine, though. There is no right or wrong way to do it.
The nice thing about this method is you no longer need to pass it in using a Hidden value since it is part of the URL. Actually, if you do pass it in with a Hidden value I believe it will be ignored.
Alternatively, since id is part of the route, you don't even need to have a separate parameter:
[HttpPost]
public ActionResult Edit(EditProductViewModel model)
public class EditProductViewModel
{
public int Id { get; set; }
}
Again, this is purely a preference thing.

I think the answer is, it depends.
Is your entire object being edited and posted back? If so I'd suggested posting back the ID as part of the model:
[HttpPost]
public ActionResult Edit(EditProductViewModel model)
Which I think is a perfectly valid thing to do. However be careful. If the resource being edited is a protected resource, ensure you validate the user has the correct privileges.
If it's a partial edit, perhaps just editing a comment through an Ajax call I'd probably be more tempted to do:
[HttpPost]
public ActionResult Edit(int id, string comment)
As creating a Model in that scenario I would see as overkill ... privilege problem still applies though :)
All that being said, I'm no expert myself :)
Incidentally ... I don't think there's anything wrong with the hidden field, I use it all the time. However it's an ajax call it may not be needed as it would be part of your posting ajax call.

Related

ASP.NET Core: asp-* attributes use request payload over model?

It seems that in ASP.NET Core, the value in asp-* attributes (e.g. asp-for) is taken from the request payload before the model. Example:
Post this value:
MyProperty="User entered value."
To this action:
[HttpPost]
public IActionResult Foo(MyModel m)
{
m.MyProperty = "Change it to this!";
return View();
}
OR this action
[HttpPost]
public IActionResult Foo(MyModel m)
{
m.MyProperty = "Change it to this!";
return View(m);
}
View renders this:
<input asp-for="MyProperty" />
The value in the form input is User entered value. and not Change it to this!.
First of all, I'm surprised that we don't need to pass the model to the view and it works. Secondly, I'm shocked that the request payload takes precedence over the model that's passed into the view. Anyone know what the rationale is for this design decision? Is there a way to override the user entered value when using asp-for attributes?
I believe this is the expected behavior/by design. Because when you submit the form, the form data will be stored to ModelState dictionary and when razor renders your form elements, it will use the values from the Model state dictionary. That is why you are seeing your form element values even when you are not passing an object of your view model to the View() method.
If you want to update the input values, you need to explcitly clear the Model state dictionary. You can use ModelState.Clear() method to do so.
[HttpPost]
public IActionResult Create(YourviewModel model)
{
ModelState.Clear();
model.YourProperty = "New Value";
return View(model);
}
The reason it uses Model state dictionary to render the form element values is to support use cases like, showing the previously submitted values in the form when there is a validation error occurs.
EDIT : I found a link to the official github repo of aspnet mvc where this is confirmed by Eilon (asp.net team member)
https://github.com/aspnet/Mvc/issues/4486#issuecomment-210603605
I can confirm your observation. What's really going to blow your mind is that this:
[HttpPost]
public IActionResult Foo (MyModel m)
{
m.MyProperty = "changed";
var result = new MyModel { MyProperty = "changed" };
return View(result);
}
...gives you the same result.
I think you should log a bug: https://github.com/aspnet/mvc/issues
Edit: I now remember this issue from previous encounters myself and concede that it isn't necessarily a bug, but rather an unintended consequence. The reasons for the result of executing this code is not obvious. There likely isn't a non-trivial way to surface a warning about this, but PRG is a good pattern to follow.

PRG Pattern in ASP.Net MVC?

I'm new to ASP.Net MVC. In PHP, I always use the PRG pattern even when the post request was invalid. It was pretty easy with session flashes (also user friendly).
In ASP.Net MVC, however, I don't see an easy way to do PRG when the request is invalid. I could think of some ways, but I don't think they are good practices and put some extra unnecessary work.
Moreover, from a couple of articles that I've read, a PRG when the request was invalid was discouraged. If it's a bad practice, then what's the better way to handle unsuccessful post requests? Is it really better off without the PRG? And should I just let the rather annoying browser warnings when a user tries to refresh the page?
In Mvc, it's normal practice to handle your Post Actions as it follows:
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult LoginForm(LoginViewModel loginViewModel)
{
if (!ModelState.IsValid)
return View("Login", loginViewModel);
return Redirect("/");
}
As you can see, the property ModelState.IsValid will tell you if the request is invalid, therefore giving you the ability to return the same view and display the error messages in the ValidationSummary when the Post request contains an error. This is the code for the View:
#using (Html.BeginForm("LoginForm", "Account"}))
{
#Html.ValidationSummary() // THIS WILL SHOW THE ERROR MESSAGES
#Html.AntiForgeryToken()
#Html.TextBoxFor(x => x.Email)
#Html.PasswordFor(x => x.Password)
<button type="submit">Submit</button>
}
We have been using PRG pattern in our asp.net mvc web apps for about 5 years. The main reason we adopted PRG was to support browser navigation (eg back, forward). Our web apps are used by customer and for front/back office operations. Our typical web page flow is starts with a login, then progresses via many list/detail view. We also incorporate partial views which also have their own viewmodel. List views will have links (GETS) for navigation. Detail views will have forms (POSTS) for navigation.
Keys aspects of our PRG:
We incorporate viewmodels so each view has a viewmodel (all data access is done in the viewmodel).
Each viewmodel has a set() & get() method to maintain the key data field values associated with the most recent instance of the view. The set/get values are persisted in sessionstate.
The set method has a parameter for each value that needs to be set. The get method is just called from the viewmodel constructor to populate the viewmodel's public "key" values.
The viewmodel will also have a public load() method that get all neccessary data for its view.
Our PRG pattern overview:
In controllers we have a separate GET method and a POST method for each action. The GET only displays a view; the POST processes the posted data.
For list (menu) views, the controller GET method calls the target view's set('item key values here') method, then invokes a RedirectToAction to to the target view's controller GET action.
The controller GET method will instantiate the viewmodel (thus causing get of set values), call its load method which uses the set/get key values to get it data, and returns the view/viewmodel.
The controller POST method will either have the viewmodel save the valid posted data then redirect to the next desired page (probably the previous list menu) -OR- if redisplay the current view if the data is invalid.
I have not documented all the PRG flow senarios that we implemented, but the above is the basic flow.
SAMPLE VIEWMODEL SET/GET METHODS
private void GetKeys() {
Hashtable viewModelKeys;
if (SdsuSessionState.Exists("RosterDetail"))
{
viewModelKeys = (Hashtable)SdsuSessionState.Get("RosterDetail");
EventId = (int)viewModelKeys["EventId"];
SessionNo = (int)viewModelKeys["SessionNo"];
viewModelKeys = null;
}
}
public static void SetKeys(int eventId, int sessionNo) {
Hashtable viewModelKeys = new Hashtable();
viewModelKeys.Add("EventId",eventId);
viewModelKeys.Add("SessionNo",sessionNo);
SdsuSessionState.Set("RosterDetail",viewModelKeys);
viewModelKeys = null;
}
SAMPLE CONTROLLER
[AcceptVerbs("Get")]
public ActionResult MenuLink(int eventId, int sessionNo, string submitButton) {
if (submitButton == RosterMenu.Button.PrintPreview) {
// P-R-G: set called viewmodel keys.
RosterDetail.SetKeys(eventId,sessionNo);
// Display page.
return RedirectToAction("Detail","Roster");
}
if (submitButton == RosterMenu.Button.Export) { etc ...}
}

asp.net mvc Serverside validation no return data

I'm building a validation form in my application. In that form there are two buttons. One to accept and one to reject. When the user press reject the rejection reason field must be provided. I check this serverside.
I first check what button is pressed and then if the field is empty I add a moddel error to the modelstate. But, because all fields in the form are readonly, those are not posted back to the server and therefor when I return the view back to usern there is no data. I'm probably missing something obvious, but cant find what to do. (I know I can make all fields in my form hidden, but due to the large amount of fields this would be really ugly)
This is my code.
[HttpPost]
public virtual ActionResult Validate(string action, Record dto) {
if(action == Global.Accept) {
ciService.Store(dto);
return RedirectToAction("Index", "Ci");
} else {
if(string.IsNullOrEmpty(dto.RejectionReason)) {
ModelState.AddModelError("RejectionReason", "REQUIRED!!!!");
return View("Validate", dto);
}
ciService.Reject(dto);
return RedirectToAction("Index", "Ci");
}
}
You need to recreate the model from the database and then change it to match whatever changes are posted in dto. Then use that combined model in the view.
Instead of passing the DTO back from the browser, I would use a hidden HTML field or a querystring parameter containing the ID that identifies the DTO. Then your POST action method would look something like:
[HttpPost]
public virtual ActionResult Validate(string action, int id)
{
// reload the DTO using the id
// now you have all the data, so just process as you did in your question
if (action == Global.Accept) { ... }
...
}
Your GET method might look something like the following then...
[HttpGet]
public virtual ActionResult Validate(int id)
{
// load the DTO and return it to the view
return View();
}
In this way you have all the data you need within your POST action method to do whatever you need.
You need to have hidden fields corresponding to each property displayed in UI.
e.g.,
#Html.LabelFor(m=>m.MyProperty) - For Display
#Html.Hiddenfor(m=>m.MyProperty) - ToPostback the value to server
If I understand right, the problem is because you don't use input.
To solve your problem insert some input hidden in your form with the value you need to be passed to the controller
#Html.HiddenFor(model => model.Myfield1)
#Html.HiddenFor(model => model.Myfield2)
that should fix the values not passed back to your actions
If you don't need these fields on the server side, simply create a new ViewModel
RecordValidateViewModel and this contains only the fields in it that need to be validated. The model binder will populate then and you will have validation only on the fields in that model rather than all the other fields you don't seem to want there.
If you need them to validate, then post them back to the server. Its not 'ugly' if hidden.

Is it possible to pass a method parameter to an attribute for that method?

I want to make a custom AuthorizeAttribute class as described here: Override Authorize Attribute in ASP.NET MVC.
The trick is, whether or not the user is authorized depends not just on what method he is executing, but the parameter as well:
[MyCustomAuthorize(id)]
[HttpGet]
public ActionResult File(Guid id)
{
}
Basically, the user will be authorized to see some Files, but not others, so I want to pass the id to my custom authorize attribute. But I can't seem to do this because the name 'id' isn't in scope.
I know I could do the logic to make sure the user has access to that File at the beginning of the method, but I have several methods that would all need this done, and it would be much cleaner if I could do it as part of the [Authorize] attribute.
No, that's not legal C#.
You can access the RouteValues inside the AuthorizeAttribute subtype, however.
Inside, e.g., OnAuthorization, you can do:
object id = filterContext.RouteData.Values["id"];
Be careful, though. You really need to know what you're doing here. The interaction between authentication, authorization, and caching is complex.
Maybe the last answer in 2011 was correct for that version HOWEVER it is possible. Create a public int (or whatever it is you need) and use that. Example:
public class RestrictToTemplateManagers : AuthorizeAttribute
{
public string GUID { get; set; }
}
[RestrictToTemplateManagers(GUID="ABC")]
public class ImportTemplatesController : Controller
{
}

How can i hold the properties of model between the actions(asp.net mvc)

Here's my model:
public class MyModel
{
public int BaseTypeField { set; get; }
public MyType UserTypeField { set; get; }
}
In the first action, i passed a MyModel to the view normally:
public ActionResult Action1()
{
MyModel model = new MyModel();
//do something with model.UserTypeField
return View(model);
}
In Action1View i can easily modify the model.BaseTypeField with HtmlHelper, but I dont wanna modify model.UserTypeField in this view(neither can i store it in HiddenFor).
Then Action1View submit the model to another action:
public ActionResult Action2(MyModel model)
{
//model.UserTypeField is lost here
return View();
}
Here comes the problem: how can i hold/save the model.UserTypeField except for something like Session??
Well, if you don't want to use session state, then your only option is to pass the information to the client and have him pass it back with his request. One way you could do this would be with a cookie. Another might be to use a hidden form field. You would include the field in your response to Action1, and the browser would automatically submit it in the request to Action2 (assuming you're using a form POST to call the action).
You have a number of options to preserve state across controller actions:
Store it in a Hidden input element in the View (though I appreciate that you say you can't, and there are plenty of good reasons why that might be the case).
Store it in Session State.
Store it in your application database (but then, you may as well use Session State).
Store it in a cookie. You can create a HttpCookie and add it to HttpContext.Current.Response.Cookies in Action1 and read it from HttpContext.Current.Request.Cookies in Action2.
If you only have a small amount of data and have no reason to use Session State elsewhere, I'd probably go for the cookie option. But Session State is there for precisely this kind of purpose. Don't be afraid to use it if it's the right thing.
Each action should have a parameter that has only properties for fields which you would like to accept from the request. The rest of the object should be loaded from the data store again. In other words, don't have Action2 take a property that takes in the whole model as it will allow your consumers to inadvertently alter more properties than they should be able to.
This may seem like a lot of work to do on every step, but you will save yourself many headaches by not having to do all of the validation for all the fields which you do not want changed. It is also easy to load the rest of the model from the data store if you wrap it up in a function.
TempData[] is intended to hold items between actions, but it does use the Session. If keys are not marked using Keep, then they are removed once the next Action is executed.
If you wanted to avoid Session fullstop, then you would have to serialize your object and send it to the client in the view (in a hidden form variable for example) and then deserialize it back into Action2.
If you wanted to use TempData (which would be simplest unless you can't use session for some reason), the syntax would just be:
public ActionResult Action1()
{
MyModel model = new MyModel();
//do something with model.UserTypeField
TempData["UserTypeField"] = model.UserTypeField;
return View(model);
}
public ActionResult Action2(MyModel model)
{
model.UserTypeField = TempData["UserTypeField"];
return View();
}

Resources