В .net core в представлениях (Razor) теперь все НЕ английские символы автоматически
конвертируются в соотвествующий хекс код.
и теперь весь русский текст, который берётся из модели, или из переменных и пр. будут
выглядеть как:
Мы не #x441;мо ...
И чтоб получить нормальный текст на русском нужно её значение выводить используя
Html.Raw()
тоесть если я в контроллере присвою переменной
string txt="текст на русском";
и в представлении напишу
Смотрите это: @txt
то в html разметке я увижу:
смотрите это: Мыне#x441;можем
Если же использовать стандартные структуры типа
то из модели весь текст с валидацией на русском автоматом преобразуется и получим
конечно для конечного пользователя ничего не изменится и на страничке он видит правильный
русский текст, однако сам html заметно растет.
Как "обьяснить" .net core, что символы латинские "не опасны" и не нужно их заменять
при выводе вместе с символами "&<> и т.п. ?
В общем сделать так как было раньше...
Ответы
Ответ 1
в файле Startup.cs
в
public void ConfigureServices(IServiceCollection services)
{
....
//Чтобы кирилические символы не переводились в соответствующий Unicode Hex Character Code
services.Configure(options =>
{
options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All);
});
}
Я пишу на C# в Asp.Net MVC. У меня есть различные компоненты: диалоговое окно, прогресс
бар, спойлер и т.д. Я их добавляю с помощью html хелперов, но также мне их часто приходится
добавлять на страницу динамически из JS, .
Мне не нравится то, что я дублирую код с C# в JS. Можно ли это как-то исправить?
Написать такую функцию, которая будет вызываться и из JS и из C#.
Возможно есть какой-нибудь компилятор, который компилит TypeScript одновременно и
в JS и в C#? Или какие-нибудь другие способы?
Ответы
Ответ 1
Единого языка для клиента и сервера в случае ASP.NET MVC нет. Такой бонус на проверенных
временем платформах существует только для JavaScript в форме Node.js (собственно, киллер
фича сей штуковины).
Для .NET есть разной степени сырости компиляторы JS. На данный момент активно развивается
Blazor, полагающийся на Mono и WebAssembly для написания SPA полностью на C#. Но вообще
есть компиляторы для JS на базе DLR и наоборот компиляторы из IL в JS, которые просто
позволят выполнить JS в рантайме .NET или наоборот безо всяких революций.
Вариантов много, сам не пользовался, так что с выбором конкретного компилятора не помогу.
Ответ 2
Можно использовать Microsoft Script Control Library для выполнения Javascript на
сервере. Правда, она на самом деле выполняет JScript, а не Javascript, но суть та же.
Чтобы это работало, группа приложений в IIS должна быть настроена на работу в 32-битном
процессе.
//Reference: COM -> Microsoft Script Control Library
public static string ExecuteJS(string code,string function,params object[] argv)
{
MSScriptControl.ScriptControl sc = new MSScriptControl.ScriptControl();
sc.Language = "JScript";
sc.AddCode(code);
object result = sc.Run(function, argv);
return result.ToString();
}
Использование на сервере:
public const string MyFunction = "function PrintSum(a,b){return a+' + '+b+' = '+(a+b);};";
public static string Func()
{
return ExecuteJS(MyFunction,"PrintSum",1.1,2.2);
}
Использование на клиенте:
Есть простая база данных клиентов. В ней 2 таблицы, в одной из которых клиент с информацией
и 2 таблица с его почтовыми ящиками. Внешний ключ связывает эти таблицы по id клиента.
Есть такой код добавления на бекенде в базу данных:
public async Task Handle(CreateCommand request, CancellationToken cancellationToken)
{
var entity = new Customer
{
CompanyName = request.CompanyName,
Country = request.Country,
City = request.City,
Address = request.Address,
ZipCode = request.ZipCode,
};
_context.Customers.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
foreach (var createCustEmail in request.Emails)
{
_context.CustomerEmails.Add(new CustomerEmail()
{
CustomerId = entity.CustomerId,
Email = createCustEmail.Email,
});
}
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
Вопрос заключается в том,как сделать добавление емейлов на стороне клиента в Razor
Pages?
Ответы
Ответ 1
У меня есть несколько разных солюшенов, которые отрабатывают разные аспекты мультиформ,
вопрос реально достаточно объёмный, поэтому вам рекомендую сделать так же: прежде,
чем делать реальное приложение сделать несколько тестовых, чтобы поиграться и посмотреть
отдельные аспекты.
Во-первых, вы можете начать с приложения в котором у вас модель емейла будет представлять
простую строку:
public class Customer
{
public int Id { get; set; }
public string Title { get; set; }
public string[] Phones { get; set; }
}
Это иногда очень удобно, в этом случае рекомендую посмотреть как хранить подобные
структуры в базе используя Entity Framework.
Мы же будем начинать с двух полноценных моделей (т.е. телефону нужно хранить не только
сам номер, но и допустим тип - рабочий/домашний/факс)
Модель контакта (мысленно можете заменять на Customer):
public class Contact
{
public Contact()
{
this.Phones = new List();
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection Phones { get; set; }
}
Модель телефона (показаны только ключевые поля):
public class Phone
{
public int Id { get; set; }
public int ContactId { get; set; }
public virtual Contact Contact { get; set; }
public string Number { get; set; }
}
Далее тупо создаём CRUD-контроллер, используя scaffolding. Вы наверное многократно
видели подобные заготовки, в них нет ничего сложного, но увы, они не умеют в мультиэлементы.
Что нужно сделать. Во-первых, добавить в форму блок для отображения div'а с телефонными
номерами:
@Html.EditorFor(model => model.LastName, new { htmlAttributes = new {
@class = "form-control" } })
@Html.ValidationMessageFor(model => model.LastName, "", new { @class
= "text-danger" })
@Html.EditorFor(x => x.Phones)
}
@Html.ActionLink("Back to List", "Index")
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Чтобы эта конструкция заработала - нужно добавить папку Views\Contacts\EditorTemplates
и в ней расположить файл Phone.cshtml (который и будет определять как отобразить наш
элемент редактирования:
@model WebApplication1.Models.Phone
Итого, получается что-то типа:
И выглядит как-то так (стилями css не украшал, так как проект учебный, поэтому выглядит
страшненько):
Почитайте букварь по этой теме, чтобы понимать, как это работает: Переопределение
шаблонов отображения и редактирования
В asp.net core принято создавать формы используя тэг-хелперы и аналога для Html.EditorFor
я не знаю, но когда я смешал два стиля и прописал Html.EditorFor - оно сработало. Ну
это просто некрасиво, слишком разнородный стиль.
Но основная проблема связана с тем, что шаблоны DisplayTemplates/EditorTemplates
для razor pages не работают, даже в 2.1 - по крайней мере когда я пробовал и в /Pages
и в /Views/Shared разные варианты не сработал ни один, увы ;(
Поэтому я и показывать стал на классическом asp'е, тем более что и проекты есть готовые ;)
Какие подводные камни вас ещё ждут. Когда вы получаете данные с формы - всё у вас
хорошо, но когда вы начинаете сохранять телефоны при редактировании - вам нужно определять
какие из телефонов были удалены, какие добавлены и какие изменились -- и соответственно,
удалённые - удалить из базы, новые добавить, а остальные изменить.
Это отдельная большая тема, которая выпивает множество крови. Я вас отправлю к вот
этому и этому вопросу, но будьте готовы потратить несколько дней на эту коварную тему,
в этой ветке я просто обозначаю проблему.
При помощи GraphDiff у вас получится сократить код до примерно такого:
// POST: Contacts/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Contact contact)
{
if (!this.ModelState.IsValid)
return this.View(contact);
var entity = db.Contacts.Include(x=>x.Phones).SingleOrDefault(x => x.Id ==
contact.Id);
if (entity == null)
return this.HttpNotFound();
this.db.UpdateGraph(contact, map => map.OwnedCollection(p => p.Phones));
this.db.SaveChanges();
return this.RedirectToAction("Index");
}
А на ванильном c# код будет раза в четыре больше.
Другой момент будет как смапить ваши классы на MediatR, это смотрите сами по конкретному
проекту.
И ещё один момент я упустил - javascript на кнопочках добавления/удаления записей.
Я его в тестовом проекте не делал, да и он различается для ванильного js/jQuery/vue/etc
- поэтому пишите свой по месту.
@User.Identity.Name по сути выдает логин. А как получить тот же Age к примеру?
public class User : IdentityUser
{
public int Age{ get; set; }
public string FIO { get; set; }
public string Addresses { get; set; }
}
Ответы
Ответ 1
По умолчанию используются только Name.
Класс SignInManager регистрирует пользователей в вашем приложении. Этот класс использует
IUserClaimsPrincipalFactory для регистрации claim'ов. Чтобы добавить claim нужно
реализовать свой IUserClaimsPrincipalFactory
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory
{
public AppClaimsPrincipalFactory(
UserManager userManager
, RoleManager roleManager
, IOptions optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{ }
public async override Task CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user);
if (user.Age > 0)
{
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("age", user.Age.ToString())
});
}
return principal;
}
}
Далее регистрируем в классе Startup в методе ConfigureServices после добавления Identity.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
services.AddScoped, AppClaimsPrincipalFactory>();
...
}
В представлении значение Age можно получить так:
@User.Claims.Where(c => c.Type == "age").Select(c => c.Value).SingleOrDefault();
Для удобства можно создать метод расширения:
public static class IdentityExtensions
{
public static string GetAge(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("age");
return (claim != null) ? claim.Value : string.Empty;
}
}
И использовать в представлении так:
@User.Identity.GetAge();
В ASP.NET RAZOR MVC5 существует такой замечательный хелпер как
Html.BeginForm()
Он может записываться как
@Html.BeginForm()
...
@Html.EndForm()
Так и
@using (Html.BeginForm())
{
...
}
Как создать пользовательский хелпер по такому принципу. Например мне нужен блок div,
блок не голый, а в нём какие-то основные элементы, допустим пять ссылок. Мне этот блок
нужно дублировать постоянно, помимо фиксированных пяти ссылок нужно внутрь пихать кучу
других элементов, это может быть кнопка/таблица/картинка/и т.д. Так вот, как это можно
реализовать с помощью таких же красивых фигурных скобочек, а не писать Html.MyPerfectDivStart()
и Html.MyPerfectDivEnd()
Есть вариант использовать MvcHtmlString и передавать в качестве параметра List
из разных других компонентов, созданных мною. Но это некрасиво смотрится!
Ответы
Ответ 1
С формами вам не получится написать
@Html.BeginForm()
...
@Html.EndForm()
Потому что майкрософт в EndForm возвращает void, а не MvcHtmlString, и вы получите
ошибку:
Error CS0029 Cannot implicitly convert type 'void' to 'object'
И нельзя иметь две перегрузки BeginForm, одна из которых возвращает строку (для вызова
без using), а вторая - new MvcDiv (нужно для разворачивания в using'е).
Как сделать хелпер, аналогичный System.Web.Mvc.Html.MvcForm для вызова через using.
Во-первых, сами extension для открытия/закрытия:
public static class MyDivHelper
{
public static MvcDiv BeginDiv(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("
Я пытаюсь понять, как в Razor Pages 2.1. добавлять роли и присваивать их пользователям. Мне необходимо создать роль администратора и присвоить её предсозданному (seed) пользователю. Я нашел, как это делается в MVC делается в MVC (вторая статья), но к сожалению, эти подход в Razor Pages у меня не сработали. Подскажите, как можно создать и присвоить роли в Razor Pages 2.1.
Ответ
Получилось сделать следующим образом. Сперва, я воспользовался кодом, предложенным пользователем Paul Madson в https://stackoverflow.com/questions/42471866/how-to-create-roles-in-asp-net-core-and-assign-them-to-users. Нижеследующий метод я вставил в файл Startup.cs. Он создает роль администратора и присваивает её предсоздаваемому пользователю. private void CreateRoles(IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService>();
var userManager = serviceProvider.GetRequiredService>();
Task roleResult;
string email = "someone@somewhere.com"; //Check that there is an Administrator role and create if not
Task hasAdminRole = roleManager.RoleExistsAsync("Administrator");
hasAdminRole.Wait(); if (!hasAdminRole.Result)
{
roleResult = roleManager.CreateAsync(new IdentityRole("Administrator"));
roleResult.Wait();
} //Check if the admin user exists and create it if not
//Add to the Administrator role Task testUser = userManager.FindByEmailAsync(email);
testUser.Wait(); if (testUser.Result == null)
{
ApplicationUser administrator = new ApplicationUser
{
Email = email,
UserName = email,
Name = email
}; Task newUser = userManager.CreateAsync(administrator, "_AStrongP@ssword!123");
newUser.Wait(); if (newUser.Result.Succeeded)
{
Task newUserRole = userManager.AddToRoleAsync(administrator, "Administrator");
newUserRole.Wait();
}
} }
Затем, в этом же файле в методе Configure я добавил аргумент IServiceProvider serviceProvider, то есть по части сигнатуры он стал выглядеть наподобие Configure(..., IServiceProvider serviceProvider). В нем же, в самом конце, я добавил строку CreateRoles(serviceProvider). Чтобы этот код работал, предварительно следует создать, например, в папке Data. следующий класс: using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Sobopedia.Data
{
public class ApplicationUser: IdentityUser
{
public string Name { get; set; }
}
}
Наконец, в методе ConfigureServices следует заменить services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
на services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
В результате, после запуска программы в таблице AspNetRoles у вас появится новая роль, а в таблице AspNetUsers новый пользователь, обладающий этой ролью. К сожалению, если вы создавали шаблон с изначальной авторизацией, то при добавлении services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();
перестанут работать страницы Login и Registration. Чтобы исправить это, необходимо:
Scaffold Identity следуя (https://docs.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.1&tabs=visual-studio). Если вы используете шаблон с предсозданной авторизацией, то смотрите раздел Scaffold identity into a Razor project with authorization.
После создания шаблона следует заменить во всем решении все IdentityUser на ApplicationUser. За исключением класса ApplicationUser, который по прежнему должен наследовать IdentityUser.
Убрать из Areas/identity/Pages/Account/Register.cs (то есть модели) все, что связано с EmailSernder, если у вас нет его имплементации.
Чтобы проверить корректность работы данного решения можно сделать следующее. В конце метода ConfigureServices в классе Startup.cs добавляем следующее: services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
}); services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact","RequireAdministratorRole");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Также, для надежности или если этот код не сработает, в модель страницы Contact можно добавить [Authorize(Roles = "Administrator")], чтобы она выглядели следующим образом: namespace Sobopedia.Pages
{
[Authorize(Roles = "Administrator")]
public class ContactModel : PageModel
{
public string Message { get; set; } public void OnGet()
{
Message = "Your contact page.";
}
}
}
Теперь, попасть на страницу Contact вы сможете только при условии, что авторизуетесь с логином someone@somewhere.com и паролем _AStrongP@ssword!123. Метод может не сработать, если при создании администратора будет использован пароль, содержащий запрещенные и не содержащий необходимые символы.
Для того чтобы логин, авторизовавшегося пользователя, отображался на всех страницах я сделал так: _Layout.cshtml ...
...
Но мне надо еще отобразить наименование школы, связанной с данным логином.
Дел в том, что есть таблица в БД: dbo.school
и логины (User.Identity.Name) совпадают с полем SchoolID. Как теперь выгрузить соответствующее поле SchoolName и отобразить его на каждой странице сайта вместе с логином. Конечно же саму информацию выгрузить я могу, реализуя интерфейс IRepository HomeController.cs public class HomeController : Controller
{
private cokoContext context = new cokoContext();
IRepository schoolRepository;
} public HomeController()
{
schoolRepository = new SchoolRepository(context);
}
Но как именно дальше должен сработать Контроллер со View?
Ответ
Можно сделать частичное представление. Controller: [ChildActionOnly]
public ActionResult GetFooter()
{
if (Session["footer"] == null)
Session["footer"] = _schoolRepository.GetById(User.Identity.Name); School entity = (School)Session["footer"]; var model = new FooterViewModel(User.Identity.Name, entity.SchoolName); return View(model);
}
Partial View: @model Namespace.Web.Models.FooterViewModel @{
Layout = null; // что бы не зациклился
}
Мне необходимо отобразить из БД в раскрывающий список ФИО, у которых столбец SubjectCode равен 1 Ege15VM.cs namespace WebApplication1.Models
{
public class Ege15VM
{
public IEnumerable Participants { get; set; }
public string Participant { get; set; } public Ege15VM()
{
cokoEntitiesWAN dataContext = new cokoEntitiesWAN();
Participants = from a in dataContext.ege_15_res_before
where a.SubjectCode == 1
select new SelectListItem
{
Text = a.surname + " " + a.name,
Value = a.ParticipantID,
Selected = false
};
}
}
}
Ege15.cshtml @model WebApplication1.Models.Ege15VM @using (Html.BeginForm())
{
Фамилия, Имя участника: @Html.DropDownListFor(x => x.Participant, Model.Participants)
Вот эта строчка: return View();
возвращает View, который будет отрендерен на основе ModelData - данных, переданных от клиента. model при этом будет null. замените ее на return View(model);
Есть два @Html.DropDownListFor(), один из которых предполагает выбор даты, а второй - времени события. Подскажите, пожалуйста, возможно ли с помощью razor-а передать два значения в качестве одной строки в контроллер из представления? Т.е. получить в результате в контроллере одну переменную формата, например, "MM/dd/yyyy/HH/mm/ss", при том что из отдельных DropDownListFor() берутся дата: "MM/dd/yyyy" и время: "HH/mm/ss". Возможно ли собрать эти строки вместе и передать в метод контроллера как одну строку?
Ответ
Вам нужна кастомная привязка модели. Механизм привязки модели в asp.net достаточно умён для простых и для большинства сложных моделей, но в подобных случаях он бессилен. Тут на помощь приходит возможность создания собственных привязчиков модели. Для этого вам потребуется создать класс, реализующий интерфейс IModelBinder. Проще это сделать, унаследовавшись от класса DefaultModelBinder.
Примерно так: public class DateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var date = DateTime.Parse(request.Form.Get("date"));
var time = TimeSpan.Parse(request.Form.Get("time"));
date = DateTime.Today.Add(time); return date;
}
}
а затем применить указать этот класс в качестве привязчика модели для вашего параметра. Вряд ли вы хотите делать приваязку модели таким образом везде в вашем проекте, поэтому стоит делать это с помощью атрибута для параметра вашего метода. Примерно так: ActionResult MyAction([ModelBinder(typeof(DateTimeModelBinder))] DateTime arg)
@User.Identity.Name по сути выдает логин. А как получить тот же Age к примеру? public class User : IdentityUser
{
public int Age{ get; set; }
public string FIO { get; set; }
public string Addresses { get; set; }
}
Ответ
По умолчанию используются только Name. Класс SignInManager регистрирует пользователей в вашем приложении. Этот класс использует IUserClaimsPrincipalFactory для регистрации claim'ов. Чтобы добавить claim нужно реализовать свой IUserClaimsPrincipalFactory public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory
{
public AppClaimsPrincipalFactory(
UserManager userManager
, RoleManager roleManager
, IOptions optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{ } public async override Task CreateAsync(ApplicationUser user)
{
var principal = await base.CreateAsync(user); if (user.Age > 0)
{
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("age", user.Age.ToString())
});
} return principal;
}
}
Далее регистрируем в классе Startup в методе ConfigureServices после добавления Identity. public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity()
.AddEntityFrameworkStores()
.AddDefaultTokenProviders(); services.AddScoped, AppClaimsPrincipalFactory>(); ...
}
В представлении значение Age можно получить так: @User.Claims.Where(c => c.Type == "age").Select(c => c.Value).SingleOrDefault();
Для удобства можно создать метод расширения: public static class IdentityExtensions
{
public static string GetAge(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("age");
return (claim != null) ? claim.Value : string.Empty;
}
}
И использовать в представлении так: @User.Identity.GetAge();
В ASP.NET RAZOR MVC5 существует такой замечательный хелпер как Html.BeginForm()
Он может записываться как @Html.BeginForm()
...
@Html.EndForm()
Так и @using (Html.BeginForm())
{
...
}
Как создать пользовательский хелпер по такому принципу. Например мне нужен блок div, блок не голый, а в нём какие-то основные элементы, допустим пять ссылок. Мне этот блок нужно дублировать постоянно, помимо фиксированных пяти ссылок нужно внутрь пихать кучу других элементов, это может быть кнопка/таблица/картинка/и т.д. Так вот, как это можно реализовать с помощью таких же красивых фигурных скобочек, а не писать Html.MyPerfectDivStart() и Html.MyPerfectDivEnd() Есть вариант использовать MvcHtmlString и передавать в качестве параметра List из разных других компонентов, созданных мною. Но это некрасиво смотрится!
Ответ
С формами вам не получится написать @Html.BeginForm()
...
@Html.EndForm()
Потому что майкрософт в EndForm возвращает void, а не MvcHtmlString, и вы получите ошибку:
Error CS0029 Cannot implicitly convert type 'void' to 'object'
И нельзя иметь две перегрузки BeginForm, одна из которых возвращает строку (для вызова без using), а вторая - new MvcDiv (нужно для разворачивания в using'е). Как сделать хелпер, аналогичный System.Web.Mvc.Html.MvcForm для вызова через using Во-первых, сами extension для открытия/закрытия: public static class MyDivHelper
{
public static MvcDiv BeginDiv(this HtmlHelper htmlHelper)
{
htmlHelper.ViewContext.Writer.Write("