diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f544f4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,90 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+.nyc_output
+coverage.*
+
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless
+
+# FuseBox cache
+.fusebox/
+
+# Ignores compiled TypeScript files
+*.js
+*.map
+
+
+Migrations/
+
+obj/
+
+bin/
+
+.vscode/
+*.js
diff --git a/.vs/Ang_form/v15/.suo b/.vs/Ang_form/v15/.suo
new file mode 100644
index 0000000..f6e8c20
Binary files /dev/null and b/.vs/Ang_form/v15/.suo differ
diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json
new file mode 100644
index 0000000..f8b4888
--- /dev/null
+++ b/.vs/ProjectSettings.json
@@ -0,0 +1,3 @@
+{
+ "CurrentProjectSetting": null
+}
\ No newline at end of file
diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json
new file mode 100644
index 0000000..6b61141
--- /dev/null
+++ b/.vs/VSWorkspaceState.json
@@ -0,0 +1,6 @@
+{
+ "ExpandedNodes": [
+ ""
+ ],
+ "PreviewInSolutionExplorer": false
+}
\ No newline at end of file
diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite
new file mode 100644
index 0000000..677a2f5
Binary files /dev/null and b/.vs/slnx.sqlite differ
diff --git a/Ang_form.csproj b/Ang_form.csproj
new file mode 100644
index 0000000..04db7b8
--- /dev/null
+++ b/Ang_form.csproj
@@ -0,0 +1,36 @@
+
+
+
+ netcoreapp2.1
+ Latest
+ wwwroot\favicon.ico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ lib\SRVTextToImage.dll
+
+
+
+
+
+
+
+
+
+
diff --git a/Ang_form.csproj.user b/Ang_form.csproj.user
new file mode 100644
index 0000000..8a2647f
--- /dev/null
+++ b/Ang_form.csproj.user
@@ -0,0 +1,22 @@
+
+
+
+ ApiControllerWithActionsScaffolder
+ root/Common/Api
+ 600
+ 600
+ True
+ False
+ True
+
+ False
+ <_SelectedScaffolderID>ApiControllerWithContextScaffolder
+ <_SelectedScaffolderCategoryPath>root/Common/Api
+ 600
+ Ang_form.Models.ApplicationContext
+ IIS Express
+
+
+ ProjectDebugger
+
+
\ No newline at end of file
diff --git a/App_Data/ang_formDB.ldf b/App_Data/ang_formDB.ldf
new file mode 100644
index 0000000..ecf333a
Binary files /dev/null and b/App_Data/ang_formDB.ldf differ
diff --git a/App_Data/ang_formDB.mdf b/App_Data/ang_formDB.mdf
new file mode 100644
index 0000000..208a293
Binary files /dev/null and b/App_Data/ang_formDB.mdf differ
diff --git a/ClientApp/app/app.component.html b/ClientApp/app/app.component.html
new file mode 100644
index 0000000..d936d63
--- /dev/null
+++ b/ClientApp/app/app.component.html
@@ -0,0 +1,84 @@
+
+
+
+
+ {{this.data | json}}
+
\ No newline at end of file
diff --git a/ClientApp/app/app.component.less b/ClientApp/app/app.component.less
new file mode 100644
index 0000000..5334fc1
--- /dev/null
+++ b/ClientApp/app/app.component.less
@@ -0,0 +1,130 @@
+@entry-field-border: 5px;
+@entry-field-height: 30px;
+
+@lcol-width: 100px;
+@rcol-width: 300px;
+
+.form {
+ text-align: left;
+ width: 430px;
+ margin: 0 auto;
+ padding: 15px 0 30px 0;
+ border-radius: 10px;
+ background-color: #f8f8f8;
+ box-shadow: 3px 3px 2px gray;
+}
+
+.header {
+ height: 36px;
+ position: relative;
+ right: 12px;
+ background-image: url("./pic/form-title.svg");
+ -webkit-filter: drop-shadow( 3px 3px 2px rgba(0, 0, 0, .7));
+ filter: drop-shadow( 5px 5px 2px rgba(59, 59, 59, 0.5););
+}
+.header.second{
+
+ height: 12px;
+ background-image: url("./pic/form-title2.svg");
+ z-index: -1;
+}
+
+p{
+ margin-top: 0px;
+ text-indent: 20px;
+ color: white;
+ font-size: 22pt;
+}
+
+.field {
+ margin: 0 15px 0 15px;
+}
+
+.field>button {
+ background-image: linear-gradient(to top, #f8f8f8 50%, #e5e5e5 50%);
+ margin-left: @lcol-width;
+ width: 140px;
+}
+
+
+.entry-field {
+ display: inline-block;
+ vertical-align: middle;
+ color: rgb(63, 63, 63);
+
+ width: @rcol-width;
+ height: @entry-field-height;
+ border: 1px inset;
+ border-radius: @entry-field-border;
+}
+
+textarea.entry-field {
+ height: 75px;
+ resize: vertical;
+}
+#captcha.entry-field {
+ width: 120px;
+ padding-left: 0px;
+}
+
+.captcha-img{
+ margin: 0 10px 0 10px;
+ display: inline-block;
+ vertical-align: middle;
+ width: 140px;
+ height: 30px;
+ border: 2px solid orange;
+ background-image: url("/api/captcha");
+}
+
+.error-field {
+ padding-left: @lcol-width;
+ height: 1em;
+}
+
+.error {
+ color: red;
+}
+
+.ng-touched.ng-invalid{
+ border-color: red;
+ }
+
+input.entry-field {
+ padding-left: 30px;
+ background-repeat: no-repeat;
+ background-size: 20px;
+ background-position: 5px;
+}
+
+#phone {
+ background-image: url("./pic/phone.svg");
+
+}
+
+#email {
+ background-image: url("./pic/mail.svg");
+}
+
+#name {
+ background-image: url("./pic/person.svg");
+}
+
+label {
+ display: inline-block;
+ vertical-align: middle;
+ width: @lcol-width;
+ font-size: 11pt;
+ font-weight: bold;
+ text-align: left;
+ color: darkslategray;
+ //text-shadow: 1px 1px 1px gray;
+}
+
+input,
+select,
+textarea {
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
\ No newline at end of file
diff --git a/ClientApp/app/app.component.ts b/ClientApp/app/app.component.ts
new file mode 100644
index 0000000..e8a3280
--- /dev/null
+++ b/ClientApp/app/app.component.ts
@@ -0,0 +1,141 @@
+import {
+ Component,
+ OnInit
+} from "@angular/core";
+import {DataService} from "../data.services";
+import {Contact, Message, Subject} from "./app.models";
+
+import {
+ FormGroup,
+ FormBuilder,
+ Validators,
+ AsyncValidatorFn,
+ AbstractControl,
+ ValidationErrors
+} from "@angular/forms";
+import {Observable} from 'rxjs';
+import {map} from 'rxjs/operators';
+
+@Component({
+ selector: "app",
+ templateUrl: "./app.component.html",
+ providers: [DataService],
+ styleUrls: [
+ "./app.component.less",
+ "../style.less"
+ ]
+})
+
+export class AppComponent implements OnInit {
+
+ title = "myapp";
+ subjects: Subject[];
+ myForm: FormGroup;
+ data: string[];
+ contact: Contact;
+
+ constructor(private fb: FormBuilder, private dataService: DataService) {}
+
+
+ isControlInvalid(controlName: string): boolean {
+ const control = this.myForm.controls[controlName];
+ const result = control.invalid && control.touched;
+ return result;
+ }
+
+ onSubmit() {
+ // this.test(this.dataService)(this.myForm.value.captcha).subscribe(data=>console.log(data));
+ const controls = this.myForm.controls;
+ /** Проверяем форму на валидность */
+ if (this.myForm.invalid) {
+ /** Если форма не валидна, то помечаем все контролы как touched*/
+ Object.keys(controls)
+ .forEach(controlName => controls[controlName].markAsTouched());
+ return;
+ } else {
+ let contact = new Contact(this.myForm.value.name, this.myForm.value.email, this.myForm.value.phone);
+ let message = new Message(contact, this.myForm.value.subject.id, this.myForm.value.message);
+
+ this.dataService.sendMessage(message).subscribe((data: string[]) => {
+ console.log("return: : ", data);
+ });
+
+
+ }
+ }
+
+ CaptchaValidator = (service: DataService): AsyncValidatorFn =>
+ (control: AbstractControl): Observable < ValidationErrors | null > =>
+ service.checkCaptcha(control.value).pipe(map((data: boolean) =>
+ data ? null : {
+ captcha: true
+ }
+ ));
+
+
+ /*
+ CaptchaValidator(service: DataService): AsyncValidatorFn {
+ return (control: AbstractControl): Observable < ValidationErrors | null > => {
+
+ return service.checkCaptcha(control.value) .pipe(
+ map((data: boolean) => {
+ console.log("map: ", data);
+ if (control.value != data) return {
+ 'captcha': true
+ };
+ else return null;
+ })
+ );
+ };
+ }
+ */
+ /*
+ CaptchaValidator = (service: DataService): AsyncValidatorFn => {
+ return (control: AbstractControl): Promise < ValidationErrors | null > | Observable < ValidationErrors | null > => {
+ return service.checkCaptcha(control.value).pipe(
+ switchMap((data:boolean) => (
+ data ?
+ {captcha: true}
+ :
+ null
+ )
+ )
+ );
+ }
+ }*/
+ ngOnInit() {
+ this.myForm = this.fb.group({
+ name: ["", [
+ Validators.required,
+ Validators.pattern(/[А-я|A-z|]$/)
+ ]],
+ email: ["", [
+ Validators.required,
+ Validators.email
+ ]],
+ phone: ["", [
+ Validators.required,
+ Validators.pattern(/^(([0-9]){10})$/)
+ ]],
+ subject: [null, [
+ Validators.required
+ ]],
+ message: [null, [
+ Validators.required
+ ]],
+ captcha: [null, [
+ Validators.required,
+ Validators.minLength(5)
+ ],
+ this.CaptchaValidator(this.dataService)
+ ]
+
+ });
+ //this.dataService.getData().subscribe((data: string) => this.data = [data]);
+ this.dataService.getSubjects().subscribe((subjects: Subject[]) => {
+ this.subjects = subjects;
+ console.log(subjects);
+ this.myForm.get('subject').setValue(subjects[0]);
+ });
+ }
+}
\ No newline at end of file
diff --git a/ClientApp/app/app.models.ts b/ClientApp/app/app.models.ts
new file mode 100644
index 0000000..00d331d
--- /dev/null
+++ b/ClientApp/app/app.models.ts
@@ -0,0 +1,27 @@
+export class Contact {
+ name: string;
+ email: string;
+ phone: number;
+
+ constructor(name: string, email: string, phone: number) {
+ this.name = name;
+ this.email = email;
+ this.phone = phone;
+ }
+}
+export class Message {
+ contact: Contact;
+ subjectId: number;
+ text: string;
+
+ constructor(contact: Contact, subjectId: number, text: string) {
+ this.contact = contact;
+ this.subjectId = subjectId;
+ this.text = text;
+ }
+}
+
+export class Subject {
+ id: number;
+ name: string;
+}
\ No newline at end of file
diff --git a/ClientApp/app/app.module.ts b/ClientApp/app/app.module.ts
new file mode 100644
index 0000000..4de530f
--- /dev/null
+++ b/ClientApp/app/app.module.ts
@@ -0,0 +1,21 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppComponent } from './app.component';
+import { ReactiveFormsModule } from '@angular/forms';
+import { DataService } from "../data.services";
+import { HttpClientModule } from '@angular/common/http';
+import {NgxMaskModule} from 'ngx-mask'
+@NgModule({
+ declarations: [AppComponent],
+ imports: [
+ BrowserModule,
+ ReactiveFormsModule,
+ HttpClientModule,
+ NgxMaskModule.forRoot()
+ ],
+
+ providers: [DataService],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/ClientApp/app/pic/form-title.svg b/ClientApp/app/pic/form-title.svg
new file mode 100644
index 0000000..7d10c16
--- /dev/null
+++ b/ClientApp/app/pic/form-title.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/ClientApp/app/pic/form-title2.svg b/ClientApp/app/pic/form-title2.svg
new file mode 100644
index 0000000..d1bef96
--- /dev/null
+++ b/ClientApp/app/pic/form-title2.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/ClientApp/app/pic/mail.svg b/ClientApp/app/pic/mail.svg
new file mode 100644
index 0000000..9c72c35
--- /dev/null
+++ b/ClientApp/app/pic/mail.svg
@@ -0,0 +1,43 @@
+
+
+
diff --git a/ClientApp/app/pic/person.svg b/ClientApp/app/pic/person.svg
new file mode 100644
index 0000000..c490c94
--- /dev/null
+++ b/ClientApp/app/pic/person.svg
@@ -0,0 +1,78 @@
+
+
+
diff --git a/ClientApp/app/pic/phone.svg b/ClientApp/app/pic/phone.svg
new file mode 100644
index 0000000..8b7b594
--- /dev/null
+++ b/ClientApp/app/pic/phone.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ClientApp/data.services.ts b/ClientApp/data.services.ts
new file mode 100644
index 0000000..75533b5
--- /dev/null
+++ b/ClientApp/data.services.ts
@@ -0,0 +1,37 @@
+import {
+ Injectable
+} from "@angular/core";
+import {
+ HttpClient
+} from "@angular/common/http";
+
+import { Observable, of, from } from "rxjs";
+import { catchError, map, tap } from "rxjs/operators";
+import {Contact, Message, Subject} from "./app/app.models";
+
+@Injectable()
+export class DataService {
+
+ constructor(private http: HttpClient) {}
+
+ getSubjects = () => {
+ return this.http.get("/api/subjects")
+ .pipe(catchError(this.handleError("getSubjects", [])));
+ }
+ sendMessage = (message: Message) => {
+ return this.http.post("/api/messages", message)
+ .pipe(catchError(this.handleError("sendMessage", [])));
+ }
+ checkCaptcha = (captcha: string) :Observable