This inspection detects file contains non-ASCII characters and doesn't have an encoding declaration at the top.


This guide will provide the necessary information on how to create a reusable components library with using Cardinal.Js framework.
Start by cloning the cardinal-custom-components-template repository.
Next, go ahead and remove the existing .git
folder from your cardinal-custom-components
folder,
initialize a new empty git repo and commit the existing content to your own repository.
You've just developed your new cardinal-custom-components library's basic structure, using the Cardinal.Js framework.
package.json
-> "web"
script).
Starts a watcher for the src
folder which will rebuild the custom components library
src/index.html
can be changed to include the developed components for testing purposes. Please not that the psk-app-root
tag is required by Cardinal.Js in order to show the content.
src/components
folder and there should be a separated folder for each.
As a starting point the template contains two sample components, psk-custom-label
and psk-custom-label2
.
As seen in these two components, anything from Cardinal.Js project can be imported and used. The import should be relative (the cardinal project will be installed at cardinal
). Path aliases cannot be properly used due to some Stencil limitations.
psk-custom-label
contains the following import:


{% if rank %}
{% include "partials/player/ranks.html" %}
{% else %}
/ tendencies
unable to find rank information for this profile (does this player own rocket league?)
{% endif %}
{% set min_replays = 1 %}
{% if games|length > min_replays %}
{% include "partials/player/spider_charts.html" %}
{% else %}
/ match history
no replays recorded for this profile (are you using the auto-uploader?)
{% endif %}
{% if games %}
{% include "partials/replay/match_history.html" %}
{% else %}
no matches recorded for this profile (are you using the auto-uploader?)
{% endif %}


Next: The Device part of a Logical Pathname Namestring, Previous: Additional Information about Parsing Logical Pathname Namestrings, Up: Syntax of Logical Pathname Namestrings
The host must have been defined as a logical pathname host; this can be done by using setf of logical-pathname-translations.
The logical pathname host name "SYS" is reserved for the implementation. The existence and meaning of SYS: logical pathnames is implementation-defined.


Last year, director Andy Tennant transformed the tale of Cinderella into "Ever After," one of 1998's most endearing sleeper hits. For his follow-up, he's tackled the culture clash between British governess Anna Leonowens and King Mongkut of Siam, the same material that's previously been dramatized in the 1946 film "Anna and the King of Siam" and musicalized as Rodgers and Hammerstein's "The King and I."
Was another telling of this story absolutely necessary? Perhaps not, although "Anna and the King," like "Ever After," puts forth some intriguing new ideas about its characters while providing a banquet for the eyes and ears. This is precisely the kind of "cast of thousands" extravaganza Hollywood rarely produces anymore and the movie's seductive visuals, charismatic performances and glorious scenery make for two and a half hours of immensely pleasurable viewing.
For those who grew up with the 1955 Yul Brynner/Deborah Kerr version of "The King and I," "Anna" comes across as slightly disorienting at first. It feels slightly strange not to hear "Getting to Know You" when Anna is introduced to her scores of Siamese pupils and though the screenplay retains the famous scene in which Anna is literally swept off her feet by the king at a ball, "Shall We Dance" is conspicuously absent from the soundtrack. Accept Tennant's film on its own terms, however, and you'll be rewarded.
Thematically, the biggest difference between the screenplays of the two movies is found in their approach to the king.
In "The King and I," Mongkut is portrayed as a steely but slightly goofy monarch whose fascination with science and English customs is often played for laughs. Anna, on the other hand, is the walking embodiment of all the glorious traits Britain once attached to its policies of Colonialism: She's single-minded but proper to a fault and knowledgeable but not overbearing. She handles Mongkut with the same firm hand and gentle manner a well-bred mother in 1862 might have used to control a temperamental child.
There's never any doubt in "The King and I" that Western ways are superior to the mysticism and superstition that rule the East, and those attitudes have led some contemporary critics to label the musical as an example of benign racism.
"Anna and the King" offers a more balanced picture of the two worlds. As portrayed by Hong Kong action star Chow Yun-Fat, Mongkut is exceedingly intelligent and powerfully sexy, a man who's not about to let an outsider rearrange his traditions. Jodie Foster's Anna is a strong-willed widow with backbone and determination, who announces upon her arrival in the Orient that "the ways of England are the ways of the world," but slowly begins to find value in certain aspects of the Siamese approach to life.
"Anna and the King" is drawn from the diaries Leonowens kept during the six years she spent in the palace and the later part of the story diverges markedly from "The King and I," as Mongkut and his family fend off a plot to seize the throne and venture into the jungle to escape a rebel army. Whether this is ultimately more accurate than Rodgers and Hammerstein's version of the story is hard to say since historians continue to argue over how much of Leonowens' writing was a romanticized embellishment of life in the royal court.
Regardless, it makes for exciting viewing, with cinematographer Caleb Deschanel providing some of the richest on-location photography since David Lean's "A Passage to India" and Sydney Pollack's "Out of Africa." Unlike "Africa" author Isak Dinesen, the Victorian Leonowens was not bold enough to reinvent her love life in the name of a good story so the romance between Anna and Mongkut remains strictly platonic -- even though the heat between Foster and Chow is all but palpable. James Sanford
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


Burna Boy's "My Money, My Baby" For "Queen & Slim" OST Will Make You Dance
Burna Boy brings a jubilant moment to "Queen & Slim: The Soundtrack".
by Noah CMy Money, My Baby
Burna Boy
Editor Rating: VERY HOTTTTT
User Rating: n/a - 0% (0)
After Burna Boy put out one of the best albums of the year with African Giant, you'd be foolish not to jump when you see that he dropped a new song. "My Money, My Baby" comes courtesy of Queen & Slim: The Soundtrack, which arrived on Friday, two weeks ahead of the film's US release. Everyone's talking about the Lena Waithe-written film - largely due to its exhilarating trailers and fantastic cast - but just as much as attention should be paid to its soundtrack. It features original songs from Megan Thee Stallion, EARTHGANG, Lil Baby, Lauryn Hill, Vince Staples and much more.
While the OST contains many sultry cuts, "My Money, My Baby" is intended to get you out of your seat and make you flail around. The Afrobeat track samples Burna's predecessor, Fela Kuti's 1972 song "Shakara." Burna's rapid repetition of the song's title over the horns in the chorus is a rapturous moment.
Check out the song below and make sure to listen to the rest of the excellent soundtrack.
Quotable Lyrics
Ladies and gentleman,
When I look at my Rollie and I see the time
Only two things come to mind:
My money, my money, my money...
My baby, my baby, my baby...


The holdings of the Student Investment Fund have grown significantly over time, reaching over $600,000 in cash and investments by the end of the 2016 fiscal year. The percent of our portfolio held in liquid assets varies depending on the perceived volatility of the market, apparent investment opportunities, and the perceived need to allow room for new investment. Because the Fund is prohibited from participating in investment activities that might allow for unlimited losses, including shorting stocks, we tend to focus on investment in companies where we can expect a large amount of upside growth, both in the near and long term.
Our investments tend to consist primarily of stock holdings, and due to the increased availability of information we like to invest primarily in large and mid cap companies. Due to the expertise of the Caltech community in technology and innovative research, roughly 40% of our investments are in the Tech and Biomedical sectors. The remaining 60% of our portfolio is invested in a variety of sectors and industries, in an effort to create a well balanced and diversified portfolio. While we do not publicly publish our exact holdings, more information is available to members of the Caltech community upon request.
In an effort to further the learning experience provided by the Student Investment Fund, we have recently started exploring the world of Quantitative Finance. We view this relatively recent advance in investing technology as an opportunity to take advantage of Caltech's expertise in technology and computation, and demonstrate to members of the Caltech community the applications of these skills in the financial sector. While we have not as of yet made this a large part of our portfolio management strategy, we are looking into various ways we can apply this growing branch of finance to our investment plans. Weekly meetings focusing on quantitative finance are held weekly in Cahill conference room 312, from 5-6 PM on Mondays.
While we are currently exploring various tools we can use to explore quantitative finance, the tools we have so far found to be the most useful are Quantiacs and Quantopian


Z
X
C
SmallTrek
by
Impbox
A game made in 48 hours for
Ludum Dare 38
LD Judging Page
Developed using
Nim
and
Nico
mute
clear save data


Grails AWS plugin intends to provide most of any other reason, an EASY WAY to access and use AWS services.Initially just amazon s3 access is provided, but more services will come as people implement or as I get some time to do it.
I really prefer people implementing it, as this is a community plugin.As other grails plugins you should install using install-plugin command


| DOSE: T+ 0:00 |
3 glasses |
oral |
Alcohol - Beer/Wine |
(liquid) |
| T+ 0:00 |
2 bowls |
smoked |
Cannabis |
(plant material) |
| T+ 4:30 |
1 line |
insufflated |
Cocaine |
(powder / crystals) |
| T+ 5:30 |
1 line |
insufflated |
Cocaine |
(powder / crystals) |
This report documents my first experience with cocaine.
Background: I am a twenty year old male and I consider myself to be in good physical and metal health. I've grown up in a secular, yet still relatively stereotypical suburban household. Throughout my high school years, I lived under the impression that the parental inculcation about drugs being 'bad things' had some merit. Because neither of my parents, nor any of my high school friends drank, I did not take my first sip of alcohol until the fall of my freshman year of college (I attend a small liberal arts school in midwest America). For me, drinking was something new, but it rapidly lost its novelty and I never really enjoyed it, as I often got sick/nauseous and was never able to really loosen up even with its aid.
My sophomore fall, I was introduced to cannabis. Before my first smoking session, I had already decided that the smell of marijuana was appealing to me and some of the effects of it sounded more appealing than those of alcohol. While I did not feel any effects from my first few sessions, over the past nine months, I have used cannabis roughly 50 times and have mastered the art of getting high. As I have always been subject to moderately high amounts of personal anxiety and paranoia, I have had many trips ruined by my own emotions. This mainly comes from the fact that my college has very strict security and I often have to be very creative so that I am not caught. Having to constantly watch your surroundings can be a huge burden on what should be a time to relax.
But don't get me wrong, I enjoy cannabis quite a lot. It has allowed me to enjoy mundane things much more, have actually helped me with my schoolwork (I am a math major and find that a moderate high can help me grasp abstract concepts in analysis and/or abstract algebra better), and provide a good overall physical experience. Most of my friends at school are drinkers only and much of my smoking is done in solitude. At times I wish that I could be immersed into a more drug-friendly culture, but the back of my mind knows that my introverted lifestyle helps me fly under the radar.
Aside from alcohol and cannabis, I have insufflated Amphetamines (Adderall) on three separate occasions, but never more than a standard 25mg dose at once. I enjoy the 'upper' effects and was able to accomplish a lot of schoolwork under Adderallís influence. I was also prescribed a cough suppressant about six months ago that contained codeine, which cured my cough and seemed to always have a subtle, yet positive effect on my mindset. I can only imagine the bliss that comes from other opiates and higher doses, yet do not wish to dabble with any of that quite yet. Henceforth, I have not used any other illegal drug or legal drug in an illegal fashion.
Setup: About a week ago, a coworker of mine made an announcement about her college graduation party, which would be held the following friday. Upon asking what she was going to have at her party, she informed me that a keg would be purchased, and that a limited amount of cannabis and cocaine would be available for 'a select few.' I've had a mild interest in cocaine ever since my interest in drugs spawned (about the time I began to use marijuana), but I had never seen it or been offered it. I asked my coworker, who I shall refer to as A, if she would acquire a small excess of cocaine, for which I would gladly offer her money. An agreement was made and my first experience with cocaine was set.
Over the course of the following week, I began to research the drug. I read all that I could find on internet sites. I gave myself several preparation talks concerning cocaine's addictiveness, assuring myself that I've never had problems with any form of addiction, drug-related or not, prior to this and that I needed to remain in a 'mind-over-matter' mindset throughout the evening. Sure, I have used marijuana rather frequently, but I do not feel that I physically crave it, nor would my life be significantly worse in the case that it was taken away from me. No matter how good cocaine was, I would not ask for any more and would wait at least one calendar month until I would allow myself to use it again, if given the opportunity.
I anticipated that its effects would be similar to those that I experienced from insufflating Adderall, yet possibly stronger. I chose to abstain from cannabis and alcohol for the week leading up to this party and told myself that unless there ended up not being any cocaine at the party, that I would not use any other substances throughout the evening either. I wanted to document the effects exclusive to the cocaine and eliminate any potential bias from certain combinations.
Results: Upon arriving at the party, I fell under the impression that there was no cocaine present and that I might have to put my first experience on hold. I was offered a beer and an opportunity to smoke some marijuana, neither which I chose to pass up. Over the next two hours, I consumed three beers and participated in two bowls of marijuana. I felt good over this time and became complacent with the thought that I was not going to be using any cocaine. My new assistant boss was also present and I knew that staying as sober as possible was probably a good idea.
About two hours after I had arrived, I was still feeling the effects of both the beer and the marijuana, yet I still considered myself to be sober enough to speak with a policeman or attempt mathematics. It was then that A approached me and explained that a guest had arrived with a cocaine offer. She seemed excited about it, so I told her that I was in. As the cocaine would still have to be purchased, it gave me some time to sober up more.
The cocaine arrived and we agreed to use it at a time that translated to three and a half hours after I arrived (ninety minutes after any other substance use). It cost $60 for what I was told was an eighth of a gram. This seemed rather expensive, but I was assured that it was 'high quality product.' From my research, I knew that a standard line of cocaine was around 50mg. So an eighth of a gram, or 125mg, would yield two and a half standard lines. I knew, though, that my bodyweight (120lbs), the fact that this was high-quality cocaine, and that I was not completely sober probably meant that I needed less of this particular cocaine at this moment in time to feel its effects. M told me that this cocaine was good enough that this amount of cocaine was plenty for the three of us to feel very good. To this day I am unsure about how much I really did consume or how much the grade of cocaine matters in dosage.
We ended up making seven lines, which did not appear all that small to me. Whether we had more cocaine in actuality, or I was not aware of how big a standard line was I will never know. Math told me that my line contained about 18mg of cocaine plus or minus human error. A, the middleman M, and I would split the cocaine. A would have three lines, I would have two, and M would have two. I planned to insufflate my first line, wait for an unknown time, and then move on to the second.
T+0.00 - I took the line up my left nostril. It went up easy, much easier than my experiences with Adderall. Nevertheless, over time, I would declare that the drip and nasal congestion from cocaine was worse than that of Adderall. At first, I felt nothing other that my heartbeat being slightly higher than normal due to excitement. I knew that Adderall came on gradually over about ten minutes. I stood with A and M, who were talking, and waited.
T+0.01 - After about ninety seconds, I felt my heartbeat increase. At first, I thought it was merely a placebo effect, as I was quite nervous, yet anticipatory about what was to come. But it continued to increase. In my mindís eye, I pictured my heartbeat being modeled on the standard xy-plane. My heartbeat looked like the graph of the exponential function.
T+0.02 - It was definitely kicking in. I began to worry a bit, as I could feel my heart pounding and my pulse increasing. My upper body felt warm. I knew that this increase was typical to the drug, but I also knew that my body had a physical limit and that the increase needed to level out before too long.
T+0.03 - I finally felt as if it had reached a plateau. My heartbeat became level, albeit still very high. Many people say that one feels euphoria - being invincible and/or the desire to clean the house. I did not feel either of these (and I did remember to think about these things). For me, the positive effects of cocaine came directly from knowing that I had reached a plateau and I was going to be fine. I felt invigorated, yet also very comfortable.
It was suprising how physical the sensations actually were. I had a warm, tingly sensation in my limbs. I began to talk with A and M, asking them a few questions about the drug and to see if they too were effected. They assured me that they were and that it was indeed good cocaine (I got the impression that it hit me, the virgin and lightweight user, harder, though).
T+0.06 - The drip was really annoying. My throat was also irritated. We moved into a second room so that A and M can use a computer. I asked for a pencil and paper so that I could write down notes about my experience.
T+0.10 - I felt my mouth getting dry. I had no desire to grind my teeth or clinch my jaw, but I did start rubbing my teeth against my lips.
T+0.15 - I felt completely sober. It felt good, but nothing spectacular. I did not feel a desire to read or do something academic, but just to sit and study my surroundings and my bodily sensations.
T+0.30 - Both the dry mouth and the drip were still present and annoying.
T+0.50 - No longer felt anything significant. I felt as if I had just awoken and drank a cup of coffee.
T+1.00 - A wanted to do our second lines. I felt no desire, neither a physical one nor a mental one, to do any more, but nevertheless I obliged. Mostly, I did not feel like experiencing the heartbeat increase again. I did, however, now have the knowledge that I gained from the first line, so I did not feel that I would be as nervous. Line two was insufflated.
T+1.05 - A 'rush' never came from this line, although some of the warmness and energy has returned. I almost felt like I was 'glowing.' I still felt very sober. We licked up the last of the cocaine, which was interesting. It does make the mouth and teeth feel numb, much like the over-the-counter products made to numb mouth sores, yet stronger.
T+1.10 - I talked to some other people for a while. I felt awake, but this line definitely did not result in the same type of journey as the first.
T+1.30 - I decided to drive home. The drive was rather uneventful and music did not sound necessarily better.
T+2.00 - I arrived home and prepare to go to bed. I still felt wide awake, but knew that I should lay down now so that I could fall asleep as soon as possible. I had to be at work in about eight hours.
T+3.00 - For some reason, only at this point did I begin to urinate quite frequently. I had a bowl of cereal and some milk. I never noticed a real loss of appetite from the cocaine. Only the nauseous effects made the sound of food somewhat unattractive.
T+3.30 - I finally fell asleep.
Next day - I felt normal. My nasal passages were a bit congested, but nothing I could not handle. My heart was back to normal. All the effects of the drug appeared to be elements of my past.
Conclusion: To me, the most surprising element of my experience was just how quick and hard the first rush hit me. I was told by M that a dose of about 1.5 times larger than the first line I had taken at once would be 'ideal' for this quality cocaine. Mentally, I have no doubt that I could have handled more at once. Physically, though, I just do not know the limits of my heart or how much harder a larger dose would push it. If I were to use cocaine again, especially in a larger dose, I would have water present and I would move around during the rush. Cocaine definitely hit me harder than Adderall, yet died out faster as well. Cocaine affected me much more physically than Adderall, yet did not stimulate me intellectually. The cocaine experience was quite similar to drinking a lot of coffee in a short amount of time. The dry mouth and lip biting were also things that cocaine gave me that I did not experience with Adderall.
In the end, I would actually prefer the Adderall rush to the cocaine rush. Considering that this was high quality cocaine, I have no doubt that Adderall is more worth its price. I do not doubt that cocaine generates good feelings to its user and I do not think that even the most skeptical of users would not feel any complacent tendencies. For me, though, it was simply a rush and not anything too extravagant.
I am glad to say that I felt no need to do any more cocaine, other than possibly in the future for experimental value. I would do it again if I were in its presence, but I do not have any desire to seek it on my own.
Experience Reports are the writings and opinions of the individual authors who submit them.
Some of the activities described are dangerous and/or illegal and none are recommended by Erowid.


One-line lists
Headline (textContent)
Disabled (textContent)
Headline with leading icon
Headline with leading avatar
Headline with leading image thumbnail
Headline with leading video
and
checkbox
Headline with leading checkbox
Headline with trailing checkbox (selected)
Headline with trailing text
Headline with trailing icon
Headline (slot)
Customized headline (slot)
Headline with leading (slot)
Two-line lists (Explicit)
Headline (textContent)
Headline (textContent)
Headline
Headline
Headline
Headline
Headline
Headline
Headline with trailing text
Headline with trailing
icon
Headline (slot)
Customized supporting text (slot)
Headline
Custom supporting slot
Headline
Oversized supporting *slot* that will wrap on small to medium sized
displays
Headline
Three-line lists (Explicit)
Headline (textContent)
Headline (textContent)
Headline
Headline
Headline
Headline
Headline
Headline
Headline with trailing text
Headline with trailing
icon
Headline (slot)
Customized supporting text (slot)
Headline
Custom supporting slot
Headline
Oversized supporting *slot* that will wrap on small to medium sized
displays. Instead of being clamped to only 1 line, this is now clamped to 2
Headline
Dynamic line count
Headline only
Headline
Headline
Headline
Oversized supporting *slot* that will wrap on small to medium sized
displays. Instead of being clamped to only 1 line, this is now clamped to 2
Headline
Interactive (HTMLAnchorElement)
href=#
Headline with
trailing icon
No link
Real link


System Architect Symbol details for "Object"
Concept (Class)
- Child Diagrams:
-
Concept (
UML Class)
Description:
"Object" which is a unit of thought.
object (Association)
- From Class:
Object
- To Class:
"Object Representation"
- From Class:
Object
- To Class:
"Object Representation"
Object (Class)
- Description:
Part of the perceivable or conceivable universe.
- Comment:
-
Previous documented definitions providing the source for the above are:
Something that exists (CBS v2).
- Definitions Used:
-
Identifier (Class Attribute)
Object Representation (Class)
- Child Diagrams:
-
Object Representation (
UML Class)
- Description:
Representation of an "Object" in a "Subject".
- Comment:
Any "Subject" may hold information about an "Object", either itself or others. Typically, such
"Subjects" are created expressly to perform this role, eg case notes, electronic patient records,
but sometimes this is done by impressing the information on a "Subject" which has other roles.
Examples would be batch number and use by date on a drug container, manufacturer logo on a capsule.
Object/Essential Type (Generalization Node)
- Base Class:
Object
- Definitions Used:
-
Object (Class)
Percept (Class)
- Child Diagrams:
-
Percept (
UML Class)
- Description:
"Object" perceived or inferred to have physical existence.


Myriam Saad
"Beirut - Reclaiming a Nation" narrates a visual story of Beirut in its golden days through a rich archive of immersive images that tell the story of the city. The images are from the 40s, 50s, 60s, and beginning of the 70s, before the civil war. The book is also an attempt to honor all the efforts currently being put forward by the Lebanese youth to reclaim a nation that has long been stolen from them. The epub version of the book can be downloaded from the Internet Archive, and a print-on-demand book is also available on Blurb. A preview of the interactive book can be found on Youtube.


HILARY AND JACKIE
*** (out of 4) - a good movie
Release Date: January 22, 1999 Starring: Emily Watson, Rachel Griffiths, David Morrissey, James Frain, Charles Dance, Celia Imrie Directed by: Anand Tucker Distributed by: October Films MPAA Rating: R (language, sexuality) URL: http://www.execpc.com/~kinnopio/reviews/1999/hilary.htm
Not since 1996's SHINE, which starred Geoffrey Rush as pianist David Helfgott, has a movie so defiantly laid bare the lives of world-class musicians. In fact, if one were to watch these films together, it would almost certainly stand as a convincing argument that a life of music naturally leads to all varieties of social ills. Yet HILARY AND JACKIE, the sophomore effort from director Anand Tucker, has something more to say -- it softly but strongly explores the complex relationships of sibling rivalry and love in a very compelling tale.
Emily Watson and Rachel Griffiths are the leads in this tale, playing the sisters Du Pré. Both musicians from childhood, Hilary (Griffiths) is renowned flutist while Jacqueline (Watson) is easily as skilled on the cello. The two comepete for and win many honors as children and as young women, both together and seperate. Their careers as musicians surpass and then bow to one another's, and although their lives take very different paths -- Hilary settles down to marriage and a family while Rachel skips across the globe on a world concert tour -- they are bound together by their deep family love.
The performances of Watson and Griffiths are excellent in style and substance. They contrast each other wonderfully, and while Watson's portrayal of Jackie Du Pré is at times eccentric and perhaps unsympathetic, both characters are charged with emotion. The Frank Cottrell Boyce script, which is based on the book by Hilary and Piers Du Pré, brings a great deal of depth to these characters as well.
The script is also to be commended for its well-polished dual nature. Cottrell Boyce adopts a style which one might expect to see in a mystery thriller (which was in fact the case with David Koepp and Robert Towne's script for MISSION: IMPOSSIBLE) and skillfully molds it to the story of two rising musicians on different courses to fame. The latter parts of his script are controversial to say the least, and the rather depressing ending takes the wind out of an envigorating story. Some will leave this movie feeling disenfranchised. Nevertheless, HILARY AND JACKIE is a movie that reaches an upper echelon of contemporary cinema through solid fundamentals and is a decent watch all around.
all contents © 1999 Craig Roush
-- Craig Roush<EMAIL_ADDRESS>-- Kinnopio's Movie Reviews http://www.execpc.com/~kinnopio
The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).
The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.
Please direct comments/criticisms of the review to relevant newsgroups.
Broken URLs inthe reviews are the responsibility of the author.
The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.
Related links: index of all rec.arts.movies.reviews reviews


{% extends 'base.html' %} {% block article %}
Created by {{ task.created_by }} on {{ task.created }}
{{ task.description|safe }}
{% if attachments %}There doesn't seem to be anything here! Change that by writing a comment!
{% endif %} {% endblock %}


Order big and get free Delivery
You look great today. And I wish you best of luck for tommorow.
Pulse will feel better than pulse toffee.
Fresher then mini mart's.
OK don't need a tag.
Sorry not the hair oil:) oil for cooking only.
Non Alcoholic :-) for other type you are under age.
Believe us-
We DON'T supply to -
either Dh1 or Dh2.
And will supply far more -
better than them :).
Fresh and juicy. An apple a day keeps max away
Bigger and not spoiled.
I know its costly.
Mango are sweet and its great.
big and juicy and gr8.


In this document, we will create a sample phonebook application based on ASP.NET Zero (ASP.NET Core & Angular 5 version) step by step. After all steps, we will have a multi-tenant, localized, authorized, configurable, testable... appliaction.
We're creating and downloading the solution named "Acme.PhoneBookDemo" as described in Getting Started document. Please follow the getting started document, run the application, login as default tenant admin (select Default as tenancy name, use admin as username and 123qwe as password) and see the dashboard below:
Logout from the application for now. We will make our application single-tenant (we will convert it to multi-tenant later). So, we open PhoneBookDemoConsts class in the Acme.PhoneBookDemo.Core project and disable multi-tenancy as shown below:
public class PhoneBookDemoConsts
{
public const string LocalizationSourceName = "PhoneBookDemo";
public const string ConnectionStringName = "Default";
public const bool MultiTenancyEnabled = false;
public const int PaymentCacheDurationInMinutes = 30;
}
Note: If you loggeg in before changing MultiTenancyEnabled to false, you might be get login error. To overcome this, you should remove cookies.
Let's begin from UI and create a new page named "Phone book".
Open src\app\shared\layout\nav\app-navigation.service.ts in the client side (Acme.PhoneBookDemo.AngularUI) which defines menu items in the application. Create new menu item as shown below (You can add it right after the dashboard menu item).
new AppMenuItem("PhoneBook", null, "flaticon-book", "/app/main/phonebook")
PhoneBook is the menu name (will localize below), null is for permission name (will set it later), flaticon-book is just an arbitrary icon class (from this set) and /phonebook is the angular route.
If you run the application, you can see a new menu item on the left menu, but it won't work (it redirect to default route) if you click to the menu item, since we haven't defined the Angular route yet.
Localization strings are defined in XML files in .Core project (in server side) as shown below:
Open PhoneBookDemo.xml (the default, English localization dictionary) and add the following line:
<text name="PhoneBook">Phone Book</text>
If we don't define "PhoneBook"s value for other localization dictionaries, default value is shown in all languages. For example, we can define it also for Turkish in PhoneBookDmo-tr.xml file:
<text name="PhoneBook">Telefon Rehberi</text>
Note: Any change in server side (including change localization texts) requires recycle of the server application. We suggest to use Ctrl+F5 if you don't need to debugging for a faster startup. In that case, it's enough to make a re-build to recycle the application.
Angular has a powerful URL routing system. ASP.NET Zero has defined routes in a few places (for modularity, see dev guide). We want to add phone book page to the main module. So, open src\app\main\main-routing.module.ts (in the client side) and add a new route just below to the dashboard:
{ path: 'phonebook', component: PhoneBookComponent }
We get an error since we haven't defined PhoneBookComponent yet. Also, we ignored permission for now (will implement later).
Create a phonebook folder inside src/app/main folder and add a new typescript file (phonebook.component.ts) in the phonebook folder as shown below:
import { Component, Injector } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { appModuleAnimation } from '@shared/animations/routerTransition';
@Component({
templateUrl: './phonebook.component.html',
animations: [appModuleAnimation()]
})
export class PhoneBookComponent extends AppComponentBase {
constructor(
injector: Injector
) {
super(injector);
}
}
We inherited from AppComponentBase which provides some common functions and fields (like localization and access control) for us. It's not required, but makes our job easier. Now, we can return back to main-routing.module.ts and add import statement for the newly created PhoneBookComponent class:
import { PhoneBookComponent } from './phonebook/phonebook.component';
As we declared in phonebook.component.ts we should create a phonebook.component.html view in the same phonebook folder:

{{l("PhoneBook")}}

PHONE BOOK CONTENT COMES HERE!

l (lower case 'L') function comes from AppComponentBase and used to easily localize texts. [@routerTransition] attribute is required for page transition animation.
And finally, Angular requires to relate every component to a module. So, we are making the following changes on the main.module.ts:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DashboardComponent } from './dashboard/dashboard.component';
import { PhoneBookComponent } from './phonebook/phonebook.component';
import { ModalModule, TabsModule, TooltipModule } from 'ngx-bootstrap';
import { AppCommonModule } from<EMAIL_ADDRESS>import { UtilsModule } from<EMAIL_ADDRESS>import { MainRoutingModule } from './main-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
ModalModule,
TabsModule,
TooltipModule,
AppCommonModule,
UtilsModule,
MainRoutingModule
],
declarations: [
DashboardComponent,
PhoneBookComponent
]
})
export class MainModule { }
Added an import statement and added PhoneBookComponent to declarations array. Now, we can refresh the page to see the new added page:
Note: Angular-cli automatically re-compiles and refreshes the page when any changes made to any file in the application.
We define entities in .Core (domain) project (in server side). We can define a Person entity (mapped to PbPersons table in database) to represent a person in phone book as shown below (I created in in a new folder/namespace named PhoneBook):
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Abp.Domain.Entities.Auditing;
namespace Acme.PhoneBookDemo.PhoneBook
{
[Table("PbPersons")]
public class Person : FullAuditedEntity
{
public const int MaxNameLength = 32;
public const int MaxSurnameLength = 32;
public const int MaxEmailAddressLength = 255;
[Required]
[MaxLength(MaxNameLength)]
public virtual string Name { get; set; }
[Required]
[MaxLength(MaxSurnameLength)]
public virtual string Surname { get; set; }
[MaxLength(MaxEmailAddressLength)]
public virtual string EmailAddress { get; set; }
}
}
Person's primary key type is int (as default). It inherits FullAuditedEntity that contains creation, modification and deletion audit properties. It's also soft-delete. When we delete a person, it's not deleted by database but marked as deleted (see entity and data filters documentations for more information). We created consts for MaxLength properties. This is a good practice since we will use same values later.
We add a DbSet property for Person entity to PhoneBookDemoDbContext class defined in .EntityFrameworkCore project.
public class PhoneBookDemoDbContext : AbpZeroDbContext<Tenant, Role, User>
{
public virtual DbSet<Person> Persons { get; set; }
//...other code
}
We use EntityFramework Code-First migrations to migrate database schema. Since we added Person entitiy, our DbContext model is changed. So, we should create a new migration to create the new table in the database.
Open Package Manager Console, run the Add-Migration "Added_Persons_Table" command as shown below:
This command will add a migration class named "Added_Persons_Table" as shown below:
public partial class Added_Persons_Table : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PbPersons",
columns: table => new
{
Id = table.Column(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
CreationTime = table.Column(nullable: false),
CreatorUserId = table.Column(nullable: true),
DeleterUserId = table.Column(nullable: true),
DeletionTime = table.Column(nullable: true),
EmailAddress = table.Column(maxLength: 255, nullable: true),
IsDeleted = table.Column(nullable: false),
LastModificationTime = table.Column(nullable: true),
LastModifierUserId = table.Column(nullable: true),
Name = table.Column(maxLength: 32, nullable: false),
Surname = table.Column(maxLength: 32, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PbPersons", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PbPersons");
}
}
We don't have to know so much about format and rules of this file. But, it's suggested to have a basic understanding of migrations. In the same Package Manager Console, we write Update-Database command in order to apply the new migration to database. After updating, we can see that PbPersons table is added to database.
But this new table is empty. In ASP.NET Zero, there are some classes to fill initial data for users and settings:
So, we can add a seperated class to fill some people to database as shown below:
namespace Acme.PhoneBookDemo.Migrations.Seed.Host
{
public class InitialPeopleCreator
{
private readonly PhoneBookDemoDbContext _context;
public InitialPeopleCreator(PhoneBookDemoDbContext context)
{
_context = context;
}
public void Create()
{
var douglas = _context.Persons.FirstOrDefault(p => p.EmailAddress == "douglas.adams@fortytwo.com");
if (douglas == null)
{
_context.Persons.Add(
new Person
{
Name = "Douglas",
Surname = "Adams",
EmailAddress =<EMAIL_ADDRESS>});
}
var asimov = _context.Persons.FirstOrDefault(p => p.EmailAddress == "isaac.asimov@foundation.org");
if (asimov == null)
{
_context.Persons.Add(
new Person
{
Name = "Isaac",
Surname = "Asimov",
EmailAddress =<EMAIL_ADDRESS>});
}
}
}
}
These type of default data is good since we can also use these data in unit tests. Surely, we should be careful about seed data since this code will always be executed in each PostInitialize of your PhoneBookEntityFrameworkCoreModule. This class (InitialPeopleCreator) is created and called in InitialHostDbBuilder class. This is not so important, just for a good code organization (see source codes).
public class InitialHostDbBuilder
{
//existing codes...
public void Create()
{
//existing code...
new InitialPeopleCreator(_context).Create();
_context.SaveChanges();
}
}
We run our project again, it runs seed and adds two people to PbPersons table:
An Application Service is used from client (presentation layer) to perform operations (use cases) of the application.
Application service interface and DTOs are located in .Application.Shared project. We creating an application service to get people from server. So, we're first creating an interface to define the person application service (while this interface is optional, we suggest you to create it):
using Abp.Application.Services;
using Abp.Application.Services.Dto;
namespace Acme.PhoneBookDemo.PhoneBook
{
public interface IPersonAppService : IApplicationService
{
ListResultDto<PersonListDto> GetPeople(GetPeopleInput input);
}
}
An application service method gets/returns DTOs. ListResultDto is a pre-build helper DTO to return a list of another DTO. GetPeopleInput is a DTO to pass request parameters to GetPeople method. So, GetPeopleIntput and PersonListDto are defined as shown below:
public class GetPeopleInput
{
public string Filter { get; set; }
}
public class PersonListDto : FullAuditedEntityDto
{
public string Name { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
}
CustomDtoMapper.cs is used to create mapping from Person to PersonListDto. FullAuditedEntityDto used to inherit audit properties automatically. See application service and DTO documentations for more information. We are adding following mappings.
...
//PhoneBook (we will comment out other lines while added new DTOs added)
configuration.CreateMap<Person, PersonListDto>();
//configuration.CreateMap<AddPhoneInput, Phone>();
//configuration.CreateMap<CreatePersonInput, Person>();
//configuration.CreateMap<Person, GetPersonForEditOutput>();
//configuration.CreateMap<Phone, PhoneInPersonListDto>();
After defining interface, we can implement it as shown below: (in .Application project)
public class PersonAppService : PhoneBookDemoAppServiceBase, IPersonAppService
{
private readonly IRepository<Person> _personRepository;
public PersonAppService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
}
public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input)
{
var people = _personRepository
.GetAll()
.WhereIf(
!input.Filter.IsNullOrEmpty(),
p => p.Name.Contains(input.Filter) ||
p.Surname.Contains(input.Filter) ||
p.EmailAddress.Contains(input.Filter)
)
.OrderBy(p => p.Name)
.ThenBy(p => p.Surname)
.ToList();
return new ListResultDto<PersonListDto>(ObjectMapper.Map<List<PersonListDto>>(people));
}
}
We're injecting person repository (it's automatically created by ABP) and using it to filter and get people from database.
WhereIf is an extension method here (defined in Abp.Linq.Extensions namespace). It performs Where condition, only if filter is not null or empty. IsNullOrEmpty is also an extension method (defined in Abp.Extensions namespace). ABP has many similar shortcut extension methods. ObjectMapper.Map method automatically converts list of Person entities to list of PersonListDto entities with using configurations in CustomDtoMapper.cs in .Application project.
We don't manually open database connection or start/commit transactions manually. It's automatically done with ABP framework's Unit Of Work system. See UOW documentation for more.
We don't handle exceptions manually (using a try-catch block). Because ABP framework automatically handles all exceptions on the web layer and returns appropriate error messages to the client. It then handles errors on the client and shows needed error information to the user. See exception handling document for more.
You can skip this section if you don't interest in automated testing.
By writing unit test, we can test PersonAppService.GetPeople method without creating a user interface. We write unit test in .Tests project in the solution.
Since we disabled multitenancy, we should disable it for unit tests too. Open PhoneBookDemoConsts class in the Acme.PhoneBook.Core project and set "MultiTenancyEnabled" to false. After a rebuild and run unit tests, you will see that some tests are skipped (those are related to multitenancy).
Let's create first test to verify getting people without any filter:
using Acme.PhoneBookDemo.People;
using Acme.PhoneBookDemo.People.Dtos;
using Shouldly;
using Xunit;
namespace Acme.PhoneBookDemo.Tests.People
{
public class PersonAppService_Tests : AppTestBase
{
private readonly IPersonAppService _personAppService;
public PersonAppService_Tests()
{
_personAppService = Resolve<IPersonAppService>();
}
[Fact]
public void Should_Get_All_People_Without_Any_Filter()
{
//Act
var persons = _personAppService.GetPeople(new GetPeopleInput());
//Assert
persons.Items.Count.ShouldBe(2);
}
}
}
We derived test class from AppTestBase. AppTestBase class initializes all system, creates an in-memory fake database, seeds initial data (that we created before) to database and logins to application as admin. So, this is actually an integration test since it tests all server-side code from entitiy framework mapping to application services, validation and authorization.
In constructor, we get (resolve) an IPersonAppService from dependency injection container. It creates the PersonAppService class with all dependencies. Then we can use it in test methods.
Since we're using xUnit, we add Fact attribute to each test method. In the test method, we called GetPeople method and checked if there are two people in the returned list as we know that there were 2 people in initial database.
Let's run the all unit tests in Test Explorer and see if it works:
As you see, it worked successfully. Now, we know that PersonAppService works properly without any filter. Let's add a new unit test to get filtered people:
[Fact]
public void Should_Get_People_With_Filter()
{
//Act
var persons = _personAppService.GetPeople(
new GetPeopleInput
{
Filter = "adams"
});
//Assert
persons.Items.Count.ShouldBe(1);
persons.Items[0].Name.ShouldBe("Douglas");
persons.Items[0].Surname.ShouldBe("Adams");
}
Again, since we know initial database, we can check returned results easily. Here, initial test data is important. When we change initial data, our test may fail even if our services are correct. So, it's better to write unit tests independed of initial data as much as possible. We could check incoming data to see if every people contains "adams" in his/her name, surname or email. Thus, if we add new people to initial data, our tests remain working.
There are many techniques on unit testing, I kept it simple here. But ASP.NET Zero template makes very easy to write unit and integration tests by base classes and pre-build test codes.
Now, we can switch to the client side and use GetPeople method to show a list of people on the UI.
First, run (prefer Ctrl+F5 to be faster) the server side application (.Web.Host project). Then run nswag/refresh.bat file on the client side to re-generate service proxy classes (they are used to call server side service methods).
Since we added a new service, we should add it to src/shared/service-proxies/service-proxy.module.ts. Just open it and add ApiServiceProxies.PersonServiceProxy to the providers array. This step is only required when we add a new service. If we change an existing service, it's not needed.
Sometimes angular-cli can not understand the file changes. In that case, stop it and re-run npm start command.
Change phonebook.component.ts as like below:
import { Component, Injector, OnInit } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { PersonServiceProxy, PersonListDto, ListResultDtoOfPersonListDto } from '@shared/service-proxies/service-proxies';
@Component({
templateUrl: './phonebook.component.html',
animations: [appModuleAnimation()]
})
export class PhoneBookComponent extends AppComponentBase implements OnInit {
people: PersonListDto[] = [];
filter: string = '';
constructor(
injector: Injector,
private _personService: PersonServiceProxy
) {
super(injector);
}
ngOnInit(): void {
this.getPeople();
}
getPeople(): void {
this._personService.getPeople(this.filter).subscribe((result) => {
this.people = result.items;
});
}
}
We inject PersonServiceProxy, call it's getPeople method and subscribe to get the result. We do this in ngOnInit function (defined in Angular's OnInit interface). Assigned returned items to the people class member.
Now, we can use this people member from the view, phonebook.component.html:

{{l("PhoneBook")}}

{{l("AllPeople")}}

{{person.name + ' ' + person.surname}}

{{person.emailAddress}}

We simply used ngFor directive to loop and render people data. See the result:
We successfully retrieved list of people from database to the page.
We normally use a javascript based rich table/grid library to show tabular data, instead of manually rendering data like that. For example, we used jTable library to show users on the Users page of ASP.NET Zero. Always use such components since they make things much more easier and provides a much better user experience.
We did not use a table component here, because we want to show basics of Angular instead of going details of a 3rd party library.
Next step is to create a modal to add a new item to phone book.
We first define CreatePerson method in IPersonAppService interface:
Task CreatePerson(CreatePersonInput input);
Then we create CreatePersonInput DTO that defines parameters of the method:
public class CreatePersonInput
{
[Required]
[MaxLength(PersonConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[MaxLength(PersonConsts.MaxSurnameLength)]
public string Surname { get; set; }
[EmailAddress]
[MaxLength(PersonConsts.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
}
CreatePersonInput is mapped to Person entity (comment out related line in CustomDtoMapper.cs and we will use mapping below). All properties are decorated by data annotation attributes to provide automatic validation. Notice that we use same consts defined in PersonConsts.cs in .Core.Shared project for MaxLength properties. After adding this class, you can remove consts from Person entity and use this new consts class.
public class PersonConsts
{
public const int MaxNameLength = 32;
public const int MaxSurnameLength = 32;
public const int MaxEmailAddressLength = 255;
}
Here, the implementation of CreatePerson method:
public async Task CreatePerson(CreatePersonInput input)
{
var person = ObjectMapper.Map<Person>(input);
await _personRepository.InsertAsync(person);
}
A Person entity is created by mapping given input, then inserted to database. We used async/await pattern here. All methods in ASP.NET Zero startup project is async. It's adviced to use async/await wherever possible.
You can skip this section if you don't interest in automated testing.
We can create a unit test method to test CreatePerson method as shown below:
[Fact]
public async Task Should_Create_Person_With_Valid_Arguments()
{
//Act
await _personAppService.CreatePerson(
new CreatePersonInput
{
Name = "John",
Surname = "Nash",
EmailAddress =<EMAIL_ADDRESS>});
//Assert
UsingDbContext(
context =>
{
var john = context.Persons.FirstOrDefault(p => p.EmailAddress == "john.nash@abeautifulmind.com");
john.ShouldNotBe(null);
john.Name.ShouldBe("John");
});
}
Test method also written using async/await pattern since calling method is async. We called CreatePerson method, then checked if given person is in the database. UsingDbContext method is a helper method of AppTestBase class (which we inherited this unit test class from). It's used to easily get a reference to DbContext and use it directly to perform database operations.
This method successfully works since all required fields are supplied. Let's try to create a test for invalid arguments:
[Fact]
public async Task Should_Not_Create_Person_With_Invalid_Arguments()
{
//Act and Assert
await Assert.ThrowsAsync<AbpValidationException>(
async () =>
{
await _personAppService.CreatePerson(
new CreatePersonInput
{
Name = "John"
});
});
}
We did not set Surname property of CreatePersonInput despite of it's required. So, it throws AbpValidationException automatically. Also, we can not send null to CreatePerson method since validation system also checks it. This test calls CreatePerson with invalid arguments and asserts that it thows AbpValidationException. See validation document for more information.
We will create a Bootstrap Modal to create a new person. ASP.NET Zero uses ngx-bootstrap library to create modals (you can use another library, but we will use it in this sample too). Final modal will be like below:
First of all, we should use nswag/refresh.bat to re-generate service-proxies. This will generate the code that is needed to call PersonAppService.CreatePerson method from client side. Notice that you should rebuild & run the server side application before re-generating the proxy scripts.
We are starting from creating a new component, named create-person-modal.component.ts into client side phonebook folder:
import { Component, ViewChild, Injector, ElementRef, Output, EventEmitter } from '@angular/core';
import { ModalDirective } from 'ngx-bootstrap';
import { PersonServiceProxy, CreatePersonInput } from '@shared/service-proxies/service-proxies';
import { AppComponentBase } from '@shared/common/app-component-base';
@Component({
selector: 'createPersonModal',
templateUrl: './create-person-modal.component.html'
})
export class CreatePersonModalComponent extends AppComponentBase {
@Output() modalSave: EventEmitter<any> = new EventEmitter<any>();
@ViewChild('modal') modal: ModalDirective;
@ViewChild('nameInput') nameInput: ElementRef;
person: CreatePersonInput;
active: boolean = false;
saving: boolean = false;
constructor(
injector: Injector,
private _personService: PersonServiceProxy
) {
super(injector);
}
show(): void {
this.active = true;
this.person = new CreatePersonInput();
this.modal.show();
}
onShown(): void {
$(this.nameInput.nativeElement).focus();
}
save(): void {
this.saving = true;
this._personService.createPerson(this.person)
.finally(() => this.saving = false)
.subscribe(() => {
this.notify.info(this.l('SavedSuccessfully'));
this.close();
this.modalSave.emit(this.person);
});
}
close(): void {
this.modal.hide();
this.active = false;
}
}
Let me explain some parts of this class:
The code is simple and easy to understand except a small hack: an active flag is used to reset validation for angular view (explained in angular's documentation).
As declared in the component, we are creating the create-person-modal.component.html file in the same folder as shown below:

<form *ngIf="active" #personForm="ngForm" novalidate (ngSubmit)="save()">

{{l("CreateNewPerson")}}

<button type="button" class="close" (click)="close()" aria-label="Close">
×
</button>

{{l("Name")}}
<input #nameInput class="form-control" type="text" name="name" [(ngModel)]="person.name" required maxlength="32">

{{l("Surname")}}
<input class="form-control" type="email" name="surname" [(ngModel)]="person.surname" required maxlength="32">

{{l("EmailAddress")}}
<input class="form-control" type="email" name="emailAddress" [(ngModel)]="person.emailAddress" required maxlength="255" pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$">

<button [disabled]="saving" type="button" class="btn btn-secondary" (click)="close()">{{l("Cancel")}}</button>
<button type="submit" class="btn btn-primary" [disabled]="!personForm.form.valid" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot')"> {{l("Save")}}</button>

</form>

Most of this code is similar for all modals. The important part is how we binded model to the view using ngModel directive. As like all components, Angular requires to relate it to a module. We should it to declarations array of main.module.ts as like shown below:
...previous imports
import { CreatePersonModalComponent } from './phonebook/create-person-modal.component';
@NgModule({
imports: [
...existing module imports...
],
declarations: [
DashboardComponent,
PhoneBookComponent,
CreatePersonModalComponent
]
})
export class MainModule { }
We need to put a "Create new person" button to the 'people list page' to open the modal when clicked to the button. To do that, we made the following changes in phonebook.component.html:

{{l("PhoneBook")}}

<button class="btn btn-primary" (click)="createPersonModal.show()"> {{l("CreateNewPerson")}}</button>

{{l("AllPeople")}}

{{person.name + ' ' + person.surname}}

{{person.emailAddress}}

<createPersonModal #createPersonModal (modalSave)="getPeople()"></createPersonModal>

Made some minor changes in the view; Added a button to open the modal and the createPersonModal component as like another HTML tag (which matches to the selector in the create-person-modal.component.ts).
At this point, anyone can enter phone book page since no authorization defined. We will define two permission:
Go to AppAuthorizationProvider class in the server side and add a new permission as shown below (you can add just below the dashboard permission):
pages.CreateChildPermission(AppPermissions.Pages_Tenant_PhoneBook, L("PhoneBook"), multiTenancySides: MultiTenancySides.Tenant);
A permission should have a unique name. We define permission names as constant strings in AppPermissions class. It's a simple constant string:
public const string Pages_Tenant_PhoneBook = "Pages.Tenant.PhoneBook";
Unique name of this permission is "Pages.Tenant.PhoneBook". While you can set any string (as long as it's unique), it's suggested to use that convention. A permission can have a localizable display name: "PhoneBook" here. (see "Adding a New Page" section for more about localization, since it's very similar). Lastly, we set that this is a tenant level permission.
AbpAuthorize attribute can be used as class level or method level to protect an application service or service method from unauthorized users. Since all server side code is located in PersonAppService class, we can declare a class level attribute as shown below:
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook)]
public class PersonAppService : PhoneBookAppServiceBase, IPersonAppService
{
//...
}
Now, let's try to enter Phone Book page by clicking the menu item:
We get an error message. This exception is thrown when any method of PersonAppService is called without required permission.
We got an exception about permission. Server did not send the data but we can still enter the page. To prevent it, open main-routing.module.ts and change the route definition like that:
{ path: 'phonebook', component: PhoneBookComponent, data: { permission: 'Pages.Tenant.PhoneBook' } }
AuthRouteGuard class automatically checks route permission data and prevents entering to the view if specified permission is not granted. Try to click Phone Book menu!
While user can not enter to the page, the menu item still there! We should also hide the Phone book menu item. It's easy, open app-navigation-service.ts and add change PhoneBook menu definition as shown below:
new AppMenuItem("PhoneBook", 'Pages.Tenant.PhoneBook', "flaticon-book", "/app/main/phonebook")
So, how we can enter the page now? Simple, go to Role Management page and edit admin role:
We see that a new permission named "Phone book" added to permissions tab. So, we can check it and save the role. After saving, we need to refresh the whole page to refresh permissions for the current user. We could also grant this permission for a specific user (see development guide document for details about roles and users).
Now, we can enter the Phone book page again.
While a permission for a page is useful and probably always needed, we may want to define additional permissions to perform some specific actions on a page, like creating a new person.
Defining a permission is similar (in the AppAuthorizationProvider class):
var phoneBook = pages.CreateChildPermission(AppPermissions.Pages_Tenant_PhoneBook, L("PhoneBook"), multiTenancySides: MultiTenancySides.Tenant);
phoneBook.CreateChildPermission(AppPermissions.Pages_Tenant_PhoneBook_CreatePerson, L("CreateNewPerson"), multiTenancySides: MultiTenancySides.Tenant);
First permission was defined before. In the second line, we are creating a child permission of first one. Remember to create a constant in AppPermissions class:
public const string Pages_Tenant_PhoneBook_CreatePerson = "Pages.Tenant.PhoneBook.CreatePerson";
This time, we're declaring AbpAuthorize attribute just for CreatePerson method:
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_CreatePerson)]
public async Task CreatePerson(CreatePersonInput input)
{
//...
}
If we run the application and try to create a person, we get an authorization error after clicking the save button. But, it's good to completely hide Create New Person button if we don't have the permission. It's very simple:
Open the phonebook.component.html view and use isGranted condition as shown below:
<button *ngIf="isGranted('Pages.Tenant.PhoneBook.CreatePerson')" class="btn btn-primary" (click)="createPersonModal.show()"> {{l("CreateNewPerson")}}</button>
In this way, the "Create New Person" button does not rendered in server and user can not see this button.
To see the button again, we can go to role or user manager and grant related permission as shown below:
As shown above, Create new person permission is a child permission of the Phone book. Remember to refresh page to get permissions updated.
Let's add a delete button in people list as shown below:
We're starting from UI in this case.
We're changing phonebook.component.html view to add a delete button (related part is shown here):
...

{{l("AllPeople")}}

{{person.name + ' ' + person.surname}}

{{person.emailAddress}}

<button id="deletePerson" (click)="deletePerson(person)" title="{{l('Delete')}}" class="btn btn-outline-danger m-btn m-btn--icon m-btn--icon-only m-btn--pill" href="javascript:;">

</button>

...
We simply added a button which calls deletePerson method (will be defined) when it's clicked. You can define a permission for 'deleting person' as we did for 'creating person' above.
We're using a LESS style here to take the button right. Created a file named phonebook.component.less (in phonebook folder) and added following lines:
.m-widget1__item{
button#deletePerson {
/* styles */
}
}
And adding the style to the phonebook.component.ts Component declaration:
@Component({
templateUrl: './phonebook.component.html',
styleUrls: ['./phonebook.component.less'],
animations: [appModuleAnimation()]
})
Now, we can now the buttons, but they don't work since we haven't defined the deletePerson method yet.
Let's leave the client side and add a DeletePerson method to the server side. We are adding it to the service interface,IPersonAppService::
Task DeletePerson(EntityDto input);
EntityDto is a shortcut of ABP if we only get an id value. Implementation (in PersonAppService) is very simple:
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_DeletePerson)]
public async Task DeletePerson(EntityDto input)
{
await _personRepository.DeleteAsync(input.Id);
}
Since we changed server side services, we should re-generate the client side service proxies via NSwag. Make server side running and use refresh.bat as we did before.
Now, we can add deletePerson method to phonebook.component.ts:
deletePerson(person: PersonListDto): void {
this.message.confirm(
this.l('AreYouSureToDeleteThePerson', person.name),
isConfirmed => {
if (isConfirmed) {
this._personService.deletePerson(person.id).subscribe(() => {
this.notify.info(this.l('SuccessfullyDeleted'));
_.remove(this.people, person);
});
}
}
);
}
It first shows a confirmation message when we click the delete button:
If we click Yes, it simply calls deletePerson method of PersonAppService and shows a notification if operation succeed. Also, removes the person from the person array using lodash library. We also added an import statement before the @Component declaration:
import * as _ from 'lodash';
Now, we will implement search functionality of GetPeople method. UI is shown below:
We added a search input to phonebook.component.html view (showing the related part of the code):

{{l("AllPeople")}} ({{people.length}})

<form autocomplete="off">

<input [(ngModel)]="filterText" name="filter" autoFocus class="form-control m-input" [placeholder]="l('SearchWithThreeDot')" type="text">

<button (click)="getPeople()" class="btn btn-primary" type="submit"></button>

</form>

{{person.name + ' ' + person.surname}}

{{person.emailAddress}}

<button id="deletePerson" (click)="deletePerson(person)" title="{{l('Delete')}}" class="btn btn-outline-danger m-btn m-btn--icon m-btn--icon-only m-btn--pill" href="javascript:;">

</button>

We also added currently filtered people count (people.length) in the header. Since we have already defined and used the filter property in phonebook.component.ts and implemented in the server side, this new code immediately works.
Until now, we did not even mention about phone numbers. It's time to extend our domain to support multiple phone numbers for a person.
Let's start by creating a new Entity, Phone in .Core project:
[Table("PbPhones")]
public class Phone : CreationAuditedEntity<long>
{
public const int MaxNumberLength = 16;
[ForeignKey("PersonId")]
public virtual Person Person { get; set; }
public virtual int PersonId { get; set; }
[Required]
public virtual PhoneType Type { get; set; }
[Required]
[MaxLength(MaxNumberLength)]
public virtual string Number { get; set; }
}
Phone entities are stored in PbPhones table. It's primary key is long and inherits creation auditing properties. It has a reference to Person entity which owns the phone number.
We added a Phones collection to the People:
[Table("PbPersons")]
public class Person : FullAuditedEntity
{
//...other properties
public virtual ICollection<Phone> Phones { get; set; }
}
We have a PhoneType enum as shown below: (in .Application.Shared project)
public enum PhoneType : byte
{
Mobile,
Home,
Business
}
Lastly, we're also adding a DbSet property for Phone to our DbContext:
public virtual DbSet<Phone> Phones { get; set; }
Our entity model has changed, so we need to add a new migration. Run this command in the .EntityFramework project's directory:
This will create a new code based migration file to create PbPhones table:
public partial class Added_Phone : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PbPhones",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
CreationTime = table.Column<DateTime>(nullable: false),
CreatorUserId = table.Column<long>(nullable: true),
Number = table.Column<string>(maxLength: 16, nullable: false),
PersonId = table.Column<int>(nullable: false),
Type = table.Column<byte>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PbPhones", x => x.Id);
table.ForeignKey(
name: "FK_PbPhones_PbPersons_PersonId",
column: x => x.PersonId,
principalTable: "PbPersons",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_PbPhones_PersonId",
table: "PbPhones",
column: "PersonId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PbPhones");
}
}
Before updating database, we can go to database InitialPeopleCreator, rename it to InitialPeopleAndPhoneCreator and add example phone numbers for example people (We renamed InitialPeopleCreator.cs to InitialPeopleAndPhoneCreator.cs):
public class InitialPeopleAndPhoneCreator
{
//...
public void Create()
{
var douglas = _context.Persons.FirstOrDefault(p => p.EmailAddress == "douglas.adams@fortytwo.net");
if (douglas == null)
{
_context.Persons.Add(
new Person
{
Name = "Douglas",
Surname = "Adams",
EmailAddress =<EMAIL_ADDRESS>Phones = new List<Phone>
{
new Phone {Type = PhoneType.Home, Number = "1112242"},
new Phone {Type = PhoneType.Mobile, Number = "2223342"}
}
});
}
var asimov = _context.Persons.FirstOrDefault(p => p.EmailAddress == "isaac.asimov@foundation.org");
if (asimov == null)
{
_context.Persons.Add(
new Person
{
Name = "Isaac",
Surname = "Asimov",
EmailAddress =<EMAIL_ADDRESS>Phones = new List<Phone>
{
new Phone {Type = PhoneType.Home, Number = "8889977"}
}
});
}
}
}
We added two phone number to Douglas, one phone number to Isaac. But if we run our application now, phones are not inserted since this seed code checks if people exists, and does not insert if it's already exists. What's solution? Since we haven't deployed yet, we can delete database (or at lease truncate people table) and re-create it. I did this.
Now, we are running our application to re-create database and seed it. You can check database to see PbPhones table and rows.
We're changing PersonAppService.GetPeople method to include phone numbers of people into return value.
First, we're changing PersonListDto to contain a list of phones:
public class PersonListDto : FullAuditedEntityDto
{
public string Name { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
public Collection<PhoneInPersonListDto> Phones { get; set; }
}
public class PhoneInPersonListDto : CreationAuditedEntityDto<long>
{
public PhoneType Type { get; set; }
public string Number { get; set; }
}
So, added also a DTO to transfer phone numbers and mapped from Phone entity. Now, we can change GetPeople method to get Phones from database:
public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input)
{
var persons = _personRepository
.GetAll()
.Include(p => p.Phones)
.WhereIf(
!input.Filter.IsNullOrEmpty(),
p => p.Name.Contains(input.Filter) ||
p.Surname.Contains(input.Filter) ||
p.EmailAddress.Contains(input.Filter)
)
.OrderBy(p => p.Name)
.ThenBy(p => p.Surname)
.ToList();
return new ListResultDto<PersonListDto>(persons.MapTo<List<PersonListDto>>());
}
We only added Include extension method to the query. Rest of the codes remains same. Furthermore, it would work without adding this, but much slower (since it will lazy load phone numbers for every person seperately).
We are adding two more methods to IPersonAppService interface as shown below:
Task DeletePhone(EntityDto<long> input);
Task<PhoneInPersonListDto> AddPhone(AddPhoneInput input);
We could create a new, seperated IPhoneAppService. It's your choice. But, we can consider Person as an aggregate and add phone related methods here. AddPhoneInput DTO is shown below:
public class AddPhoneInput
{
[Range(1, int.MaxValue)]
public int PersonId { get; set; }
[Required]
public PhoneType Type { get; set; }
[Required]
[MaxLength(PhoneConsts.MaxNumberLength)]
public string Number { get; set; }
}
We used PhoneConsts.MaxNumberLength for Number field. You should create this consts in .Core.Shared.
public class PhoneConsts
{
public const int MaxNumberLength = 16;
}
Now, we can implement these methods:
public async Task DeletePhone(EntityDto<long> input)
{
await _phoneRepository.DeleteAsync(input.Id);
}
public async Task<PhoneInPersonListDto> AddPhone(AddPhoneInput input)
{
var person = _personRepository.Get(input.PersonId);
await _personRepository.EnsureCollectionLoadedAsync(person, p => p.Phones);
var phone = ObjectMapper.Map<Phone>(input);
person.Phones.Add(phone);
//Get auto increment Id of the new Phone by saving to database
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<PhoneInPersonListDto>(phone);
}
(Note: We injected IRepository<Phone, long> in the constructor and set to _personRepository field, as similar to _personRepository)
DeletePhone method is simple. It only deletes phone with given id.
AddPhone method gets the person from database and add new phone to person.Phones collection. Then is save changes. Saving changes causes inserting new added phone to database and get it's Id. Because, we are returning a DTO that contains newly created phone informations including Id. So, it should be assigned before mapping in the last line. (Notice that; normally it's not needed to call CurrentUnitOfWork.SaveChangesAsync. It's automatically called at the end of the method. We called it in the method since we need to save entity and get it's Id immediately. See UOW document for more.)
There may be different approaches for AddPhone method. You can directly work with a phone repository to insert new phone. They all have different pros and cons. It's your choice.
Final UI is shown below:
When we click the edit icon for a person, it's row is expanded and all phone numbers are shown. Then we can delete any phone by clicking the icon at left. We can add a new phone from the inputs at last line.
Changes in view are shown below:

{{person.name + ' ' + person.surname}}

{{person.emailAddress}}

<button (click)="editPerson(person)" title="{{l('Edit')}}" class="btn btn-outline-primary m-btn m-btn--icon m-btn--icon-only m-btn--pill">

</button>
<button id="deletePerson" (click)="deletePerson(person)" title="{{l('Delete')}}" class="btn btn-outline-danger m-btn m-btn--icon m-btn--icon-only m-btn--pill" href="javascript:;">

</button>

{{l("Type")}}
{{l("PhoneNumber")}}

<button (click)="deletePhone(phone, person)" class="btn btn-outline-danger m-btn m-btn--icon m-btn--icon-only m-btn--pill">

</button>

{{getPhoneTypeAsString(phone.type)}}
{{phone.number}}

<button (click)="savePhone()" class="btn btn-sm btn-success">

</button>

<select name="Type" [(ngModel)]="newPhone.type"class="form-control">
<option value="0">{{l("PhoneType_Mobile")}}</option>
<option value="1">{{l("PhoneType_Home")}}</option>
<option value="2">{{l("PhoneType_Business")}}</option>
</select>

<input type="text" name="number" [(ngModel)]="newPhone.number" class="form-control" />

We added an edit button for each person. Then added a table for each person that shows phones of the related person and allows adding a new phone. Phones table is only shown if we click the edit button.
Before changing PhoneBookComponent, we should re-generate service-proxies using nswag as did above. And finally we can change PhoneBookComponent as shown below:
import { Component, Injector, OnInit } from '@angular/core';
import { AppComponentBase } from '@shared/common/app-component-base';
import { appModuleAnimation } from '@shared/animations/routerTransition';
import { PersonServiceProxy, PersonListDto, ListResultDtoOfPersonListDto, PhoneInPersonListDto, AddPhoneInput, AddPhoneInputType, PhoneInPersonListDtoType } from '@shared/service-proxies/service-proxies';
import * as _ from 'lodash';
@Component({
templateUrl: './phonebook.component.html',
styleUrls: ['./phonebook.component.less'],
animations: [appModuleAnimation()]
})
export class PhoneBookComponent extends AppComponentBase implements OnInit {
people: PersonListDto[] = [];
filter: string = '';
editingPerson: PersonListDto = null;
newPhone: AddPhoneInput = null;
constructor(
injector: Injector,
private _personService: PersonServiceProxy
) {
super(injector);
}
ngOnInit(): void {
this.getPeople();
}
getPeople(): void {
this._personService.getPeople(this.filter).subscribe((result) => {
this.people = result.items;
});
}
deletePerson(person: PersonListDto): void {
this.message.confirm(
this.l('AreYouSureToDeleteThePerson', person.name),
isConfirmed => {
if (isConfirmed) {
this._personService.deletePerson(person.id).subscribe(() => {
this.notify.info(this.l('SuccessfullyDeleted'));
_.remove(this.people, person);
});
}
}
);
}
editPerson(person: PersonListDto): void {
if (person === this.editingPerson) {
this.editingPerson = null;
} else {
this.editingPerson = person;
this.newPhone = new AddPhoneInput();
this.newPhone.type = AddPhoneInputType._0;
this.newPhone.personId = person.id;
}
};
getPhoneTypeAsString(phoneType: PhoneInPersonListDtoType): string {
switch (phoneType) {
case PhoneInPersonListDtoType._0:
return this.l('PhoneType_Mobile');
case PhoneInPersonListDtoType._1:
return this.l('PhoneType_Home');
case PhoneInPersonListDtoType._2:
return this.l('PhoneType_Business');
default:
return '?';
}
};
deletePhone(phone, person): void {
this._personService.deletePhone(phone.id).subscribe(() => {
this.notify.success(this.l('SuccessfullyDeleted'));
_.remove(person.phones, phone);
});
};
savePhone(): void {
if (!this.newPhone.number) {
this.message.warn('Please enter a number!');
return;
}
this._personService.addPhone(this.newPhone).subscribe(result => {
this.editingPerson.phones.push(result);
this.newPhone.number = '';
this.notify.success(this.l('SavedSuccessfully'));
});
};
}
Now we want to edit name, surname and e-mail of people:
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_EditPerson)]
public async Task<GetPersonForEditOutput> GetPersonForEdit(GetPersonForEditInput input)
{
var person = await _personRepository.GetAsync(input.Id);
return ObjectMapper.Map<GetPersonForEditOutput>(person);
}
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_EditPerson)]
public async Task EditPerson(EditPersonInput input)
{
var person = await _personRepository.GetAsync(input.Id);
person.Name = input.Name;
person.Surname = input.Surname;
person.EmailAddress = input.EmailAddress;
await _personRepository.UpdateAsync(person);
}

<form *ngIf="active" #personForm="ngForm" novalidate (ngSubmit)="save()">

{{l("EditPerson")}}

<button type="button" class="close" (click)="close()" aria-label="Close">
×
</button>

{{l("Name")}}
<input #nameInput class="form-control" type="text" name="name" [(ngModel)]="person.name" required maxlength="32">

{{l("Surname")}}
<input class="form-control" type="email" name="surname" [(ngModel)]="person.surname" required maxlength="32">

{{l("EmailAddress")}}
<input class="form-control" type="email" name="emailAddress" [(ngModel)]="person.emailAddress" required maxlength="255" pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$">

<button [disabled]="saving" type="button" class="btn btn-secondary" (click)="close()">{{l("Cancel")}}</button>
<button type="submit" class="btn btn-primary" [disabled]="!personForm.form.valid" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot')"> {{l("Save")}}</button>

</form>

Add those lines to phonebook.component.html::
<button (click)="editPerson(person)" title="{{l('Edit')}}" class="btn btn-outline-primary m-btn m-btn--icon m-btn--icon-only m-btn--pill">

</button>
<button *ngIf="isGranted('Pages.Tenant.PhoneBook.EditPerson')" (click)="editPersonModal.show(person.id)" title="{{l('EditPerson')}}" class="btn btn-outline-success m-btn m-btn--icon m-btn--icon-only m-btn--pill">

</button>
<button id="deletePerson" (click)="deletePerson(person)" title="{{l('Delete')}}" class="btn btn-outline-danger m-btn m-btn--icon m-btn--icon-only m-btn--pill" href="javascript:;">

</button>
.
.
.
.
.
<createPersonModal #createPersonModal(modalSave)="getPeople()"></createPersonModal>
<editPersonModal #editPersonModal (modalSave)="getPeople()"></editPersonModal>
import { Component, ViewChild, Injector, ElementRef, Output, EventEmitter } from '@angular/core';
import { ModalDirective } from 'ngx-bootstrap';
import { PersonServiceProxy, EditPersonInput } from '@shared/service-proxies/service-proxies';
import { AppComponentBase } from '@shared/common/app-component-base';
@Component({
selector: 'editPersonModal',
templateUrl: './edit-person-modal.component.html'
})
export class EditPersonModalComponent extends AppComponentBase {
@Output() modalSave: EventEmitter<any> = new EventEmitter<any>();
@ViewChild('modal') modal: ModalDirective;
@ViewChild('nameInput') nameInput: ElementRef;
person: EditPersonInput = new EditPersonInput();
active: boolean = false;
saving: boolean = false;
constructor(
injector: Injector,
private _personService: PersonServiceProxy
) {
super(injector);
}
show(personId): void {
this.active = true;
this._personService.getPersonForEdit(personId).subscribe((result)=> {
this.person = result;
this.modal.show();
});
}
onShown(): void {
// $(this.nameInput.nativeElement).focus();
}
save(): void {
this.saving = true;
this._personService.editPerson(this.person)
.subscribe(() => {
this.notify.info(this.l('SavedSuccessfully'));
this.close();
this.modalSave.emit(this.person);
});
this.saving = false;
}
close(): void {
this.modal.hide();
this.active = false;
}
}
Add those lines to main.module.ts::
import { EditPersonModalComponent } from './phonebook/edit-person-modal.component';
.
.
.
.
.
.
declarations: [
DashboardComponent,
PhoneBookComponent,
CreatePersonModalComponent,
EditPersonModalComponent
]
.
.
We have built a fully functional application until here. Now, we will see how to convert it to a multi-tenant application easily. Logout from the application before any change.
We disabled multi-tenancy at the begginning of this document. Now, re-enabling it in PhoneBookDemoConsts class:
public const bool MultiTenancyEnabled = true;
In a multi-tenant application, a tenant's entities should be isolated by other tenants. For this example project, every tenant should have own phone book with isolated people and phone numbers.
When we implement IMustHaveTenant interface, ABP automatically filters data based on current Tenant, while retrieving entities from database. So, we should declare that Person entity must have a tenant using IMustHaveTenant interface:
public class Person : FullAuditedEntity, IMustHaveTenant
{
public virtual int TenantId { get; set; }
//...other properties
}
We may want to add IMustHaveTenant interface to also Phone entity. This is needed if we directly use phone repository to get phones. In this sample project, it's not needed.
Since entities have changed, we should create a new database migration:
Add-Migration "Implemented_IMustHaveTenant_For_Person"
This command creates a new code-first database migration. The migration class adds an annotation this is needed for automatic filtering. We don't have to know what it is since it's done automatically. It also adds a TenantId column to PbPersons table as shown below:
migrationBuilder.AddColumn<int>(name: "TenantId",table: "PbPersons",nullable: false,defaultValue: 1);
I added defaultValue as 1 to AddColumn options. Thus, current people are automatically assigned to default tenant (default tenant's id is always 1).
Now, we can update the database again:
Update-Database
It's finished! We can test the application. Run the project, login as the host admin (click Change link and clear tenancy name) shown below:
After login, we see the tenant list which only contains a default tenant. We can create a new tenant:
I created a new tenant named trio. Now, tenant list has two tenants:
I can logout and login as trio tenant admin (Change current tenant to trio):
After login, we see that phone book is empty:
It's empty because trio tenant has a completely islolated people list. You can add people here, logout and login as different teants (you can login as default tenant for example). You will see that each tenant has an isolated phone book and can not see other's people.
In this document, we built a complete example that covers most parts of the ASP.NET Zero system. We hope that it will help you to build your own application.
We intentionally used different approaches for similar tasks to show you different styles of development. ASP.NET Zero provides an architecture but does not restrict you. You can make your own style development.
You should purchase ASP.NET Zero in order to get source code. After purchasing, you can get the sample project from private Github repository: https://github.com/aspnetzero/aspnet-zero-samples


Amazon-Price-Tracker
%%NOTE%%
Add a new article to your watchlist
Add to watchlist
Price trend
Alphabetical: A-Z
Alphabetical: Z-A
Price: Low to High
Price: High to Low
Price difference (last update - today)
">
%%INNERCONTAINER%%


Abort the active operation such that the output does not become manifest.
Specifically, if supported on an output stream, a successful abort() MUST guarantee that the stream will not be made visible in the close() operation.
@InterfaceAudience.Public @InterfaceStability.Unstable public interface Abortable { /** * Abort the active operation without the output becoming visible. * * This is to provide ability to cancel the write on stream; once * a stream is aborted, the write MUST NOT become visible. * * @throws UnsupportedOperationException if the operation is not supported. * @return the result. */ AbortableResult abort(); /** * Interface for the result of aborts; allows subclasses to extend * (IOStatistics etc) or for future enhancements if ever needed. */ interface AbortableResult { /** * Was the stream already closed/aborted? * @return true if a close/abort operation had already * taken place. */ boolean alreadyClosed(); /** * Any exception caught during cleanup operations, * exceptions whose raising/catching does not change * the semantics of the abort. * @return an exception or null. */ IOException anyCleanupException(); } }
Aborts the ongoing operation such that no output SHALL become visible when the operation is completed.
Unless and until other File System classes implement Abortable, the interface is specified purely for output streams.
Abortable.abort() MUST only be supported on output streams whose output is only made visible when close() is called, for example. output streams returned by the S3A FileSystem.
The stream MUST implement Abortable and StreamCapabilities.
if unsupported: throw UnsupportedException if not isOpen(stream): no-op StreamCapabilities.hasCapability("fs.capability.outputstream.abortable") == True
After abort() returns, the filesystem MUST be unchanged:
FS' = FS
A successful abort() operation MUST guarantee that when the streamclose() is invoked no output shall be manifest.
Strictly then:
if Abortable.abort() does not raise UnsupportedOperationException then returns, then it guarantees that the write SHALL NOT become visible and that any existing data in the filesystem at the destination path SHALL continue to be available.
That is, the postconditions of close() becomes:
FS' = FS
An application MUST be able to verify that a stream supports the Abortable.abort() operation without actually calling it. This is done through the StreamCapabilities interface.
If a stream instance supports Abortable then it MUST return true in the probe hasCapability("fs.capability.outputstream.abortable")
If a stream instance does not support Abortable then it MUST return false in the probe hasCapability("fs.capability.outputstream.abortable")
That is: if a stream declares its support for the feature, a call to abort() SHALL meet the defined semantics of the operation.
FileSystem/FileContext implementations SHOULD declare support similarly, to allow for applications to probe for the feature in the destination directory/path.
If a filesystem supports Abortable under a path P then it SHOULD return true to PathCababilities.hasPathCapability(path, "fs.capability.outputstream.abortable") This is to allow applications to verify that the store supports the feature.
If a filesystem does not support Abortable under a path P then it MUST return false to PathCababilities.hasPathCapability(path, "fs.capability.outputstream.abortable")


Thank you for riding with Lyft!
Thanks for riding with Damon!
September 21, 2017 at 6:54 PM
Ride Details
Lyft fare (2.62mi, 13m 10s)
$11.87
Apple Pay (MasterCard)
$11.87
Make expensing business rides easy
Enable business profile on Lyft to make expensing rides quick and easy.
Get Business Profile
Pricing FAQ · Help Center
Receipt #qwertyuiopasdfghjklzxcvbnm
Map data © OpenStreetMap contributors
© Lyft 2017
548 Market St #68514
Work at Lyft
San Francisco, CA 94104
Become a Driver


Bulls stun Quins, Kabras, KCB stay unbeaten
by Capital SportNAIROBI, Kenya, Nov 10 – The Kenya Harlequins vs Western Bulls match seemed to be a one sided fixture from the onset with Quins being the clear favourites to claim the victory at the end of match day three.
Echulusi however showed up to this encounter in search of a win that they clearly worked and earned in the last 40 minutes of the match beating Quins 21-19.
Iddris Kakai was first on the scoreboard for the Kenya Harlequins giving them an early 7-0 lead after Eric Shikuku’s conversion sliced the posts.
The electric combination of Patrice Agunda and David Ambunya were next on the score sheet with a try each that were followed by Shikuku converting Agunda’s try and missing Ambunya’s for a 19-0 half time score.
In relentless fashion, Derrick Ashiundu’s hat trick bolted Bulls from a 0-19 deficit to a 21-19 full time score as all conversions were successful in the second half, claiming their first victory of the season.
Kabras Sugar, KCB and Homeboyz are the three sides that have won all their matches so far and are placed first, second and third respectively on the Kenya Cup standings table.
Kabras beat Impala Saracens 25-3 at the Kakamega Showgrounds, KCB handed Nakuru a 13-50 loss in Ruaraka while Homeboyz survived an Oilers scare to secure a 25-20 win at the Jamhuri Showgrounds.
The Blad Bin fans were served to a high scoring match as Kenyatta University’s Blakblad welcomed Nondies to their backyard.
Nondies who were fresh from a painful 17-19 loss to Kisumu last weekend ensured they were the better side as they won that encounter 38-23.
Mwamba registered the highest score of the weekend as they beat Kisumu 65-22 in an entertaining fixture at the Impala Club.
Championship Round Up
The KRU Championship League has Strathmore Leos and Masinde Muliro University of Science and Technology in the first and second positions respectively. Both sides have won their opening three matches, however, Strathmore leos are top with a total of 14 points while MMUST has 13 points.
The Leos crushed Catholic Monks 40-10 in Madaraka while MMUST registered a staggering 38 points across Eldoret RFC’s try line and they also managed to keep a clean sheet at that.
The Coastal fixture pitting South Coast Pirates against Mombasa saw the Pirates walk away with a comfortable and hard earned 36-3 win. Mean Machine picked up their first win of the season when they beat the Northern Suburbs Cubs 36-3 at the University of Nairobi grounds.
In Nakuru County, Egerton Wasps also secured their first win of the season with a 21-15 score against third placed University of Eldoret. USIU RFC were awarded a walk over after their opponents Kisii RFC failed to honour the fixture.
-Courtesy KRU
(Visited 13 times, 1 visits today)


Your browser doesn't support canvas
Your browser doesn't support canvas
Your browser doesn't support canvas


I earned my B.F.A. in design from Howard University in DC and my M.A. in International Studies from UCLA. After traveling around the world, I settled in greater Seattle five years ago.
I was raised in FlatLand, IL, and I've lived in Washington for six years, having fallen in love with the area as a child. I volunteer at Woodland Park Zoo and enjoy spending time with my cats. I read lots of books, and also play video games. It's all really quite banal.
I'm from Shaw Island and have lived in the Pacific Northwest most of my years. I love to learn; anything from spinning wool to coding. My B.A is in humanities and I love to read and study people, history and the evolution of ideas.


The goal of sjosmooth is to provide kernel smoothed estimates for time to event data.
You can install the released version of sjosmooth from GitHub with:
remotes::install_github("ddsjoberg/sjosmooth")
The sjosmooth package has two primary functions. The first calculates kernel-weighted regression models (sm_regression
). The user specifies a regression model and a variable for weighting, and sm_regression
will estimate a weighted regression model for each unique value of the specified variable.
The second function (sm_predict
) calculates kernel-weighted predictions from regression models (i.e. outcomes that can be calculated from the predict
function).
The package was written generally, such that any regression model should be compatible. However, only survival::coxph()
, and survival::survreg()
, stats::lm()
, and stats::glm()
models were tested.