I am writing a Web API with ASP.NET Web API, and make use of the following View Model.
I seem to be having a problem with the data binding when there are two validation attributes on a particular property (i.e. [Required] and [StringLength(10)]).
When posting a JSON value from a client to a controller action of the form:
// POST api/list
public void Post([FromBody] TaskViewModel taskVM)
I observe the following:
If I remove one of the multiple attributes everything is bound OK;
If I leave in the multiple attributes, the client recieves a 500 internal server error and the body of the Post method is never reached.
Any ideas why this happens?
Cheers
public class TaskViewModel
{
//Default Constructor
public TaskViewModel() { }
public static TaskViewModel MakeTaskViewModel(Task task)
{
return new TaskViewModel(task);
}
//Constructor
private TaskViewModel(Task task)
{
this.TaskId = task.TaskID;
this.Description = task.Description;
this.StartDate = task.StartDate;
this.Status = task.Status;
this.ListID = task.ListID;
}
public Guid TaskId { get; set; }
[Required]
[StringLength(10)]
public string Description { get; set; }
[Required]
[DataType(DataType.DateTime)]
public System.DateTime StartDate { get; set; }
[Required]
public string Status { get; set; }
public System.Guid ListID { get; set; }
}
You need to inspect what is inside in the 500 internal server
make sure that you turn customerror off in your web.config
If you selfhost web.API you need to set GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
use your browser development console's network tab (in IE, Chrome you can get the console with F12) or if you are using FireFox then use FireBug or a thrid party tool like Fiddler.
Then you can see what went wrong on the server and go further to solve your problem.
In your case this is in the response:
"Message":"An error has occurred.","ExceptionMessage":"Property
'StartDate' on type 'MvcApplication3.Controllers.TaskViewModel' is
invalid. Value-typed properties marked as [Required] must also be
marked with [DataMember(IsRequired=true)] to be recognized as
required. Consider attributing the declaring type with [DataContract]
and the property with
[DataMember(IsRequired=true)].","ExceptionType":"System.InvalidOperationException"
So your problem is not that you have two attributes but that you've marked your properties with [Required] to solve this the exception tells you what to do.
You need to add [DataMember(IsRequired=true)] to your required properties where the property type is a value type (e.g int, datatime, etc.):
So change your TaskViewModel to:
[DataContract]
public class TaskViewModel
{
//Default Constructor
public TaskViewModel() { }
[DataMember]
public Guid TaskId { get; set; }
[Required]
[DataMember]
[StringLength(10)]
public string Description { get; set; }
[Required]
[DataMember(IsRequired = true)]
[DataType(DataType.DateTime)]
public System.DateTime StartDate { get; set; }
[Required]
[DataMember]
public string Status { get; set; }
[DataMember]
public System.Guid ListID { get; set; }
}
Some side notes:
You need to reference the System.Runtime.Serialization dll in order to use the DataMemberAttribute
You need to mark your class with [DataContract] and you need to mark all of its properties with [DataMember] not just the required ones.
Related
I did some research but I couldn't find exactly what I wanted.
I have an endless menu. I have a MenuDTO and a MenuViewModel that I use for this menu. I had no problem matching between model and DTO, but am having trouble mapping DTO to ViewModel. Obviously I couldn't find the solution, can you help?
My MenuDTO Object
public class MenuDto : BaseDto
{
public string Name { get; set; }
public string Icon { get; set; }
public string Order { get; set; }
public string Url { get; set; }
public bool IsVisible { get; set; }
public int ParentId { get; set; }
public MenuDto ParentMenu { get; set; }
public List<MenuDto> Menus { get; set; }
}
And MenuViewModel
public class MenuViewModel
{
public int Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Order { get; set; }
public string Url { get; set; }
public bool IsVisible { get; set; }
public int ParentId { get; set; }
public MenuViewModel ParentMenu { get; set; }
public List<MenuViewModel> Menus { get; set; }
}
This is how I mapped the MenuDTO and MenuViewModel objects.
public class WebProfile : Profile
{
public WebProfile()
{
CreateMap<MenuDto, MenuViewModel>();
CreateMap<MenuViewModel, MenuDto>();
}
}
I call this way in the controller
var navMenuItems = _mapper.Map<List<MenuViewModel>(_menuService.GetNavMenus());
Although all fields are mapped, I get an error on the Menus field.
The error message I get is;
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
MenuDto -> MenuViewModel
BiPortal2020.Business.ServiceDTOs.Menu.MenuDto -> BiPortal2020.WebUI.Areas.Admin.Models.Menu.MenuViewModel
lambda_method(Closure , MenuDto , MenuViewModel , ResolutionContext )
AutoMapperMappingException: Error mapping types.
Mapping types:
Object -> List`1
System.Object -> System.Collections.Generic.List`1
The error message implies - AutoMapper, either cannot map between MenuDto and MenuViewModel, or it cannot locate the defined mappings.
I've tested your mappings and they are totally fine. So, what possibility remains is AutoMapper cannot locate your mappings.
I'm Assuming the Business Layer and UI Layer you mentioned in the comment section are two separate projects. Since the WebProfile is defined in the UI Layer, you have to tell AutoMapper that it should search that assembly to find the mappings. Since your mappings between Models and DTOs are working, I can guess you've already done the same for BusinessProfile which is defined in the Business Layer.
I don't know about your existing code, but you could do something like this - in the Startup.Configure method add/modify the following line -
services.AddAutoMapper(typeof(IDtoMapping), typeof(IViewModelMapping));
where IDtoMapping and IViewModelMapping are two marker interface (empty interface, used only to identify the assembly they are declared in) declared in the Business Layer and UI Layer, respectively.
I'm learning about the Bind attribute and I have a doubt.
I can use the Bind attribute to include/exclude the data that will be posted, so.
Would it not be better to use a specific ViewModel instead of the Bind attribute?
Think about what happened if your entity changes overtime, then you might force to change all your different viewModels which you have created instead of using Include or Exclude. it will get hard to maintain your code.
Suppose you have this :
public class PersonalViewModel
{
private int PersonalID { get; set; }
public string PersonalName { get; set; }
public string PersonalFamily { get; set; }
public byte? GenderID { get; set; }
public string PersonalPhone { get; set;}
}
Consider these :
public string ShowPersonalToAll(
[Bind(Exclude = "PersonalPhone")]PersonalViewModel newPersonal)
{...}
OR
public class PersonalViewModel
{
private int PersonalID { get; set; }
public string PersonalName { get; set; }
public string PersonalFamily { get; set; }
public byte? GenderID { get; set; }
}
Now What if saving personal's mobile become important! and if you have created different customized ViewModel for several action (depends on application's business)?
Then you have to change the main ViewModel and all the other Customize ViewModel, While by using Exclude there is no need to change ViewModels, no need to change actions and the main ViewModel just changes.
Let's say we have a class like so:
public class Plan
{
public string PlanCode { get; set; } //Required
public string Name { get; private set; }
public string Description { get; set; }
public string SuccessUrl { get; set; }
}
Is there a way to make intellisense show required fields/properties different, such as italicized or a shade of red?
One would be able to determine what properties would be required quickly if calling a Create() method for instance.
Note: When I say show up different in intellisense I don't mean the tool-tip text that you see when the member is highlighted. I specifically mean the text of the member itself.
No, I don't believe you can. You should design your objects with required fields in mind, i.e. constructors.
So any fields that the object requires should be parameters in a constructor.
public class Plan
{
public string PlanCode { get; set; } //Required
public string Name { get; private set; }
public string Description { get; set; }
public string SuccessUrl { get; set; }
public Plan(string planCode)
{
PlanCode = planCode;
}
}
This is allows your code to clearly express its intention. Any developer using the Plan class will know that PlanCode is required, as it is enforced by the constructor.
I am applying validation using DataAnnotations to an MVC ViewModel which is a composite of several entity framework objects and some custom logic. The validation is already defined for the entity objects in interfaces, but how can I apply this validation to the ViewModel?
My initial idea was to combine the interfaces into one and apply the combined interface to the ViewModel, but this didn't work. Here's some sample code demonstrating what I mean:
// interfaces containing DataAnnotations implemented by entity framework classes
public interface IPerson
{
[Required]
[Display(Name = "First Name")]
string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
string LastName { get; set; }
[Required]
int Age { get; set; }
}
public interface IAddress
{
[Required]
[Display(Name = "Street")]
string Street1 { get; set; }
[Display(Name = "")]
string Street2 { get; set; }
[Required]
string City { get; set; }
[Required]
string State { get; set; }
[Required]
string Country { get; set; }
}
// partial entity framework classes to specify interfaces
public partial class Person : IPerson {}
public partial class Address : IAddress {}
// combined interface
public interface IPersonViewModel : IPerson, IAddress {}
// ViewModel flattening a Person with Address for use in View
[MetadataType(typeof(IPersonViewModel))] // <--- This does not work.
public class PersonViewModel : IPersonViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
My real-world problem involves about 150 properties on the ViewModel, so it's not as trivial as the sample and retyping all the properties seems like a horrible violation of DRY.
Any ideas on how to accomplish this?
In order for this to work you will need to manually associate the interfaces as metadata for your concrete classes.
I expected to be able to add multiple MetadataType attributes but that is not permitted.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] // Notice AllowMultiple
public sealed class MetadataTypeAttribute : Attribute
Therefore, this gives a compilation error:
[MetadataType(typeof(IPerson))]
[MetadataType(typeof(IAddress))] // <--- Duplicate 'MetadataType' attribute
public class PersonViewModel : IPersonViewModel
However, it works if you only have one interface. So my solution to this was to simply associate the interfaces using a AssociatedMetadataTypeTypeDescriptionProvider and wrap that in another attribute.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetadataTypeBuddyAttribute : Attribute
{
public MetadataTypeBuddyAttribute(Type modelType, Type buddyType)
{
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
modelType,
buddyType
),
modelType);
}
}
In my situation (MVC4) the data annotation attributes on my interfaces already worked. This is because my models directly implement the interfaces instead of having multi-level inheritance. However custom validation attributes implemented at the interface level do not work.
Only when manually associating the interfaces all the custom validations work accordingly. If I understand your case correctly this is also a solution for your problem.
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IPerson))]
[MetadataTypeBuddy(typeof(PersonViewModel), typeof(IAddress))]
public class PersonViewModel : IPersonViewModel
based on answer here, I couldn't somehow make that MetadataTypeBuddy attribute works. I'm sure that we must set somewhere that MVC should be calling that attribute. I managed to get it work when I run that attribute manually in Application_Start() like this
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IPerson));
new MetadataTypeBuddyAttribute(typeof(PersonViewModel), typeof(IAddress));
The MetadataTypeBuddy attribute did not work for me.
BUT adding "new" MetadataTypeBuddyAttribute in the "Startup" did work BUT it can lead to complex code where the developer is not aware to add this in the "Startup" for any new classes.
NOTE: You only need to call the AddProviderTransparent once at the startup of the app per class.
Here is a thread safe way of adding multiple Metadata types for a class.
[AttributeUsage(AttributeTargets.Class)]
public class MetadataTypeMultiAttribute : Attribute
{
private static bool _added = false;
private static readonly object padlock = new object();
public MetadataTypeMultiAttribute(Type modelType, params Type[] metaDataTypes)
{
lock (padlock)
{
if (_added == false)
{
foreach (Type metaDataType in metaDataTypes)
{
System.ComponentModel.TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(
modelType,
metaDataType
),
modelType);
}
_added = true;
}
}
}
}
I have a Solution under the solution there is few Projects one of the called DomainModel,
in which i write my models and other stuff mainly infrastructure.
Now i have another project called WebUI in which i do my UI (Views, Controllers , etc...)
I want to use Remote attribute in DomainModel project which must implemented in WebUI certain view.
When i use it in DomainModel it's gives me an error, that it does not recognize the Controller and it's correct it does not recognize it because the if I add the reference of WebUI the Vs begin to swear at me because it will be a circular reference.
How to implement this?
this is my code
Controller that serves the RemoteValidation
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public class RemoteValidationController : Controller
{
public JsonResult CheckPassword(string SmsCode)
{
return Json(12345, JsonRequestBehavior.AllowGet);
}
}
//The real entity in DomainModel project
public class SmsCustomer
{
public int CustomerId { get; set; }
public string Cli { get; set; }
//this is what i have to validate on server
public virtual string SmsCode { get; set; }
public DateTime InsertDate { get; set; }
public int CustomerDaysChoiceId { get; set; }
public int CustomerAmountChoiceId { get; set; }
[Required(ErrorMessage = "error")]
[StringLength(128, ErrorMessage = "error")]
public string SelectedWords { get; set; }
public SmsCustomerDaysChoice CustomerDaysChoice { get; set; }
public SmsCustomerAmountChoice CustomerAmountChoice { get; set; }
}
this is my entity after i extend it with the remote attr in WebUI.Models
public class Customer : SmsCustomer
{
[Required(ErrorMessage = "Error required")]
[StringLength(9, ErrorMessage = "Error length")]
[Remote("CheckPassword", "RemoteValidation", ErrorMessage = "Error remote")]
public override string SmsCode { get; set; }
}
this is my view
#Html.TextBoxFor(c => c.SmsCode)
//error span
<span class="checkbox-form-error" data-valmsg-for="SmsCode" data-valmsg-replace="true"> </span>
The remote validation stuff is very specific to the WebUI project.
Because of this, I'd create a View model that inherits from the actual class, and then override the property that needs remote validation. Then you should be able to specify the controller/action for remote validation.
You can also put your validation in a class of its own, like ScottGu demonstrates here:
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
(Look down the post, before the last step)
Also take a look at this: Adding DataAnnontations to Generated Partial Classes