I'm using ValueInjecter to flatten/unflatten view models into domain objects created by Entity Framework (4.3.1) model-first. All of my VARCHAR columns in my database are NOT NULL DEFAULT '' (personal preference, no desire to open up a holy war here). On post, the view model comes back with any string property that has no value as null, so when I attempt to inject it back into my domain model class, EF barks at me for attempting to set a property with IsNullable=false to null. Example (over-simple):
public class ThingViewModel
{
public int ThingId{get;set;}
public string Name{get;set;}
}
public class Thing
{
public global::System.Int32 ThingId
{
//omitted for brevity
}
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String Name
{
//omitted for brevity
}
}
Then, my controller post looks like this:
[HttpPost]
public ActionResult Edit(ThingViewModel thing)
{
var dbThing = _thingRepo.GetThing(thing.ThingId);
//if thing.Name is null, this bombs
dbThing.InjectFrom<UnflatLoopValueInjection>(thing);
_thingRepo.Save();
return View(thing);
}
I'm using UnflatLoopValueInjection because I have nested types in the actual domain version of Thing. I attempted to write a custom ConventionInjection to convert null strings to string.Empty, but it appears that UnflatLoopValueInjection switches it back to null. Is there a way I can get ValueInjecter not to do this?
Nuts, I just figured it out with help from the wiki. The solution appears to be to extend UnflatLoopValueInjection:
public class NullStringUnflatLoopValueInjection : UnflatLoopValueInjection<string, string>
{
protected override string SetValue(string sourceValue)
{
return sourceValue ?? string.Empty;
}
}
Related
I have created the following type in my MVC 6 application:
public class EncryptedType
{
...
}
I have a controller method as follows:
public IActionResult Index(EncryptedType id)
{
...
}
So given the the url would be something like:
http://localhost/Area1/Controller1/Index/fgf23237dsd
Where the EncryptedType class can handle converting to/from a string.
Currently the id is defaulting to the parameterless constructor value. What do I need to do to make this automatically convert the string id on the url to an instance?
Naturally I could use a string but I just feel having an explicit type to represent the type in the method is more explicit.
This should work out of the box.
For example, if this is EncryptedType:
public class EncryptedType
{
public string Id {get; set;}
public string Name {get; set;}
}
Then, if your query string looks something like this:
http://localhost:5000/Index?Id=1&Name=MyName
The data from the query string will be parsed automatically to an instance of this class.
It is not possible to instansiate the class with an other constructor than the default one.
Please look at the docs:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding
There is a section there that states the following:
In order for binding to happen the class must have a public default
constructor and member to be bound must be public writable properties.
When model binding happens the class will only be instantiated using
the public default constructor, then the properties can be set.
OK, I guess today I have my googling head on, so was able to work through a number of Stack Overflows and Microsoft docs. There are essentially 4 parts to wire up. These are:
IModelBinder implementation
IModelBinderProvider implementation
Mvc Service Registration Options
Attribute on the Controller Action
IModelBinder implementation
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class EncryptedTypeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.ModelType == typeof(EncryptedType))
{
EncryptedType decodedEncryptedTypeParameter;
ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
string theStringToConvertToEncryptedType = value.FirstValue;
// add the custom convert from string to your type here, and set on the bindingContext.Result. We still return
// this value from the method wrapped in a Task.
if (EncryptedType.TryParse(theStringToConvertToEncryptedType, out decodedEncryptedTypeParameter))
{
bindingContext.Result = ModelBindingResult.Success(decodedEncryptedTypeParameter);
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.FromResult(bindingContext.Result);
}
return Task.FromResult(ModelBindingResult.Failed());
}
}
IModelBinderProvider implementation
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class EncryptedTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
return new EncryptedTypeModelBinder();
}
}
Mvc Service Registration Options
services.AddMvc().AddMvcOptions(a =>
a.ModelBinderProviders.Add(new EncryptedTypeModelBinderProvider()));
Attribute on the Controller Action
public IActionResult Index(
[ModelBinder(BinderType = typeof(EncryptedTypeModelBinder))] EncryptedType id)
I am working in MVC4 and want to define a model using an Uppercase attribute. The idea would be that the presence of the Uppercase attribute would cause the model value to be converted to uppercase when it arrived at the server.
At the moment I have the following code within the model:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode, Uppercase=true)]
public string Account
{
get { return _account; }
set
{
if (value != null)
_account = value.ToUpper();
}
}
But what I would really like is this:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode)]
[Uppercase]
public string Account { get; set; }
I think that I may need to create the Uppercase attribute as a ValidationAttribute to ensure it gets fired when the model hits the server. But that seems a bit wrong, as I'm not really validating the data. Is there a better way?
Also, is there any way to ensure the invocation order on the attributes? I really want to convert the data to uppercase before the custom StringValidation attribute fires, as this checks the case of the text in the regex pattern.
To add a bit of background to this, I want to reduce the need to add code to uppercase the data. The nirvana would be a single attribute, which updates the data on the way into the server, either in the model binding or validation stage. This attribute can then be referenced in the StringValidation attribute to amend the RegEx value used in its checks. I can also then lookup this attribute in a custom TextBoxFor helper method, such that I can add text-transform: uppercase so it looks correct on the client side.
Does anyone have any ideas out there?
I have managed to get this working, to a point, so here's my solution for others to appraise.
Once point to note was that the full solution couldn't be achieved because I couldn't get the Modelmetadata inside the StringValidation.IsValid() attribute. The particular issue I had here was that I could get the Metadata, however I could not get the PropertyName from it, only the DisplayName. There were multiple options out there, but the fact that some of my properties have the same DisplayName means that I couldn't be sure that the ProprtyName was the one I was actually validating.
Here's the code for the ValidationAttribute:
public class StringValidationAttribute : ValidationAttribute, IClientValidatable, IMetadataAware {
private bool _uppercase;
public StringValidationAttribute(bool uppercase = false) {
_uppercase = uppercase;
}
...
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["Uppercase"] = _uppercase;
}
}
I then created a new IModelBinder implementation:
public class StringBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null)
return null;
if (bindingContext.ModelMetadata.AdditionalValues.ContainsKey("Uppercase")) {
if ((bool)bindingContext.ModelMetadata.AdditionalValues["Uppercase"]])
return result.AttemptedValue.ToUpper();
}
return result.AttemptedValue;
}
}
And registered that in myGlobal.asax file:
ModelBinders.Binders.Add(typeof(string), new StringBinder());
The code so far will cause any string input coming into MVC to be converted to Uppercase if it has StringValidationAttribute attached to it on the model, and where the uppercase indicator has been set.
Next, to achieve my desire of making the html forms be uppercase too, I implemented a new EditorTemplate named string.cshtml. In this view I added:
RouteValueDictionary htmlAttributes = new RouteValueDictionary();
if ((bool)ViewData.ModelMetadata.AdditionalValues["Uppercase"]) {
htmlAttributes.Add("class", "Uppercase");
}
#Html.TextBox("", Model, htmlAttributes)
With the CSS as;
.Uppercase {
text-transform: uppercase;
}
Hope this post helps some others out there.
For Web API purpose it is better to convert the incoming json to uppercase or lowercase.
public class ToUpperCase : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.Value.ToString().ToUpper();
}
}
[Display(Name = "PNR NAME")]
[JsonConverter(typeof(Annotations.ToUpperCase))]
public string PNR { get; set; }
OR Globally;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//.......... others
JsonMediaTypeFormatter jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings();
jSettings.Converters.Add(new UpperCaseStringConverter());
jsonFormatter.SerializerSettings = jSettings;
}
You're right, ValidationAttribute is not the right fit. It seems like doing this at the Model Binding stage would be a better idea. See this article for a detailed explanation of how to customize this behavior.
Based on the information provided there, I believe you should be able to create an attribute based on CustomModelBinderAttribute like this:
[AttributeUsage(AttributeTargets.Property)]
public class UppercaseAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new UppercaseModelBinder();
}
private class UppercaseModelBinder : DefaultModelBinder
{
public override object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var strValue = value as string;
if (strValue == null)
return value;
return strValue.ToUpperInvariant();
}
}
}
I have not tested this. Let me know if it works or not.
NOTE:
I'm adding on to this post because until I discovered the approach I now use, I read this and tried all above unsuccessfully.
I generally use a two part process when dealing with forcing text data to be formatted as uppercase. 1. at the view and 2. at the controller
At the view layer so that the user knows data is going to be used in the uppercase form. This can be down through htmlAttributes used in the EditorFor HTML helper.
#HTML.EditorFor(model => model.Access_Code, new { htmlAttributes = new Style= "text-transform:uppercase"}})
Now this only forces the data seen and entered by the user to uppercase and not the data sent to the server. To do that requires some code in the associated method in the controller.
I add the ToUpper() method to the target attribute of the object being passed back to the contoller. Here is hypothetical example showing this.
public ActionResult verify(int? id)
{
var userData = db.user.Where (i=> i.userID == id).Single();
userData.Access_Code = userData.Access_Code.ToUpper();
...
}
can anybody provide a link for creating a viewmodel for the edmx designer class
suppose my edmx file is named School.edmx and it has school.Designer.cs class.
In the designer class i have the folowing entity object
[EdmEntityTypeAttribute(NamespaceName="teamworkModel", Name="User")]
[Serializable()]
[DataContractAttribute(IsReference=true)]
public partial class User : EntityObject
{
#region Primitive Properties
[EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Int32 User_Pk
{
get
{
return _User_Pk;
}
set
{
if (_User_Pk != value)
{
OnUser_PkChanging(value);
ReportPropertyChanging("User_Pk");
_User_Pk = StructuralObject.SetValidValue(value);
ReportPropertyChanged("User_Pk");
OnUser_PkChanged();
}
}
}
private global::System.Int32 _User_Pk;
partial void OnUser_PkChanging(global::System.Int32 value);
partial void OnUser_PkChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
[Required(ErrorMessage="Please enter your name")]
[StringLength(20,ErrorMessage="Name cannot exceed 20 characters")]
[RegularExpression(#"^([a-zA-Z0-9 \.\&\'\-]+)$", ErrorMessage = "Invalid name")]
public global::System.String User_Name
{
get
{
return _User_Name;
}
set
{
OnUser_NameChanging(value);
ReportPropertyChanging("User_Name");
_User_Name = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("User_Name");
OnUser_NameChanged();
}
}
private global::System.String _User_Name;
partial void OnUser_NameChanging(global::System.String value);
partial void OnUser_NameChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
[Email(ErrorMessage="Invalid email address")]
[Required(ErrorMessage="Please enter email address")]
public global::System.String User_Mail_Id
{
get
{
return _User_Mail_Id;
}
set
{
OnUser_Mail_IdChanging(value);
ReportPropertyChanging("User_Mail_Id");
_User_Mail_Id = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("User_Mail_Id");
OnUser_Mail_IdChanged();
}
}
private global::System.String _User_Mail_Id;
partial void OnUser_Mail_IdChanging(global::System.String value);
partial void OnUser_Mail_IdChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
[Required(ErrorMessage="Please enter password")]
[StringLength(20,ErrorMessage="Password cannot exceed 20 characters")]
[RegularExpression(#"^([a-zA-Z0-9 \.\&\'\-]+)$", ErrorMessage = "Invalid password")]
public global::System.String User_Password
{
get
{
return _User_Password;
}
set
{
OnUser_PasswordChanging(value);
ReportPropertyChanging("User_Password");
_User_Password = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("User_Password");
OnUser_PasswordChanged();
}
}
private global::System.String _User_Password;
partial void OnUser_PasswordChanging(global::System.String value);
partial void OnUser_PasswordChanged();
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.DateTime User_Creation_Date
{
get
{
return _User_Creation_Date;
}
set
{
OnUser_Creation_DateChanging(value);
ReportPropertyChanging("User_Creation_Date");
_User_Creation_Date = StructuralObject.SetValidValue(value);
ReportPropertyChanged("User_Creation_Date");
OnUser_Creation_DateChanged();
}
}
private global::System.DateTime _User_Creation_Date;
partial void OnUser_Creation_DateChanging(global::System.DateTime value);
partial void OnUser_Creation_DateChanged();
I have the following columns in the above entity object (User table)User_PK,User_Name,User_Password,User_Email_ID.....
Please can anyone suggest how to create a viewmodel for the above entity object that contains all the above columns except User_Password and User_Email_ID because i need to use it as strongly typed viewmodel for my view.I also need to use another table in the same viewmodel with selected columns....
i had gone through a lot of documents..i already spent 1 and half day for this
can anybody help..
i know this question is asked repeatedly but i cannt find the right way in doing it...
Thanks
Edit: Modified to answer comment about extra properties from other Entities
This may help
public class UserViewModel
{
public int Pk{get;private set;}
public string Name{get;set;}
public DateTime CreationDate{get;set;}
public string ProjectName{get;set;}
public DateTime ProjectCreated{get;set;}
}
The ViewModel is a flattened version of you entities.
You have to write your viewmodel classes yourself.
Just create a class called something like MyWebApp.Models.User which contains the properties you need and map the edmx class to it in your controller.
For mapping i recommend using the automapper which makes mapping life easier.
EDIT
This is how you use automapper:
Say your DAL class is called User and your viewmodel class is called UserViewModel.
You have to tell automapper once which classes you want to map:
AutoMapper.Mapper.CreateMap<User, UserViewModel>();
Then you can map objects from one type to another with a single line:
User user = dal.GetUser();
UserViewModel model = AutoMapper.Mapper.Map<User, UserViewModel>(user);
Automapper can even map collections of objects. So if your model has a property like IEnumerable<Projects> it will map this as well.
I get VS to do most of the work for me by right clicking on the edmx design surface and selecting Add Code Generation Item > EF 5.x DBContext Generator > Add.
You should then see a [xxx].tt file in the same folder as your edmx file. If you expand this file you should see a POCO class for each of your entities.
I copy the entities as required into my viewmodel and delete the ones I don't need.
Once I'm happy with my viewmodel I delete the [xx].tt file, right click properties on the edmx design surface > Properties > Code Generation strategy > default.
I'd also highly recommend using Automapper as Jan mentioned in his answer.
I have this action in my RESTful application on MVC3:
[HttpPut]
public void Rest(ViewModel view_model, int id)
{
//doing something with view_model
}
Where ViewModel class is a class for passing data to/from client Javascript (I don`t want to pass pure DB entities):
public class ViewModel
{
public ViewModel() //parameterless constructor, needed for accepting as parameter in action
{
}
public ViewModel(Model m)
{
id = m.ID;
Title = m.Title;
}
public int? id { get; set; }
private string _title;
public string Title
{
get
{
if (String.IsNullOrWhiteSpace(_title)) throw new Exception("Empty field");
return _title;
}
set
{
_title = value;
}
}
}
BUT when I make PUT request with that data:
{ "id" : 7, "Title" : "Hello world!" }
I get that "Empty field" exception. Seems like something is trying to get Title property, even before it has been set with incoming "Hello world!" data.
Why?
And where can I get some information, how this whole operation works, i.e. looking for object ViewModel that specified as action parameter, in actual XHR-request.
Thank you for your thoughts.
Seems like something is trying to get Title property, even before it
has been set with incoming "Hello world!" data. Why?
It is the default model binder. And more specifically the BindProperty method. This method is called during binding and it uses reflection to call the getter. Because the model binder recurses down the object hierarchy graph it first needs to get the value of the property, build a binding context and model metadata for each property and then invoke the SetProperty method.
If you don't want the title property to be empty use the validation mechanisms and auto implemented properties:
[Required]
public string Title { get; set; }
and then in your RESTful action check if the ModelState.IsValid. It is much more easier and MVCish:
[HttpPut]
public ActionResult Rest(ViewModel view_model, int id)
{
if (!ModelState.IsValid)
{
...
}
// doing something with view_model
...
}
Say I have a model like so:
public class MyViewModel {
//some properties
public string MyString {get;set;}
public Dictionary<string,string> CustomProperties {get;set;}
}
And I am presenting the dictionary property like this:
<%= Html.EditorFor(m => m.CustomProperties["someproperty"]) %>
All is working well, however I have implemented a custom validator to validate the properties of this dictionary, but when returning a ModelValidationResult I can not get the member name referenced correctly (which chould be CustomProperties[someproperty] I believe). All the items in the list which are properties are bound correctly to their errors (I want the error class in the text box so I can highlight it).
Here is my code for the custom validator so far
public class CustomValidator : ModelValidator
{
public Custom(ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
if (Metadata.PropertyName.Equals("mystring", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() {Message = "normal property validator works!!"};
}
else if (Metadata.PropertyName.Equals("customproperties", StringComparison.OrdinalIgnoreCase))
{
yield return new ModelValidationResult() { MemberName = "CustomProperties[someproperty]", Message = "nope!" };
}
}
}
It appears like something is filling in the MemberName property further up, and ignoring what I put in there
Cheers,
Amar
It appears to me that you are making validation more difficult than it needs to be. Have you taken a look at DataAnnotations which are built into the framework? Scott Gu's blog talks about this. It's a really nice (and easy) way to do validation of models.