Giter Site home page Giter Site logo

pdevito3 / craftsman Goto Github PK

View Code? Open in Web Editor NEW
1.1K 37.0 66.0 2.92 MB

A .NET scaffolding tool to help you stop worrying about boilerplate and focus on your business logic ๐Ÿš€

Home Page: https://wrapt.dev

License: MIT License

C# 100.00%
web-api dotnet-core scaffolding template-engine csharp cli craftsman cli-commands vertical-slice-architecture docker

craftsman's People

Contributors

jheizer avatar ken1nil avatar meladkamari avatar nm777 avatar pdevito3 avatar rolandta avatar sshquack avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

craftsman's Issues

Add an Authorization Layer to Gateway Scaffolding

Currently, gateways do not have authorization scaffolding built in and can not be used with apis that have authorization protection.

One should be able to create a new gateway that complements an authorization protected api and pass authorization info through that gateway.

POST returns 500 when primarykey != entitynameID

I'd expect to be able to submit a POST and get 201, but am getting 500

Steps to reproduce

A build like the below will have a broken POSt because PK is expecting ReportRequestId instad of ReportRequest. Need to fix to use primary key

DomainName: BrokenPost
BoundedContexts:
- SolutionName: Reporting
  Port: 1210
  DbContext:
   ContextName: ReportingDbContext
   DatabaseName: ReportingDbContext
   Provider: SqlServer
  Entities:
  - Name: ReportRequest
    Properties:
    - Name: ReportId
      IsPrimaryKey: true
      Type: guid
      CanFilter: true
      CanSort: true
    - Name: Provider
      Type: string
      CanFilter: true
      CanSort: true
    - Name: Target
      Type: string
      CanFilter: true
      CanSort: true
  SwaggerConfig:
    Title: RideRequest Swgger Doc
    Description: This is my swagger doc
    SwaggerEndpointName: "v1"
    AddSwaggerComments: true

Further technical details

Craftsman version (dotnet tool list -g): 0.9.3

Integration tests fails to run sql server on M1 chip

The integration tests fail to run on macOS with M1 because Craftsman relies on mssql server image which has not yet been port to ARM64. The current workaround is to use azure sql edge. See this blog for details.

Steps to reproduce

  • Scaffold any default example on macOS with M1 chip
  • Try to run integration tests
  • Notice that the tests hang-up because the mssql server docker image did not start

Further technical details

Craftsman version (dotnet tool list -g):

โฏ dotnet tool list -g
Package Id      Version      Commands
--------------------------------------
craftsman       0.12.3       craftsman
dotnet-ef       6.0.1        dotnet-ef

Environment info

โฏ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.101
 Commit:    ef49f6213a

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  12.1
 OS Platform: Darwin
 RID:         osx.12-arm64
 Base Path:   /usr/local/share/dotnet/sdk/6.0.101/

Host (useful for support):
  Version: 6.0.1
  Commit:  3a25a7f1cc

.NET SDKs installed:
  6.0.101 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Potential solutions

It took a few hours to figure this error. So we could do few things to help anyone else running into this issue:
Option 1: It might be worth to call this out in the getting started docs for folks with M1 chip
Option 2: Detect at Craftsman builder runtime (aka new:* commands) that the machine is a macOS with arm64 and generate azure-sql-edge
Option 3: At test runtime detect that the machine is a macOS with arm64 and use azure-sql-edge image. Can be found using the following:

> System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture
Arm64

> System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
true

Option 3 seems like a better solution since it would be clear in the code as why azure-sql-edge was chosen over ms-sql-server with some comments. I'd be happy to make a PR if your prefer option 3!

Use with basic Test Yaml fails

Hi,

i really like wrapt.dev and would like to test it with this simple setup:

On my workstation mystation i use a sqlexpress instance like a normal sqlserver.I did also create a database test.
I did test craftman with this yaml file:


SolutionName: Test.Api
DbContext:
  ContextName: TestDbContext
  DatabaseName: test
  Provider: SqlServer
Entities:
- Name: Info
  Properties:
  - Name: InfoId
    IsPrimaryKey: true
    Type: int
    CanFilter: true
    CanSort: true
  - Name: MyInfo
    Type: string
    CanFilter: true
    CanSort: true
  - Name: MySpeed
    Type: int
    CanFilter: true
    CanSort: true
  - Name: MyType
    Type: string
    CanFilter: true
    CanSort: true
Environments:
  - EnvironmentName: Startup
    ConnectionString: "Data Source=mystation\\sqlexpress;Database=test;Integrated Security=false;User ID=sa;Password=start;"
    ProfileName: Prod     
SwaggerConfig:
  Title: Test WebApi
  Description: Our API uses a REST based design, leverages the JSON data format, and relies upon HTTPS for transport. We respond with meaningful HTTP response codes and if an error occurs, we include error details in the response body. API Documentation is at xxxxx/dev/docs
  AddSwaggerComments: true

I do only get a message "Your template file was parsed successfully." but it stucks here and does not complete..
Is there way to enable verbose debugging, so i can see where the problem resists ?

Empty Controller generated when no features added

An entity without defined features creates a empty controller, it contains non existent namespace reference (maybe if no features, don't create controller)

Further technical details

Craftsman version (dotnet tool list -g): 11.1

Getting started new example throws error on M1 chip

I tried the create new example per docs on macOS arm64 and it throws the following error.

Steps to reproduce

โฏ craftsman new:example

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Create an Example Project โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
What would you like to name this project (e.g. MyExampleProject)? Craftsman01
File scaffolding for RecipeManagement was successful.
Database Migrations for RecipeManagement were successful.
TypeInitializationException: The type initializer for 'LibGit2Sharp.Core.NativeMethods' threw an
exception.
      In order to help diagnose loading problems, consider setting the DYLD_PRINT_LIBRARIES environment
     variable: dlopen(libgit2-106a5f2, 0x0001): tried: 'libgit2-106a5f2' (no such file),
     '/usr/local/lib/libgit2-106a5f2' (no such file), '/usr/lib/libgit2-106a5f2' (no such file),
     '/Users/me/Desktop/code/csharp/libgit2-106a5f2' (no such file),
     '/usr/local/lib/libgit2-106a5f2' (no such file), '/usr/lib/libgit2-106a5f2' (no such file)
       at git_libgit2_init()
       at InitializeNativeLibrary()
       at cctor()
  at git_repository_init_ext(git_repository*& repository, FilePath path, GitRepositoryInitOptions options)
  at git_repository_init_ext(FilePath workdirPath, FilePath gitdirPath, Boolean isBare)
  at Init(String path, Boolean isBare)
  at Init(String path)
  at GitSetup(String solutionDirectory) in Utilities.cs:393
  at CreateNewDomainProject(String domainDirectory, IFileSystem fileSystem, DomainProject domainProject)
     in NewDomainProjectCommand.cs:115
  at Run(String buildSolutionDirectory, IFileSystem fileSystem) in NewExampleCommand.cs:41

Further technical details

Craftsman version (dotnet tool list -g):

โฏ dotnet tool list -g
Package Id      Version      Commands
--------------------------------------
craftsman       0.12.3       craftsman
dotnet-ef       6.0.1        dotnet-ef

Environment info

โฏ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.101
 Commit:    ef49f6213a

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  12.1
 OS Platform: Darwin
 RID:         osx.12-arm64
 Base Path:   /usr/local/share/dotnet/sdk/6.0.101/

Host (useful for support):
  Version: 6.0.1
  Commit:  3a25a7f1cc

.NET SDKs installed:
  6.0.101 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

Git version

โฏ git --version
git version 2.34.1

Libgit2 info

โฏ brew info libgit2
libgit2: stable 1.3.0 (bottled), HEAD
C library of Git core methods that is re-entrant and linkable
https://libgit2.github.com/
/opt/homebrew/Cellar/libgit2/1.3.0 (102 files, 3.5MB) *
  Poured from bottle on 2021-12-21 at 11:05:13
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/libgit2.rb
License: GPL-2.0-only
==> Dependencies
Build: cmake โœ˜, pkg-config โœ”
Required: libssh2 โœ”
==> Options
--HEAD
        Install HEAD version
==> Analytics
install: 5,007 (30 days), 23,757 (90 days), 60,775 (365 days)
install-on-request: 1,839 (30 days), 12,159 (90 days), 31,213 (365 days)
build-error: 0 (30 days)

I tried this with few different example type (BasicAuth, FK) and they all throw the same error.

Auditable fields throwing an exception

Had a report on discord for the below that I need to investigate.

Thank you always for this great work, the BaseEntity properties have private setters, but you are trying to assign them in UpdateAuditFields method of the DbContext class directly which raises an exception.

Stuck after parsing template file

The script stops after displaying the message

Your template file was parsed successfully.

The script creates a solution in a sub-folder, but it doesn't have any entities or swagger in it.

Steps to reproduce

  • Create a new yaml file with the CarbonKitchen.Api template
  • Open a new powershell window in the file's directory
  • Run ./ApiTemplateName.yaml

Further technical details

Craftsman version (dotnet tool list -g): 0.8.1

  • Checked that the console wasn't on select mode so it wasn't stopped by that
  • Tried running it several times.
  • Once I left it running for a few hours.

###Yaml file used
ApiTemplateName.zip

Allow ID fields to be Byte, Int16, 32 or 64 instead of Guid

First I must say that Wrapt is a FANTASTIC work. Super useful. Congratulations.

But I noticed that all "Id" fields generated by it are Guid type, which translates to a UNIQUEIDENTIFIER datatype in SQL Server.

This datatype takes 16 bytes per row, which grows really fast on tables with lots of records.

It would be great if we can specify the data type of the Id field... Default can still be Guid, but it would be nice to have the option to choose between Byte (TinyInt), Int16 (SmallInt), Int32 (Int) or Int64 (BigInt). Some tables has a very predictable number of rows and it would be nice to save some bytes by allocating only the necessary amount of space to that field.

Greetings from Brazil ๐Ÿ‡ง๐Ÿ‡ท

Migrations command failing

I created a YAML file and generated the projects. API is running properly, but when I try to run the migrations command it is failing. Screenshot of the Powershell migration command output.

image

Steps to reproduce

  1. Create a YAML file.
  2. Generate project using craftsman.
  3. Run the migration command. - dotnet ef migrations add "InitialMigration" --project Infrastructure.Persistence --startup-project WebApi --output-dir Migrations
  4. Including the YAML file.
SolutionName: TodoApi
DbContext:
 ContextName: TodoDbContext
 DatabaseName: TodoDb
 Provider: SqlServer
Entities:
- Name: TodoItem
  Properties:
  - Name: TodoId
    IsPrimaryKey: true
    Type: int
    CanFilter: true
    CanSort: true
  - Name: Name
    Type: string
    CanFilter: true
    CanSort: true
  - Name: IsCompleted
    Type: bool
    CanFilter: true
    CanSort: true
Environments:
  - EnvironmentName: Startup
    ConnectionString: "Data Source=.;Initial Catalog=TodoDb;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;"
    ProfileName: Dev
SwaggerConfig:
  Title: Todo Api Service
  Description: API for managing todo items.
  ApiContact: 
    Name: Anuraj
    Email: [email protected]
    Url: https://dotnetthoughts.net

Exception and Stack Trace

Build started...
Build succeeded.
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Migrations.IMigrator'. This is often because no database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.DesignTimeServiceCollectionExtensions.<>c__DisplayClass1_0.<AddDbContextDesignTimeServices>b__7(IServiceProvider _)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.EnsureServices(IServiceProvider services)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Unable to resolve service for type 'Microsoft.EntityFrameworkCore.Migrations.IMigrator'. This is often because no database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.

Further technical details

Craftsman version (dotnet tool list -g): 0.7.0

Bootstrapping new project with yaml file fails with unhelpful error (missing directory)

Wanted to try this out and followed the tutorial. Wrote this yaml file:

SolutionName: SoftThornApi
DbContext:
 ContextName: SoftThornDbContext
 DatabaseName: SoftThorn
 Provider: SqlServer
Entities:
- Name: Product
  Properties:
  - Name: ProductId
    IsPrimaryKey: true
    Type: int
    CanFilter: true
    CanSort: true
  - Name: Name
    Type: string
    CanFilter: true
    CanSort: true
  - Name: ProductImageId
    IsPrimaryKey: false
    Type: int?
    CanFilter: true
    CanSort: false
- Name: ProductImage
  Properties:
  - Name: ProductImageId
    IsPrimaryKey: true
    Type: int
    CanFilter: true
    CanSort: true
  - Name: Image
    Type: byte[]
    CanFilter: false
    CanSort: false
Environments:
  - EnvironmentName: Production
    ConnectionString: "Data Source=myprodserver;Initial Catalog=CarbonKitchen;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;"
    ProfileName: Prod
  - EnvironmentName: Qa
    ConnectionString: "Data Source=myqaserver;Initial Catalog=CarbonKitchen;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;"
    ProfileName: Qa
SwaggerConfig:
  Title: SoftThorn
  Description: Our API uses a REST based design, leverages the JSON data format, and relies upon HTTPS for transport. We respond with meaningful HTTP response codes and if an error occurs, we include error details in the response body. API Documentation is at carbonkitchen.com/dev/docs
  ApiContact: 
    Name: SoftThorn
    Email: [email protected]
    Url: localhost

Steps to reproduce

  • install the tool
  • install template
  • update tool
  • update template
  • write yaml file
  • run craftsman new:api D:\Drop\Desktop\SoftThorn.yaml
  • somehow missing template?

cli output:

An unhandled exception occurred when running the API command.
The error details are:
The `E:\Code\SoftThorn\SoftThornApi\Domain\Entities` directory could not be found.

When creating that folder manually, it complains that the folder already exists.

Further technical details

Craftsman version (dotnet tool list -g): craftsman 0.4.2 craftsman
Foundation version (dotnet new -u): wasnt isntalled for some reason

Entity Id type

Hello,

When I created a new:domain the resulting project contained Entities that contained an ID field of type GUID that was autogenerated by craftsman. I am wondering if there is a way to define the data type for the ID? Right now if I add an Id as a Property to my Entity then two Id fields are created resulting in build errors. The use case I have is that the Id field needs to be an INT because the legacy database we are using uses autoincremented Ids.

Should paginated results have a default sort order?

When using a MSSQL server for the database back-end and hitting a base route for an entity, entity framework gives the following warning:

warn: Microsoft.EntityFrameworkCore.Query[10102]
      The query uses a row limiting operator ('Skip'/'Take') without an 'OrderBy' operator. This may lead to unpredictable results.

Here is an example reproduction:

  1. Set up a local MSSQL server and create a database with the name ExampleDb.

  2. Adjust the MSSQL username and password below, and save the following Example.API.yaml file.

SolutionName: Example.API
AddGit: false

DbContext:
 ContextName: ExampleDbContext
 DatabaseName: ExampleDb
 Provider: SqlServer

Entities:
- Name: MyEntity
  Properties:
  - Name: MyEntityId
    IsPrimaryKey: true
    Type: int
    CanFilter: true
    CanSort: true
  - Name: Title
    Type: string
    CanFilter: true
    CanSort: true

Environments:
  - EnvironmentName: Local
    ConnectionString: "Data Source=localhost;Initial Catalog=ExampleDb;User Id=sa;Password=P@$$w0rd;"
    ProfileName: Local
  1. Executing the following shell commands creates the API, creates and runs database migrations, and starts the API service:
echo 'n' | craftsman new:api ${PROJECT_NAME}.yaml
echo ""
cd Example.API
export ASPNETCORE_ENVIRONMENT=Local
dotnet ef migrations add "InitialMigration" --project Infrastructure.Persistence --startup-project WebApi --output-dir Migrations
dotnet ef database update --project Infrastructure.Persistence --startup-project WebApi
cd WebApi
dotnet run --launch-profile Local
  1. On the Swagger page, execute the "Try it out" for the /api/MyEntitys route and note the warnings in the shell window.

I should note that I'm working on a Mac running macOS 11.1 (Big Sur) and dotnet 5.

If you're open to it, here is a possible solution:
master...nm777:default-sort-order

Entities with Guid PK should include ID in create DTO and override capability in POST

Currently, when an entity is created with a Guid PK, it is not added to the creation DTO. This means that there is no way to add an entity with a DTO and it will always get added with 000-000...

Two things should be added:

  1. The PK should be added to the creation DTO when the PK is a Guid
  2. The POST should have an override that creates a guid when none is given.

How to return an error when creating a filter with faulty values ?

I've encounterd something very strange..
When i'm trying to execute a GET request to retrieve a list of items, i can also specify a filter but when those values are wrong ( ex. guid == leaves) the result still gives me a complete list with all the entities back from the database. ( ex. GetUsers returns all the users)

It looks like there is no validation on the filters.
How do i return an error when the result of the filter returns nothing?

Craftsman and Kubernetes

Hi there!
We have created a service using craftsman that we intend to run in AWS EKS. To handle service to service communication and AWS resource orchestration we are looking at using DAPR. Have you looked at integrating DAPR into wrapt and craftsman before? Do you have any thoughts on using DAPR?

Using Controller vs ControllerBase

I noticed that when the code is scaffolded that the Controller uses Controller not ControllerBase. Based on the docs that Microsoft provides, I think this should use ControllerBase

Update docs on Wrapt

Hi Paul,

Great work on the craftsman project. I'm looking forward to being a hardcore user of wrapt. Spent about a day on the documentation and also looking at the source code. I see a lot of changes that were made in 0.11.x aren't updated on the website.

Would appreciate it if you could update the documentation.

Best,
Deepak

Update Fluent Assertions to 6.1.0

  • Update Fluent Assertions to 6.1.0
  • In the Functional Tests, there are changes to the syntax of the HttpCodes (from int to constant)
  • the Integration Tests, exception assert (changed to Async)

Option for views

Hello, this is fantastic, can you please allow us to scaffold basic views/tables/grid just like the default ASP MVC CRUD views

https://github.com/ZeekoZhu/TextTemplatingCore
https://github.com/muhammadazam/MVC-T4-Templates/blob/master/CodeTemplates/MvcControllerWithContext/Controller.cs.t4
https://github.com/ligershark/side-waffle/tree/master/TemplatePack/ItemTemplates/Web/ASP.NET/Custom%20T4%20Files/MvcView


ModelMetadataFunctions.cs.include.t4

<#+
// Gets the related entity information for an association property where there is an associated foreign key property.
// Note: ModelMetadata.RelatedEntities contains only the related entities associated through a foreign key property.

RelatedModelMetadata GetRelatedModelMetadata(PropertyMetadata property){
    RelatedModelMetadata propertyModel;
    IDictionary<string, RelatedModelMetadata> relatedProperties;
    if(ModelMetadata.RelatedEntities != null)
    {
        relatedProperties = ModelMetadata.RelatedEntities.ToDictionary(item => item.AssociationPropertyName);
    }
    else
    {
        relatedProperties = new Dictionary<string, RelatedModelMetadata>();
    }
    relatedProperties.TryGetValue(property.PropertyName, out propertyModel);

    return propertyModel;
}

// A foreign key, e.g. CategoryID, will have an association name of Category
string GetAssociationName(PropertyMetadata property) {
    RelatedModelMetadata propertyModel = GetRelatedModelMetadata(property);
    return propertyModel != null ? propertyModel.AssociationPropertyName : property.PropertyName;
}

// A foreign key, e.g. CategoryID, will have a value expression of Category.CategoryID
string GetValueExpression(PropertyMetadata property) {
    RelatedModelMetadata propertyModel = GetRelatedModelMetadata(property);
    return propertyModel != null ? propertyModel.AssociationPropertyName + "." + propertyModel.DisplayPropertyName : property.PropertyName;
}

// This will return the primary key property name, if and only if there is exactly
// one primary key. Returns null if there is no PK, or the PK is composite.
string GetPrimaryKeyName() {
    return (ModelMetadata.PrimaryKeys != null && ModelMetadata.PrimaryKeys.Count() == 1) ? ModelMetadata.PrimaryKeys[0].PropertyName : null;
}

bool IsPropertyGuid(PropertyMetadata property) {
    return String.Equals("System.Guid", property.TypeName, StringComparison.OrdinalIgnoreCase);
}
#>

Imports.include.t4

<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Entity" #>
<#@ assembly name="System.Data.Linq" #>
<#@ ScaffoldingAssembly Processor="ScaffoldingAssemblyLoader" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.Linq.Mapping" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Microsoft.AspNet.Scaffolding.Core.Metadata" #>
<#@ parameter type="System.String" name="ViewDataTypeName" #>
<#@ parameter type="System.String" name="ViewDataTypeShortName" #>
<#@ parameter type="System.Boolean" name="IsPartialView" #>
<#@ parameter type="System.Boolean" name="IsLayoutPageSelected" #>
<#@ parameter type="System.Boolean" name="ReferenceScriptLibraries" #>
<#@ parameter type="System.Boolean" name="IsBundleConfigPresent" #>
<#@ parameter type="System.String" name="ViewName" #>
<#@ parameter type="System.String" name="LayoutPageFile" #>
<#@ parameter type="System.String" name="JQueryVersion" #>
<#@ parameter type="Microsoft.AspNet.Scaffolding.Core.Metadata.ModelMetadata" name="ModelMetadata" #>
<#@ parameter type="System.Version" name="MvcVersion" #>

View With controller context

<#@ template language="C#" HostSpecific="True" Debug="True" #>
<#@ output extension="cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data.Linq" #>
<#@ ScaffoldingAssembly Processor="ScaffoldingAssemblyLoader" #>
<#
string routePrefix;
if (String.IsNullOrEmpty(AreaName)) 
{
    routePrefix = ControllerRootName;
}
else
{
    routePrefix = AreaName + "/" + ControllerRootName;
}
#>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="Microsoft.AspNet.Scaffolding.Core.Metadata" #>
<#@ parameter type="System.String" name="ControllerName" #>
<#@ parameter type="System.String" name="ControllerRootName" #>
<#@ parameter type="System.String" name="Namespace" #>
<#@ parameter type="System.String" name="AreaName" #>
<#@ parameter type="System.String" name="ContextTypeName" #>
<#@ parameter type="System.String" name="ModelTypeName" #>
<#@ parameter type="System.String" name="ModelVariable" #>
<#@ parameter type="Microsoft.AspNet.Scaffolding.Core.Metadata.ModelMetadata" name="ModelMetadata" #>
<#@ parameter type="System.String" name="EntitySetVariable" #>
<#@ parameter type="System.Boolean" name="UseAsync" #>
<#@ parameter type="System.Boolean" name="IsOverpostingProtectionRequired" #>
<#@ parameter type="System.String" name="BindAttributeIncludeText" #>
<#@ parameter type="System.String" name ="OverpostingWarningMessage" #>
<#@ parameter type="System.Collections.Generic.HashSet<System.String>" name="RequiredNamespaces" #>
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
<# if (UseAsync) { #>
using System.Threading.Tasks;
<# } #>
using System.Net;
using System.Web;
using System.Web.Mvc;
<# foreach (var namespaceName in RequiredNamespaces) { #>
using <#= namespaceName #>;
<# } #>

namespace <#= Namespace #>
{
<#
    var contextTypeName = ContextTypeName;
    var entitySetName = ModelMetadata.EntitySetName;
    var entitySetVar = EntitySetVariable ?? (String.IsNullOrEmpty(entitySetName) ? entitySetName : (entitySetName.Substring(0, length:1).ToLowerInvariant() + entitySetName.Substring(1)));
    var primaryKeyName = ModelMetadata.PrimaryKeys[0].PropertyName;
    var primaryKeyShortTypeName = ModelMetadata.PrimaryKeys[0].ShortTypeName;
    var primaryKeyDefaultValue = ModelMetadata.PrimaryKeys[0].DefaultValue;
    var primaryKeyType = ModelMetadata.PrimaryKeys[0].TypeName;
    var primaryKeyNullableTypeName = GetNullableTypeName(primaryKeyType, primaryKeyShortTypeName);
    var lambdaVar = ModelVariable[0];
    var relatedProperties = ModelMetadata.RelatedEntities.ToDictionary(item => item.AssociationPropertyName);

    string bindAttribute;
    if (IsOverpostingProtectionRequired)
    {
        bindAttribute = String.Format("[Bind(Include = \"{0}\")] ", BindAttributeIncludeText);
    }
    else
    {
        bindAttribute = String.Empty;
    }
#>
    public class <#= ControllerName #> : Controller
    {
        private <#= ContextTypeName #> db = new <#= ContextTypeName #>();

        // GET: <#= routePrefix #>
<# if (UseAsync) { #>
        public async Task<ActionResult> Index()
<# } else { #>
        public ActionResult Index(int page = 1, int pageSize = 10, string sortBy = "Id", bool isAsc = true, string search = null)
<# } #>
        {
<#  var includeExpressions = "";
        includeExpressions = String.Join("", relatedProperties.Values.Select(property => String.Format(".Include({0} => {0}.{1})", lambdaVar, property.AssociationPropertyName)));
#>
<# if(!String.IsNullOrEmpty(includeExpressions)) { #>
            var <#= entitySetVar #> = db.<#= entitySetName #><#= includeExpressions #>;

            // Search Logic
            <#= entitySetVar #> = <#= entitySetVar #>.Where(s => search == null<#  
int i=0;
var properties = ModelMetadata.Properties;
foreach (PropertyMetadata property in properties) {
    if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey && !property.IsComplexType) { 
        if(IsString(property.TypeName)){#> 
                || (s.<#= GetValueExpression(property) #> != null && s.<#= GetValueExpression(property) #>.ToLower().Contains(search.ToLower()))<#
        }
        else if(IsBool(property.TypeName)){#> 
                || (s.<#= GetValueExpression(property) #>.ToString().ToLower().Contains(search.ToLower()))<#
        }
        else if(HasValueExpression(property)){#> 
                //// Todo: This makes the Query slow. Need to find a better way
                //|| (s.<#= GetValueExpression(property) #> != null && s.<#= GetValueExpression(property) #>.ToString().ToLower().Contains(search.ToLower()))<#
        }
        else{#>
        <#}
    }
}#> 
            );
            // End Search Logic

			ViewBag.TotalPages = (int)Math.Ceiling((double)<#= entitySetVar #>.Count() / pageSize);
            ViewBag.CurrentPage = page;
            ViewBag.PageSize = pageSize;           
            ViewBag.Search = search;
            ViewBag.SortBy = sortBy;
            ViewBag.IsAsc = isAsc;

<#		if (UseAsync) { #>
            return View(await <#= entitySetVar #>.OrderBy(o=>o.<#= primaryKeyName #>).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync());
<#		} else { #>
            return View(<#= entitySetVar #>.OrderBy(o=>o.<#= primaryKeyName #>).Skip((page - 1) * pageSize).Take(pageSize).ToList());
<#		} #>
<# } else { #>
            // Search Logic
            var <#= entitySetVar #> = db.<#= entitySetName #>.Where(s => search == null<#  
int i=0;
var properties = ModelMetadata.Properties;
foreach (PropertyMetadata property in properties) {
    if (property.Scaffold && !property.IsPrimaryKey && !property.IsForeignKey && !property.IsComplexType) { 
        if(IsString(property.TypeName)){#> 
                || (s.<#= GetValueExpression(property) #> != null && s.<#= GetValueExpression(property) #>.ToLower().Contains(search.ToLower()))<#
        }
        else if(IsBool(property.TypeName)){#> 
                || (s.<#= GetValueExpression(property) #>.ToString().ToLower().Contains(search.ToLower()))<#
        }
        else if(HasValueExpression(property)){#> 
                //// Todo: This makes the Query slow. Need to find a better way
                //|| (s.<#= GetValueExpression(property) #> != null && s.<#= GetValueExpression(property) #>.ToString().ToLower().Contains(search.ToLower()))<#
        }
        else{#>
        <#}
    }
}#> 
            );
            // End Search Logic

            ViewBag.TotalPages = (int)Math.Ceiling((double)<#= entitySetVar #>.Count() / pageSize);
            ViewBag.CurrentPage = page;
            ViewBag.PageSize = pageSize;           
            ViewBag.Search = search;
            ViewBag.SortBy = sortBy;
            ViewBag.IsAsc = isAsc;


<#		if (UseAsync) { #>
            return View(await <#= entitySetVar #><#= includeExpressions #>.OrderBy(o=>o.<#= primaryKeyName #>).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync());
<#		} else { #>
            return View(<#= entitySetVar #><#= includeExpressions #>.OrderBy(o=>o.<#= primaryKeyName #>).Skip((page - 1) * pageSize).Take(pageSize).ToList());
<#		} #>
<# } #>
        }

        // GET: <#= routePrefix #>/Details/5
<# if (UseAsync) { #>
        public async Task<ActionResult> Details(<#= primaryKeyNullableTypeName #> id)
<# } else { #>
        public ActionResult Details(<#= primaryKeyNullableTypeName #> id)
<# } #>
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
<# if (UseAsync) { #>
            <#= ModelTypeName #> <#= ModelVariable #> = await db.<#= entitySetName #>.FindAsync(id);
<# } else { #>
            <#= ModelTypeName #> <#= ModelVariable #> = db.<#= entitySetName #>.Find(id);
<# } #>
            if (<#= ModelVariable #> == null)
            {
                return HttpNotFound();
            }
            return View(<#= ModelVariable #>);
        }

        // GET: <#= routePrefix #>/Create
        public ActionResult Create()
        {
<# foreach (var property in relatedProperties.Values) { #>
            ViewBag.<#= property.ForeignKeyPropertyNames[0] #> = new SelectList(db.<#= property.EntitySetName #>, "<#= property.PrimaryKeyNames[0] #>", "<#= property.DisplayPropertyName #>");
<# } #>
            return View();
        }

        // POST: <#= routePrefix #>/Create
<# if (IsOverpostingProtectionRequired) {
    foreach (var line in OverpostingWarningMessage.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) { 
#>
        // <#= line #>
<# } } #>
        [HttpPost]
        [ValidateAntiForgeryToken]
<# if (UseAsync) { #>
        public async Task<ActionResult> Create(<#= bindAttribute #><#= ModelTypeName #> <#= ModelVariable #>)
<# } else { #>
        public ActionResult Create(<#= bindAttribute #><#= ModelTypeName #> <#= ModelVariable #>)
<# } #>
        {
            if (ModelState.IsValid)
            {
<# if(!String.IsNullOrEmpty(primaryKeyType) && String.Equals("System.Guid", primaryKeyType, StringComparison.OrdinalIgnoreCase)) { #>
                <#= ModelVariable #>.<#= primaryKeyName #> = Guid.NewGuid();
<# } #>
                db.<#= entitySetName #>.Add(<#= ModelVariable #>);
<# if (UseAsync) {#>
                await db.SaveChangesAsync();
<# } else { #>
                db.SaveChanges();
<# } #>
                return RedirectToAction("Index");
            }

<# foreach (var property in relatedProperties.Values) { #>
            ViewBag.<#= property.ForeignKeyPropertyNames[0] #> = new SelectList(db.<#= property.EntitySetName #>, "<#= property.PrimaryKeyNames[0] #>", "<#= property.DisplayPropertyName #>", <#= ModelVariable #>.<#= property.ForeignKeyPropertyNames[0] #>);
<# } #>
            return View(<#= ModelVariable #>);
        }

        // GET: <#= routePrefix #>/Edit/5
<# if (UseAsync) { #>
        public async Task<ActionResult> Edit(<#= primaryKeyNullableTypeName #> id)
<# } else { #>
        public ActionResult Edit(<#= primaryKeyNullableTypeName #> id)
<# } #>
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
<# if (UseAsync) { #>
            <#= ModelTypeName #> <#= ModelVariable #> = await db.<#= entitySetName #>.FindAsync(id);
<# } else { #>
            <#= ModelTypeName #> <#= ModelVariable #> = db.<#= entitySetName #>.Find(id);
<# } #>
            if (<#= ModelVariable #> == null)
            {
                return HttpNotFound();
            }
<# foreach (var property in relatedProperties.Values) { #>
            ViewBag.<#= property.ForeignKeyPropertyNames[0] #> = new SelectList(db.<#= property.EntitySetName #>, "<#= property.PrimaryKeyNames[0] #>", "<#= property.DisplayPropertyName #>", <#= ModelVariable #>.<#= property.ForeignKeyPropertyNames[0] #>);
<# } #>
            return View(<#= ModelVariable #>);
        }

        // POST: <#= routePrefix #>/Edit/5
<# if (IsOverpostingProtectionRequired) {
    foreach (var line in OverpostingWarningMessage.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)) { 
#>
        // <#= line #>
<# } } #>
        [HttpPost]
        [ValidateAntiForgeryToken]
<# if (UseAsync) { #>
        public async Task<ActionResult> Edit(<#= bindAttribute #><#= ModelTypeName #> <#= ModelVariable #>)
<# } else { #>
        public ActionResult Edit(<#= bindAttribute #><#= ModelTypeName #> <#= ModelVariable #>)
<# } #>
        {
            if (ModelState.IsValid)
            {
                db.Entry(<#= ModelVariable #>).State = EntityState.Modified;
<# if (UseAsync) { #>
                await db.SaveChangesAsync();
<# } else { #>
                db.SaveChanges();
<# } #>
                return RedirectToAction("Index");
            }
<# foreach (var property in relatedProperties.Values) { #>
            ViewBag.<#= property.ForeignKeyPropertyNames[0] #> = new SelectList(db.<#= property.EntitySetName #>, "<#= property.PrimaryKeyNames[0] #>", "<#= property.DisplayPropertyName #>", <#= ModelVariable #>.<#= property.ForeignKeyPropertyNames[0] #>);
<# } #>
            return View(<#= ModelVariable #>);
        }

        // GET: <#= routePrefix #>/Delete/5
<# if (UseAsync) { #>
        public async Task<ActionResult> Delete(<#= primaryKeyNullableTypeName #> id)
<# } else { #>
        public ActionResult Delete(<#= primaryKeyNullableTypeName #> id)
<# } #>
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
<# if (UseAsync) { #>
            <#= ModelTypeName #> <#= ModelVariable #> = await db.<#= entitySetName #>.FindAsync(id);
<# } else { #>
            <#= ModelTypeName #> <#= ModelVariable #> = db.<#= entitySetName #>.Find(id);
<# } #>
            if (<#= ModelVariable #> == null)
            {
                return HttpNotFound();
            }
            return View(<#= ModelVariable #>);
        }

        // POST: <#= routePrefix #>/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
<# if (UseAsync) { #>
        public async Task<ActionResult> DeleteConfirmed(<#= primaryKeyShortTypeName #> id)
<# } else { #>
        public ActionResult DeleteConfirmed(<#= primaryKeyShortTypeName #> id)
<# } #>
        {
<# if (UseAsync) { #>
            <#= ModelTypeName #> <#= ModelVariable #> = await db.<#= entitySetName #>.FindAsync(id);
<# } else { #>
            <#= ModelTypeName #> <#= ModelVariable #> = db.<#= entitySetName #>.Find(id);
<# } #>
            db.<#= entitySetName #>.Remove(<#= ModelVariable #>);
<# if (UseAsync) { #>
            await db.SaveChangesAsync();
<# } else { #>
            db.SaveChanges();
<# } #>
            return RedirectToAction("Index");
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
<#+
// This function converts the primary key short type name to its nullable equivalent when possible. This is required to make
// sure that an HTTP 400 error is thrown when the user tries to access the edit, delete, or details action with null values.
    string GetNullableTypeName(string typeName, string shortTypeName)
    {
        // The exceptions are caught because if for any reason the type is user defined, then the short type name will be used.
        // In that case the user will receive a server error if null is passed to the edit, delete, or details actions.
        Type primaryKeyType = null;
        try
        {
            primaryKeyType = Type.GetType(typeName);
        }
        catch
        {
        }
        if (primaryKeyType != null && (primaryKeyType.IsPrimitive || IsGuid(typeName)))
        {
            return shortTypeName + "?";
        }
        return shortTypeName;
    }

    bool IsGuid(string typeName) {
        return String.Equals("System.Guid", typeName, StringComparison.OrdinalIgnoreCase);
    } 
   
    bool IsString(string typeName) {
        return String.Equals("System.String", typeName, StringComparison.OrdinalIgnoreCase);
    } 

    bool IsDateTime(string typeName) {
        return String.Equals("System.DateTime", typeName, StringComparison.OrdinalIgnoreCase);
    }   

    bool IsDecimal(string typeName) {
        return String.Equals("System.Decimal", typeName, StringComparison.OrdinalIgnoreCase);
    }   

    bool IsInt(string typeName) {
        return String.Equals("System.Int32", typeName, StringComparison.OrdinalIgnoreCase) ||  String.Equals("System.Integer", typeName, StringComparison.OrdinalIgnoreCase) ||  String.Equals("System.Int64", typeName, StringComparison.OrdinalIgnoreCase);
    }   

    bool IsBool(string typeName) {
        return String.Equals("System.Boolean", typeName, StringComparison.OrdinalIgnoreCase);
    }

    RelatedModelMetadata GetRelatedModelMetadata(PropertyMetadata property){
        RelatedModelMetadata propertyModel;
        IDictionary<string, RelatedModelMetadata> relatedProperties;
        if(ModelMetadata.RelatedEntities != null)
        {
            relatedProperties = ModelMetadata.RelatedEntities.ToDictionary(item => item.AssociationPropertyName);
        }
        else
        {
            relatedProperties = new Dictionary<string, RelatedModelMetadata>();
        }
        relatedProperties.TryGetValue(property.PropertyName, out propertyModel);

        return propertyModel;
    }

    // A foreign key, e.g. CategoryID, will have an association name of Category
    string GetAssociationName(PropertyMetadata property) {
        RelatedModelMetadata propertyModel = GetRelatedModelMetadata(property);
        return propertyModel != null ? propertyModel.AssociationPropertyName : property.PropertyName;
    }

    // A foreign key, e.g. CategoryID, will have a value expression of Category.CategoryID
    string GetValueExpression(PropertyMetadata property) {
        RelatedModelMetadata propertyModel = GetRelatedModelMetadata(property);
        return propertyModel != null ? propertyModel.AssociationPropertyName + "." + propertyModel.DisplayPropertyName : property.PropertyName;
    }

    // A foreign key, e.g. CategoryID, will have a value expression of Category.CategoryID
    bool HasValueExpression(PropertyMetadata property) {
        RelatedModelMetadata propertyModel = GetRelatedModelMetadata(property);
        return propertyModel != null;
    }

    // This will return the primary key property name, if and only if there is exactly
    // one primary key. Returns null if there is no PK, or the PK is composite.
    string GetPrimaryKeyName() {
        return (ModelMetadata.PrimaryKeys != null && ModelMetadata.PrimaryKeys.Count() == 1) ? ModelMetadata.PrimaryKeys[0].PropertyName : null;
    }
#>

Adding Column type to the column property

Could you kindly add the table column type ?
Thank you in advance.

Abdoessalam

My suggestion is :
To add Column type property - the column attribute in EF core is as follows :
Column Attribute: [Column (string name, Properties:[Order = int],[TypeName = string])
ex
[Column("DoB", Order = 1, TypeName="DateTime2")]

The suggested steps to add it :

a- open Craftsman\Models\EntityProperty.cs file, append the following property:

    public string ColumnType { get; set; }

b- open Craftsman\Builders\EntityBuilder.cs file and replace the following:

     if (!string.IsNullOrEmpty(entityProperty.ColumnName))                         
        attributeString += @$"        [Column(""{entityProperty.ColumnName}"",TypeName=""{entityProperty.ColumnType}"")]{Environment.NewLine}";              
    
    with the following:

     if (!string.IsNullOrEmpty(entityProperty.ColumnName))
        {
            if (!string.IsNullOrEmpty(entityProperty.ColumnType))
                attributeString += @$"        [Column(""{entityProperty.ColumnName}"",TypeName=""{entityProperty.ColumnType}"")]{Environment.NewLine}";
            else
                attributeString += @$"        [Column(""{entityProperty.ColumnName}"")]{Environment.NewLine}";
        }

PUT throws 500 when submitting an unchanged object

if i submit a PUT with an object identical to the one in the db, the save will not succeed because EF will not do anything to it. need to add a check to see if the object is identical and, if so, return an OK?

Only Windows?

Is this tool only intended for windows?

I am already getting errors when running the craftsman new:api command.

craftsman new:api $HOME/projects/work/template.yaml Your template file was parsed successfully. An unhandled exception occured when running the API command. The error details are: The /home/user/projects/work\Template.Api/Domain\Entitiesdirectory could not be found.

IsPrimaryKey property

Hi,
Are you changing how to deal with primary keys?

No problems with running craftsman version 0.11.1
but, will produce the following error when run on a clone of dev branch, Am I missing something?

YamlException: (Line: 39, Col: 7, Idx: 1089) - (Line: 39, Col: 7, Idx: 1089): Exception during deserialization
SerializationException: Property 'IsPrimaryKey' not found on type 'Craftsman.Models.EntityProperty'.
at GetProperty(Type type, Object container, String name, Boolean ignoreUnmatched)
at GetProperty(Type type, Object container, String name, Boolean ignoreUnmatched)
at Deserialize(IParser parser, Type expectedType, Func`3 nestedObjectDeserializer, Object& value)
at DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer)
....
.....

The BaseEntity class

Hi, I could not override the primary key Id property of the BaseEntity class.

Is there a way to use our natural primary keys when available. Or better yet, can you change it to generic "BaseEntity"?

Best regards,

Abdoessalam

Missing dependencies on craftsman new example

craftsman new example
create solution with Missing dependencies , that is related to RabbitMQ Bus.

image

Steps to reproduce

dotnet tool install -g craftsman
craftsman new:example

What steps can we follow to reproduce the issue?
craftsman new:example

Further technical details

craftsman 0.14.0 craftsman

Getting started does not work on dotnet 6 macOS M1

Thanks for this fantastic project! I'm trying out Craftsman for the first time following https://wrapt.dev/docs/installation The CLI installs successfully but errors out when invoked.

Steps to reproduce

โฏ dotnet tool install -g craftsman
You can invoke the tool using the following command: craftsman
Tool 'craftsman' (version '0.12.2') was successfully installed.

โฏ craftsman
It was not possible to find any compatible framework version
The framework 'Microsoft.AspNetCore.App', version '5.0.0' (arm64) was not found.
  - The following frameworks were found:
      6.0.1 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]

You can resolve the problem by installing the specified framework and/or SDK.

The specified framework can be found at:
  - https://aka.ms/dotnet-core-applaunch?framework=Microsoft.AspNetCore.App&framework_version=5.0.0&arch=arm64&rid=osx.12-arm64

โฏ echo $?
150

โฏ craftsman list
It was not possible to find any compatible framework version
The framework 'Microsoft.AspNetCore.App', version '5.0.0' (arm64) was not found.
  - The following frameworks were found:
      6.0.1 at [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]

You can resolve the problem by installing the specified framework and/or SDK.

The specified framework can be found at:
  - https://aka.ms/dotnet-core-applaunch?framework=Microsoft.AspNetCore.App&framework_version=5.0.0&arch=arm64&rid=osx.12-arm64

โฏ dotnet tool update -g craftsman
Tool 'craftsman' was reinstalled with the latest stable version (version '0.12.2').

Further technical details

Environment info

โฏ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   6.0.101
 Commit:    ef49f6213a

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  12.1
 OS Platform: Darwin
 RID:         osx.12-arm64
 Base Path:   /usr/local/share/dotnet/sdk/6.0.101/

Host (useful for support):
  Version: 6.0.1
  Commit:  3a25a7f1cc

.NET SDKs installed:
  6.0.101 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.1 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.1 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

Dependencies are working as expected:

โฏ dotnet tool list --global
Package Id      Version      Commands
--------------------------------------
craftsman       0.12.2       craftsman
dotnet-ef       6.0.1        dotnet-ef

~/Downloads
โฏ dotnet ef --version
Entity Framework Core .NET Command-line Tools
6.0.1

~/Downloads
โฏ docker --version
Docker version 20.10.11, build dea9396

Dotnet 6 is the only version with native arm64 support for M1 mac. So i do not have dotnet 5 installed.
Screenshot 000008@2x

I created a small web API template with dotnet 6 to verify that it successfully works as expected. How do I get past this error?

Upgrade template to the new program.cs format

The generated code is still using the old startup.cs/program.cs format. I was having problems when deploying to azure with serilog, after converting to the new program.cs format I was able to successfully deploy to azure. Below is the new program.cs file, it would be great if this can be done when the code is generated

using Autofac.Extensions.DependencyInjection;
using Test.Api.Databases;
using Test.Api.Extensions.Application;
using Test.Api.Extensions.Services;
using Test.Api.Seeders.DummyData;
using Serilog;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console()
.WriteTo.Seq("http://localhost:5341"));
Log.Information("Starting application");

var services = builder.Services;
services.AddSingleton(Log.Logger);
// TODO update CORS for your env
services.AddCorsService("Test.ApiCorsPolicy", builder.Environment);
services.AddInfrastructure(builder.Configuration, builder.Environment);
services.AddControllers()
.AddJsonOptions(o => o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
services.AddApiVersioningExtension();
services.AddWebApiServices();
services.AddHealthChecks();

// Dynamic Services
services.AddSwaggerExtension(builder.Configuration);

var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();

var serviceScopeFactory = app.Services.GetService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
    var context = (ApplicationDbContext)scope.ServiceProvider.GetService(typeof(ApplicationDbContext));
    context.Database.EnsureCreated();
    BlogPostSeeder.SeedSampleBlogPostData(context);
    BlogCategorySeeder.SeedSampleBlogCategoryData(context);
    BlogTagSeeder.SeedSampleBlogTagData(context);
    UserSeeder.SeedSampleUserData(context);
    SubscriptionSeeder.SeedSampleSubscriptionData(context);
    ContactFormSeeder.SeedSampleContactFormData(context);

}

}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

// For elevated security, it is recommended to remove this middleware and set your server to only listen on https.
// A slightly less secure option would be to redirect http to 400, 505, etc.
app.UseHttpsRedirection();

app.UseCors("Test.ApiCorsPolicy");

app.UseSerilogRequestLogging();
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/api/health");
endpoints.MapControllers();
});

// Dynamic App
app.UseSwaggerExtension(builder.Configuration);

app.Run();

public partial class Program { } // --> Needed for Functional Unit Test

Migrate from Newtonsoft.Json to System.Text.Json

Problem I am trying to solve

  • Improve default performance, security and standards compliance of Json ser/deser
  • Currently the default scaffold builders use Newtonsoft.Json here
  • The .NET team has written a detailed article on System.Text.Json here
  • The migration guide lists the problems it solves and the gaps in comparison to Newtonsoft.Json here
  • The MassTransit project is migrating to System.Text.Json and saw a 2x increase in performance while using less memory here and here.

Solution I'd like

  • Make System.Text.Json the default. Since a lot of web APIs rely on JSON ser/de-ser, this allows newcomers to realize the performance benefits while eliminating security risk and improving JSON standards compliance.

I'd love to contribute this change, if this is something that sounds interesting to you!

Integration tests break when adding Auth

Describe what isn't working as expected

When using Auth, the API will run fine, but the integration tests will break, throwing a -------- System.InvalidOperationException : Scheme already exists: Identity.Application error.

I started googling for this and it seems like the main resolution is generally to remove AddDefaultIdentity to either stop a clash with IdentityHostingStartup or prevent IdentityHostintgStartup.cs from causing some overlap.

I'm not using AddDefaultIdentity and I'm not seeing a IdentityHostintgStartup.cs get generated, so I'm not quite sure what the deal is here. Presumably, something is calling AddAuthentication with the same identity scheme twice. Some minor discussion is here. This may be be due to CustomWebApplicationFactory running through startup multiple times, but I need to investigate more.

It does look like, when debugging any integration test that services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<IdentityDbContext>().AddDefaultTokenProviders(); is getting hit twice and, when commenting that line out, I get a different error: -------- System.InvalidOperationException : Scheme already exists: Bearer which, agian, is presumably happening because of startup getting run twice in CustomWebApplicationFactory.

I need to really dive into this subject more and figure out a good resolution and workflow for this as this has happened to me with numerous other services in the past.

Handle relative paths in cli commands

@JuanCarbo I just started a write up for this and when I went to try it in my CLI (i.e. craftsman new:api ..\ApiTemplateName.yaml) and it worked just fine. Could you confirm that it still doesn't work for you?

The Name annotation will only be produced for get actions.

The Name annotation will only be produced for get action [HttpGet(Name = "GetAddresss")] but not for other actions in the controller.
Which will produce generic methods for some client generators and errors for the others.

 [HttpGet(Name = "GetAddresss")]     -> Ok
[HttpPost]    without the name property  -> not ok

Tutorial craftsman new:domain CarbonKitchen.yaml fails on Linux

Creating a template file by copying the one provided in tutorial section "Environment and Swagger Setup", and even placing it in the same directory I run the command craftsman new:domain from leads to the following output:

No such file or directory
An unhandled exception occurred when running the API command.
The error details are: 
No such file or directory

Nevertheless the following empty directory structure gets created:

CarbonKitchen
โ””โ”€โ”€ RecipeManagement
    โ”œโ”€โ”€ src
    โ””โ”€โ”€ tests

Further technical details

Craftsman version (craftsman version):
v0.9.3.0

Nightly builds

Hi,
Is there nightly builds available to try some added feature?

Thank you.

Abdoessalam

Tests break in v0.12 when using `DateOnly` or `TimeOnly`

This isn't a bug in craftsman, but in a library used in wrapt projects.

Autobogus can not generate fakes on entities using DateOnly or TimeOnly. This is because Bogus did not support it. They have since released 34.x which provides this support, but autobogus needs to bring it in to their package. You can track the progress of it here.

Creating new project ends up with error on Linux (case-sensitive file system)

Any actions of initializing new project with craftsman end up with an error:
ERROR: The /home/thestacks/source/repos/thestacks/MyExampleProject/RecipeManagement/src/Re cipeManagement/Properties/launchsettings.json file could not be found.`

Launching command craftsman new:example does not work too.

In my opinion this is a problem with case-sensitive file system of Linux.
The file is there, and it's called launchSettings.json not launchsettings.json

This bug is a blocker, I cannot use craftsman at all because of this error.

Steps to reproduce

  1. Use Ubuntu (or any linux os)
  2. Launch terminal
  3. Launch craftsman new:example
  4. Continue with the setup
  5. You can see the error as above.

Further technical details

Craftsman version (dotnet tool list -g): 0.14.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.