i want to provide a method of changing the labels associated with controls which will be rendered on the page via a lookup to a sql table.
Ideally i want to inject the meta data display field which is then rendered on the page using the helper.
#Html.LabelFor(m => m.city)
Since this would need to be a sql lookup at runtime, i cannot just change the class scaffolding tt template to stamp on a displayname annotation at design time.
I thought of 3 potential methods.
rewrite all the html.helpers i want to use. Problem with this is you would need to replicate all the functionality of the existing helper prior to making the changes.
write a custom data annotation and stamp it on each property in the class i.e.
[MyCustomNameAttribute]
public string city{ get; set; }
then hopefully in the MyCustomNameAttribute class i can find both the linq field i am referring to, a reference to the metadatamodel and a database context using these i can retrieve and replace the DisplayName based on potential name customisations configured by the User. I tried to do this but was unable to find out how the [Display(Name="city")] annotation works in the background.
Modify the entity model backing code to inject the name into the metadatamodel.
Does anyone have any experience of the above?
Cheers
Tim
You have to create a custom ModelMetaDataProvider by extending the DataAnnotationsModelMetadataProvider.
public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(
IEnumerable<Attribute> attributes,
Type containerType,
Func<object> modelAccessor,
Type modelType,
string propertyName)
{
var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if(meta.DisplayName == null)
{
// TO DO read the display value from database and assign here
meta.DisplayName = ..
}
return meta;
}
}
Then you have to set that in the Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new CustomModelMetadataProvider();
}
I'm assuming that you want to set only the display name from db but suppose you want to load the complete modelmetadata from db then I would suggest you to create a custom ModelMetadataProvider by implementing the abstract class ModelMetadataProvider.
Hitting the database every time definitely not a good idea so we have to workout for some caching strategy.
We have to hit the database for every new containerType (I guess) and read the metadata information for the container along with all its properties and store in the cache with key as the containerType (this could be a difficult job).
Related
I need to add a prefix to the name of form elements that are rendered within a form. I've created a custom attribute to decorate a property that accepts the name of another property whose value will be used for the name prefix.
public class HtmlElementNamePrefixPropertyAttribute : Attribute {
public string PropertyName { get; set; }
public HtmlElementNamePrefixPropertyAttribute(string propertyName) {
PropertyName = propertyName;
}
}
And my custom ModelMetadataProvider:
public class AddressModelMetadataProvider : DataAnnotationsModelMetadataProvider {
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {
ModelMetadata metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
HtmlElementNamePrefixPropertyAttribute nameAttribute = attributes.OfType<HtmlElementNamePrefixPropertyAttribute>().FirstOrDefault();
if (nameAttribute != null) {
ModelMetadata prefixMetadata = ModelMetadataProviders.Current.GetMetadataForProperty(modelAccessor, metadata.ContainerType, nameAttribute.PropertyName);
metadata.PropertyName = string.Format("{0}{1}", prefixMetadata.Model, metadata.PropertyName);
}
return metadata;
}
}
As an example, if I decorate Address1 with HtmlElementNamePrefixAttribute:
[HtmlElementNamePrefix("AddressType")]
public string Address1 { get; set; }
public AddressType AddressType { get; set; }
And in my view render a textbox for Address1:
#Html.TextBoxFor(m => m.Address1)
It should render as (assuming that AddressType == AddressType.Home)
<input name="HomeAddress1" value="123 Street way"/>
I have a few problems:
I'm not sure how to effect the rendered name HTML attribute from AddressModelMetadataProvider or what property in ModelMetadata would allow me to do that. My current attempt was to change the PropertyName property of ModelMetadata, but that doesn't have a setter.
If possible, I don't want to create a new HtmlHelper as this attribute could apply to any type of form element that would be rendered in an Address. I also don't want to create a string EditorTemplate since this scenario only applies to an Address object and its properties.
To give a better understanding of what I'm trying to accomplish and why, let me give a brief explanation of the project and its purpose.
Our application allows users to create "fields". From the end users perspective, "fields" can be a single line textbox, multi line textbox (textarea), chooseMany (which is a group of checkboxes), chooseOne (which is a dropdown), address (which consists of more than one form element and can be of type home, business or other), contact (email, phone, and fax), and others. I've simplified a great deal, but I think this gets the point across.
All this information is stored in the database ("field" values, "field" metadata, which "fields" are on the requested "form", etc.), and at runtime used to configure the "form" the user is requesting (i.e., /forms/123). So the form may have a textbox and an address "field", or maybe a home address "field" and a business address "field". Each "form" is created and configured by an administrative user. These "fields" or rather models, inherit from IDataItem and have their own views (templates) that describe how they should be rendered to the UI.
Because of the dynamic nature of a "form" custom model binding and validation was needed (i.e., custom ValidationAttributes, ValidatorProviders, ModelBinders, etc.). Validation rules and logic are applied dynamically at runtime using custom and standard ValidationAttributes (i.e., RequiredAttribute is used for simple "fields" like a single line textbox or chooseOne). This is done at runtime, because the administrator building the "form" can mark a "field" as required or not (as well as other validation constraints).
Required validation for an address "field" is different, because an address is not considered complete unless all parts of the address are filled out (with the exception of address2 and address3).
For client side validation we're using the standard MVC client validation library, jQuery validate. Here in lies my problem... Error messages are applied by jQuery validate based on the name of the form element. So if address1 is invalid, but there is a home address "field" and a business address "field" on the form then a distinction needs to be made between each form element name, so that the error message can be applied to the correct element. This is the biggest reason why I need to prefix the address form element names with AddressType.
This is a very long-winded simplified explanation, but I think it relays the purpose of the application and why my problem exists.
I have a ViewModel for which want to be able to set Metadata properties dynamically. For example I would like to be able to customise the DisplayName and validation error messages using the value of other model properties. I plan to do this with a custom MetadataProvider, following Brad Wilson's article.
I want the provider only to be used with selected ViewModels. So my question is, how do I configure that? I have seen examples using ModelMetadataProviders.Current = new MyModelMetadataProvider(), but this would presumably use the custom provider for all model classes entities. Is it possible to configure a provider for a single ViewModel?
You can't.
However, you can act as a proxy for all other models. Something like:
public class YourProvider<TViewModel>
{
public YourProvider(InnerProvider provider) {}
public ModelMetaData GetMetaData(SomeContext context)
{
if (context.ModelType != typeof(TViewModel))
return _innerProvider.GetMetaData(context);
//Other logic here.
}
}
And finally assign it as:
ModelMetadataProviders.Current
= new MyModelMetadataProvider<CustomViewModel>(ModelMetadataProviders.Current);
I'm pretty sure changing the current model metadata provider in the ViewModel is not safe once you start getting multiple users on the site, let alone thread safe. You might be able to use the attribute method but you'll still have to implement your own ModelMetadataProvider and set it to Current at the start of your app, then inspect for certain attributes and determine your ModelMetaData to return, if there are none then fall through to the base implementation. Though to be honest, the amount of restrictions that you're talking about, having it only handle selected view models but not being allowed to know or test for those view models? It sounds like you're doing something wrong elsewhere...
UPDATE: When I needed a ModelMetadata provider I created one that looks something like this...
public class MyMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
if ((containerType != typeof(MyType))
return base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
//setup custom ModelMetadata here
}
}
In my ASP.NET MVC project, my actions typically call a Service layer to get data. I use the same dozen or so POCOs for all my models. I also plan on using the Service layer in console applications and maybe expose a web api at some point.
To make my database operations more efficient, my service layer only hydrates the properties in the model that are relevant to the particular method (which at this point is mostly driven by the needs of my controller actions).
So for example I might have a class Order with properties Id, Name, Description, Amount, Items. For a given service call I might only need to populate Id, Name, Items. A consumer of that service won't necessarily know that Amount is 0 only because it didn't populate the property.
Similarly, the consumer won't know whether Items is empty b/c there actually aren't any items, or whether this particular service method just doesn't populate that property.
And for a third example, say one of my views displays an ItemCount. I don't want to fully populate my Items collection, I just need an additional property on my "model". I don't want to add this property to my POCO that other service methods will be using because it's not going to be populated anywhere else.
So the natural solution is to make a POCO designed specifically for that method with only those 3 properties. That way the consumer can know that all properties will be populated with its real values. The downside to this is that I'll end writing tons of similarly shaped models.
Any advice on which method works best?
You could use Nullable Types to indicate the missing properties with a null.
For example:
class Order {
public int Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
public decimal? Amount {get;set;}
public List<Item> Items {get;set;}
}
And then if Items == null, it wasn't set. If it's an empty new List<Item>(), it's set but empty. Same for Amount. If Amount.HasValue == false, it wasn't set. If Amount.Value is 0.0d, it's set and the item is free.
Why don't you use LINQ projection?
One service method does something like:
return DbContext.Orders.Select(o => new { Id = o.Id, Name = o.Name, Description = o.Description });
while the other service method does something like:
return DbContext.Orders.Select(o => o);
I'm not sure how your application is architected, but this may be a way around creating 100's of POCO's.
Hope this helps! Good luck.
You could pass in a selector Func that returns dynamic:
public IEnumerable<dynamic> GetOrders(Func<Order, dynamic> selector) { ... }
I'm not sure how you are accessing data, but the following shows how this would work using a List<T>:
class Program
{
static void Main(string[] args)
{
var service = new Service();
var orderNames = service.GetOrders(o => new { o.Name });
foreach (var name in orderNames)
Console.WriteLine(name.Name);
Console.ReadLine();
}
}
public class Service
{
private List<Order> _orders = new List<Order>
{
new Order { Id = 1, Name = "foo", Description = "test order 1", Amount = 1.23m },
new Order { Id = 2, Name = "bar", Description = "test order 1", Amount = 3.45m },
new Order { Id = 3, Name = "baz", Description = "test order 1", Amount = 5.67m }
};
public IEnumerable<dynamic> GetOrders(Func<Order, dynamic> selector)
{
return _orders.Select(selector);
}
}
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
}
The use of nullable values is a good solution, however it has the downside you have no way to matk required fields. That is you cannot use a required attribute on any property. So if there is field that is obligatory in some views you have no way to represent it.
If you don't need required fileds validation this is ok. Otherwise, you need a way to represent which fileds are actually used, and then to write a custom validation provider.
A simple way to do this is to use a "Mask" class with the same property names of the original class, but with all fields boolean: a true values means the field is in use.
I used a similar solution in a system where the properties to be shown are configured in a configuration files...so it was the unique option for me since I had no possibility to represent all combination of properties. HOWEVER, I used the "Mask" class also in the View, so I was able to do all the job with just one View..with a lot of ifs.
Now if your 150 service methods and probably about 150 Views...are all different, then maybe it is simpler to use also several classes ...that is in the worst case 150 classes..the extra work to write them is negligible if compared to the effort of preparing 150 different Views.
However this doesnt mean you need 150 POCO classes. You might use an unique POCO class that is copied into an adequate class just into the presentation Layer. The advantage of this approach is that you can put different validation attributes on the various classes and you don't need to write a custom Validation provider.
Return the entire POCO with nullable types as mentioned by #sbolm. You can then create a ViewModel per MVC page view that receives a model with the specific properties it needs. This will take more performance (insignificant) and code, but it keeps your service layer clean, and keeps your views "dumb" in that they are only given what they need and have no direct relation to the service layer.
I.e. (example class from #sbolm)
class Order {
public int Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
public decimal? Amount {get;set;}
public List<Item> Items {get;set;}
}
// MVC View only needs to know the name and description, manually "map" the POCO properties into this view model and send it to the view
class OrderViewModel {
public string Name {get;set;}
public string Description {get;set;}
}
I would suggest that instead of modifying the models or creating wrapper models, you have to name the service methods such that they are self-explanatory and reveals the consumer what they returns.
The problem with the nullable approach is it makes the user to feel that the property is not required or mandatory and they try inserting instances of those types without setting those properties. Is it won't be bad having nullables every-where?
It won't be a good approach to change the domain models since all you want is just to populate some of the properties instead of that you create service with names and descriptions that are self-explanatory.
Take the Order class itself as the example, say one service method returns the Order with all the items and the other one returns only the details of the Order but not the items. Then obviously you may have to create two service methods GetOrderItems and GetOrderDetail, this sounds so simple, yes it is! but notice the service method names itself tells the client what it is going to return. In the GetOrderDetail you can return an empty items or null (but here I suggest a null) that doesn't matter much.
So for new cases you don't need to frequently change the models but all you got to do is add or remove the service methods and that's fine. Since you are creating a service you can create a strong documentation that says what method does what.
I would not performance optimize this to much unless you realy get performance problems.
I would only distinguish between returning a flat object and an object with a more complete object graph.
I would have methods returning flat objects called something like GetOrder, GetProduct.
If more complete object graphs are requested they would be called : GetOrderWithDetails.
Do you use the POCO classes for the typed views? If yes: try to make new classes that serve as dedicated ViewModels. These ViewModels would contain POCO classes. This will help you keeping the POCO classes clean.
To expand on the nullable idea, you could use the fluentvalidation library to still have validation on the types dependent on whether they are null or not. This would allow you to have a field be required as long as it was not null or any other validation scheme you can think of. Example from my own code as I had a similar requirement:
Imports FluentValidation
Public Class ParamViewModelValidator
Inherits AbstractValidator(Of ParamViewModel)
Public Sub New()
RuleFor(Function(x) x.TextBoxInput).NotEmpty.[When](Function(x) Not (IsNothing(x.TextBoxInput)))
RuleFor(Function(x) x.DropdownListInput).NotEmpty.[When](Function(x) Not (IsNothing(x.DropdownListInput)))
End Sub
End Class
I've successfully created a custom field inheriting from SPFieldText and happily have complete control over rendering it on the input form as a control.
Problem:
I have a need to create a link to a popup with the ListId and ListitemID in the querystring when rendering the field using GetFieldValueAsHtml().
Something like this:
public class CustomField : SPFieldText
{
public CustomField (SPFieldCollection fields, string fieldName)
: base(fields, fieldName)
{
}
public CustomField (SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName)
{
}
public override string GetFieldValueAsHtml(object value)
{
return string.Format(
"javascript:window.open('{0}/_layouts/Popup.aspx?ListId={1}&ItemId={2}','Popup','status=0,scrollbars=0,titlebar=0,resizable=1,toolbar=0,location=0,width=600,height=500');return false;",
SPContext.Current.Web.ServerRelativeUrl.TrimEnd('/'),
LISTID, LISTITEM.ID
);
}
Clearly SPContext doesn't hold a reference to the list or item and none of the properties seem to expose the current item. I tried overloading properties in the control but these don't seem to be invoked when rendering the field.
// None of these properties are invoked when rendering the field as above
public class CustomFieldControl : TextField
{
public override object ItemFieldValue
public override object ListItemFieldValue
public override string Text
public override object Value
}
I've experimented with the RenderPattern in fldtypes_Custom.xml but again this is also ignored when rendering the field using GetFieldValueAsHtml();
Am I naively expecting something that's not possible?
I'm open to any approach that avoids rewriting the web part... or just tell me it can't be done.
(The existing web part renders a grid and calls GetFieldValueAsHtml(). We know we can change the web part to achieve this but that's not an ideal solution for other reasons).
Not sure if this will work with SharePoint 2007, but with SharePoint 2010 one can easily fetch the currently being displayed ListItem by using SPContext.Current.ListItem.
For anyone stumbling across this, I confirmed that what I was aiming to do is not possible.
We were forced to make changes in the web part to achieve this level of customization. As outlined in the question, The existing web part renders a grid and calls GetFieldValueAsHtml().
Let's say I have a DB table with columns A and B and I've used the Visual Studio designer to create a Linq objects for this table. All fields are marked NOT NULL.
Now I'm trying to edit this record using typical MVC form editing and model binding, but field B doesn't need to be editable, so I don't include it in the form.
When the post handler binds the object it doesn't populate field B, leaving it null. Now when I save the object I get a DB error saying field B can't be NULL.
The code to save looks something like:
m_DataContext.MyObjects.Attach(myObject);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues, myObject);
m_DataContext.SubmitChanges();
How do I get this to work? Do I need to include field B as a hidden field on the form - I don't really want to do this as it may be updated by other users at the same time and I don't want to stomp over it.
I've found the solution to this problem revolves around getting the entity object associated with the data context before applying the changes. There's a couple of ways of doing this which I've described in separate answers below.
Descend into SQL
This approach ditches LINQ in favour of straight SQL:
public override void SaveMyObject(MyObject o)
{
// Submit
m_DataContext.ExecuteCommand("UPDATE MyObjects SET A={0} WHERE ID={1}", o.ID, o.A);
}
I like this approach the best because of it's simplicity. As much as I like LINQ I just can't justify it's messiness with this problem.
Use a Custom Model Binder
This approach uses a custom model binder to create the entity object and associate with the data context, before the binding takes place.
public class MyObjectBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
MyObject a = ((MyObjectController)controllerContext.Controller).Repository.GetMyObjectForUpdate(bindingContext.ValueProvider["ID"].AttemptedValue.ToString());
return a;
}
}
The repository then creates the object and associates it with the data context:
public Object GetMyObjectForUpdate(string id)
{
MyObject o=new MyObject();
o.ID=id;
m_DataContext.Articles.Attach(o);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
return o;
}
The action handler needs to be attributed to use the model binder...
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditMyObject([ModelBinder(typeof(MyObjectBinder))] MyObject o)
{
if (!ModelState.IsValid)
return View("EditMyObject", a);
Repository.SaveMyObject(a);
return RedirectToAction("Index");
}
and finally SaveMyObject simply calls datacontext.SubmitChanges().
For this to work I also needed to set the update check attributes on all columns to Never (in the dbml file).
Overall, this approach works but is messy.
Use Two Entity Objects
This approach uses two entity objects, one for the model binding and one LINQ:
public override void SaveMyObject(MyObject o)
{
// Create a second object for use with linq and attach to the data context
MyObject o2 = new MyObject();
o2.ID = o.ID;
m_DataContext.Articles.Attach(o2);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
// Apply fields edited by the form
o2.A = o.A;
// Submit
m_DataContext.SubmitChanges();
}
This approeach doesn't require any special handling in the controller (ie: no custom model binding) but still requires
the Update Check property to be set to Never in the dbml file.
You could add a timestamp field and check one on the page with the one in the DB (hiding the timestamp field as well). If a user has updated the record, a concurrency error is returned and the page is refreshed, or left the same iwth the users changes.