Block passing URL's parameter inside action method to the view - asp.net

Is there any possibility to block (by attribute for example) passing URL's parameters into view?
public ActionResult UserForm(Guid? entityId, bool isCopy = false)
{
ViewBag.IsNewOrCopy = entityId.IsNullOrEmptyOrCopy();
if (ViewBag.IsNewOrCopy)
return View(new UserFormViewModel() {Roles= service.GetRoles(entityId, isCopy) });
return View(service.GetUser(entityId.Value));
}
For example I would like to stop passing entityId parameter because when I update changes via post method the entityId (#Html.HiddenFor(m => m.EntityId)) is also passed via viewmodel although I would like it to be passed as null variable.

Related

How exactly does a .net MVC controller know which View() to return?

When I want to call a new page in .net, say the "About.cshtml" page, I use the following code in the HomeController:
public ActionResult About()
{
ViewBag.Title = "About";
return View();
}
To call it I'd use a link to "/Home/About". And if I wanted to create a new page called "Contact.cshtml", for example, I'd copy the above and replace About with Contact.
I know that the route in "/Home" calls the HomeController. But how, exactly, does that controller know to return the About.cshtml page? I assume it's based on the name of the function. But this doesn't sound right to me. About() isn't an HTTP verb like Get() or Post(), and the name of the function normally shouldn't define what it does, unless it already existed.
Also, when exactly is View() defined, and when is it assigned to the About.cshtml page?
Finally, is there an attribute that would allow me to return the About.cshtml page with a different function name (as I can set a function to respond to Get with the [HttpGet] attribute)?
But how, exactly, does that controller know to return the About.cshtml page?
Because the action method name is About:
public ActionResult About()
The route found that method by the URL:
/Home/About
If the URL didn't include the action:
/Home
Then it would look for a default action. Normally this is Index(), as configured by the default route mapping:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
Note how a default value is defined for both controller and action if none is provided on the URL.
the name of the function normally shouldn't define what it does
Why on Earth not? A function name should exactly define what that function does.
Also, when exactly is View() defined
It's in the base controller class.
Finally, is there an attribute that would allow me to return the About.cshtml page with a different function name
Not an attribute per se, but you can specify the view name when calling View():
return View("SomeOtherView");
only to explain a few more (the David's response is so good), View() is an object of type ViewResultBase, in class Controller;
protected internal ViewResult View()
{
return View(viewName: null, masterName: null, model: null);
}
ViewResultBase has a method ExecuteResult() that receives a parameter of type ControllerContext (this parameter has the info about the request) and inside this method, if the name of the view is null, the view name is established based on the url (read the explain of David about the routing) that is called accesing to the RouteData:
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(ViewName))
{
ViewName = context.RouteData.GetRequiredString("action");
}
....
}
Here, if you watch the RouteData you can view that the called action is Index, and this value is set to the ViewName property:
Regards,

Parameters in RedirectToAction don't passed down

I'm trying to pass down parameters to my Action.
There are several parameters that I need to pass down.
When debugging, I found that the simple types of parameters got their values, whereas my own class parameters is null.
return RedirectToAction("Histories", new {MyUser = user, sortOrder = "name_desc" });
And here is the Action method:
public ActionResult Histories(ApplicationUser MyUser, string sortOrder, int? page)
I did a research and found , it seems that only objects which can be serialized can be passed down.
So I simply added an annotation [Serializable] on my ApplicationUser class, and it doesn't work.
So I'm wondering what's the best practice to pass down my objects?
I certainly know I can put the MyUser into Session["CurrentUser"], but I just don't like this old fashion.
Thank you.
You have not passed int? page value, it should be like this
return RedirectToAction("Histories", new {MyUser = user,
sortOrder = "name_desc",
page =1 });
or you need to use default parameter value like this
public ActionResult Histories(ApplicationUser MyUser,
string sortOrder,
int? page = 1)

Use a viewmodel with web api action

I just read this post by Dave Ward (http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/), and I'm trying to throw together a simple web api controller that will accept a viewmodel, and something just isn't clicking for me.
I want my viewmodel to be an object with a couple DateTime properties:
public class DateRange
{
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
Without changing anything in the stock web api project, I edit my values controller to this:
public IEnumerable<float> Get()
{
DateRange range = new DateRange()
{
Start = DateTime.Now.AddDays(-1),
End = DateTime.Now
};
return Repo.Get(range);
}
// GET api/values/5
public IEnumerable<float> Get(DateRange id)
{
return Repo.Get(range);
}
However, when I try to use this controller, I get this error:
Multiple actions were found that match the request:
System.Collections.Generic.IEnumerable1[System.Single] Get() on type FEPIWebService.Controllers.ValuesController
System.Collections.Generic.IEnumerable1[System.Single] Get(FEPIWebService.Models.DateRange) on type FEPIWebService.Controllers.ValuesController
This message appears when I hit
/api/values
or
/api/values?start=01/01/2013&end=02/02/2013
How can I solve the ambiguity between the first and second get actions?
For further credit, if I had this action
public void Post(DateRange value)
{
}
how could I post the Start and End properties to that object using jQuery so that modelbinding would build up the DateRange parameter?
Thanks!
Chris
The answer is in detail described here: Routing and Action Selection. The Extract
With that background, here is the action selection algorithm.
Create a list of all actions on the controller that match the HTTP request method.
If the route dictionary has an "action" entry, remove actions whose name does not match this value.
Try to match action parameters to the URI, as follows:
For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude
optional parameters.
From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are
case insensitive and do not depend on the parameter order.
Select an action where every parameter in the list has a match in the URI.
If more that one action meets these criteria, pick the one with the most parameter matches.
4.Ignore actions with the [NonAction] attribute.
Other words, The ID parameter you are using, is not SimpleType, so it does not help to decide which of your Get methods to use. Usually the Id is integer or guid..., then both methods could live side by side
If both of them would return IList<float>, solution could be to omit one of them:
public IEnumerable<float> Get([FromUri]DateRange id)
{
range = range ?? new DateRange()
{
Start = DateTime.Now.AddDays(-1),
End = DateTime.Now
};
return Repo.Get(range);
}
And now both will work
/api/values
or
/api/values?Start=2011-01-01&End=2014-01-01

How does ASP.NET MVC pass model to the view without explicitly passing it

Here is one of the examples that I've seen on how to do validation on Controller:
[HttpPost]
public ViewResult Create(MyModel response)
{
if (ModelState.IsValid)
{
return View("Thanks");
}
else
{
return View();
}
}
If there are validation errors, than return View() method is called without any parameters. Obviously you have #Html.ValidationSummary() in your View and Model has all required property attributes.
The data that was entered into the form was preserved and displayed again when the view was rendered with the validation summary.
My question: how is the data preserved? Since it was not passed to the View like
return View(response);
Thanks a lot.
Sincerely,
Vlad
It is because the values have been bound to the model state which is passed back along to the view. This is along the same issue/question on why you cannot change a model value and return the view again. Meaning, let's assume I have the following property on my Viewmodel
public string Name {get;set;}
Using the controller below, I cannot change the ViewModel property without also either clearing the ModelState or updating the value in the model state. Try it!
[HttpPost]
public ViewResult Create(MyModel response)
{
response.Name = response.Name + "Some Random String"
return View();
}
The name property will remain unchanged. Essentially, once the ModelBinding occurs, the values from your form (ViewModel) are bound to the model state, which is why you do not have to pass the model back to the view.
As a side note, I always pass the model back in my call to return View();, it just seems more correct and a little easier to read

Propagating QueryString parameter in RedirectToAction calls

I want to make sure that a particular parameter in the QueryString, in my case the request_id is propagated to the redirected action.
Say for example, I have an Action First,
[HttpPost]
public ActionResult First()
{
////////////////////
// Lots of code ...
////////////////////
return RedirectToAction("Second");
}
Now say, the First postback had a parameter in the QueryString, which I would like to pass to the Second action. One way to do it would be to pass the value in the RedirectToAction call itself,
string requestId = Request.QueryString[REQUEST_ID_KEY];
return RedirectToAction("Second", new { REQUEST_ID_KEY = requestId });
But I have to do this in a series of Actions and I am unwilling to incorporate request id propagation logic inside the action. It would be better if I could incorporate this inside an ActionFilter, but I cant figure out how to add parameters to the QueryString from an ActionFilter. Any ideas?
public class PreserveQueryStringAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var redirectResult = filterContext.Result as RedirectToRouteResult;
if (redirectResult == null)
{
return;
}
var query = filterContext.HttpContext.Request.QueryString;
// Remark: here you could decide if you want to propagate all
// query string values or a particular one. In my example I am
// propagating all query string values that are not already part of
// the route values
foreach (string key in query.Keys)
{
if (!redirectResult.RouteValues.ContainsKey(key))
{
redirectResult.RouteValues.Add(key, query[key]);
}
}
}
}
and then:
[HttpPost]
[PreserveQueryString]
public ActionResult First()
{
////////////////////
// Lots of code ...
////////////////////
return RedirectToAction("Second");
}
If you need it in subsequent action than please add it that param in Session or TempData (But need to re-assign in each action) so you dont need to pass it as a querystring in each action. In case of session, once you done with all actions than remove that key from the Session.
Here is a blogpost I wrote on how to fluently add querystring parameters in the action

Resources