I would like to imitate after the controller binding process and bind the query string into a model but not via controller.
I have access to Request.QueryString from type NameValueCollection.
How can i force it to get bind the same as mvc4 binding the models via the controller.
For example i have this class:
public class Example
{
public string Name {get;set;}
public string LastName {get;set;}
}
and NameValueCollection(Request.QueryString) that created by the Request object from the url that is look like ?Name=James&Lastname=Bow.
Any suggestions?
Why not create an ActionFilter then override the OnActionExecuted method. ActionExecutedContext would have access to Request.QueryString. You can then populate the filterContext.Controller.ViewData.Model from the query string.
Related
For what reason should we apply these attributes in ASP.NET Core model binding?
What are the consequences of not using them?
Isn't the model binding engine able to search through the incoming request and map them to Controller action method parameters without these attributes:
[FromQuery] - Gets values from the query string.
[FromRoute] - Gets values from route data.
[FromForm] - Gets values from posted form fields.
[FromBody] - Gets values from the request body.
[FromHeader] - Gets values from HTTP headers.
See this Controller action method examples:
public ActionResult<Pet> Create([FromBody] Pet pet)
public ActionResult<List<Pet>> Search([FromRoute] string breed, [FromQuery] string color, [FromQuery] int age)
We can also apply the attributes to the model class:
public class Pet
{
public string Name { get; set; }
[FromQuery]
public string Breed { get; set; }
}
Source: Microsoft Docs
Controller action method examples without attributes:
public ActionResult<Pet> Create(Pet pet)
public ActionResult<List<Pet>> Search(string breed, string color, int age)
You could check the Sources description:
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
Form fields
The request body (For controllers that have the [ApiController] attribute.)
Route data
Query string parameters
Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. If the default source is not correct, we can use one of the following attributes to specify the source:
[FromQuery] - Gets values from the query string.
[FromRoute] - Gets values from route data.
[FromForm] - Gets values from posted form fields.
[FromBody] - Gets values from the request body.
[FromHeader] - Gets values from HTTP headers.
For example:
When using the following method, it will get the pet data from the form fields:
public ActionResult<Pet> Create(Pet pet)
If using the following method, it will get the parameter from the default source. We can pass the parameter via the Form or Query string.
public ActionResult<List<Pet>> Search(string breed, string color, int age)
If adding attribute to above method, like this:
public ActionResult<List<Pet>> Search([FromQuery]string breed, [FromQuery]string color, [FromQuery]int age)
You could only pass the parameter via the Query string. In this scenario, if you pass the parameters via Form, the parameters in the action method will be Null.
So, by using these attributes we could specify the model binding source, without to scan the default source list.
Let's say I have the following input tag which utilizes the built-in tag helper:
#model ProductViewModel
<label asp-for="Product.Id"></label>
In my case, this expands into the following:
<label for="Product_Id">Id</label>
I see that asp-for is expecting a ModelExpression:
In tag helper implementations, I often see a property like the following:
public ModelExpression For { get; set; }
It appears that this is automatically populated when the tag helper is used.
Is there a way to instantiate a ModelExpression directly in C#?
I.e. something like this:
var exp = new ModelExpression("Product.Id",...)
I'd like to be able to generate "Product_Id" and "Id" from Product.Id as the input tag helper did.
As far as I know, you can specify that your property is to be set to the name of some property on the View's Model object by declaring your property with the ModelExpression type. This will enable any developer using your property to get IntelliSense support for entering a property name from the Model object. More importantly, your code will be passed the value of that property through the ModelExpression's Model property.
Sample code as below:
[HtmlTargetElement("employee-details")]
public class EmployeeDetailTagHelper : TagHelper
{
[HtmlAttributeName("for-name")]
public ModelExpression EmployeeName { get; set; }
[HtmlAttributeName("for-designation")]
public ModelExpression Designation { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "EmployeeDetails";
output.TagMode = TagMode.StartTagAndEndTag;
var sb = new StringBuilder();
sb.AppendFormat("<span>Name: {0}</span> <br/>", this.EmployeeName.Model);
sb.AppendFormat("<span>Designation: {0}</span>", this.Designation.Model);
output.PreContent.SetHtmlContent(sb.ToString());
}
}
Code in the View page:
#model WebApplication7.Models.EmployeeViewModel
<div class="row">
<employee-details for-name="Name" for-designation="Designation"></employee-details>
</div>
Code in the Model
public class EmployeeViewModel
{
public string Name { get; set; }
public string Designation { get; set; }
}
From above code, you can see that we could custom the attribute name. More detail information about using the ModelExpression, check the following links:
Creating Custom Tag Helpers With ASP.NET Core MVC
Expression names
I'd like to be able to generate "Product_Id" and "Id" from Product.Id
as the input tag helper did.
Besides, do you mean you want to change the Product. Id to Product_Id, in my opinion, I'm not suggesting you change it, because generally we can use "_" as a separator in the property name. So, if we are using Product.Id, it means the Product's Id property, and the Product_Id means there have a Product_Id property.
To answer the question:
Is there a way to instantiate a ModelExpression directly in C#"
Yes you can, through IModelExpressionProvider and its CreateModelExpression method. You can get an instance of this interface through DI.
Now, if you're already in your view and working with tag helpers, Zhi Lv's answer is all you need, as the functionality is built-in and much easier to use. You only need IModelExpressionProvider for when you're in your Razor Page, Controller, or perhaps some custom middleware. Personally, I find this functionality useful for my Ajax handlers that need to return one of my ViewComponents that has a ModelExpression argument (so that I can easily call it from my Pages/Views too.)
To call CreateModelExpression, you'll need a strongly-typed instance of ViewData. In Razor Pages, this is as easy as casting the ViewData property to the strongly-typed instance of your PageModel's type (presuming you don't have a page model hierarchy):
var viewData = (ViewDataDictionary<IndexModel>)ViewData;
If you're using MVC and you're in the controller, that won't exist yet. Best you can do is make your own instance.
var viewData = new ViewDataDictionary<ErrorViewModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary());
Once you get your strongly-typed ViewData instance, you can obtain your desired ModelExpression like this, just like using a lambda expression in your views:
var myPropertyEx = _modelExpressionProvider.CreateModelExpression(viewData,
m => m.MyProperty);
I have a 2 views with model as Account. From view one, I am using RedirectToAction to go to view two and sending the model object as below:
[HttpPost]
public ActionResult Login(Account account)
{
//Some code here
return RedirectToAction("Index", "AccountDetail", account);
}
AccountDetail controller looks like this:
public ActionResult Index(Account account)
{
return View("ViewNameHere", account);
}
The model object contains a property like this:
public class Account
{
// Some code here
public List<Details> Details{
get;
set;
}
In the first controller, before making call to RedirectToAction there is one item in Details. However, in the Index method of second controller, there is nothing.
Can someone help pointing out the flaw here? Since I am beginner with MVC, cannot seem to figure it out.
You should not pass a complex object to a GET method. Apart from the ugly URL it would create, you could easily exceed the query string limit and throw an exception.
In any case you cannot pass a collection (or a complex object containing a collection) to a GET method using RedirectToAction(). Internally the method uses reflection to generate the query string by calling the .ToString() method of each property of the model, which in the case of your collection property will be something like ../AccountDetail/Index?Details=System.Collections.Generic.List<Details>.
When the Index() method is called, a new instance of Account is initialized and the value of its property Details is attempted to be set to the string System.Collections.Generic.List<Details> which fails and the result is that property Details is null.
Options include passing an identifier and get the collection from the repository or Session or TempData
I'm looking for a way to convert a mvc4 model to querystring.
The built-in mechanism of mvc4 is allowing me to do something like this:
#Url.Action("SearchWithQueryString","Search", new {#Title = "Title", #Author= " Author", #Date = "date"})
The result of this command is:
Url/Search/SearchWithQueryString?Title=title&Author=author&date=date
My goal is to pass a poco model and get the same result.
for example, if I have this class:
public class Test
{
public string Title {get;set;}
public string Author {get;set;}
public string Date {get;set;}
}
I want to be able to do something like this with using the built-in mechanism:
#Url.Action("SearchWithQueryString","Search", new Test())
and get the same result as I got previously.
Any ideas?
You should use the RouteValueDictionary class. This allows you to convert a model to a QueryString:
#Url.Action("SearchWithQueryString", "Search", new RouteValueDictionary(new Test()))
Where new Test() could also be Model for example.
I am trying to come up with the best pattern for passing data to my _layout.cshtml page.
I am toying with creating a common base class from which all view specific models derive. This base class would be recognized by my _layout.cshtml and used to fill in details about the user and load proper images in the header, etc. For example, here is a snippet of it.
public abstract class ViewModelBase
{
public string Username { get; set; }
public string Version { get; set; }
}
At the top of my _layout.cshtml I have...
#model MyProject.Web.Controllers.ViewModelBase
I need a common area to hydrate the information required by the model, and am planning to use the following pattern...
Each action method creates and hydrates a model derived from
ViewModelBase.
The action completes.
I create a ActionFilterAttribute and override OnActionExecuted to cast the
current Result to ViewModelBase.
If the conversion is successful, then I populate the ViewModelBase details with the relevant data.
Here are my questions...
Is the use of a ActionFilterAttribute (OnActionExecuted) a good pattern for what I am trying to do?
I am not able to see how to get the Result created in the action from the HttpActionExecutedContext. How is this done?
I follow the same approach and use a base ViewModel class which all my other viewModels inherit from.
Then, I have a base controller that all controller inherit from. In there, I have one method that takes care of initializing the view model:
protected T CreateViewModel<T>() where T : ViewModel.BaseViewModel, new()
{
var viewModelT = new T {
HeaderTitle = "Welcome to my domain",
VisitorUsername = this.VisitorUsername,
IsCurrentVisitorAuthenticated = this.IsCurrentVisitorAuthenticated,
//...
};
return viewModelT;
}
Then on each controller, when I want to create the view model, I simply call the base controller's method:
var vm = base.CreateViewModel<MyPageCustomViewModel>();