Еще немного о валидации в ASP.NET

Автор: admin от 17-01-2018, 21:50, посмотрело: 190

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



public class MoveProductParam
{
   public ProductId {get; set; }

   public CategoryId {get; set; }
}

//...
if(!dbContext.Products.Any(x => x.Id == par.ProductId))
    return BadRequest("Product not found");

if(!dbContext.Categories.Any(x => x.Id == par.CategoryId ))
    return BadRequest("Category not found");


Мы достойны лучшего

public class MoveProductParam
{
   [EntityId(typeof(Product))]
   public ProductId {get; set; }

   [EntityId(typeof(Category))]
   public CategoryId {get; set; }
}
mayorovp подсказал в комментариях, что можно пойти дальше и сделать ModelBinder. Чтобы можно было так:



public class MoveProductParam
{
   [ModelBinder(typeof(EntityModelBinder))]
   public Product Product{get; set; }

   [ModelBinder(typeof(EntityModelBinder))]
   public Category Category{get; set; }
}


Сказано — сделано. К сожалению в метод Find нельзя передать строку из запроса. Нужно знать тип первичного ключа. Скорее всего эта информация доступна из контекста бд, но я так глубоко не копал, поэтому вытащил тип свойства Id. Чтобы было побыстрее добавил TypeAccessor из FastMember.



public class EntityModelBinder: IModelBinder
{
	private readonly Func<Type, object, object> _getter;

	public EntityModelBinder(Func<Type, object, object> getter)
	{
		_getter = getter;
	}

	public Task BindModelAsync(ModelBindingContext bindingContext)
	{
		var value = bindingContext.ActionContext
			.HttpContext
			.Request
			.Query[bindingContext.ModelName]
			.FirstOrDefault();

		if (value == null)
		{
			bindingContext.ModelState
                             .AddModelError(bindingContext.ModelName,
                             "Id for "bindingContext.ModelName" is null");
			return Task.CompletedTask;
		}

		try
		{
			var typeAccessor = TypeAccessor
                            .Create(bindingContext.ModelType);
			
			// Не все называеют Id так.
                        // Лучше покопаться в конфигурации EF, чтобы вытащить тип PK
			// Если кто подскажет в комментариях где это буду благодарен

			var id = Convert.ChangeType(value,
                            typeAccessor.GetMembers()
                                .First(y => y.Name == "Id")
                                .Type);
			
                        var result = _getter(bindingContext.ModelType, id);
			bindingContext.Result = ModelBindingResult.Success(result);
		}
		catch (Exception e)
		{
			bindingContext.ModelState.AddModelError(
                            bindingContext.ModelName, e.Message);
		}
		
		return Task.CompletedTask;
	}

//Сигнатура делегата только в качестве примера. Может быть другой интерфейс
// с явной семантикой
services.AddScoped<Func<Type, object, object(x =>
{
	object Func(Type t, object o)
	{
		var typeAccessor = TypeAccessor.Create(t);
		var dbContext = x.GetService<DemoContext>();
		return dbContext.Find(t, o);
	};
	
	return Func;
});


Осталось реализовать IModelBinderProvider, чтобы назначить этот байндер на все Entity из контекста.

Источник: Хабрахабр

Категория: Программирование

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *