How to group by in MvvmCross RecyclerView? - mvvmcross

How to group by in MvvmCross RecyclerView?
MvvmCross.Droid.Support.V7.RecyclerView 6.1.2
<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
android:id="#+id/recyclerView"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:MvxItemTemplate="#layout/appointment_feed_item"
app:MvxBind="ItemsSource Items" />

MvvmCorss includes a neat mechanism for this since version 4.1.5. It’s called TemplateSelector and defined in the interface IMvxTemplateSelector. The purpose of this interface is to map your item to a layout id based on a rule that you can define.
So it is not exactly "group by", but you can use one layout for the group header and another one for each item of the group.
public class TypeTemplateSelector : IMvxTemplateSelector
{
private readonly Dictionary<Type, int> _typeMapping;
public TypeTemplateSelector()
{
_typeMapping = new Dictionary<Type, int>
{
{typeof(HeaderGroupViewModel), Resource.Layout.header_group},
{typeof(GroupItemViewModel), Resource.Layout.group_item}
};
}
public int GetItemViewType(object forItemObject)
{
return _typeMapping[forItemObject.GetType()];
}
public int GetItemLayoutId(int fromViewType)
{
return fromViewType;
}
}
And in the axml you set it using the full qualified class name of the selector followed by the assembly name:
<MvvmCross.Droid.Support.V7.RecyclerView.MvxRecyclerView
android:id="#+id/recyclerView"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:MvxItemTemplate="#layout/appointment_feed_item"
app:MvxTemplateSelector="RecyclerViewDifferentTemplates.Droid.TypeTemplateSelector,RecyclerViewDifferentTemplates.Droid"
app:MvxBind="ItemsSource Items" />
Source and more info: RecyclerView TemplateSelector in MvvmCross
HIH

Related

How to Assign Async Value to Razor Component [Parameter] Property?

I am attempting to create my first Razor Component in a Blazor Server-side project. The Razor Component is named MyComponent and has a property configured to retrieve its value from input:
MyComponent.razor
[Parameter]
public int Count {get; set;}
I am pulling the count from an injected service configured via IServiceCollection, which looks like this:
public interface ICountingService
{
ValueTask<int> Get();
}
The hosting page, Index.razor looks like the following:
#page "/"
#inject ICountingService Counter
<h1>Hello World!</h1>
<MyComponent Count="#Counter.Get()" />
However, I cannot seem to bind the correct value for the Count property.
I get the following error:
cannot convert from 'System.Threading.Tasks.ValueTask<int>' to 'int'
All of the examples I have found for assigning [Parameter] values to Razor Components are synchronous, and the only asynchronous values I have found are for callbacks and methods (not parameters).
Further, searching online did not return anything obvious so I am posting here in hopes of finding an answer.
Note that I am aware of using protected override async Task OnInitializedAsync and storing a value in there, but that seems like a lot of required ceremony compared to the approach above, especially when considering the multiple services and properties that I will ultimately have to bind.
So, how does one assign values from an asynchronous call to a Razor Component [Parameter] property in the way that I would prefer?
The problem is, Counter.Get() isn't an int value; it's a Task that will have an int at some undefined point either now or in the future. So you can't assign its value to something that's expecting an int right now, because that int doesn't necessarily exist yet.
You've already got the answer, and though it "seems like a lot of ceremony", it's really the only way to do this:
Create an int property to hold the value.
Declare an async method
In that method, assign the awaited value of Counter.Get() to the int that's holding the value
Set the component's Count property equal to the int property
It may feel like a lot of ceremony, but you should be grateful. Asynchrony is inherently very complicated, and having async/await available already takes care of about 95% of the hard work for you. If you think this solution is messy, you oughtta see what it would take to get it right without async/await!
Try this.
#page "/"
#inject ICountingService Counter
<h1>Hello World!</h1>
<MyComponent Count="#CounterValue" />
#code{
public int CounterValue {get; set;}
protected override async Task OnInitializedAsync()
{
CounterValue = await Counter.Get();
}
}
After #mason-wheeler and #rich-bryant provided their answers, I went to think about this a little more and found my solution, which I have posted here:
https://github.com/Mike-E-angelo/Blazor.ViewProperties
I am calling it a ViewProperty which looks like the following:
public interface IViewProperty
{
ValueTask Get();
}
public sealed class ViewProperty<T> : IViewProperty
{
public static implicit operator ViewProperty<T>(ValueTask<T> instance) => new ViewProperty<T>(instance);
readonly ValueTask<T> _source;
public ViewProperty(ValueTask<T> source) => _source = source;
public T Value { get; private set; }
public bool HasValue { get; private set; }
public async ValueTask Get()
{
Value = await _source;
HasValue = true;
}
public override string ToString() => Value.ToString();
}
You then pair it with a component base type that then iterates through the component's view properties and invokes their respective asynchronous operations:
public abstract class ViewPropertyComponentBase : ComponentBase
{
protected override async Task OnParametersSetAsync()
{
var properties = GetType().GetRuntimeProperties();
foreach (var metadata in properties.Where(x => x.GetCustomAttributes<ParameterAttribute>().Any() &&
typeof(IViewProperty).IsAssignableFrom(x.PropertyType)))
{
if (metadata.GetValue(this) is IViewProperty property)
{
await property.Get().ConfigureAwait(false);
}
}
}
}
A sample razor component that uses the above:
MyComponent.razor
#inherits ViewPropertyComponentBase
#if (Count.HasValue)
{
<p>Your magic number is #Count.</p>
}
else
{
<p>Loading, please wait...</p>
}
#code {
[Parameter]
public ViewProperty<int> Count { get; set; }
}
The resulting use is a clean view with direct bindings and no need for overrides or other additional ceremony:
#page "/"
#inject ICounter Count
<h1>Hello, world!</h1>
Welcome to your new app.
<MyComponent Count="#Count.Count()" />
(NOTE that my posted example and above uses reflection, which is slow. In the actual version of the solution that I am using, I compile the member access as lambda expressions and cache the result. You can find that by starting here if you are brave enough to poke around.)
It feels a bit hacky, but you could do something like this:
<MyComponent Count="#Counter.Get().Result" />

.NET Core Blazor App: How to pass data between pages?

I just started learning how to make websites with using Blazor template. But I don't know how to pass the data from one page to another. It is a bit different than .NET CORE MVC web application and I couldn't find an example for this.
<p>Solve This problem: #rnd1 * #rnd2 = ?</p>
<input type="text" name="result" bind="#result" />
<input type="button" onclick="#calculate" value="Submit" />
I want to send the value in my textbox to the another page. How can I do this?
You can pass it as a parameter.
In the page you want to navigate to, add the parameter to your route:
#page "/navigatetopage/{myvalue}"
and make sure the parameter exists in that page:
[Parameter]
private string myvalue{ get; set; }
In the same page you can pick that up in:
protected override void OnParametersSet()
{
//the param will be set now
var test = myvalue;
}
Now in your start page make sure to navigate to the second page including the value:
uriHelper.NavigateTo($"/navigatetopage/{result}");
That uriHelper needs to be injected like this:
#inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper
UPDATE PREVIEW-9
on preview-9 you should use navigationManager instead of uriHelper, it also has a NavigateTo method
#inject Microsoft.AspNetCore.Components.NavigationManager navigationManager
More recent versions support the following:
The page you are navigating from:
<NavLink href="/somepage/1">Navigate to page 1</NavLink>
The page you are navigating to:
#page "/somepage/{childId:int}"
<h1>Some Page</h1>
<p role="status">Current child: #childId</p>
#code {
[Parameter]
public int childId { get; set; }
}
I personally prefer to add query string to the url.
For example when I want to pre-select tab when page is loaded:
Call the url like http://localhost:5000/profile?TabIndex=2
In your code you can parse this using NavigationManager and QueryHelpers
Add using Microsoft.AspNetCore.WebUtilities;
Then override one of the lifecycle methods and parse the query parameter
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Uri uri = this.Nav.ToAbsoluteUri(this.Nav.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("TabIndex", out StringValues values))
{
if (values.SafeAny())
{
_ = int.TryParse(values.First(), out int index);
this.TabIndex = index;
}
}
}
}
I got an error while testing Flores answer in the page were we passing a data
Below is the current Page
#inject Microsoft.AspNetCore.Components.NavigationManager navigationManager
Int64 Id {get;set;}
<MudMenuItem #onclick="#(() => NextPage(#Id))">Next Page</MudMenuItem>
//So Here I am passing the ID which is long
private void NextPage(Int64 Id){
navigationManager.NavigateTo($"/secondPage/{Id}");
}
Second Page
Instead of using -Id only you need to cast it to long or else it throws an error
-From
#page "/pettyCashAuditTrail/{Id}"
-To
#page "/pettyCashAuditTrail/{Id:long}"
[Parameter] public Int64 Id{ get; set; }

How to make the asp-for input tag helper generate camelCase names?

If I have a view model like this:
public class MyModel{
public DateTime? StartDate {get;set;}
}
And on a view an input tag is used with an asp-for tag helper like so:
<input asp-for="StartDate" />
The default html that is generated by this is
<input type="datetime" id="StartDate" name="StartDate" value="" />
But what I want it to generate is html that looks like this:
<input type="datetime" id="startDate" name="startDate" value="" />
How can I make the asp-for input tag helper generate camel case names like above without having to make my model properties camelCase?
After studying the code that #Bebben posted and the link provided with it, I continued to dig more into the Asp.Net Core source code. And I found that the designers of the Asp.Net Core provided some extensibility points that could be leveraged to achieve lower camelCase id and name values.
To do it, we need to implement our own IHtmlGenerator which we can do by creating a custom class that inherits from DefaultHtmlGenerator. Then on that class we need to override the GenerateTextBox method to fix the casing. Or alternatively we can override the GenerateInput method to fix the casing of name and id attribute values for all input fields (not just input text fields) which is what I chose to do. As a bonus I also override the GenerateLabel method so the label's for attribute also specifies a value using the custom casing.
Here's the class:
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Text.Encodings.Web;
namespace App.Web {
public class CustomHtmlGenerator : DefaultHtmlGenerator {
public CustomHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache) : base
(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory,
htmlEncoder, clientValidatorCache) {
//Nothing to do
}
public CustomHtmlGenerator(
IAntiforgery antiforgery,
IOptions<MvcViewOptions> optionsAccessor,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder,
ClientValidatorCache clientValidatorCache,
ValidationHtmlAttributeProvider validationAttributeProvider) : base
(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder,
clientValidatorCache, validationAttributeProvider) {
//Nothing to do
}
protected override TagBuilder GenerateInput(
ViewContext viewContext,
InputType inputType,
ModelExplorer modelExplorer,
string expression,
object value,
bool useViewData,
bool isChecked,
bool setId,
bool isExplicitValue,
string format,
IDictionary<string, object> htmlAttributes) {
expression = GetLowerCamelCase(expression);
return base.GenerateInput(viewContext, inputType, modelExplorer, expression, value, useViewData,
isChecked, setId, isExplicitValue, format, htmlAttributes);
}
public override TagBuilder GenerateLabel(
ViewContext viewContext,
ModelExplorer modelExplorer,
string expression,
string labelText,
object htmlAttributes) {
expression = GetLowerCamelCase(expression);
return base.GenerateLabel(viewContext, modelExplorer, expression, labelText, htmlAttributes);
}
private string GetLowerCamelCase(string text) {
if (!string.IsNullOrEmpty(text)) {
if (char.IsUpper(text[0])) {
return char.ToLower(text[0]) + text.Substring(1);
}
}
return text;
}
}
}
Now that we have our CustomHtmlGenerator class we need to register it in the IoC container in place of the DefaultHtmlGenerator. We can do that in the ConfigureServices method of the Startup.cs via the following two lines:
//Replace DefaultHtmlGenerator with CustomHtmlGenerator
services.Remove<IHtmlGenerator, DefaultHtmlGenerator>();
services.AddTransient<IHtmlGenerator, CustomHtmlGenerator>();
Pretty cool. And not only have we solved the id and name casing issue on the input fields but by implementing our own custom IHtmlGenerator, and getting it registered, we have opened the door on all kinds of html customization that can be done.
I'm starting to really appreciate the power of a system built around an IoC, and default classes with virtual methods. The level of customization available with little effort under such an approach is really pretty amazing.
Update
#Gup3rSuR4c pointed out that my services.Remove call must be an extension method that's not included in the framework. I checked, and yep that true. So, here is the code for that extension method:
public static class IServiceCollectionExtensions {
public static void Remove<TServiceType, TImplementationType>(this IServiceCollection services) {
var serviceDescriptor = services.First(s => s.ServiceType == typeof(TServiceType) &&
s.ImplementationType == typeof(TImplementationType));
services.Remove(serviceDescriptor);
}
}
The simplest way to do this is to just write
<input asp-for="StartDate" name="startDate" />
Or do you want to have it generated completely automatically in camel case, for the whole application?
To do that, it seems like you have to implement your own InputTagHelpers in Microsoft.AspNetCore.Mvc.TagHelpers.
Here is the method where the name is generated:
private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
{
var format = Format;
if (string.IsNullOrEmpty(format))
{
format = GetFormat(modelExplorer, inputTypeHint, inputType);
}
var htmlAttributes = new Dictionary<string, object>
{
{ "type", inputType }
};
if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName))
{
htmlAttributes["multiple"] = "multiple";
}
return Generator.GenerateTextBox(
ViewContext,
modelExplorer,
For.Name,
value: modelExplorer.Model,
format: format,
htmlAttributes: htmlAttributes);
}
(The above code is from https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs, Apache License, Version 2.0, Copyright .NET Foundation)
The line is "For.Name". The name is sent into some other methods, and the one that in the end gives the final name is in a static class (Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider), so nothing we can really plug into easily.

Custom Html helpers generate html via a view .cshtml file

I am trying to create my own Html helper that will allow me to reuse some functionality across any web application. If I wanted to reuse this control in a single wep app I could create a .cshtml file and call it via the Html.Partial("") method and pass in a Model.
However as I have a class library project for my custom Html helpers I am creating the html with a string builder like this simplified version
StringBuilder htmlBuilder = new StringBuilder("<div class='myClass'>")
foreach(var item in MyItems)
{
htmlBuilder.Append($"item : {item.Name}");
}
htmlBuilder.append("</div>");
This makes it a pain to maintain especially as my control gets more features.
Is there a recommended way to leverage the razor engine where I can write the html in a .cshtml file with a model and then generate the html instead of using a string builder?
Yes. You can use a templated HTML helper to separate your view (HTML elements) from your model.
However, the downside is that you generally must put the templates either in the /Views/Shared/DisplayTemplates folder or a /DisplayTemplates folder inside of the view folder that represents the current controller. In the latter case, you can only use the template inside of that specific folder. It is possible to make a custom view engine that will pull the default templates as resources of a DLL file - see the MvcSiteMapProvider project for an example view engine implementation.
Example Templated HTML Helper
public class MyHelperModel
{
public string Title { get; set; }
public string Body { get; set; }
}
// Extension Methods for HTML helper
public static class MyHelperExtensions
{
public static MvcHtmlString MyHelper(this HtmlHelper helper, string title, string body)
{
return MyHelper(helper, title, body, null);
}
public static MvcHtmlString MyHelper(this HtmlHelper helper, string title, string body, string templateName)
{
// Build the model
var model = BuildModel(title, body);
// Create the HTML helper for the model
return CreateHtmlHelperForModel(helper, model)
.DisplayFor(m => model, templateName);
}
private static MyHelperModel BuildModel(string title, string body)
{
// Map to model
return new MyHelperModel
{
Title = title,
Body = body
};
}
private static HtmlHelper<TModel> CreateHtmlHelperForModel<TModel>(this HtmlHelper helper, TModel model)
{
return new HtmlHelper<TModel>(helper.ViewContext, new ViewDataContainer<TModel>(model));
}
}
public class ViewDataContainer<TModel>
: IViewDataContainer
{
public ViewDataContainer(TModel model)
{
ViewData = new ViewDataDictionary<TModel>(model);
}
public ViewDataDictionary ViewData { get; set; }
}
MyHelperModel.cshtml
The default conventions use a display template with the same name as the model when no templateName argument is passed (or it is null). Therefore, this will be our default HTML helper format. Note that you could instead just hard-code the HTML elements into the helper in the default case instead of using a template (or going the extra mile of creating a view engine).
As mentioned above, this should be in the /Views/Shared/DisplayTemplates/ folder, but you could make a custom view engine to pull the default template from a DLL.
#model MyHelperModel
<h3>#Model.Title</h3>
<p>#Model.Body</p>
CustomHtmlHelperTemplate.cshtml
Here is a named template that can be used within the application to change the HTML elements applied to the HTML helper.
As mentioned above, this should be in the /Views/Shared/DisplayTemplates/ folder, but you could make a custom view engine to pull the default template from a DLL.
#model MyHelperModel
<h1>#Model.Title</h1>
<p><i>#Model.Body</i></p>
Usage
#Html.MyHelper(
"This is the default template",
"This is what happens when we don't pass a template name to the HTML helper.")
#Html.MyHelper(
"This is a custom template",
"This is a custom template with different HTML elements than the default template.",
"CustomHtmlHelperTemplate")
NOTE: To ensure the helpers are available in the views, you need to add the namespaces in the /Views/Web.config file at <system.web.webPages.razor><pages><namespaces>.
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization" />
<add namespace="System.Web.Routing" />
<!-- Add your namespaces here -->
<add namespace="MyProject.HtmlHelperNamespace" />
<add namespace="MyProject.HtmlHelperNamespace.Models" />
</namespaces>
</pages>
</system.web.webPages.razor>
You can maintain and generate at runtime the output of a Razor View / PartialView (cshtml), using this code:
public static string GetViewPageHtml(Controller controller, object model, string viewName)
{
ViewEngineResult result = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
if (result.View == null)
throw new Exception(string.Format("View Page {0} was not found", viewName));
controller.ViewData.Model = model;
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (System.Web.UI.HtmlTextWriter output = new System.Web.UI.HtmlTextWriter(sw))
{
ViewContext viewContext = new ViewContext(controller.ControllerContext, result.View, controller.ViewData, controller.TempData, output);
result.View.Render(viewContext, output);
}
}
return sb.ToString();
}
You call it like this (from a Controller)
string result = GetViewPageHtml(this, viewModel, "~/Views/Home/Index.cshtml");

Entity Framework 4.1 Database First does not add a primary key to the DbContext T4 generated class

I am just getting started with Entity Framework 4.1, trying out the "database first" mode. When EF generates a Model class with the "ADO.Net DbContext Generator," shouldn't it identify the primary key for the class with a [Key] attribute? Without this, it appears incompatible with the T4 MVCScaffolding.
Here are the details:
Using the Entity Data Model Designer GUI, I have added a simple "country" table to the model from my existing database. The GUI correctly identifies a single integer identity key field named "PK" as my primary key. (Alas! I'm a new user so I can't add a screenshot. I've included the CSDL instead below.) However, when EF generates code using the "ADO.Net DbContext Generator", it does not identify the PK field as the key field in the generated class (see code excerpt below).
The CSDL for the "country" table:
<edmx:ConceptualModels>
<Schema Namespace="EpiDataModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
<EntityContainer Name="EpiModelEntities" annotation:LazyLoadingEnabled="true">
<EntitySet Name="countries" EntityType="EpiDataModel.country" />
</EntityContainer>
<EntityType Name="country">
<Key>
<PropertyRef Name="PK" />
</Key>
<Property Name="PK" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
<Property Name="Abbreviation" Type="String" Nullable="false" MaxLength="200" Unicode="false" FixedLength="false" />
<Property Name="Name" Type="String" MaxLength="1024" Unicode="false" FixedLength="false" />
<Property Name="Description" Type="String" MaxLength="1024" Unicode="false" FixedLength="false" />
<Property Name="Sequence" Type="Int32" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
Here's the autogenerated code:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace MvcApplication1.Areas.Epi.Models
{
public partial class country
{
public int PK { get; set; }
public string Abbreviation { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Nullable<int> Sequence { get; set; }
}
}
This causes a problem when I try to scaffold a controller using the MVCScaffolding T4 template. I get an error "No properties appear to be primary keys." The command and output from the NuGet Package Manager Console is below:
PM> scaffold controller MvcApplication1.Areas.Epi.Models.country -Area Epi -NoChildItems -DbContextType MvcApplication1.Areas.Epi.Models.EpiModelEntities -Force
Scaffolding countriesController...
Get-PrimaryKey : Cannot find primary key property for type 'MvcApplication1.Areas.Epi.Models.country'. No properties appear to be primary keys.
At C:\work\EPI\EPIC_MVC3\sandbox\MvcApplication1\packages\MvcScaffolding.1.0.6\tools\Controller\MvcScaffolding.Controller.ps1:74 char:29
+ $primaryKey = Get-PrimaryKey <<<< $foundModelType.FullName -Project $Project -ErrorIfNotFound
+ CategoryInfo : NotSpecified: (:) [Get-PrimaryKey], Exception
+ FullyQualifiedErrorId : T4Scaffolding.Cmdlets.GetPrimaryKeyCmdlet
However, if I manually change the generated class to add a [Key] attribute to the field, then the exact same scaffolding command shown above works fine:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; // manually added
namespace MvcApplication1.Areas.Epi.Models
{
public partial class country
{
[Key] // manually added
public int PK { get; set; }
public string Abbreviation { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Nullable<int> Sequence { get; set; }
}
}
So why aren't EF Database First and the T4 MVCScaffolding playing nice together? And even without the scaffolding issue, don't the EF classes need to know what the key field(s) are?
T4 Templates doesn't use data annotations because classes generated from templates don't need them. EF also don't need them because mapping is defined in XML files not in code. If you need data annotations you must either:
Modify T4 template to use them (this requires understanding of EF metadata model)
Don't use templates and use code first instead
Use buddy classes to manually add data annotations and hope that scaffolding will recognize them
If someone does want to do this, I found some good interesting templates on
james mannings github
Those templates have more functionality, but the bit I pulled out of these was:
1) Replace the usings at the top of Entity.tt with
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
<#
if (efHost.EntityFrameworkVersion >= new Version(4, 4))
{
WriteLine("using System.ComponentModel.DataAnnotations.Schema;");
}
#>
2) Then find this line (that prints out the properties)
<#= Accessibility.ForProperty(property) #> <#= typeUsage #> <#= code.Escape(property) #> { get; set; }
3) and prepend this template code
var attributes = new List<string>();
var isPartOfPrimaryKey = efHost.EntityType.KeyMembers.Contains(property);
var primaryKeyHasMultipleColumns = efHost.EntityType.KeyMembers.Count > 1;
if (isPartOfPrimaryKey)
{
if (primaryKeyHasMultipleColumns)
{
var columnNumber = efHost.EntityType.KeyMembers.IndexOf(property);
attributes.Add(String.Format("[Key, Column(Order = {0})]", columnNumber));
}
else
{
attributes.Add("[Key]");
}
}
PushIndent(new string(' ', 8));
foreach (var attribute in attributes)
{
WriteLine(attribute);
}
ClearIndent();