I have a domain object Owner. This has some data annotations which will be used in the web api post method to validate.
public class Owner
{
[Required(ErrorMessage ="Please select a title")]
public string Title { get; set; }
[Required(ErrorMessage = "First name is required")]
[MaxLength(100, ErrorMessage ="Too long")]
public string Firstname { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string Lastname { get; set; }
public string PostalAddressStreet { get; set; }
public string PostalAddressSuburb { get; set; }
public string PostalAddressState { get; set; }
}
Now i need to send this object's validation rules (defined in data annotations) to the front end in a get request. I was looking at this question which explains how to do this in MVC. But couldnt get it to working in a web api Get method. This is what i tried.
[HttpGet]
[Route("GetOwnerDefinition")]
public string GetPetOwnerDefinition()
{
Owner owner = new Owner();
System.Web.Http.Metadata.Providers.DataAnnotationsModelMetadataProvider metaProvider = new System.Web.Http.Metadata.Providers.DataAnnotationsModelMetadataProvider();
var metaData = metaProvider.GetMetadataForProperty(null, typeof(Owner), "Firstname");
var validationRules = metaData.GetValidators(GlobalConfiguration.Configuration.Services.GetModelValidatorProviders());
foreach(System.Web.Http.Validation.Validators.DataAnnotationsModelValidator modelValidator in validationRules)
{
//need help here
}
At the end of the day i need to generate a JSON definition as below.
{"Firstname": "John",
"ValidationRules":[{"data-val-required":"This field is required.", "data-val-length-max":100}]}
Not sure whether it is the best way, this is what i have done. I have created a new instance of MVC context inside a web api method.
var mvcContext = new System.Web.Mvc.ControllerContext();
See the code below.
public Dictionary<string, string> GetValidationDefinition(object container, Type type)
{
var modelMetaData = System.Web.Mvc.ModelMetadataProviders.Current.GetMetadataForProperties(container, type);
var mvcContext = new System.Web.Mvc.ControllerContext();
var validationAttributes = new Dictionary<string, string>();
foreach (var metaDataForProperty in modelMetaData)
{
var validationRulesForProperty = metaDataForProperty.GetValidators(mvcContext).SelectMany(v => v.GetClientValidationRules());
foreach (System.Web.Mvc.ModelClientValidationRule rule in validationRulesForProperty)
{
string key = metaDataForProperty.PropertyName + "-" + rule.ValidationType;
validationAttributes.Add(key, System.Web.HttpUtility.HtmlEncode(rule.ErrorMessage ?? string.Empty));
key = key + "-";
foreach (KeyValuePair<string, object> pair in rule.ValidationParameters)
{
validationAttributes.Add(key + pair.Key, System.Web.HttpUtility.HtmlAttributeEncode(pair.Value != null ? Convert.ToString(pair.Value, System.Globalization.CultureInfo.InvariantCulture) : string.Empty));
}
}
}
return validationAttributes;
}
Related
I am developing a web api .I got the following error . same code structure working for fetchbyid ,post,edit . What did i wrong here . please help me.
Catalog.cs:
public class Catalog
{
[JsonProperty("id")]
public Guid? Id { get; set; }
[JsonProperty("VendorName")]
public string VendorName { get; set; }
// public List<Industy> Industy { get; set; }
public Industy Industy { get; set; }
public Catalog()
{
if (Id == null)
{
Id = Guid.NewGuid();
}
else
{
Id = Id;
}
// this.Industy = new List<Industy>();
}
}
public async Task<IEnumerable<Catalog>> FetchListAsync(
Guid? itemId)
{
var feedOptions =
new FeedOptions
{
MaxItemCount = -1,
EnableCrossPartitionQuery = true
};
var query = new SqlQuerySpec
{
QueryText = "SELECT * FROM c"
};
var orderDocumentQuery =
_cosmosClient.CreateDocumentQuery<Catalog>(
UriFactory.CreateDocumentCollectionUri(
_azureCosmosDbOptions.Value.DatabaseId, "catalog"), query, feedOptions)
.AsDocumentQuery();
var orderList =
new List<Catalog>();
Console.WriteLine(orderDocumentQuery.ToString());
while (orderDocumentQuery.HasMoreResults)
{
orderList.AddRange(
await orderDocumentQuery.ExecuteNextAsync<Catalog>());
}
return orderList;
}
error:
JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[CatalogAPI.Entities.Industy]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path 'Industy.Id', line 1, position 117.
What do i doing wrong??
The POCO object in Cosmos DB needs to have it's id field to be a string.
You need to replace public Guid? Id { get; set; } with public string Id { get; set; }.
I have one list populated from Database with properties code_type , description , min_value, max_value, id etc
I want to create another lists from list 1 with filter condition value of code_type with only three properties
i.e description , min_value, & max value (code_type is not required). This revised list will be used for to bind View in MVC
\Kindly help for the same
Below is my code for the same. If code ="04" Then populate list for caste & so on. Can I Use Linq for the same?
public class MyPrefernce
{
public string memberid { get; set; }
public string code { get; set; }
public string description { get; set; }
public long? min_value { get; set; }
public long? max_value { get; set; }
public long? sysid { get; set; }
public string isChecked { get; set; }
public List<Caste> lcaste;
public List<MyPrefernce> getPrefence(long sysmemberid, string memberid)
{
List<MyPrefernce> lstObj = new List<MyPrefernce>();
string strQuery = "proc_Get_Member_Preference";
Connection cobj = new Connection();
string strConnection = cobj.getConnectionString();
SqlConnection con = new SqlConnection(strConnection);
con.Open();
SqlCommand cmd = new SqlCommand(strQuery, con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#nSysMemberId", sysmemberid);
SqlDataAdapter ada = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
ada.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
{
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
MyPrefernce obj = new MyPrefernce();
obj.code = ds.Tables[0].Rows[i]["code"].ToString();
obj.isChecked = ds.Tables[0].Rows[i]["isChecked"].ToString();
obj.min_value = Convert.ToInt64(ds.Tables[0].Rows[i]["min_value"]);
obj.max_value = Convert.ToInt64(ds.Tables[0].Rows[i]["max_value"]);
obj.sysid = sysid;
obj.memberid = memberid;
lstObj.Add(obj);
}
}
return lstObj;
}
}
public class Caste
{
public int sysId { get; set; }
public string decription { get; set; }
public string? isChecked { get; set; }
}
You should create a new class, defining the fields you need, and use auto-mapper to map the the fields between classes.
I'm trying to create my profile type page for my simple blog site. I have two simple model class like this:
public class UserInfoModel
{
public string UserName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
public class NewPost
{
public string PostTitle { get; set; }
public string PostStory { get; set; }
}
I have created a joint model class of user & post to pass to view like this:
public class UserPostModel
{
public UserInfoModel User { get; set; }
public NewPost Post { get; set; }
}
The methods I wrote to retrieve the user & post info are like this:
public int GetUserID(string _UserName)
{
using (var context = new TourBlogEntities1())
{
var UserID = from s in context.UserInfoes
where s.UserName == _UserName
select s.UserID;
return UserID.Single();
}
}
public UserInfo GetUserDetails(int _UserID)
{
using (var context = new TourBlogEntities1())
{
var UserDetails = (from s in context.UserInfoes
where s.UserID == _UserID
select s).Single();
return UserDetails;
}
}
public Post GetUserPosts(int _UserID)
{
using (var context = new TourBlogEntities1())
{
var entity = (from s in context.Posts
where s.UserID == _UserID
select s).Single();
return entity;
}
}
And finally I'm calling all my method from my controller action like this:
[Authorize]
public ActionResult MyProfile()
{
var Business = new Business();
var UserID=Business.GetUserID(User.Identity.Name);
var UserEntity=Business.GetUserDetails(UserID);
var PostEntity=Business.GetUserPosts(UserID);
var model = new UserPostModel();
model.User.UserName = UserEntity.UserName; // problem showing here
model.User.Email = UserEntity.Email;
model.Post.PostTitle = PostEntity.PostTitle;
model.Post.PostStory = PostEntity.PostStory;
return View("MyProfile",model);
}
A run time error showing like " object is not referenced to a object type or null object". I worked ok in a very similar way while passing single model. Whats I'm doing wrong here?
Modified your UserPostModel
public class UserPostModel
{
public UserPostModel()
{
User = new UserInfoModel();
Post = new Post();
}
public UserInfoModel User { get; set; }
public NewPost Post { get; set; }
}
NOTE: check each value before set to model it should not be null.
I have a custom attribute called FileLink that uses DisplayForModel() to generate an ActionLink to a controller with an ID value.
The attribute looks like this:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class FileLinkAttribute : Attribute
{
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string IdPropertyName { get; set; }
public FileLinkAttribute(string actionName, string controllerName, string idPropertyName)
{
ActionName = actionName;
ControllerName = controllerName;
IdPropertyName = idName;
}
}
Is it used in a viewmodel, like so:
public class MyViewModel
{
[HiddenInput(DisplayValue = false)]
public int? Id { get; set; }
[HiddenInput(DisplayValue = false)]
public int? TargetId { get; set; }
[FileLink("SomeAction", "SomeController", "TargetId")]
public string Name { get; set; }
}
I have a custom MetadataProvider derived from DataAnnotationsModelMetadataProvider that puts the attribute values into the metadata AdditionalValues as so:
// retrieve the values to generate a file link
var fileLinkAttributes = attributes.OfType<FileLinkAttribute>();
if (fileLinkAttributes.Any())
{
var fileLinkAttribute = fileLinkAttributes.First();
metadata.AdditionalValues["FileLinkAttribute"] = fileLinkAttribute;
metadata.TemplateHint = "FileLink";
}
The goal is to have DisplayForModel call the DisplayTemplate below to generate a link with the value in TargetId from MyViewModel. So if TargetId is 5, the link will be "SomeController/SomeAction/5"
// This DisplayTemplate for use with the "FileLinkAttribute"
//
// Usage: [FileLinkAttribute("ActionName","ControllerName","IdPropertyName")]
var fileLinkAttribute = (FileLinkAttribute)ViewData.ModelMetadata.AdditionalValues["FileLinkAttribute"];
var targetId = *<GET VALUE OF CONTAINER MODEL IdPropertyName>*;
#Html.ActionLink((string)ViewData.Model, fileLinkAttribute.ActionName, fileLinkAttribute.ControllerName, new { Id = targetId}, null)
With all this in place, I am unable to access the containing object to pull the property value from TargetId. I have tried to do this within the Attribute, within the Provider, and within the DisplayTemplate, with no luck.
Is there a way or place I can access this value to accomplish my intent to read a property value from the viewmodel object containing the attribute?
After reading this post describing the purpose of modelAccessor in a custom DataAnnotationsModelMetadataProvider:
What is the "Func<object> modelAccessor" parameter for in MVC's DataAnnotationsModelMetadataProvider?
I was able to piece together this (very hackish) solution implemented in the custom provider:
// retrieve the values to generate a file link
var fileLinkAttributes = attributes.OfType<FileLinkAttribute>();
if (fileLinkAttributes.Any())
{
var fileLinkAttribute = fileLinkAttributes.First();
// modelAccessor contains a pointer the container, but the type is not correct but contains the name of the correct type
if (modelAccessor.Target.GetType().ToString().Contains("System.Web.Mvc.AssociatedMetadataProvider"))
{
// get the container model for this metadata
FieldInfo containerInfo = modelAccessor.Target.GetType().GetField("container");
var containerObject = containerInfo.GetValue(modelAccessor.Target);
// get the value of the requested property
PropertyInfo pi = containerObject.GetType().GetProperty(fileLinkAttribute.IdPropertyName);
string idPropertyValue = pi.GetValue(containerObject, null).ToString();
fileLinkAttribute.IdPropertyValue = idPropertyValue;
}
metadata.AdditionalValues["FileLinkAttribute"] = fileLinkAttribute;
metadata.TemplateHint = "FileLink";
}
I am not sure how to derefence a Func to get it's actual target, and the type name comes out all munged wierdly in this example, such as
"System.Web.Mvc.AssociatedMetadataProvider+<>c__DisplayClassb"
So I convert to a string to check if it contains "System.Web.Mvc.AssociatedMetadataProvider". Probably not the best thing but this is the only way I could get it to work.
Oh, I also added a 4th property to the attribute to contain the value:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class FileLinkAttribute : Attribute
{
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string IdPropertyName { get; set; }
public string IdPropertyValue { get; set; }
public FileLinkAttribute(string actionName, string controllerName, string idPropertyName)
{
ActionName = actionName;
ControllerName = controllerName;
IdPropertyName = idPropertyName;
}
}
and in the DisplayTemplate I do:
var modelId = fileLinkAttribute.IdPropertyValue;
This should be an editor template for strsing or object as that'swhat you are applying the attribute on. So inside ~/Views/Shared/EditorTemplates/string.cshtml:
#model string
#{
var fileLinkAttribute = (FileLinkAttribute)ViewData.ModelMetadata.AdditionalValues["FileLinkAttribute"];
var targetId = fileLinkAttribute.IdPropertyName;
}
#Html.ActionLink(Model, fileLinkAttribute.ActionName, fileLinkAttribute.ControllerName, new { Id = targetId}, null)
and in your main view:
#Html.EditorFor(x => x.Name)
I´m started to work with AutoMapper today...
But I´m having some problem with Dropdown model...
What I have so far :
User Model
public class User : Entity
{
public virtual string Name { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public virtual Role Role { get; set; }
}
Role Model
public class Role : Entity
{
public virtual string Name { get; set; }
}
UserUpdateViewModel
public class UserUpdateViewModel
{
public int Id{get;set;}
[Required(ErrorMessage = "Required.")]
public virtual string Name { get; set; }
[Required(ErrorMessage = "Required."), Email(ErrorMessage = "Email Invalid."), Remote("EmailExists", "User", ErrorMessage = "Email already in use.")]
public virtual string Email { get; set; }
[Required(ErrorMessage = "Required.")]
public virtual string Password { get; set; }
[Required(ErrorMessage = "Required")]
public virtual string ConfirmPassword { get; set; }
[Required(ErrorMessage = "Required.")]
public int RoleId { get; set; }
public IList<Role> Roles { get; set; }
}
UserController
public ActionResult Update(int id=-1)
{
var _user = (_userRepository.Get(id));
if (_user == null)
return RedirectToAction("Index");
Mapper.CreateMap<User, UserUpdateViewModel>();
var viewModel = Mapper.Map<User, UserUpdateViewModel>(_user);
viewModel.Roles = _roleRepository.GetAll();
return View(viewModel);
}
[HttpPost, Transaction]
public ActionResult Update(UserViewModel user)
{
if (ModelState.IsValid)
{
user.Password = _userService.GetPasswordHash(user.Password);
Mapper.CreateMap<UserViewModel, User>();
var model = Mapper.Map<UserViewModel, User>(user); //model.Role = null
_userRepository.SaveOrUpdate(model); //ERROR, because model.Role = null
return Content("Ok");
}
return Content("Erro").
}
View Update
...
#Html.DropDownListFor(model => model.RoleId, new SelectList(Model.Roles, "Id", "Name"), "-- Select--", new { #class = "form radius" })
...
Some considerations:
1 - I´m returning Content() because is all Ajax enabled using HTML 5 PushState etc etc
2 - In my Update(POST one) method, my model returned by Autommapper has Role = null
Why my Role returned by Automapper is null?
Is that the right way to work with AutoMapper? Any tip?
Thanks
The map is failing because you are trying to map a single Role directly to a collection of Roles. And a collection of Roles back to a single Role. You cant directly map between these as they are different types.
If you wanted to map a Role to a List then you could use a custom value resolver.
Mapper.CreateMap<User , UserUpdateViewModel>()
.ForMember(dest => dest.Roles, opt => opt.ResolveUsing<RoleToCollectionResolver>())
Public class RoleToCollectionResolver: ValueResolver<User,IList<Role>>{
Protected override IList<Role> ResolveCore(User source){
var roleList = new List<Role>();
roleList.Add(source.Role);
Return roleList;
}
}