I am working with asp.net core webapi( 6.0). I am using fluent validation for validating.
public class Parameter
{
public string? code { get; set; }
public DateTime? TransactionDateFrom { get; set; }
}
public class ParameterValidator : AbstractValidator<Parameter>
{
public ParameterValidator()
{
RuleFor(model => model.code)
.Matches("^ABC.{10}$")
.WithMessage("**barcode should be 13 char long and starts with ABC**");
RuleFor(model => model.TransactionDateFrom.ToString())
.Matches(#"^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$")
.WithMessage("Date should be in the pattern of **YYYY-MM-DD**");
}
}
I am writing unit test using xunit, flunt validation. It does not work
[Fact]
public void Validate_TransactionDateFrom_InValid()
{
var param = new Parameter{ TransactionDateFrom = DateTime.Now };
var result = _validator.TestValidate(param);
result.ShouldHaveValidationErrorFor(parameter => parameter.TransactionDateFrom);
}
[Fact]
public void Validate_code_Format_InValid()
{
var param = new Parameter{ code = "2791231231233" };
var result = _validator.TestValidate(param);
result.ShouldHaveValidationErrorFor(parameter => parameter.code);
}
Related
I'm facing an issue when AfterMap is not triggered.
Here are the conditions for this issue to occur:
I want to map values on an existing object from its abstract base type
ConstructUsing is provided as it is here where the concrete type will be chosen
Here the whole xunit (after doing dotnet new xunit -o PbAutoMapper) test:
using System;
using AutoMapper;
using Xunit;
namespace PbAutoMapper
{
public class UnitTest1
{
[Fact]
public void Test1()
{
var mapperConfig = new MapperConfiguration(config =>
{
config.CreateMap<SourceClass, BaseDestinationClass>()
.ConstructUsing(this.Construct)
.ForMember(i => i.MyNewProperty, o => o.Ignore())
.AfterMap(this.AfterMap);
config.CreateMap<SourceClass, DestinationClass>();
});
var mapper = mapperConfig.CreateMapper();
var src = new SourceClass { MyProperty = 1 };
var dest = mapper.Map<BaseDestinationClass>(src);
Assert.Equal("1", dest.MyNewProperty); // This works...
dest = new DestinationClass();
mapper.Map(src, dest);
Assert.Equal("1", dest.MyNewProperty); // This failed because AfterMap wasn't triggered
}
private DestinationClass Construct(SourceClass s, ResolutionContext ctx)
{
var d = new DestinationClass();
ctx.Mapper.Map(s, d, ctx);
return d;
}
private void AfterMap(SourceClass s, BaseDestinationClass d)
{
d.MyNewProperty = s.MyProperty.ToString();
}
}
public class SourceClass
{
public int MyProperty { get; set; }
}
public abstract class BaseDestinationClass
{
public string MyNewProperty { get; set; }
}
public class DestinationClass : BaseDestinationClass
{
}
}
Note: this code that seems to be implementable differently is actually over simplified to reveal my problem in the easiest way.
I am porting a PHP/CI API that uses $params = $this->uri->uri_to_assoc() so that it can accept GET requests with many combinations, such as:
https://server/properties/search/beds/3/page/1/sort/price_desc
https://server/properties/search/page/2/lat/34.1/lon/-119.1
https://server/properties/search
etc
With lots of code like:
$page = 1;
if (!empty($params['page'])) {
$page = (int)$params['page'];
}
The two ASP.NET Core 2.1 techniques I've tried both seem like a kludge so I would appreciate any guidance on a better solution:
1) Conventional routing with catchall:
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Properties}/{action=Search}/{*params}"
);
});
But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.
2) Attribute routing:
[HttpGet("properties/search")]
[HttpGet("properties/search/beds/{beds}")]
[HttpGet("properties/search/beds/{beds}/page/{page}")]
[HttpGet("properties/search/page/{page}/beds/{beds}")]
public IActionResult Search(int beds, double lat, double lon, int page = 1, int limit = 10) {
}
Obviously putting every combination of allowed search parameters and values is tedious.
Changing the signature of these endpoints is not an option.
FromPath value provider
What you are wanting is to bind a complex model to part of the url path. Unfortunately, ASP.NET Core does not have a built-in FromPath binder. Fortunately, though, we can build our own.
Here is an example FromPathValueProvider in GitHub that has the following result:
Basically, it is binding domain.com/controller/action/key/value/key/value/key/value. This is different than what either the FromRoute or the FromQuery value providers do.
Use the FromPath value provider
Create a route like this:
routes.MapRoute(
name: "properties-search",
template: "{controller=Properties}/{action=Search}/{*path}"
);
Add the [FromPath] attribute to your action:
public IActionResult Search([FromPath]BedsEtCetera model)
{
return Json(model);
}
And magically it will bind the *path to a complex model:
public class BedsEtCetera
{
public int Beds { get; set; }
public int Page { get; set; }
public string Sort { get; set; }
}
Create the FromPath value provider
Create a new attribute based on FromRoute.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property,
AllowMultiple = false, Inherited = true)]
public class FromPath : Attribute, IBindingSourceMetadata, IModelNameProvider
{
/// <inheritdoc />
public BindingSource BindingSource => BindingSource.Custom;
/// <inheritdoc />
public string Name { get; set; }
}
Create a new IValueProviderFactory base on RouteValueProviderFactory.
public class PathValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
var provider = new PathValueProvider(
BindingSource.Custom,
context.ActionContext.RouteData.Values);
context.ValueProviders.Add(provider);
return Task.CompletedTask;
}
}
Create a new IValueProvider base on RouteValueProvider.
public class PathValueProvider : IValueProvider
{
public Dictionary<string, string> _values { get; }
public PathValueProvider(BindingSource bindingSource, RouteValueDictionary values)
{
if(!values.TryGetValue("path", out var path))
{
var msg = "Route value 'path' was not present in the route.";
throw new InvalidOperationException(msg);
}
_values = (path as string).ToDictionaryFromUriPath();
}
public bool ContainsPrefix(string prefix) => _values.ContainsKey(prefix);
public ValueProviderResult GetValue(string key)
{
key = key.ToLower(); // case insensitive model binding
if(!_values.TryGetValue(key, out var value)) {
return ValueProviderResult.None;
}
return new ValueProviderResult(value);
}
}
The PathValueProvider uses a ToDictionaryFromUriPath extension method.
public static class StringExtensions {
public static Dictionary<string, string> ToDictionaryFromUriPath(this string path) {
var parts = path.Split('/');
var dictionary = new Dictionary<string, string>();
for(var i = 0; i < parts.Length; i++)
{
if(i % 2 != 0) continue;
var key = parts[i].ToLower(); // case insensitive model binding
var value = parts[i + 1];
dictionary.Add(key, value);
}
return dictionary;
}
}
Wire things together in your Startup class.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddMvcOptions(options =>
options.ValueProviderFactories.Add(new PathValueProviderFactory()));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes => {
routes.MapRoute(
name: "properties-search",
template: "{controller=Properties}/{action=Search}/{*path}"
);
});
}
}
Here is a working sample on GitHub.
Edit
My other answer is a better option.
General Idea
$params = $this->uri->uri_to_assoc() turns a URI into an associative array, which is basically a .NET Dictionary<TKey, TValue>. We can do something similar in ASP.NET Core. Lets say we have the following routes.
app.UseMvc(routes => {
routes.MapRoute(
name: "properties-search",
template: "{controller=Properties}/{action=Search}/{*params}"
);
});
Bind Uri Path to Dictionary
Action
public class PropertiesController : Controller
{
public IActionResult Search(string slug)
{
var dictionary = slug.ToDictionaryFromUriPath();
return Json(dictionary);
}
}
Extension Method
public static class UrlToAssocExtensions
{
public static Dictionary<string, string> ToDictionaryFromUriPath(this string path) {
var parts = path.Split('/');
var dictionary = new Dictionary<string, string>();
for(var i = 0; i < parts.Length; i++)
{
if(i % 2 != 0) continue;
var key = parts[i];
var value = parts[i + 1];
dictionary.Add(key, value);
}
return dictionary;
}
}
The result is an associative array based on the URI path.
{
"beds": "3",
"page": "1",
"sort": "price_desc"
}
But now I have to parse the params string for the key/value pairs and am not able to take advantage of model binding.
Bind Uri Path to Model
If you want model binding for this, then we need to go a step further.
Model
public class BedsEtCetera
{
public int Beds { get; set; }
public int Page { get; set; }
public string Sort { get; set; }
}
Action
public IActionResult Search(string slug)
{
BedsEtCetera model = slug.BindFromUriPath<BedsEtCetera>();
return Json(model);
}
Additional Extension Method
public static TResult BindFromUriPath<TResult>(this string path)
{
var dictionary = path.ToDictionaryFromUriPath();
var json = JsonConvert.SerializeObject(dictionary);
return JsonConvert.DeserializeObject<TResult>(json);
}
IMHO you are looking at this from the wrong perspective.
Create a model:
public class FiltersViewModel
{
public int Page { get; set; } = 0;
public int ItemsPerPage { get; set; } = 20;
public string SearchString { get; set; }
public string[] Platforms { get; set; }
}
API Endpoint:
[HttpGet]
public async Task<IActionResult> GetResults([FromRoute] ViewModels.FiltersViewModel filters)
{
// process the filters here
}
Result Object (dynamic)
public class ListViewModel
{
public object[] items;
public int totalCount = 0;
public int filteredCount = 0;
}
I am using AutoMapper 6.2.0 in my ASP.NET MVC 5 application.
When I call my view through controller it shows all things right. But, when I refresh that view, Visual Studio shows an error:
System.InvalidOperationException: 'Mapper already initialized. You must call Initialize once per application domain/process.'
I am using AutoMapper only in one controller. Not made any configuration in any place yet nor used AutoMapper in any other service or controller.
My controller:
public class StudentsController : Controller
{
private DataContext db = new DataContext();
// GET: Students
public ActionResult Index([Form] QueryOptions queryOptions)
{
var students = db.Students.Include(s => s.Father);
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
});
return View(new ResulList<StudentViewModel> {
QueryOptions = queryOptions,
Model = AutoMapper.Mapper.Map<List<Student>,List<StudentViewModel>>(students.ToList())
});
}
// Other Methods are deleted for ease...
Error within controller:
My Model class:
public class Student
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual Father Father { get; set; }
public virtual Sarparast Sarparast { get; set; }
public virtual Zamin Zamin { get; set; }
public virtual ICollection<MulaqatiMehram> MulaqatiMehram { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
My ViewModel Class:
public class StudentViewModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual FatherViewModel Father { get; set; }
public virtual SarparastViewModel Sarparast { get; set; }
public virtual ZaminViewModel Zamin { get; set; }
}
If you want/need to stick with the static implementation in a unit testing scenario, note that you can call AutoMapper.Mapper.Reset() before calling initialize. Do note that this should not be used in production code as noted in the documentation.
Source: AutoMapper documentation.
When you refresh the view you are creating a new instance of the StudentsController -- and therefore reinitializing your Mapper -- resulting in the error message "Mapper already initialized".
From the Getting Started Guide
Where do I configure AutoMapper?
If you're using the static Mapper method, configuration should only happen once per AppDomain. That means the best place to put the configuration code is in application startup, such as the Global.asax file for ASP.NET applications.
One way to set this up is to place all of your mapping configurations into a static method.
App_Start/AutoMapperConfig.cs:
public class AutoMapperConfig
{
public static void Initialize()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
...
});
}
}
Then call this method in the Global.asax.cs
protected void Application_Start()
{
App_Start.AutoMapperConfig.Initialize();
}
Now you can (re)use it in your controller actions.
public class StudentsController : Controller
{
public ActionResult Index(int id)
{
var query = db.Students.Where(...);
var students = AutoMapper.Mapper.Map<List<StudentViewModel>>(query.ToList());
return View(students);
}
}
I've used this method before and it worked till version 6.1.1
Mapper.Initialize(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())
);
Since version 6.2, this doesn't work any more. To correctly use Automapper create a new Mapper and us this one like this:
var mapper = new MapperConfiguration(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())).CreateMapper();
var model = mapper.Map<ContactModel>(this);
In case you really need to "re-initialize" AutoMapper you should switch to the instance based API to avoid System.InvalidOperationException: Mapper already initialized. You must call Initialize once per application domain/process.
For example, when you are creating the TestServer for xUnit tests you can just set ServiceCollectionExtensions.UseStaticRegistration inside fixure class constructor to false to make the trick:
public TestServerFixture()
{
ServiceCollectionExtensions.UseStaticRegistration = false; // <-- HERE
var hostBuilder = new WebHostBuilder()
.UseEnvironment("Testing")
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
For Unit Testing, you can add Mapper.Reset() to your unit test class
[TearDown]
public void TearDown()
{
Mapper.Reset();
}
You can use automapper as Static API and Instance API ,
Mapper already initialized is common issue in Static API , you can use mapper.Reset()
where you initialized mapper but this this not an answer at all.
Just try with instance API
var students = db.Students.Include(s => s.Father);
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Student, StudentViewModel>();
});
IMapper iMapper = config.CreateMapper();
return iMapper.Map<List<Student>, List<StudentViewModel>>(students);
Automapper 8.0.0 version
AutoMapper.Mapper.Reset();
Mapper.Initialize(
cfg => {
cfg.CreateMap<sourceModel,targetModel>();
}
);
You can simply use Mapper.Reset().
Example:
public static TDestination MapToObject<TSource, TDestination>(TSource Obj)
{
Mapper.Initialize(cfg => cfg.CreateMap<TSource, TDestination>());
TDestination tDestination = Mapper.Map<TDestination>(Obj);
Mapper.Reset();
return tDestination;
}
If you are using MsTest you can use the AssemblyInitialize attribute so that mapping gets configured only once for that assembly (here test assembly). This is generally added into to the base class of controller unit tests.
[TestClass]
public class BaseUnitTest
{
[AssemblyInitialize]
public static void AssemblyInit(TestContext context)
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmailAddress));
});
}
}
I hope this answer helps
If you are using Mapper in UnitTest and your tests more then one, You may use Mapper.Reset()
`
//Your mapping.
public static void Initialize()
{
Mapper.Reset();
Mapper.Initialize(cfg =>
{
cfg.CreateMap<***>
}
//Your test classes.
[TestInitialize()]
public void Initialize()
{
AutoMapping.Initialize();
}`
private static bool _mapperIsInitialized = false;
public InventoryController()
{
if (!_mapperIsInitialized)
{
_mapperIsInitialized = true;
Mapper.Initialize(
cfg =>
{
cfg.CreateMap<Inventory, Inventory>()
.ForMember(x => x.Orders, opt => opt.Ignore());
}
);
}
}
Can someone maybe explain to me what this means and why am i getting it.
System.InvalidOperationException : When called from 'VisitLambda',
rewriting a node of type 'System.Linq.Expressions.ParameterExpression'
must return a non-null value of the same type. Alternatively, override
'VisitLambda' and change it to not visit children of this type.
I am getting it from my unit tests I am running the latest .net core 2 with EF core. all my tests were fine till i upgraded then i started getting the error.
The funny thing is, is that when i run the project the line were it fails in the the tests is ok.
This is my Test
[Fact]
public async Task GetUserProfileAsync_Where_Employee_Exist_Test()
{
// Given
var user = TestPrincipal.CreatePrincipalForEmployeeUser();
using (var factory = new TestContextFactory())
using (var context = factory.CreateInMemoryDatabase<ApplicationContext>())
{
this.SetDependencies(context);
var data = EmployeeValueHelper.GetEmployeeValues();
context.AddRange(data);
context.SaveChanges();
var sut = new ProfileService(new DbContextRepository<Data.Models.Employees.Employee>(context), this.userService, this.moqEmploymentStatusService.Object);
// When
// -> this method goes to a service and calls the below FindByIdAsync
var actual = await sut.GetProfileForUserAsync(user);
// Then
Assert.Equal(10, actual.EmployeeId);
}
}
public async Task<Employee> FindByIdAsync(long id)
{
var profile = await this.repository.Set
.Include(_ => _.Address) --> IT FAILS ON THIS LINE, IF I REMOVE THE INCLUDE THEN IT WORKS
.Include(_ => _.EmployeeImage)
.SingleOrDefaultAsync(_ => _.EmployeeId == id);
if (profile == null)
{
return null;
}
return profile;
}
UPDATE
Service Layer
public class ProfileService : GenericService<Employee>, IProfileService
{
private readonly DbContextRepository<Employee> repository;
private readonly IUserService userService;
public ProfileService(DbContextRepository<Employee> repository, IUserService userService)
: base(repository)
{
this.repository = repository;
this.userService = userService;
}
public Task<Employee> GetProfileForUserAsync(ClaimsPrincipal user)
{
var id = this.userService.GetEmployeeId(user);
return id.HasValue ? this.FindByIdAsync(id.Value) : null;
}
public async Task<Employee> FindByIdAsync(long id)
{
var profile = await this.repository.Set
.Include(_ => _.Address)
.Include(_ => _.EmployeeImage)
.SingleOrDefaultAsync(_ => _.EmployeeId == id);
if (profile == null)
{
return null;
}
return profile;
}
}
Employee Model
public class Employee : IValidatableObject
{
[Key]
[Column("pkEmpID")]
public long EmployeeId { get; set; }
[Column("fkCompanyID")]
public long CompanyId { get; set; }
public virtual Company Company { get; set; }
[Display(Name = "lblEmpNumber")]
public string EmpNumber { get; set; }
public virtual IList<Address> Address { get; set; } = new List<Address>();
// WITH SOME EXTRA STUFF NOT NEEDED FOR THIS
}
Repository
public class DbContextRepository<TEntity> : IGenericRepository<TEntity>, IDisposable
where TEntity : class
{
public DbContextRepository(ApplicationContext context)
{
this.Context = context;
this.Set = context.Set<TEntity>();
this.SetWithNoTracking = this.Set.AsNoTracking();
}
public ApplicationContext Context { get; }
public DbSet<TEntity> Set { get; }
public IQueryable<TEntity> SetWithNoTracking { get; }
// WITH SOME EXTRA STUFF NOT NEEDED FOR THIS
}
Hope this will shed more light
I am trying to model the following using EF 4.1 and cannot get past this exception ("Collection was modified; enumeration operation may not execute").
Models:
public class Workflow
{
public List<WorkflowStage> Stages { get; set; }
}
public class WorkflowStage
{
public virtual List<WorkflowNextStage> WorkflowNextStages { get; set; }
}
public abstract class WorkflowNextStage
{
public virtual WorkflowStage NextStage { get; set; }
}
public class SuccessStage : WorkflowNextStage
{
}
public class FailureStage : WorkflowNextStage
{
}
Configuration:
modelBuilder.Entity<WorkflowStage>()
.HasMany(x => x.WorkflowNextStages)
.WithRequired()
.Map(m => m.MapKey("CurrentStageId"));
modelBuilder.Entity<WorkflowNextStage>()
.HasRequired(x => x.NextStage)
.WithMany()
.Map(x => x.MapKey("NextStageId"))
.WillCascadeOnDelete(false);
Failing code:
using (var ctx = new SBContext())
{
var workflow = new Workflow();
var stage = new WorkflowStage();
stage.WorkflowNextStages = new List<WorkflowNextStage>
{
new SuccessStage() {NextStage = stage},
new FailureStage() {NextStage = stage}
};
workflow.Stages = new List<WorkflowStage> {stage};
ctx.Workflows.Add(workflow);
ctx.SaveChanges();
}
Setting the 'new SuccessStage' above to a different stage works just fine.
I am a bit stumped on this one...anyone have any ideas?
Do you have a foreach loop where you are iterating through the collection you are modifying? If so this may cause this error.