Is it possible to specify an object as a parameter in MVC with default values in some way?
E.g.
public virtual ViewResult Index(RequirementFilters requirementFilters)
I'd like to initialize the values of a couple of parameters on RequirementFilters?
At the moment I am doing
public virtual ViewResult Index(int status=1, bool required =false)
I wanted to create a Filter Object so I could re-use it but I can't figure out way of setting defaults for the object in the Action Parameters.
Thanks
Graeme
You could create a custom ActionFilter attribute and create an instance of your Filter Object there. You can provide some properties through the custom attribute.
Here's an example:
public class DefaultQuerySettingsAttribute : ActionFilterAttribute
{
public string ParameterName { get; set; }
public Type SettingsType { get; set; }
public int Rows { get; set; }
public string SortColumn { get; set; }
public string SortOrder { get; set; }
public bool PagingEnabled { get; set; }
public DefaultQuerySettingsAttribute()
{
this.ParameterName = "settings";
var defaultSettings = new QuerySettings();
this.Rows = defaultSettings.Rows;
this.SortColumn = defaultSettings.SortColumn;
this.SortOrder = defaultSettings.SortOrder;
this.PagingEnabled = defaultSettings.PagingEnabled;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (filterContext.ActionParameters.ContainsKey(this.ParameterName))
{
var querySettings = filterContext.ActionParameters[this.ParameterName] as QuerySettings;
if (querySettings == null || string.IsNullOrWhiteSpace(querySettings.SortColumn))
filterContext.ActionParameters[this.ParameterName] = this.GetQuerySettings();
}
}
private QuerySettings GetQuerySettings()
{
var querySettings = (QuerySettings)Activator.CreateInstance(SettingsType ?? typeof(QuerySettings));
querySettings.Rows = Rows;
querySettings.SortColumn = SortColumn;
querySettings.SortOrder = SortOrder;
querySettings.PagingEnabled = PagingEnabled;
return querySettings;
}
}
ParameterName is the name of the argument in the action method (requirementFilters in your case).
You can also specify actual type that will be instantiated by providing SettingsType.
Users sometimes prefer to see the defaults on screen, rather than allowing the system to hide the defaults internally.
A better way of having defaults will be to actually show the defaults on int UI, in the HTML by rendering it with together with the defaults. That way when someone posts the page, the defaults which you pre-rendered is also posted and binded to the model.
So try and see if you can render with defaults whatever for you are rendering and posted to the Index action.
Finally, if you can't do it that way, what is preventing you from initializing the properties with default values in the no-arg constructor while creating the object?
EDIT
Or you can use the C# language feature the null coalescent operator to implement defaults. Look here to read about it.
As long as you don't need to change the defaults per action, you can set them in the default constructor of the Model.
Related
I want to pass values using model as parameter.
This is basically the mvc web api app
This is my Model class
public class ConversionModel
{
public double value { get; set; }
public int qty { get; set; }
public double result { get; set; }
public string from { get; set; }
public string to { get; set; }
}
This is My controller code
[HttpGet]
[Route("api/Conversion/Currency")]
public double Currency(ConversionModel c)
{
return c.value;
}
And my url is
http://localhost:5267/api/Conversion/Currency?value=123
But is showing me an error
Object reference not set to an instance of an object.
You cannot pass an object in query string. You need to write all parameters in your Route annotation.
[Route("api/Conversion/Currency/{value}/{qty}/{result}/{from}/{to}")]
And then in your action:
public double Currency(double value, int qty, double result, string from, string to) {
var conversionModel = new ConversionModel();
conversionModel.value = value;
conversionModel.qty = qty;
conversionModel.result = result;
conversionModel.from = from;
conversionModel.to = to;
// Rest of your code.
}
You are only providing an integer value, it cannot be converted to ConversionModel.
You can either use post
[HttpPost]
public double Currency([FromBody]ConversionModel c)
which is more suitable for a complex object.
Or pass the values as separate get parameters, constructing a ConversionModel in the method body.
Or use get with [FromUri]; this still requires supplying all the individual parameter-values. (see here)
Passing a collection of individual values is a little fragile/clumsy, I would prefer to use post. Besides which, it is unlikely that you need all the values, and an instance of the class, if you will be simply returning a double value, so post is most likely to be appropriate.
I'm using OData v5/Web API 2.2 to create an endpoint that will return a list of employees from each company.
My problem occurs when I try to implement server-side paging while also using the OData $expand property. When I try to make a call to
http://localhost:60067/Companies?$expand=Employees
I get an error that says "Could not find a property named 'Employees' on type 'System.Web.OData.Query.Expressions.SelectAllAndExpand_1OfCompanyApiModel'"
However, when I removed the EnableQuery attribute the call to the endpoint or when I didn't expand it works as expected. Does anyone have an idea of what I am doing wrong? I've been googling this for a while but haven't found anything.
Here are some code snippets -
Data Models:
public class CompanyApiModel
{
[Key]
public Guid CompanyGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
public List<EmployeeApiModel> Employees { get; set; }
}
public class EmployeeApiModel
{
[Key]
public Guid EmployeeGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
}
CompaniesController.cs:
[EnableQuery(PageSize = 10)] // If I comment this out everything works
//[EnableQuery] // This fails as well
public IHttpActionResult Get(ODataQueryOptions<CompanyApiModel> queryOptions)
{
var companies = GetCompanies(queryOptions);
return Ok(companies);
// return Ok(companies.AsQueryable()); // This doesn't work either
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new OptionsRoutingConvention());
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
// below code allows endpoints to respond with either XML or JSON, depending on accept header preferences sent from client
// (default in absence of accept header is JSON)
var odataFormatters = ODataMediaTypeFormatters.Create();
config.Formatters.InsertRange(0, odataFormatters);
config.EnsureInitialized();
}
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "Demos";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<CompanyApiModel>("Companies");
builder.EntitySet<EmployeeApiModel>("Employees");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
Figured out the problem. We were overriding the EnableQuery attribute somewhere in our code and calling it EnableMappedQuery and applying it to the controller. Thus instead of having [EnableQuery(PageSize = 10)] I should have had [EnableMappedQuery(PageSize = 10)].
EnableQuery Attribute do many works,
1. it will validate the queryoption for you.
2. it will apply the queryoption for you.
3. it can add some querysettings like PageSize.
Your scenario not working is because your GetCompanies is already applied the queryoption, so when EnableQuery get the result and apply the queryoption again, it fails, it can't find the expand property, my suggestion is just return original Company and let EnableQuery do the reset of work for you, ODataQueryOption in parameter is also not needed.
If you realy do some custom work in GetCompanies and don't need EnableQuery to apply for you, you can add PageSize in ODataQuerySettings when you call method ODataQueryOptions.ApplyTo(IQueryable, ODataQuerySettings).
I have a web method that accepts object
[WebMethod]
public static void GetObject(object data)
{
}
Also, I have 2 classes:
class ConnectionString
{
public string ConnectionString { get; set; }
public DatabaseType DatabaseType { get; set; }
}
class Path
{
public string Path { get; set; }
public bool IsNetwork { get; set; }
}
On client side, using javascript, i defined 2 similar classes as well:
function ConnectionString() {
this.ConnectionString = '';
this.DatabaseType = 0;
};
function Path() {
this.Path = '';
this.IsNetwork = false;
};
Now, according to user decision, he can ether choose to create log in database or file system. When I send data to the method, my object resulted as null. If I create method
for each object, it works. Is there a way to unbox or desirialize from OBJECT type to ?
You need to create two method overloads that each take in one of the possible classes. In the current implementation the engine does not know what classes should be put in the WSDL...
If you are using WCF you could use [KnownType] attribute to specify which classes your method supports.
I am trying to create a custom attribute in mvc to use it's parameters in a view as breadCrumb.
well, this is the code of the attribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class BreadCrumbAttribute : Attribute {
public BreadCrumbAttribute(string title, string parent, string url) {
this._title = title;
this._parent = parent;
this._url = url;
}
#region named parameters properties
private string _title;
public string Title {
get { return _title; }
}
private string _url;
public string Url {
get { return _url; }
}
private string _parent;
public string Parent {
get { return _parent; }
}
#endregion
#region positional parameters properties
public string Comments { get; set; }
#endregion
}
this is the call of the attribute
[BreadCrumbAttribute("tile", "parent name", "url")]
public ActionResult Index() {
//code goes here
}
this is a way of how I'd like to get the values. (this is a partial view)
System.Reflection.MemberInfo inf = typeof(ProductsController);
object[] attributes;
attributes = inf.GetCustomAttributes(typeof(BreadCrumbAttribute), false);
foreach (Object attribute in attributes) {
var bca = (BreadCrumbAttribute)attribute;
Response.Write(string.Format("{0}><a href={1}>{2}</a>", bca.Parent, bca.Url, bca.Title));
}
Unfortunately, the attribute didn't get call with the way I implement it. Although, If I add the attribute in Class instead of an Action method it worked.
How could I make it work?
Thanks
The problem is that you are using reflection to get the attributes for the class, so naturally it does not include attributes defined on the action method.
To get those, you should define an ActionFilterAttribute, and in the OnActionExecuting or OnActionExecuted method, you can use filterContext.ActionDescriptor.GetCustomAttributes() method (MSDN description here).
Note that with this solution, you will likely have two different types of attributes: The first one is the one you wrote, to define the breadcrumbs. The second is the one that looks at the attributes on the executing action and builds up the breadcrumb (and presumably adds it to the ViewModel or sticks it in HttpContext.Items or something).
We're trying to get a conditional attribute to work, case in point, there's a boolean (checkbox) that if checked, its related text is required. So, ideally we'd have something like ...
public bool Provision { get; set; }
[ConditionalRequirement(IsNeededWhenTrue = Provision)]
public string ProvisionText { get; set; }
Is this even possible?
Alternate idea (not as elegant?)
public bool Provision2 { get; set; }
[PropertyRequired(RequiredBooleanPropertyName = "Provision2")]
public string Provision2Text { get; set; }
I'd hate to use the magic string method ... but any other ideas?
Ended up rolling my own. Basically you create a valiation method that does your normal check of yes, no, whatever and collects them in some kind of error collection. The rub with this is sending it BACK to the Model itself. So I got lazy and strongly typed it as such ...
public static void AddError<T>(this ErrorCollection errorCollection, Expression<Func<T, object>> expression, string friendlyUiName)
{
var propertyName = GetPropertyName(expression.ToString(), expression.Parameters[0].Name);
var propertyInfo = typeof (T).GetProperty(propertyName);
var resultError = DetermineOutput(friendlyUiName, propertyInfo.PropertyType);
errorCollection.Errors.Add(new ValidationError(propertyName, resultError));
}
so then you're validation statements have something like this in them ...
if (FirstName.IsEmpty())
EntityErrorCollection.AddError<SomeClass>(x => x.FirstName, "First Name");
Then within the controller, a simple check and port it BACK to the model if it (isn't valid of course) ...
foreach (var error in someObject.EntityErrorCollection.Errors)
ModelState.AddModelError(error.Property, error.Message);
There's probably a more cleaner way of doing this but so far, this has been working just fine.