Here is my development requirement,
My label values are stored in the database, and I still want to use the data annotation in a declarative way, this is to make my model more readable.
And here is my approach,
I decided to write custom DisplayNameAttribute, where the default value provided by my model will be overwritten by the value retrieved from the database.
Here is the property defined in the model,
[CustomDisplay(Name: "First Name")]
[CustomRequired(ErrorMessage: "{0} is required")]
public String FirstName { get; set; }
Here is the custom display name attribute class,
public class CustomDisplayAttribute : DisplayNameAttribute
{
private string _defaultName;
private string _displayName;
public CustomDisplayAttribute(string Name)
{
_defaultName = Name;
}
public override string DisplayName
{
get
{
if (String.IsNullOrEmpty(_displayName))
{
_displayName = DAO.RetrieveValue(**ModelName**, _defaultName);
}
return _displayName;
}
}
}
Now, you can see in the above code, ModelName is something I need, but I don't have!!
While debugging, I dig into ModelMetadataProviders.Current and can see the availability of the current model in action. But, as it is part of non-public static members I am unable to access it through my code.
I have written the below method to retrieve the model name through reflection,
private static string GetModelName()
{
var modelName = String.Empty;
FieldInfo info = typeof(CachedAssociatedMetadataProvider<CachedDataAnnotationsModelMetadata>)
.GetField("_typeIds", BindingFlags.NonPublic | BindingFlags.Static);
var types = (ConcurrentDictionary<Type, string>)info.GetValue(null);
modelName = types.FirstOrDefault().Key.Name;
return modelName;
}
But the problem is, the types collection provides me entries for all the models (visited at least once by the user). And there is no clue to know, which is currently in action!!
IMHO Attributes should not be used to make database calls. Attributes should be used to add metadata to Classes/Properties etc...
So If you're willing to change your code to be more like the Microsoft architecture for MVC then you'd have your custom Attribute and a custom ModelMetadataProvider:
public class CustomDisplayAttribute : Attribute
{
public CustomDisplayAttribute(string name)
{
Name = name;
}
public string Name { get; private set; }
}
Then a new ModelMetadataProvider:
public class DatabaseModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
public DatabaseModelMetadataProvider()
{
}
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
var displayAttribute = containerType == null
? null as CustomDisplayAttribute
: containerType.GetProperty(propertyName)
.GetCustomAttributes(false)
.OfType<CustomDisplayAttribute>()
.FirstOrDefault();
if (displayAttribute != null)
{
var displayValue = DAO.RetrieveValue(containerType.ToString(), displayAttribute.Name)
metadata.DisplayName = displayValue;
}
return metadata;
}
}
Where
public class MyViewModel
{
public MyPropertyType PropertyName { get; set; }
}
containerType = MyViewModel
modelType = MyPropertyType
propertyName = PropertyName
Then register the provider (global.asax or whatever):
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider();
Also you can take a look at the ModelMetadata it has a few other things you might want to change in the future.
Related
I have a model such as
public class MyModel
{
public MyObject myObject {get;set;}
}
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
With out using a custom model binder everything works great. I am trying to implement a model binder and not getting anywhere -- the resources that I have come from are
https://www.youtube.com/watch?v=qDRORgoZxZU (returns null model to the controller)
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/ (controller dies on the constructor)
http://hotzblog.com/asp-net-vnext-defaultmodelbinder-and-automatic-viewmodel-string-trim/ (can not even find MutableObjectModelBinder in the .net-core namespace)
Ideally what I want is to track which properties where set by the ModelBinder.
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
public List<String> ModifiedProperties {get;set;}
}
when the object is created by the ModelBinder for each property that is being set it adds it to the ModifiedProperties list.
This is solution. You need to implement IModelBinderProvider and IModelBinder
public class EntityFrameworkModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
//We only want to invoke the CustomeBinder on IBaseEntity classes
if (context.Metadata.ContainerType != null && context.Metadata.ContainerType.GetInterfaces().Contains(typeof(SurgeOne.Core.IBaseEntity)))
{
//We only create the custom binder on value types. E.g. string, guid, etc
if (context.Metadata.ModelType.GetTypeInfo().IsValueType ||
context.Metadata.ModelType == typeof(System.String))
{
return new EntityFrameworkModelBinder();
}
}
return null;
}
}
And IModelBinder
public class EntityFrameworkModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
//Get the value
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
// no entry
return Task.CompletedTask;
}
//Set the value -- not sure what this does
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
//Set the value -- this has to match the property type.
System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(bindingContext.ModelType);
object propValue = typeConverter.ConvertFromString(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(propValue);
//Code to track changes.
return Task.CompletedTask;
} //BindModelAsync
}
I want to inject a UIHint attribute into a model object on the fly. I have been using the ICustomTypeDescriptor to create a class that will inject a UIHint into an instance of an object:
public sealed class UIHintDescriptionProvider : TypeDescriptionProvider
{
private string PropertyName;
private string HintValue;
public UIHintDescriptionProvider(TypeDescriptionProvider parent, string propertyName, string hintValue)
: base(parent)
{
this.PropertyName = propertyName;
this.HintValue = hintValue;
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new UIHintDescriptor(base.GetTypeDescriptor(objectType, instance), this.PropertyName, this.HintValue);
}
}
public sealed class UIHintDescriptor : CustomTypeDescriptor
{
private string PropertyName;
private string HintValue;
internal UIHintDescriptor(ICustomTypeDescriptor parent, string propertyName, string hintValue)
: base(parent)
{
this.PropertyName = propertyName;
this.HintValue = hintValue;
}
public override PropertyDescriptorCollection GetProperties()
{
// Enumerate the original set of properties and create our new set with it
PropertyDescriptorCollection originalProperties = base.GetProperties();
List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();
foreach (PropertyDescriptor pd in originalProperties)
{
if (pd.Name == this.PropertyName)
{
Attribute attr = new UIHintAttribute(this.HintValue);
var newProp = TypeDescriptor.CreateProperty(typeof(object), pd, attr);
newProperties.Add(newProp);
}
else
{
newProperties.Add(pd);
}
}
// Finally return the list
return new PropertyDescriptorCollection(newProperties.ToArray(), true);
}
}
I then set this in my controller using:
UIHintDescriptionProvider provider =
new UIHintDescriptionProvider(TypeDescriptor.GetProvider(typeof(PageContentItem)), "Text",
"wysiwyg");
TypeDescriptor.AddProvider(provider, item);
Inspection in the controller of this object using the functions of TypeDescriptor indicate that this attribute has indeed been set however it does not appear in my view at all. Stepping through the MVC3 source shows all the other attributes but not the one I have just set.
Does MVC3 do any caching of object type descriptions in the background that could account for that fact?
Any other suggestions for injecting an attribute into an object instance at runtime?
This is probably because of 'timing'.
Try using a custom ModelMetadataProvider to programmatically set model property attributes like 'UIHint' or 'DisplayName' or ...
Have a look here.
I am creating a fluent HtmlHelper in MVC - to create a grid based on HTML.
I am aware of mvc contrib and WebGrid - but I am making my own and have a specific problem:
I have to enter this:
#Html.DSGridFor().AddColumn(x=>x.FirstOrDefault().Message)
but I want to be able to type this:
#Html.DSGridFor().AddColumn(x=>x.Message)
The code that gets called when I start with #Html.DSGridFor() - taking in the page based model.
public static DSGridHelper<TModel> DSGridFor<TModel>(this HtmlHelper<TModel> html)
{
return new DSGridHelper<TModel>(html);
}
and then within the class DSGridHelper I have this:
public DSGridHelper<TModel> AddColumn(Expression<Func<TModel, dynamic>> property, string HeaderText = null)
{
string ColumnName = (property.Body as MemberExpression).Member.Name;
DSGridColumn DSGC = new DSGridColumn();
DSGC.ColumnName = ColumnName;
DSGC.HeaderText = HeaderText ?? ColumnName;
DSColumnList.Add(DSGC);
return this;
}
public List<DSGridColumn> DSColumnList { get; set; }
and the column class at the moment is really basic:
public class DSGridColumn
{
public DSGridColumn()
{
}
public string ColumnName { get; set; }
public string HeaderText { get; set; }
}
I can get this code working fine with string based column names, but I want the declaring code in the razor page to be simple in format and strongly typed. At the moment I have to type x=>x.First().Message but I really only need x=>x.Message to identify the column.
I appreciate any help.
UPDATE
Thanks to Justin I can now provide my/our code.
View:
#(Html.DSGridFor3().AddColumn(x => x.Message)
.AddColumn(x => x.Host)
.ToMvcString())
HTML Helper call:
public static DSGridHelper3<T> DSGridFor3<T>(this HtmlHelper<IEnumerable<T>> htmlHelper)
{
return new DSGridHelper3<T>(htmlHelper);
}
Returning class:
public class DSGridHelper3<T>
{
private HtmlHelper _htmlHelper;
//private IEnumerable<T> _dataList;
public List<DSGridColumn> DSColumnList { get; set; }
public DSGridHelper3(HtmlHelper<IEnumerable<T>> htmlHelper)
{
_htmlHelper = htmlHelper;
// _dataList = htmlHelper.ViewData.Model;
DSColumnList = new List<DSGridColumn>();
}
public DSGridHelper3<T> AddColumn(Expression<Func<T, object>> property)
{
string columnName = (property.Body as MemberExpression).Member.Name;
DSGridColumn DSGC = new DSGridColumn();
DSGC.ColumnName = columnName;
DSGC.HeaderText = columnName;
DSColumnList.Add(DSGC);
return this;
}
public MvcHtmlString ToMvcString()
{
sb.Append("<table>");
sb.Append("<tr>");
sb.Append("<td>");
sb.Append("hello world within a table");
sb.Append(#"</td>");
sb.Append("<td>");
sb.Append("hello world within a table");
sb.Append(#"</td>");
sb.Append(#"</tr>");
sb.Append(#"</table>");
return new MvcHtmlString(sb.ToString());
}
}
UPDATE 2
If you wanted to manually insert a different type (perhaps because you are going to get a small amount of table data from ViewData rather than the model of the page) then here is some more code:
View:
#(Html.DSGridFor3<DanSoftware.MVC.Areas.Errors.Code.ELMAH_Error>().AddColumn(x => x.Message).ToMvcString();)
Alternative signature for the DSGridHelper ...helper
public static DSGridHelper3<T> DSGridFor3<T>(this HtmlHelper htmlHelper)
{
return new DSGridHelper3<T>(htmlHelper);
}
Additional constructor:
public DSGridHelper3(HtmlHelper htmlHelper)
{
_htmlHelper = htmlHelper;
// _dataList = htmlHelper.ViewData.Model;
DSColumnList = new List<DSGridColumn>();
}
Hope this helps someone and thanks Justin!
I dont have Visual Studio with me but I'll take a stab at this...
I would take in a collection as a datatype either in your DsGridFor method or in the AddColumn method. This will allow you to send Strongly-typed arguments from a collection. Say you wanted a generic method of AddColumn for a given collection with access to the class properties vs the collection methods, it would look something like this (just an example):
public static DSGridHelper<T> AddColumn<T>(this HtmlHelper<IEnumerable<T>> htmlHelper, Expression<Func<T, object>> property) where T : class
{
string columnName = (property.Body as MemberExpression).Member.Name;
DSGridColumn DSGC = new DSGridColumn();
DSGC.ColumnName = ColumnName;
DSGC.HeaderText = HeaderText ?? ColumnName;
DSColumnList.Add(DSGC);
return this;
}
For your situation, to new-up a DsGridHelper class I might explicitly set a model-type first and then add overloads as I go:
public static DSGridHelper<T> DSGridFor<T>(this HtmlHelper<IEnumerable<T>> htmlHelper) where T : class
{
return new DSGridHelper<T>(htmlHelper);
}
And then my DsGridHelper might look something like this:
public class DsGridHelper<T>
{
private HtmlHelper _htmlHelper;
private IEnumerable<T> _dataList;
public DsGridHelper(HtmlHelper<IEnumerable<T>> htmlHelper)
{
_htmlHelper = htmlHelper;
_dataList = htmlHelper.ViewData.Model;
}
public DsGridHelper<T> AddColumn(Expression<Func<T, object>> property)
{
string columnName = (property.Body as MemberExpression).Member.Name;
DSGridColumn DSGC = new DSGridColumn();
DSGC.ColumnName = ColumnName;
DSGC.HeaderText = HeaderText ?? ColumnName;
DSColumnList.Add(DSGC);
return this;
}
}
I want to scan a type for it's properties and the annotated attributes and return an object with the following structure
public class PropertyContext
{
public object PropertyValue { get; set; }
public object SourceType { get; set; }
public Attribute Annotation { get; set; }
}
I have this query
var query = from property in _target.GetType().GetProperties()
from attribute in Attribute.GetCustomAttributes(property, true)
select new PropertyContext
{
Annotation = attribute,
SourceType = _target,
};
This is executed deferred so i only generate the PropertyContext while the calling method needs them.
Now i want to fill the PropertyValue property of the PropertyContext object.
To get the value of the property i have have a call to an other component like this
_propertyValueAccessor.GetValue(_target, property)
My question is, how i can modify the query in a way that
*
the value is only read once
but only if a PropertyContext is created
How about:
var query = from property in _target.GetType().GetProperties()
let attributes = Attribute.GetCustomAttributes(property, true)
where attributes.Any()
let val = _propertyValueAccessor.GetValue(_target, property)
from attribute in attributes
select new PropertyContext
{
PropertyValue = val,
Annotation = attribute,
SourceType = _target,
};
I am fairly new to MVC, but after playing with it (MVC 3/Razor), I am hooked.
I have a few questions:
1) What is the best, or most widely used pattern to develop MVC apps in? Repository, DDD, UOW?
2) I am using the Entity Framework 4, so could some please explain to me or point me to a good source that will explain the Repository Pattern w/EF4? Doesn't EF4 take place as the business layer and the data access layer? Does the Repository Pattern even provide a benefit?
3) Also, one last question, could someone explain the whole relationship between the Controller, the Model and the View? I get the basics, but maybe a little more in depth of the correct way to use it. View Models - Say I have a view that displays customer info, and one that edits it, should I have a view model and an edit model, or can the be passed around?
4) Examples??
Thanks for the help up front,
$("Sam")
** EDIT **
Am I on the right track here:
Public Class HomeController
Inherits System.Web.Mvc.Controller
Function Index(ByVal id As Integer) As ActionResult
Return View(New HomeModel)
End Function
<HttpPost()> _
Function Index(ByVal Model As HomeModel) As ActionResult
Return View(Model)
End Function
End Class
Public Class HomeModel
Private _Repository As IRepository(Of Customer)
Public Property Customer As Customer
Public Sub New()
End Sub
Public Sub New(ByVal ID As Integer)
_Repository = New CustomerRepository
Customer = _Repository.GetByID(ID)
End Sub
End Class
Public Interface IRepository(Of T)
Function GetByID(ByVal ID As Integer) As T
Sub Add(ByVal Entity As T)
Sub Delete(ByVal Entity As T)
End Interface
Public Class CustomerRepository
Implements IRepository(Of Customer)
Public Sub Add(ByVal Entity As Customer) Implements IRepository(Of Customer).Add
End Sub
Public Sub Delete(ByVal Entity As Customer) Implements IRepository(Of Customer).Delete
End Sub
Public Function GetByID(ByVal ID As Integer) As Customer Implements IRepository(Of Customer).GetByID
Return New Customer With {.ID = ID, .FirstName = "Sam", .LastName = "Striano"}
End Function
End Class
Public Class Customer
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
End Class
I use generic repositories that get instantiated in a service class (using Dependency Injection with Ninject).
The service class essentially performs two functions:
It provides all the methods that the controller will consume.
It has a property called ViewModel, that essentially maps the data that the views need into a MyViewModel class.
The Controller consumes the service class. With this "pattern", your controllers look like:
namespace ES.eLearningFE.Areas.Courses.Controllers
{
public partial class CourseController : Controller
{
ICourseDisplayService service;
public CourseController(ICourseDisplayService service)
{
this.service = service;
}
public virtual ActionResult Display(int CourseId, int StepOrder, string PupilName, string TutorName)
{
service.CourseId = CourseId;
service.StepOrder = StepOrder;
service.PupilName = PupilName;
service.TutorName = TutorName;
if (Request.IsAjaxRequest())
{
return PartialView(service.ViewModel);
}
else
{
return View(service.ViewModel);
}
}
}
}
The ViewModel class only hold display data and no methods (except the odd really simple method to retrieve data from another property that is, for example a List<> object).
Works really well. An example of a service class:
namespace ES.eLearning.Domain.Services.Courses
{
public class SqlCourseDisplayService : ICourseDisplayService
{
DataContext db;
public SqlCourseDisplayService(DbDataContextFactory contextFactory)
{
db = contextFactory.Make();
CoursesRepository = new SqlRepository<Course>(db);
StepsRepository = new SqlRepository<CourseStep>(db);
StepLinksRepository = new SqlRepository<StepLink>(db);
UserCoursesRepository = new SqlRepository<UserCourse>(db);
CourseTutorsRepository = new SqlRepository<CourseTutor>(db);
UsersRepository = new SqlRepository<User>(db);
}
#region ICourseDisplayService Members
public ViewModels.CourseDisplayVM ViewModel
{
get
{
return new ViewModels.CourseDisplayVM
{
CourseId = this.CourseId,
CourseName = this.Course.Name,
Steps = this.Steps,
ActiveStepIndex = this.ActiveStepIndex,
CurrentStepIndex = this.CurrentStepIndex,
Pupil = new UserDto { UserId = this.PupilId, UserName = this.PupilName },
Tutors = this.GetTutors(this.CourseId),
Tutor = tutorName == null ? null : new UserDto { UserName = this.TutorName, UserId = this.TutorId}
};
}
}
#region Entities
int courseId;
public int CourseId
{
get
{
if (courseId == 0) throw new ApplicationException("Invalid Course Id!");
return courseId;
}
set
{
if (value == 0) throw new ApplicationException("Invalid Course Id!");
try
{
Course = (from c in CoursesRepository.Query where c.CourseId == value select c).First();
Steps = Course.CourseSteps.ToList();
courseId = value;
}
catch {throw new ApplicationException("No Course found for Course Id: " + value);}
}
}
public Data.Course Course { get; private set; }
public int StepOrder { get; set; }
public List<Data.CourseStep> Steps { get; private set; }
public int ActiveStepIndex
{
get
{
if (PupilName == null)
{
throw new ApplicationException("Pupil not set!");
}
if (CourseId == 0)
{
throw new ApplicationException("Course not set!");
}
try
{
var x = (from uc in UserCoursesRepository.Query where (uc.IdCourse == CourseId) && (uc.UserName == PupilName) select uc).First();
return x.ActiveStepIndex;
}
catch { throw new ApplicationException("Could not get Active Step!"); }
}
}
#endregion
#region Users
string tutorName;
public string TutorName
{
get
{
if (tutorName == null) throw new ApplicationException("Invalid call to get Tutor Name [Null Tutor Name]!");
return tutorName;
}
set
{
tutorName = value;
TutorId = (Guid)Membership.GetUser(tutorName).ProviderUserKey;
}
}
public Guid TutorId { get; set; }
string pupilName;
public string PupilName
{
get { return pupilName; }
set
{
pupilName = value;
PupilId = (Guid)Membership.GetUser(pupilName).ProviderUserKey;
}
}
public Guid PupilId { get; set; }
#endregion
#region Utility Properties
public int CurrentStepIndex { get; set; }
public int StepCount
{
get
{
return Steps == null ? 0 : Steps.Count();
}
}
#endregion
#region Private Utilities
private List<UserDto> GetTutors(int CourseId)
{
return (from ct in CourseTutorsRepository.Query join u in UsersRepository.Query
on ct.TutorName equals u.UserName
where (ct.CourseId == courseId)
select new UserDto { UserName = ct.TutorName, UserId = u.UserId }).ToList();
}
#endregion
#region Repositories
private IRepository<Course> CoursesRepository
{
get;
set;
}
private IRepository<CourseStep> StepsRepository
{
get;
set;
}
private IRepository<StepLink> StepLinksRepository
{
get;
set;
}
private IRepository<UserCourse> UserCoursesRepository
{
get;
set;
}
private IRepository<CourseTutor> CourseTutorsRepository
{
get;
set;
}
private IRepository<User> UsersRepository
{
get;
set;
}
#endregion
#endregion
}
}
May not be everyone's choice, but hey, it works for me... AND (more importantly) my clients and their users.
Edit
As requested in the comment below, the Repository that I use:
namespace ES.eLearning.Domain
{
public class SqlRepository<T> : IRepository<T> where T : class
{
DataContext db;
public SqlRepository(DataContext db)
{
this.db = db;
}
#region IRepository<T> Members
public IQueryable<T> Query
{
get { return db.GetTable<T>(); }
}
public List<T> FetchAll()
{
return Query.ToList();
}
public void Add(T entity)
{
db.GetTable<T>().InsertOnSubmit(entity);
}
public void Delete(T entity)
{
db.GetTable<T>().DeleteOnSubmit(entity);
}
public void Attach(T entity)
{
db.GetTable<T>().Attach(entity);
}
public void Save()
{
db.SubmitChanges();
}
#endregion
}
}
And the IRepository Interface:
namespace Wingspan.Web.Mvc
{
public interface IRepository<TEntity> where TEntity : class
{
List<TEntity> FetchAll();
IQueryable<TEntity> Query {get;}
void Add(TEntity entity);
void Delete(TEntity entity);
void Attach(TEntity entity);
void Save();
}
}
This should help you getting started. There are a lot of tutorials and videos available; for example:
Understanding Models, Views and Controllers
The ASP.NET MVC 2.0 basics and excellent introduction by Scott Hanselman. Personally one of my favorite speakers.
And also at www.asp.net; there are a few tutorials/examples to help you getting started. For example the Music Store sample
Unfortunately, I'm not so familiar with EF4/Repository pattern. But here's a blogpost about this pattern.
1) I would say that the repository pattern is the most widely used, then there is inversion of controll too.
2) I can't really point out the benefits with using a repository for entity framework other than that the controller should not know about how to acces data other then asking a repository. This makes it easy to switch it out sometime.
You can also eager load the data to make sure that the view don't call the database in every iteration of a foreach, for example a collection of users to display data from a child entity. You can probly do this anyway, but I feel that the repository is the right place to do it.
3) I can't tell you about the concept in a more in depth way, but I can tell some about viewmodels. In my opinion you should only use viewmodels if there is anything more then one entity you want to send to the view, for example a list of countries. You can alo use a viewmodel to "flatten" out very complex objects.
I would defiantly say the repository pattern is used a lot. This pattern can be used with Dependency Injection. Using Dependency Injection makes Unit Testing a breeze because you can snap different repositories to an abstract repoistory. Check out http://ninject.org/ for a simple to use Dependecy injector for .NET.
View Models should hold display data and transfer that data from the controller to the view. If you want to edit and display customer info, take a look at this