.Net Core Seed Database from Json Files - asp.net-core

I've been searching ways to seed data on a .Net Core 3.1 MVC app. I've found many samples, starting from the documentation. What I haven't found was good, real-life full-examples using (Json/XML) file. The closest one I found doesn't show how to properly get a (Json/XML) file's location no matter the platform used. I haven't been able to successfully integrate DI solutions found on other stack overflow questions makes me think it's not possible. Hoping someone can help.
Thanks!

The closest one I found doesn't show how to properly get a (Json/XML) file's location no matter the platform used.
If your json file exists in the physical disks,you just use the original physical location:
using (StreamReader r = new StreamReader(#"C:\xxx\xxx\test.json"))
If your json file exists in the project and in the root project,use the following location:
using (StreamReader r = new StreamReader(#"test.json"))
If it exists in the folders in the project,you could use the location like below:
using (StreamReader r = new StreamReader(#"wwwroot/test.json"))
And another thing you need to know,althogh you have multiple models with relationships,be sure the json should contain only one model's data.
Here is a example code:
1.Model:
public class Test
{
public int Id { get; set; }
public string Name { get; set; }
public int UserId { get; set; }
public List<User> User { get; set; }
}
public class User
{
public int Id { get; set; }
public int Age { get; set; }
}
2.DbContext:
public class YourDbContext : DbContext
{
public YourDbContext(DbContextOptions<YourDbContext> options)
: base(options)
{
}
public DbSet<Test> Test { get; set; }
public DbSet<User> User { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Test>().HasData(SeedTestData());
}
public List<Test> SeedTestData()
{
var tests = new List<Test>();
using (StreamReader r = new StreamReader(#"test.json"))
{
string json = r.ReadToEnd();
tests = JsonConvert.DeserializeObject<List<Test>>(json);
}
return tests;
}
public List<User> SeedUserData()
{
var users = new List<User>();
using (StreamReader r = new StreamReader(#"test2.json"))
{
string json = r.ReadToEnd();
users = JsonConvert.DeserializeObject<List<User>>(json);
}
return users;
}
}
3.Run following command in Package Manager Console:
>PM Add-Migration init
>PM Update-Database
test.json:
[
{
"id": 1,
"name": "aa"
},
{
"id": 2,
"name": "bb"
},
{
"id": 3,
"name": "cc"
}
]
test2.json:
[
{
"id": 1,
"age": 34
},
{
"id": 2,
"age": 21
}
]

Related

Swagger Swashbuckle Asp.NET Core: show details about every enum is used

I have the following enum:
public enum TicketQuestionType
{
General = 1,
Billing = 2,
TMS = 3,
HOS = 4,
DeviceManagement = 5
}
and model class:
public class TicketCreateApi
{
public string Subject { get; set; }
public TicketQuestionType QuestionType { get; set; } = TicketQuestionType.General;
public TicketType Type { get; set; } = TicketType.Problem;
public TicketStatus Status { get; set; } = TicketStatus.New;
public TicketPriority Priority { get; set; } = TicketPriority.Normal;
public string Description { get; set; }
public List<string> Attachments { get; set; }
public int? DeviceId { get; set; }
public int? DriverId { get; set; }
}
my API method uses it:
Task<IActionResult> Create(TicketCreateApi model);
Swagger generates the following:
and this:
so, we can see only default value and no way to see available list of enum (names and values).
I would like to show it. How to do it?
we can see only default value and no way to see available list of enum
(names and values). I would like to show it. How to do it?
To display the enums as strings in swagger, you configure the JsonStringEnumConverter, adding the following line in ConfigureServices :
services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
The output as below:
If you want to display the enums as stings and int values, you could try to create a EnumSchemaFilter to change the schema. code as below:
public class EnumSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
{
model.Enum.Clear();
Enum.GetNames(context.Type)
.ToList()
.ForEach(name => model.Enum.Add(new OpenApiString($"{Convert.ToInt64(Enum.Parse(context.Type, name))} = {name}")));
}
}
}
Configure the SwaggerGen to use above ShemaFilter.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ToDo API",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Shayne Boyer",
Email = string.Empty,
Url = new Uri("https://twitter.com/spboyer"),
},
License = new OpenApiLicense
{
Name = "Use under LICX",
Url = new Uri("https://example.com/license"),
}
});
c.SchemaFilter<EnumSchemaFilter>();
});
The result like this:
I tried EnumSchemaFilter. I got some error every time i submited the request.
Because the serialize enum string to int
I used this code block and i hope it works
services.AddControllersWithViews()
.AddJsonOptions(
opts =>
{
var enumConverter = new JsonStringEnumConverter();
opts.JsonSerializerOptions.Converters.Add(enumConverter);
});

Better Way of Dealing With Circular References? - EF Core 3.1 and Web API

I am currently trying to progress with EF Core with a one-to-many (a user has many items). A tutorial or three later I managed to get things working with two very small and simple tables; however, I got a json exception: A possible object cycle was detected which is not supported which indicated that I had circular references.
Here is my code that gets around the issue using DTO objects, but is there a more cleaner way I can get around this issue as typing the, though it works, felt a bit wrong.
User:
namespace TestWebApplication.Database
{
public class User
{
[Key]
public int UserId { get; set; }
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
public ICollection<Items> Items { get; set; }
}
}
Items:
namespace TestWebApplication.Database
{
public class Items
{
[Key]
public int ItemId { get; set; }
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public virtual User User { get; set; }
}
}
DtoItems:
namespace TestWebApplication.Database.DTOs
{
public class DtoItems
{
public string Item { get; set; }
public string Category { get; set; }
public string Type { get; set; }
public DtoUser User { get; set; }
}
}
DtoUser:
namespace TestWebApplication.Database.DTOs
{
public class DtoUser
{
public string UserName { get; set; }
public string Dob { get; set; }
public string Location { get; set; }
}
}
TestController:
[HttpGet]
[Route("getitems")]
public ActionResult<List<Items>> GetItems()
{
List<Items> items = _myContext.Items.Include(i => i.User).ToList();
// DTOs
List<DtoItems> dtoItems = new List<DtoItems>();
foreach (var i in items)
{
var dtoItem = new DtoItems
{
Item = i.Item,
Category = i.Category,
Type = i.Type,
User = new DtoUser
{
UserName = i.User.UserName,
Dob = i.User.Dob,
Location = i.User.Location
}
};
dtoItems.Add(dtoItem);
}
return Ok(dtoItems);
}
The output from endpoint:
[
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xx"
}
},
{
"item": "xxx",
"category": "xxx",
"type": "xxx",
"user": {
"userName": "xxx",
"dob": "xxx",
"location": "xxx"
}
}
]
In my opinion, the use of a DTO is the correct way of dealing with this issue. The fact that your datamodel does not serialize well is trying to hint to you that you should not be serializing your datamodel from the API at all.
I think returning a DTO also solves further issues down the road (What if you want to return all properties of the UserModel except one, maybe it's a sensitive property you don't want to just return from your API, what if your UserModel in the DB gets more navigation properties that you don't want to return?).
There is really only two other ways of handling this.
You can switch to Newtonsoft.Json which has support for handling reference loops and you can configure it one single line
Like so :
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
System.Text.Json does not have support for doing this (yet). Follow this Github Issue for more info : https://github.com/dotnet/runtime/issues/30820
You use the JsonIgnore attribute to force non serialization of properties which will work but... It looks weird to have an EntityFramework model have JSON Serialization options on it...
So your best bet, stick with the DTO.
More info :
https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/
https://github.com/dotnet/runtime/issues/30820
https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm

How to display Swashbuckle parameter object only with fields that should actually be sent?

I'm starting to work with Swagger using the Swashbuckle library for AspNetCore.
And when putting in my API an object with references it presents as if it were necessary to send all the fields of the references, when only the identifier (Id)
Here's an example:
Model Structure:
public class Cidade
{
public long Id { get; set; }
public string Nome { get; set; }
public Uf Uf { get; set; }
}
public class Uf
{
public long Id { get; set; }
public string Nome { get; set; }
public Pais Pais { get; set; }
}
public class Pais
{
public long Id { get; set; }
public string Nome { get; set; }
}
And the following API:
[Produces("application/json")]
[Route("api/Cidade")]
public class CidadeController : Controller
{
// POST: api/Cidade
[HttpPost]
public void Post([FromBody]Cidade value)
{
}
}
The result in Swagger is as follows:
And what I would like is the following (only up to uf.id):
How can I get this result?
I followed the logic of #HelderSepu answer, to get my solution, which would be as follows:
I built a Schema filter to add an example to the reference properties (Ref), which has a property called "Id":
public class ApplySchemaRefIdExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaFilterContext context)
{
if (schema.Properties != null)
{
foreach (var p in schema.Properties)
{
if (p.Value.Example == null && p.Value.Ref != null)
{
var reference = context.SystemType.GetProperty(p.Value.Ref.Split("/").LastOrDefault());
if (reference != null)
{
var id = reference.PropertyType.GetProperty("Id");
if (id != null)
{
p.Value.Example = new
{
Id = 123
};
p.Value.Ref = null;
}
}
}
}
}
}
}
On Startup.cs:
services.AddSwaggerGen(c =>
{
// ...
c.SchemaFilter<ApplySchemaRefIdExtensions>();
});
Result for the same example of the question:
I was looking on my samples and I think I found something you can use:
http://swagger-net-test.azurewebsites.net/swagger/ui/index?filter=P#/PolygonVolume/PolygonVolume_Post
On my case I'm adding more, you need less, but still what you need is just a custom example...
the JSON looks like this:
"PolygonVolumeInsideParameter": {
"properties": {
"Points": {
"items": {
"$ref": "#/definitions/Location"
},
"xml": {
"name": "Location",
"wrapped": true
},
"example": [
{
"Lat": 1.0,
"Lon": 2.0
},
{
"Lat": 5.0,
"Lon": 6.0
}
],
"type": "array"
},
"PlanId": {
"type": "string"
}
},
"xml": {
"name": "PolygonVolumeInsideParameter"
},
"type": "object"
},
And on swashbuckle I added the example it with an ISchemaFilter my code is here:
https://github.com/heldersepu/Swagger-Net-Test/blob/master/Swagger_Test/App_Start/SwaggerConfig.cs#L891

Setting analyzer from plugin in ElasticSearch with NEST

my first SO post !
I'm trying to set a Stempel Analyzer (ES analyzer for polish language) for a string field. I can do it through PUT request:
{
"doc": {
"_source": {
"enabled": false
},
"properties": {
"file": {
"type": "attachment",
**"analyzer": "polish"**,
"fields": {
"content": {
"type": "string",
"term_vector": "with_positions_offsets"
}
}
}
}
}
}
and it works fine. Trying to do the same thing through NEST.
[ElasticProperty(Name = "_content", TermVector = TermVectorOption.WithPositionsOffsets, Analyzer = "polish")]
public string Content { get; set; }
is not working neither do:
client.CreateIndex(index, b => b.AddMapping<DocInES>(m => m
.MapFromAttributes()
.Properties(props => props
.String(s => s
.Name(p => p.File.Content)
.Analyzer("polish")
))));
When I'm using
var result = client.Analyze(a => a.Index("doc").Analyzer("polish").Text("...text..."));
It works fine, so .NET is detecting this analyzer.
I'm using ES 2.1.1. &
NEST 1.7.1
EDIT:
from what I inspected it seems that NEST is not doing Mapping of Attributes of Attachment class created in .NET. It does Map Attributes of Document class
[ElasticType(Name = "docInES")]
public class DocInES {
public int InstitutionId { get; set;}
public int DocumentId { get; set; }
[ElasticProperty(Store = true, Analyzer = "polish")]
public string Title { get; set; }
[ElasticProperty(Type = FieldType.Attachment)]
public Attachment File { get; set; }
}
But not the Attachment class:
public class Attachment {
[ElasticProperty(Name = "content2", Store = true)]
public string Content { get; set; }
[ElasticProperty(Name = "content_type2")]
public string ContentType { get; set; }
[ElasticProperty(Name = "name2", Analyzer = "english")]
public string Name { get; set; }
}
You should probably check the compatibility matrix on Github.
Nest 1.7.1 is not compatible with ES 2.1.1

RavenDB Spatial Index - Child Property

I've got a spatial index and search working in a prototype and now I'm trying to integrate it into our proper solution that has a slightly different data model and it's not coming back with any search results.
These are my classes
public class SpatialDataModel
{
public int Id { get; set; }
public string Title { get; set; }
public SpatialDataShapeModel SpatialDataShape { get; set; }
}
public class SpatialDataShapeModel
{
public int Id { get; set; }
public DbGeography Shape { get; set; }
}
I've defined the index a couple of ways:
public class SpatialDatasIndex: AbstractIndexCreationTask<SpatialDataModel>
{
public SpatialDatasIndex()
{
Map = points => from p in points
select new
{
Id = p.Id,
__ = SpatialGenerate("Coords", p.SpatialDataShape.Shape.Latitude, p.SpatialDataShape.Shape.Longitude)
};
}
}
and
public class SpatialDatasIndex: AbstractIndexCreationTask<SpatialDataModel>
{
public SpatialDatasIndex()
{
Map = points => from p in points
select new
{
Id = p.Id,
__ = SpatialGenerate("Coords", p.SpatialDataShape.Shape.AsText())
};
Spatial(x=>x.SpatialDataShape.Shape, o=>o.Geography.Default());
}
}
Here i'm using the type DbGeography and and trying to get out the WKT
and I'm trying to query like this:
using (var session = _documentStore.OpenSession())
{
data = session.Query<SpatialDataModel, SpatialDatasIndex>()
.Customize(x => x.WithinRadiusOf(
fieldName: "Coords",
radius: 1,
latitude: query.Lat,
longitude: query.Lng))
.Take(query.PageSize)
.ToList();
}
Any help getting this working would be great, I can't find much documentation and the bits on the Raven site I've read many times.
UPDATE
JSON
{
"Title": "Test 1",
"SpatialDataShape": {
"Id": 1,
"Shape": {
"Geography": {
"CoordinateSystemId": 4326,
"WellKnownText": "POINT (-0.1217937469482422 51.51461834694225)"
}
}
}
}