Подтвердить что ты не робот

Как настроить Automapper в ASP.NET Core

Я относительно новичок в .NET, и решил решить .NET Core вместо изучения "старых способов". Я нашел подробную статью о настройке AutoMapper для .NET Core здесь, но есть ли более простое прохождение для новичков?

4b9b3361

Ответ 1

Я понял! Вот подробности:

  1. Добавьте основной пакет AutoMapper в ваше решение через NuGet.
  2. Добавьте пакет внедрения зависимостей AutoMapper в ваше решение с помощью NuGet.

  3. Создайте новый класс для профиля сопоставления. (Я создал класс в главном каталоге решений с именем MappingProfile.cs и добавил следующий код.) В качестве примера я буду использовать объект User и UserDto.

    public class MappingProfile : Profile {
        public MappingProfile() {
            // Add as many of these lines as you need to map your objects
            CreateMap<User, UserDto>();
            CreateMap<UserDto, User>();
        }
    }
    
  4. Затем добавьте AutoMapperConfiguration в Startup.cs, как показано ниже:

    public void ConfigureServices(IServiceCollection services) {
        // .... Ignore code before this
    
       // Auto Mapper Configurations
        var mappingConfig = new MapperConfiguration(mc =>
        {
            mc.AddProfile(new MappingProfile());
        });
    
        IMapper mapper = mappingConfig.CreateMapper();
        services.AddSingleton(mapper);
    
        services.AddMvc();
    
    }
    
  5. Чтобы вызвать сопоставленный объект в коде, сделайте что-то вроде следующего:

    public class UserController : Controller {
    
        // Create a field to store the mapper object
        private readonly IMapper _mapper;
    
        // Assign the object in the constructor for dependency injection
        public UserController(IMapper mapper) {
            _mapper = mapper;
        }
    
        public async Task<IActionResult> Edit(string id) {
    
            // Instantiate source object
            // (Get it from the database or whatever your code calls for)
            var user = await _context.Users
                .SingleOrDefaultAsync(u => u.Id == id);
    
            // Instantiate the mapped data transfer object
            // using the mapper you stored in the private field.
            // The type of the source object is the first type argument
            // and the type of the destination is the second.
            // Pass the source object you just instantiated above
            // as the argument to the _mapper.Map<>() method.
            var model = _mapper.Map<UserDto>(user);
    
            // .... Do whatever you want after that!
        }
    }
    

Я надеюсь, что это поможет кому-то начать с ASP.NET Core! Я приветствую любые отзывы или критику, так как я все еще новичок в мире .NET!

Ответ 2

Шаг Использовать AutoMapper с ASP.NET Core.

Шаг 1. Установка AutoMapper.Extensions.Microsoft.DependencyInjection из пакета NuGet.

enter image description here

Шаг 2. Создайте папку в решении, чтобы сохранить сопоставления с именем "сопоставления".

enter image description here

Шаг 3. После добавления папки Mapping мы добавили класс с именем " MappingProfile ", это имя может быть уникальным и полезным для понимания.

В этом классе мы собираемся сохранить все отображения.

enter image description here

Шаг 4. Инициализация Mapper при запуске "ConfigureServices"

В классе запуска нам нужно инициализировать созданный нами профиль, а также зарегистрировать службу AutoMapper.

  Mapper.Initialize(cfg => cfg.AddProfile<MappingProfile>());

  services.AddAutoMapper();

Фрагмент кода, чтобы показать метод ConfigureServices, где нам нужно инициализировать и зарегистрировать AutoMapper.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }


    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });


        // Start Registering and Initializing AutoMapper

        Mapper.Initialize(cfg => cfg.AddProfile<MappingProfile>());
        services.AddAutoMapper();

        // End Registering and Initializing AutoMapper

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    }}

Шаг 5. Получите вывод.

Чтобы получить результат Mapped, нам нужно вызвать AutoMapper.Mapper.Map и передать правильный пункт назначения и источник.

AutoMapper.Mapper.Map<Destination>(source);

CodeSnippet

    [HttpPost]
    public void Post([FromBody] SchemeMasterViewModel schemeMaster)
    {
        if (ModelState.IsValid)
        {
            var mappedresult = AutoMapper.Mapper.Map<SchemeMaster>(schemeMaster);
        }
    }

Ответ 3

Я хочу расширить ответы @theutz, а именно эту строку:

// services.AddAutoMapper(typeof(Startup));  // <-- newer automapper version uses this signature.

Существует ошибка (вероятно) в AutoMapper.Extensions.Microsoft.DependencyInjection версии 3.2.0. (Я использую .NET Core 2.0)

Это решено в этой проблеме GitHub. Если ваши классы, наследующие класс профиля AutoMapper, существуют вне сборки, в которой находится класс запуска, они, вероятно, не будут зарегистрированы, если ваша инъекция AutoMapper выглядит следующим образом:

services.AddAutoMapper();

если вы не укажете явно, какие сборки искать в профилях AutoMapper.

Это можно сделать так в вашем Startup.ConfigureServices:

services.AddAutoMapper(<assembies> or <type_in_assemblies>);

где "сборки" и "type_in_assemblies" указывают на сборку, где указаны классы профиля в вашем приложении. Например:

services.AddAutoMapper(typeof(ProfileInOtherAssembly), typeof(ProfileInYetAnotherAssembly));

Я полагаю (и подчеркиваю это слово), что из-за последующей реализации перегрузки без параметров (исходный код из GitHub):

public static IServiceCollection AddAutoMapper(this IServiceCollection services)
{
     return services.AddAutoMapper(null, AppDomain.CurrentDomain.GetAssemblies());
}

мы полагаемся на то, что CLR уже имеет сборку JIT, содержащую профили AutoMapper, которые могут быть или не быть правдой, поскольку они соединяются только при необходимости (более подробно см. этот вопрос Qaru).

Ответ 4

theutz 'ответ здесь очень хороший, я просто хочу добавить это:

Если вы позволяете вашему профилю сопоставления наследовать от MapperConfigurationExpression вместо Profile, вы можете просто добавить тест, чтобы проверить настройку отображения, что всегда удобно:

[Fact]
public void MappingProfile_VerifyMappings()
{
    var mappingProfile = new MappingProfile();

    var config = new MapperConfiguration(mappingProfile);
    var mapper = new Mapper(config);

    (mapper as IMapper).ConfigurationProvider.AssertConfigurationIsValid();
}

Ответ 5

В моем Startup.cs(Core 2.2, Automapper 8.1.1)

services.AddAutoMapper(new Type[] { typeof(DAL.MapperProfile) });            

В моем проекте доступа к данным

namespace DAL
{
    public class MapperProfile : Profile
    {
        // place holder for AddAutoMapper (to bring in the DAL assembly)
    }
}

В моем определении модели

namespace DAL.Models
{
    public class PositionProfile : Profile
    {
        public PositionProfile()
        {
            CreateMap<Position, PositionDto_v1>();
        }
    }

    public class Position
    {
        ...
    }

Ответ 6

Я решил это таким образом (похоже на выше, но я чувствую, что это более чистое решение) для .NET Core 2.2/Automapper 8.1.1 с Extensions.DI 6.1.1.

Создать класс MappingProfile.cs и заполнить конструктор Maps (я планирую использовать один класс для хранения всех моих отображений)

    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Source, Dest>().ReverseMap();
        }
    }

В Startup.cs добавьте ниже, чтобы добавить в DI (аргумент сборки предназначен для класса, который содержит ваши конфиги отображения, в моем случае это класс MappingProfile).

//add automapper DI
services.AddAutoMapper(typeof(MappingProfile));

В Controller используйте его так же, как любой другой объект DI

    [Route("api/[controller]")]
    [ApiController]
    public class AnyController : ControllerBase
    {
        private readonly IMapper _mapper;

        public AnyController(IMapper mapper)
        {
            _mapper = mapper;
        }

        public IActionResult Get(int id)
        {
            var entity = repository.Get(id);
            var dto = _mapper.Map<Dest>(entity);

            return Ok(dto);
        }
    }


Ответ 7

Я использую AutoMapper 6.1.1 и asp.net Core 1.1.2.

Прежде всего, определите классы профиля, унаследованные классом профиля Automapper. Я создал интерфейс IProfile, который пуст, цель состоит только в том, чтобы найти классы этого типа.

 public class UserProfile : Profile, IProfile
    {
        public UserProfile()
        {
            CreateMap<User, UserModel>();
            CreateMap<UserModel, User>();
        }
    }

Теперь создайте отдельный класс, например, Mappings

 public class Mappings
    {
     public static void RegisterMappings()
     {            
       var all =
       Assembly
          .GetEntryAssembly()
          .GetReferencedAssemblies()
          .Select(Assembly.Load)
          .SelectMany(x => x.DefinedTypes)
          .Where(type => typeof(IProfile).GetTypeInfo().IsAssignableFrom(type.AsType()));

            foreach (var ti in all)
            {
                var t = ti.AsType();
                if (t.Equals(typeof(IProfile)))
                {
                    Mapper.Initialize(cfg =>
                    {
                        cfg.AddProfiles(t); // Initialise each Profile classe
                    });
                }
            }         
        }

    }

Теперь в веб-проекте MVC Core в файле Startup.cs в конструкторе вызовите класс Mapping, который будет инициализировать все сопоставления во время загрузки приложения.

Mappings.RegisterMappings();

Ответ 8

Для ASP.NET Core (протестировано с использованием 2. 0+ и 3.0), если вы предпочитаете читать исходную документацию: https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection/blob/master/README.md

В противном случае выполнение следующих 4 шагов:

  1. Установите AutoMapper.Extensions.Microsoft.DependancyInjection из nuget.

  2. Просто добавьте несколько профильных классов.

  3. Затем добавьте ниже в свой класс startup.cs. services.AddAutoMapper(OneOfYourProfileClassNamesHere)

  4. Затем просто вставьте IMapper в свои контроллеры или куда вам нужно:

public class EmployeesController {

    private readonly IMapper _mapper;

    public EmployeesController(IMapper mapper){

        _mapper = mapper;
    }

А если вы хотите использовать ProjectTo сейчас, просто:

var customers = await dbContext.Customers.ProjectTo<CustomerDto>(_mapper.ConfigurationProvider).ToListAsync()

Ответ 9

services.AddAutoMapper(); не работает для меня (Я использую Asp.Net Core 2.0)

После настройки, как показано ниже

   var config = new AutoMapper.MapperConfiguration(cfg =>
   {                 
       cfg.CreateMap<ClientCustomer, Models.Customer>();
   });

инициализировать преобразователь IMapper mapper = config.CreateMapper();

и добавьте объект mapper к сервисам как singleton services.AddSingleton(mapper);

таким образом, я могу добавить DI в контроллер

  private IMapper autoMapper = null;

  public VerifyController(IMapper mapper)
  {              
   autoMapper = mapper;  
  }

и я использовал, как показано ниже, в моих методах действий

  ClientCustomer customerObj = autoMapper.Map<ClientCustomer>(customer);

Ответ 10

о theutz ответе, нет необходимости указывать параммер IMapper mapper в конструкторе контроллеров.

вы можете использовать Mapper, поскольку он является статическим членом в любом месте кода.

public class UserController : Controller {
   public someMethod()
   {
      Mapper.Map<User, UserDto>(user);
   }
}

Ответ 11

Чтобы добавить то, что Арве Систад упомянул для тестирования. Если по какой-либо причине вы похожи на меня и хотите сохранить структуру наследования, представленную в решении theutz, вы можете настроить MapperConfiguration следующим образом:

var mappingProfile = new MappingProfile();
var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile(mappingProfile);
});
var mapper = new Mapper(config);

Я сделал это в NUnit.

Ответ 12

Я обнаружил, что с автоматом трудно начать. Когда мне не удалось вставить его в конструктор класса Test, я полностью его отбросил. Вместо этого я буду использовать ModelBindings для свойств, которые нельзя редактировать, и Data Transfer Classes для свойств, которые должны быть скрыты и обрабатывать сопоставление самостоятельно.

Ответ 13

Ошибка:

NullReferenceException: Object reference not set to an instance of an object.

NullReferenceException: Object reference not set to an instance of an object.
DatingApp.API.Controllers.UsersController.GetUsers() in UsersController.cs, line 29

Stack Query Cookies Headers
NullReferenceException: Object reference not set to an instance of an object.
DatingApp.API.Controllers.UsersController.GetUsers() in UsersController.cs
+
        [HttpGet]
        public async Task<IActionResult> GetUsers()
        {
            var users = await _repo.GetUsers();

            var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);
            return Ok(usersToReturn);
        }
        [HttpGet("{id}")]
        public async Task<IActionResult> GetUser(int id)
Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)'

'at DatingApp.API.Controllers.UsersController.GetUsers() in H:\Projects\DatingApp.API\Controllers\UsersController.cs:line 29
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at System.Threading.Tasks.ValueTask'1.get_Result()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

AutoMapperProfiles.cs:

using System.Linq;
using AutoMapper;
using DatingApp.API.Dtos;
using DatingApp.API.Models;

namespace DatingApp.API.Helpers
{
    public class AutoMapperProfiles : Profile
    {
        public AutoMapperProfiles()
        {
            AllowNullCollections = true;

            CreateMap<User, UserForListDto>()
                .ForMember(dest => dest.PhotoUrl, opt => {
                    opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
                })
                .ForMember(dest => dest.Age, opt =>{
                    opt.MapFrom(src => src.DateOfBirth.CalculateAge());
                });

            CreateMap<User, UserForDetailDto>()
                .ForMember(dest => dest.PhotoUrl, opt => {
                    opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
                })
                .ForMember(dest => dest.Age, opt =>{
                    opt.MapFrom(src => src.DateOfBirth.CalculateAge());
                });

            CreateMap<Photo, PhotosForDetailDto>();
        }
    }
}

UserForListDto.cs:

using System;

namespace DatingApp.API.Dtos
{
    public class UserForListDto
    {
        public int Id { get; set; }

        public string Username { get; set; }

        public string Gender { get; set; }

        public int Age { get; set; }

        public string KnownAs { get; set; }

        public DateTime Created { get; set; }

        public DateTime LastActive { get; set; }

        public string City { get; set; }

        public string Country { get; set; }

        public string PhotoUrl { get; set; }
    }
}

PhotosForDetailDto.cs

using System;

namespace DatingApp.API.Dtos
{
    public class PhotosForDetailDto
    {
        public int Id { get; set; }

        public string Url { get; set; }

        public string Description { get; set; }

        public DateTime DateAdded { get; set; }

        public bool IsMain { get; set; }
    }
}

Photo.cs:

using System;

namespace DatingApp.API.Models
{
    public class Photo
    {
        public int Id { get; set; }

        public string Url { get; set; }

        public string Description { get; set; }

        public DateTime DateAdded { get; set; }

        public bool IsMain { get; set; }

        public User User { get; set; }

        public int UserId { get; set; }
    }
}

User.cs

using System;
using System.Collections.Generic;

namespace DatingApp.API.Models
{
    public class User
    {
        public int Id { get; set; }

        public string Username { get; set; }

        public byte[] PasswordHash { get; set; }

        public byte[] PasswordSalt { get; set; }

        public string Gender { get; set; }

        public DateTime DateOfBirth { get; set; }

        public string KnownAs { get; set; }

        public DateTime Created { get; set; }

        public DateTime LastActive { get; set; }

        public string Introduction { get; set; }

        public string LookingFor { get; set; }

        public string Interests { get; set; }

        public string City { get; set; }

        public string Country { get; set; }

        public ICollection<Photo> Photos { get; set; }
    }
}

Extensions.cs:

using System;
using Microsoft.AspNetCore.Http;

namespace DatingApp.API.Helpers
{
    public static class Extensions
    {
        public static void AddApplicationError(this HttpResponse response, string message)
        {
            response.Headers.Add("Application-Error", message);
            response.Headers.Add("Access-Control-Expose-Headers", "Application-Error");
            response.Headers.Add("Access-Control-Allow-Origin", "*");
        }

        public static int CalculateAge(this DateTime theDateTime)
        {
            var age = DateTime.Today.Year - theDateTime.Year;
            if (theDateTime.AddYears(age) > DateTime.Today) {
                age--;
            }

            return age;
        }
    }
}

IDatingRepository.cs:

using System.Collections.Generic;
using System.Threading.Tasks;
using DatingApp.API.Models;

namespace DatingApp.API.Data
{
    public interface IDatingRepository
    {
        void Add<T>(T entity) where T : class;

        void Delete<T>(T entity) where T : class;

        Task<bool> SaveAll();

        Task<IEnumerable<User>> GetUsers();

        Task<User> GetUser(int id);
    }
}

DatingRepository.cs:

using System.Collections.Generic;
using System.Threading.Tasks;
using AutoMapper;
using DatingApp.API.Models;
using Microsoft.EntityFrameworkCore;

namespace DatingApp.API.Data
{
    public class DatingRepository : IDatingRepository
    {
        private readonly DataContext _context;
        private readonly IMapper _mapper;
        public DatingRepository(DataContext context, IMapper mapper)
        {
            _context = context;
            _mapper = mapper;
        }

        public void Add<T>(T entity) where T : class
        {
            _context.Add(entity);
        }

        public void Delete<T>(T entity) where T : class
        {
            _context.Remove(entity);
        }

        public async Task<bool> SaveAll()
        {
            return await _context.SaveChangesAsync() > 0;
        }

        public async Task<IEnumerable<User>> GetUsers()
        {
            var users = await _context.Users.Include(p => p.Photos).ToListAsync();

            return users;
        }

        public async Task<User> GetUser(int id)
        {
            var user = await _context.Users.Include(p => p.Photos)
                .FirstOrDefaultAsync(u => u.Id == id);

            return user;
        }
    }
}

Ответ 14

Asp.Net Core 2.2 с AutoMapper.Extensions.Microsoft.DependencyInjection.

public class MappingProfile : Profile
{
  public MappingProfile()
  {
      CreateMap<Domain, DomainDto>();
  }
}

В Startup.cs

services.AddAutoMapper(typeof(List.Handler));

Ответ 15

Мне нравится много ответов, особенно @saineshwar. Я использую .net Core 3.0 с AutoMapper 9.0, поэтому я чувствую, что пришло время обновить свой ответ.

То, что у меня работало, было в Startup.ConfigureServices(...) зарегистрировать сервис следующим образом:

    services.AddAutoMapper(cfg => cfg.AddProfile<MappingProfile>(), 
                               AppDomain.CurrentDomain.GetAssemblies());

Я думаю, что остальная часть ответа @saineshwar остается идеальной. Но если кому-то интересно, код моего контроллера:

[HttpGet("{id}")]
public async Task<ActionResult> GetIic(int id)
{
    // _context is a DB provider
    var Iic = await _context.Find(id).ConfigureAwait(false);

    if (Iic == null)
    {
        return NotFound();
    }

    var map = _mapper.Map<IicVM>(Iic);

    return Ok(map);
}

И мой класс картирования:

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Iic, IicVM>()
            .ForMember(dest => dest.DepartmentName, o => o.MapFrom(src => src.Department.Name))
            .ForMember(dest => dest.PortfolioTypeName, o => o.MapFrom(src => src.PortfolioType.Name));
            //.ReverseMap();
    }
}

----- ОБНОВЛЕНИЕ -----

После прочтения документов, связанных в комментариях Лучана Баргаоану, я думаю, что лучше немного изменить этот ответ.

Без параметров services.AddAutoMapper() (у которого был ответ @saineshwar) больше не работает (по крайней мере, для меня). Но если вы используете сборку NuGet AutoMapper.Extensions.Microsoft.DependencyInjection, фреймворк может проверять все классы, расширяющие AutoMapper.Profile (например, мой, MappingProfile).

Таким образом, в моем случае, когда класс принадлежит одной и той же исполняющей сборке, регистрация службы может быть сокращена до services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());
(Более элегантный подход мог бы быть расширением без параметров с этим кодированием).

Спасибо, Люциан!