I have a new ABP Commercial project generated with the Suite: Blazor Server UI, Entity Framework with SQL Database, using tiered deployment. I'm attempting to extend the default Identity User entity with extra properties. I have added the new properties in the ModuleExtensionConfigurator.cs and EfCoreEntityExtensionMappings.cs files, like so...
In ModuleExtensionConfigurator.cs (IdentityUserExtraProperties holds the properties' string names):
private static void ConfigureExtraProperties()
{
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>(
IdentityUserExtraProperties.MiddleName,
property =>
{
property.Attributes.Add(new StringLengthAttribute(64));
}
).AddOrUpdateProperty<string>(
IdentityUserExtraProperties.StreetAddress,
property =>
{
property.Attributes.Add(new StringLengthAttribute(256));
}
).AddOrUpdateProperty<string>(
IdentityUserExtraProperties.SuiteNumber,
property =>
{
property.Attributes.Add(new StringLengthAttribute(256));
}
).AddOrUpdateProperty<string>(
IdentityUserExtraProperties.City,
property =>
{
property.Attributes.Add(new StringLengthAttribute(128));
}
).AddOrUpdateProperty<State>(
IdentityUserExtraProperties.State
).AddOrUpdateProperty<string>(
IdentityUserExtraProperties.ZIP,
property =>
{
property.Attributes.Add(new StringLengthAttribute(11));
}
);
});
});
}
In EfCoreEntityExtensionMappings.cs:
public static void Configure()
{
WebAppGlobalFeatureConfigurator.Configure();
WebAppModuleExtensionConfigurator.Configure();
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
IdentityUserExtraProperties.MiddleName,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
)
.MapEfCoreProperty<IdentityUser, string>(
IdentityUserExtraProperties.StreetAddress,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(256);
}
)
.MapEfCoreProperty<IdentityUser, string>(
IdentityUserExtraProperties.SuiteNumber,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(256);
}
)
.MapEfCoreProperty<IdentityUser, string>(
IdentityUserExtraProperties.City,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(128);
}
)
.MapEfCoreProperty<IdentityUser, State>(
IdentityUserExtraProperties.State
)
.MapEfCoreProperty<IdentityUser, string>(
IdentityUserExtraProperties.ZIP,
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(11);
}
);
});
}
These properties have their columns in the database table, show up on the default CRUD page, and I'm able to access and save these values through the entity's ExtraProperties dictionary. However, now I would like to have these properties show up on the profile management page (select profile pic in top right, choose My Account, select Personal Info tab). They aren't added there by default.
What steps would I go through to override the Personal Info tab on the Profile Management page and add fields for changing these extra properties?
Related
I've two arrays/Lists:
users =[object1,object2,object3]; all of the same type: Class User
teams =[object1,object2,object3]; all of the same type: Class Team
I want to create a new array dynamically containing objects of type SupportTeams which has properties of type User and Team.
I think I should use the map operator to convert the arrays to my new array.
Something like this
List<SupportTeams> combiner(List<User> users, List<Team> teams) {
return users.map((user) {
teams.map((team) {
SupportTeams(user,team);
});
}).toList();
}
You're almost there. First thing's first, you need to return from your calls within map:
List<SupportTeams> combiner(List<User> users, List<Team> teams) {
return users.map((user) {
return teams.map((team) {
return SupportTeams(user,team);
});
}).toList();
}
However, this still won't work 100% as you'll have a list of lists. You'll need to flatten the result using expand:
List<SupportTeams> combiner(List<User> users, List<Team> teams) {
return users.map((user) {
return teams.map((team) {
return SupportTeams(user,team);
});
}).expand((i) => i).toList();
}
If you want to be even more concise, you can use => notation instead of returns:
List<SupportTeams> combiner(List<User> users, List<Team> teams) =>
users.map((user) =>
teams.map((team) =>
SupportTeams(user,team))).expand((i) => i).toList();
Finally, as #lrn pointed out in the comments you can use control flow collections to do this much more cleanly:
List<SupportTeams> combiner(List<User> users, List<Team> teams) => [
for (var user in users)
for (var team in teams)
SupportTeams(user, team)
];
I have a model class named user :
export class User {
constructor(
public UserId?: number,
public Name?: string,
public Password?: string,
public IsActive?: boolean,
public RoleId?: number
) {
}
}
For ng2-select component, I need these properties : text and id.
Now when I set them via
this.userService.getAllUsers().subscribe(
data => this.users = data,
err => this.error(err));
there are no text and id property.
Is there a method where I can set these properties on initialization.
I dont wan't to write everytime a workaround with :
data.forEach(g =>
{
g.text = g.Name;
g.id = g.Id;
});
this.users = data;
Since it seems you are not typing your array users to your User class anyway in the above code snippet, so you could introduce another class and where you handle this assignment in map.
export class UserSelect {
constructor(
public text: string,
public id: number
) {
}
and in your Service:
getAllUsers() {
return this.http......
.map(res => res.json().map(x => new UserSelect(x.Name, x.Id)))
}
and then you just subscribe normally
this.userService.getAllUsers()
.subscribe(data => {
this.users = data;
})
Then you would have an Array of type UserSelect which has the proper properties you need :) Of course you can extend this and include any properties you need, keeping in mind that not knowing to which extent you are using the users array and if you are needing the other properties, or original properties...
I have an array of Threads Objects with ID, title and a isBookmarked:boolean.
I've created a Subject and I want to subscribe to it in order to get an array of Thread Objects with isBookmarked=true.
https://plnkr.co/edit/IFGiM8KoSYW6G0kjGDdY?p=preview
Inside the Service I have
export class Service {
threadlist:Thread[] = [
new Thread(1,'Thread 1',false),
new Thread(2,'Thread 2',true),
new Thread(3,'Thread 3',false),
new Thread(4,'Thread 4',true),
new Thread(5,'Thread 5',true),
new Thread(6,'Thread 6',false),
new Thread(7,'Thread 7',false),
]
threadlist$:Subject<Thread[]> = new Subject<Thread[]>()
update() {
this.threadlist$.next(this.threadlist)
}
}
in the component
export class AppComponent implements OnInit {
localThreadlist:Thread[];
localThreadlistFiltered:Thread[];
constructor(private _service:Service){}
ngOnInit():any{
//This updates the view with the full list
this._service.threadlist$.subscribe( threadlist => {
this.localThreadlist = threadlist;
})
//here only if isBookmarked = true
this._service.threadlist$
.from(threadlist)//????
.filter(thread => thread.isBookmarked == true)
.toArray()
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
})
}
update() {
this._service.update();
}
}
which Instance Method do I use in general to split an array?
Also is there a better way to do it?
Thanks
You would leverage the filter method of JavaScript array within the map operator of observables:
this._service.threadlist$
.map((threads) => {
return threads.filter((thead) => thread.isBookmarked);
})
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
});
See this plunkr: https://plnkr.co/edit/COaal3rLHnLJX4QmvkqC?p=preview.
As I'm learning Angular 2 I used an observable to fetch some data via an API. Like this:
getPosts() {
return this.http.get(this._postsUrl)
.map(res => <Post[]>res.json())
.catch(this.handleError);
}
My post model looks is this:
export class Post {
constructor(
public title: string,
public content: string,
public img: string = 'test') {
}
The problem I'm facing is that the map operator doesn't do anything with the Post model. For example, I tried setting a default value for the img value but in the view post.img displays nothing. I even changed Post[] with an other model (Message[]) and the behaviour doesn't change. Can anybody explain this behaviour?
I had a similar issue when I wanted to use a computed property in a template.
I found a good solution in this article:
http://chariotsolutions.com/blog/post/angular-2-beta-0-somnambulant-inauguration-lands-small-app-rxjs-typescript/
You create a static method on your model that takes an array of objects and then call that method from the mapping function. In the static method you can then either call the constructor you've already defined or use a copy constructor:
Mapping Method
getPosts() {
return this.http.get(this._postsUrl)
.map(res => Post.fromJSONArray(res.json()))
.catch(this.handleError);
}
Existing Constructor
export class Post {
// Existing constructor.
constructor(public title:string, public content:string, public img:string = 'test') {}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj['title'], obj['content'], obj['img']));
}
}
Copy Constructor
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Object) {
this.title = obj['title'];
this.content = obj['content'];
this.img = obj['img'] || 'test';
}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj);
}
}
If you're using an editor that supports code completion, you can change the type of the obj and array parameters to Post:
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Post) {
this.title = obj.title;
this.content = obj.content;
this.img = obj.img || 'test';
}
// New static method.
static fromJSONArray(array: Array<Post>): Post[] {
return array.map(obj => new Post(obj);
}
}
You can use the as keyword to de-serialize the JSON to your object.
The Angular2 docs have a tutorial that walks you through this. However in short...
Model:
export class Hero {
id: number;
name: string;
}
Service:
...
import { Hero } from './hero';
...
get(): Observable<Hero> {
return this.http
.get('/myhero.json')
.map((r: Response) => r.json() as Hero);
}
Component:
get(id: string) {
this.myService.get()
.subscribe(
hero => {
console.log(hero);
},
error => console.log(error)
);
}
Im trying to combine those technologies, but nothing good comes, as entity framework meta-datas doesn't get consumed by breeze.js, even all configurations where setup, it's a bit tricky situation, there is literally no examples of that, so this is my sample code which doesn't work properly, but somehow maybe someone will find my mistake and eventually help to solve this puzzle or will find it as starting point.
OdataService.ts
'use strict';
module twine.components {
class MetadataStoreOptions implements breeze.MetadataStoreOptions{
namingConvention:breeze.NamingConvention = breeze.NamingConvention.defaultInstance;
}
class Manager implements breeze.EntityManagerOptions {
metadataStore: breeze.MetadataStore;
constructor( public dataService: breeze.DataService) {
}
}
class DataServiceOptions implements breeze.DataServiceOptions {
serviceName = 'http://twine.azurewebsites.net/odata';
hasServerMetadata = true;
}
export class ODataService {
options: Manager;
manager: breeze.EntityManager;
metadataStore: breeze.MetadataStore;
storeOptions: MetadataStoreOptions;
static $inject: string[] = ['$http', '$rootScope'];
cache: twine.Model.IEntity[];
constructor(private $http: ng.IHttpService, private $rootScope: ng.IRootScopeService){
this.storeOptions = new MetadataStoreOptions();
this.metadataStore = new breeze.MetadataStore(this.storeOptions);
this.options = new Manager( new breeze.DataService( new DataServiceOptions() ));
this.options.metadataStore = this.metadataStore;
this.manager = new breeze.EntityManager( this.options );
breeze.config.initializeAdapterInstance('dataService', 'webApiOData', true);
//this.manager.fetchMetadata((meta) => {
// this.metadataStore.importMetadata(meta);
//});
}
All( query:breeze.EntityQuery, successCallback: Function, failCallback?: Function ): void {
this.manager.executeQuery( query )
.then( ( data: breeze.QueryResult ) => {
successCallback( data );
this.$rootScope.$apply();
})
.catch( ( reason: any ) => {
if ( failCallback ) {
failCallback( reason );
}
});
}
Get( key:number, successCallback: Function, failCallback?: Function ): void {
//this.manager.fetchMetadata();
//var entityType = this.manager.metadataStore.getEntityType('Tag');
//var entityKey = new breeze.EntityKey(entityType, key);
this.manager.fetchEntityByKey( 'Tag', key )
.then( ( data: breeze.EntityByKeyResult ) => {
successCallback( data );
this.$rootScope.$apply();
})
.catch( ( reason: any ) => {
if ( failCallback ) {
failCallback( reason );
}
});
}
}
}
And this is tagController.ts
'use strict';
module twine.routes {
interface ITagsScope extends ng.IScope {
vm: TagsCtrl;
}
interface ITagsCtrl extends twine.components.ITwineRoute{
tags:any[];
getTags: () => void;
tag: any[];
getTag: (id:number) => void;
}
export class TagsCtrl implements ITagsCtrl{
/* #ngInject */
static controllerId: string = 'TagsController';
static controllerAsId: string = 'tagsCtrl';
static $inject: string[] = ["$scope", "ODataService", '$route'];
entityQueryName: string = 'Tag';
query: breeze.EntityQuery;
tags:any;
tag: any;
constructor (private $scope: ITagsScope, private ODataService: twine.components.ODataService, $route: ng.route.IRouteService) {
this.query = new breeze.EntityQuery(this.entityQueryName);
if($route.current && $route.current.params.id){
this.getTag($route.current.params.id);
}
else {
this.getTags();
}
}
getTags() {
this.ODataService.All(this.query , (data) => {
this.tags = data.results[0].value;
}, (error) => {
console.log('error', error);
});
}
getTag(id:number){
this.ODataService.Get(id , (data) => {
this.tag = data.results[0].value;
}, (error) => {
console.log('error', error);
});
}
}
}
There are many errors, on different configurations, sometimes it's There is no resourceName for this query or EntityKey must be set, or Other stupid errors which are indeed doesn't have to appear because it's a typescript which doesn't allow type mismatches, but the configuration itself is not correct.
And this is abstract controller
[EnableCors(origins: "*", headers: "*", methods: "*")]
public abstract class EntityController<T> : ODataController where T: Entity
{
protected ODataRepository<T> repo = new ODataRepository<T>();
private static ODataValidationSettings _validationSettings = new ODataValidationSettings();
public EntityController()
{
}
// GET: odata/Entity
[EnableQuery]
public IQueryable<T> Get(ODataQueryOptions<T> queryOptions)
{
try
{
queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
Trace.WriteLine(ex.Message);
return null;
}
return repo.All();
}
// GET: odata/Entity(5)
[EnableQuery]
public SingleResult<T> Get([FromODataUri] long key, ODataQueryOptions<T> queryOptions)
{
try
{
queryOptions.Validate(_validationSettings);
}
catch (ODataException ex)
{
Trace.WriteLine(ex.Message);
return null;
}
return SingleResult.Create(repo.All().Where(x=>x._id == key));
}
//ommitted
}
And lastly this is ASP.NET webApi configuration
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Конфигурация и службы веб-API
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
// Маршруты веб-API
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//CORS
var cors = new EnableCorsAttribute(
"*",
"*",
"*",
"DataServiceVersion, MaxDataServiceVersion"
);
config.EnableCors(cors);
// Маршруты Odata
//config.EnableQuerySupport();
config.AddODataQueryFilter();
Builder<Account>(config);
Builder<Branch>(config);
Builder<Bucket>(config);
Builder<Ingredient>(config);
Builder<Like>(config);
Builder<Meetup>(config);
Builder<Shot>(config);
Builder<Skill>(config);
Builder<Tag>(config);
Builder<Team>(config);
}
private static void Builder<T>(HttpConfiguration config) where T: class
{
var entityType = Activator.CreateInstance<T>().GetType();
if (entityType != null)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<T>(entityType.Name);
config.Routes.MapODataServiceRoute("odata_" + entityType.Name, "odata", builder.GetEdmModel());
}
}
}
For testing purpose i have this working backed OData service at http://twine.azurewebsites.net/odata/Tag, (currently no restrictions by CORS, feel free) last entity can be changed to other name based on webApi configuration Build method. Please feel free to ask any other information. If someone need whole source, im willing to publish on github
Update
Forget to mension, problem is in method Get of ODataService. I cannot bind metadata from server to breeze, method All works fine. But query fetchByEntityKey throws errors as described above
evc, please take a look at the following article under AngularJs Apps/Projects. You'll find sample projects that you can actually follow and learn using AngularJs, Breeze, Asp.net Web api. You're right, there's a lot of material out there but there not that effective. Hope it helps. http://www.learn-angularjs-apps-projects.com/
Have a look at the Breeze samples, especially WEB Api OData and Breeze. It's sample tutorial for a angularjs+webapi+odata+breeze, no typescript (but isn't javascript just typescript :). For your WEB Api controllers you should definitely install this nuget package :
PM> Install-Package Breeze.Server.WebApi2
This will allow you to build a breeze aware api controller and expose breeze odata metada easily using the Breeze.Server.ContextProviderClass.
Also you can check the excelent but paid training from Pluralsight. It covers SPA from scratch using JavaScript, Angular, and Breeze.
http://www.pluralsight.com/courses/build-apps-angular-breeze